Chromium Code Reviews| OLD | NEW |
|---|---|
| (Empty) | |
| 1 // Copyright 2016 The Chromium Authors. All rights reserved. | |
| 2 // Use of this source code is governed by a BSD-style license that can be | |
| 3 // found in the LICENSE file. | |
| 4 | |
| 5 #include "content/browser/loader/async_resource_handler.h" | |
| 6 | |
| 7 #include <stddef.h> | |
| 8 #include <stdint.h> | |
| 9 #include <memory> | |
| 10 #include <string> | |
| 11 #include <tuple> | |
| 12 #include <utility> | |
| 13 #include <vector> | |
| 14 | |
| 15 #include "base/bind.h" | |
| 16 #include "base/bind_helpers.h" | |
| 17 #include "base/feature_list.h" | |
| 18 #include "base/logging.h" | |
| 19 #include "base/memory/ptr_util.h" | |
| 20 #include "base/memory/ref_counted.h" | |
| 21 #include "base/memory/shared_memory_handle.h" | |
| 22 #include "base/memory/weak_ptr.h" | |
| 23 #include "base/process/process.h" | |
| 24 #include "base/process/process_handle.h" | |
| 25 #include "base/run_loop.h" | |
| 26 #include "base/strings/string_number_conversions.h" | |
| 27 #include "content/browser/loader/resource_dispatcher_host_impl.h" | |
| 28 #include "content/browser/loader/resource_loader.h" | |
| 29 #include "content/browser/loader/resource_loader_delegate.h" | |
| 30 #include "content/browser/loader/resource_message_filter.h" | |
| 31 #include "content/browser/loader/resource_request_info_impl.h" | |
| 32 #include "content/common/resource_messages.h" | |
| 33 #include "content/common/resource_request.h" | |
| 34 #include "content/public/browser/resource_context.h" | |
| 35 #include "content/public/browser/resource_request_info.h" | |
| 36 #include "content/public/common/content_features.h" | |
| 37 #include "content/public/common/process_type.h" | |
| 38 #include "content/public/common/resource_type.h" | |
| 39 #include "content/public/test/mock_resource_context.h" | |
| 40 #include "content/public/test/test_browser_thread_bundle.h" | |
| 41 #include "ipc/ipc_message.h" | |
| 42 #include "ipc/ipc_message_macros.h" | |
| 43 #include "net/http/http_response_headers.h" | |
| 44 #include "net/http/http_util.h" | |
| 45 #include "net/url_request/url_request.h" | |
| 46 #include "net/url_request/url_request_context.h" | |
| 47 #include "net/url_request/url_request_job_factory_impl.h" | |
| 48 #include "net/url_request/url_request_test_job.h" | |
| 49 #include "net/url_request/url_request_test_util.h" | |
| 50 #include "testing/gtest/include/gtest/gtest.h" | |
| 51 #include "ui/base/page_transition_types.h" | |
| 52 #include "url/gurl.h" | |
| 53 | |
| 54 namespace content { | |
| 55 | |
| 56 namespace { | |
| 57 | |
| 58 // This string is repeated as necessary to create a response body of the | |
| 59 // required size. It is 8 bytes so that repeated doubling can achieve any power | |
| 60 // of 2 size exactly. | |
| 61 const char kBaseData[] = "Hello. "; | |
| 62 | |
| 63 // Response short enough to be inlined into a single IPC. | |
| 64 const GURL kTestURLShort("test:short"); | |
| 65 | |
| 66 // Response can be sent in a single chunk (not inlined). | |
| 67 const GURL kTestURLMedium("test:medium"); | |
| 68 | |
| 69 // Size of response to kTestURLMedium. It should be a power of 2 and larger than | |
| 70 // kInlinedLeadingChunkSize. | |
| 71 const size_t kMediumLength = 4096; | |
| 72 | |
| 73 // Response requires two chunks. | |
| 74 const GURL kTestURLLarge("test:large"); | |
| 75 | |
| 76 // Size of response to kTestURLLarge. It should be a power of 2 and larger than | |
| 77 // kMaxAllocationSize. | |
| 78 const size_t kLargeLength = 64 * 1024; | |
| 79 | |
| 80 std::string RepeatData(size_t target_size) { | |
| 81 std::string data = kBaseData; | |
| 82 data.reserve(target_size); | |
| 83 while (data.size() < target_size) | |
| 84 data += data; | |
| 85 return data; | |
| 86 } | |
|
kinuko
2016/07/11 02:35:24
Why can't we use simple method like std::string(ta
Adam Rice
2016/07/11 05:16:36
Oh. Yes. Sorry. Done.
| |
| 87 | |
| 88 // This test job adds a Content-Length header and implements | |
| 89 // GetTotalReceivedBytes(). | |
| 90 class TestJob : public net::URLRequestTestJob { | |
| 91 public: | |
| 92 TestJob(net::URLRequest* request, net::NetworkDelegate* network_delegate) | |
| 93 : net::URLRequestTestJob(request, network_delegate, true) {} | |
| 94 | |
| 95 // URLRequestJob implementation: | |
|
kinuko
2016/07/11 02:35:25
Should we have a TODO to implement this in URLRequ
Adam Rice
2016/07/11 05:16:36
Done.
| |
| 96 int64_t GetTotalReceivedBytes() const override { | |
| 97 std::string http_headers = net::HttpUtil::ConvertHeadersBackToHTTPResponse( | |
| 98 response_headers_->raw_headers()); | |
| 99 return http_headers.size() + offset_; | |
| 100 } | |
| 101 | |
| 102 protected: | |
| 103 // Override URLRequestTestJob behaviour. | |
| 104 void StartAsync() override { | |
|
kinuko
2016/07/11 02:35:25
I think it's easier to extend / more readable if w
Adam Rice
2016/07/11 05:16:36
That is simpler. Done.
| |
| 105 std::string data; | |
| 106 if (request_->url() == kTestURLShort) { | |
| 107 data = kBaseData; | |
| 108 } else if (request_->url() == kTestURLMedium) { | |
| 109 data = RepeatData(kMediumLength); | |
| 110 } else if (request_->url() == kTestURLLarge) { | |
| 111 data = RepeatData(kLargeLength); | |
| 112 } | |
| 113 if (data.empty()) { | |
| 114 URLRequestTestJob::StartAsync(); | |
| 115 return; | |
| 116 } | |
| 117 | |
| 118 std::string headers = test_headers(); | |
| 119 response_headers_ = new net::HttpResponseHeaders( | |
| 120 net::HttpUtil::AssembleRawHeaders(headers.data(), headers.size())); | |
| 121 | |
| 122 response_data_ = data; | |
| 123 response_headers_->AddHeader("Content-Length: " + | |
| 124 base::SizeTToString(data.size())); | |
| 125 URLRequestTestJob::StartAsync(); | |
| 126 } | |
| 127 | |
| 128 private: | |
| 129 DISALLOW_COPY_AND_ASSIGN(TestJob); | |
| 130 }; | |
| 131 | |
| 132 class TestJobProtocolHandler | |
| 133 : public net::URLRequestJobFactory::ProtocolHandler { | |
| 134 public: | |
| 135 net::URLRequestJob* MaybeCreateJob( | |
| 136 net::URLRequest* request, | |
| 137 net::NetworkDelegate* network_delegate) const override { | |
| 138 return new TestJob(request, network_delegate); | |
| 139 } | |
| 140 }; | |
| 141 | |
| 142 // A subclass of ResourceMessageFilter that records IPC messages that are sent. | |
| 143 class RecordingResourceMessageFilter : public ResourceMessageFilter { | |
| 144 public: | |
| 145 RecordingResourceMessageFilter(ResourceContext* resource_context, | |
| 146 net::URLRequestContext* request_context) | |
| 147 : ResourceMessageFilter( | |
| 148 0, | |
| 149 PROCESS_TYPE_RENDERER, | |
| 150 nullptr, | |
| 151 nullptr, | |
| 152 nullptr, | |
| 153 nullptr, | |
| 154 nullptr, | |
| 155 base::Bind(&RecordingResourceMessageFilter::GetContexts, | |
| 156 base::Unretained(this))), | |
| 157 resource_context_(resource_context), | |
| 158 request_context_(request_context) { | |
| 159 set_peer_process_for_testing(base::Process::Current()); | |
| 160 } | |
| 161 | |
| 162 const std::vector<std::unique_ptr<IPC::Message>>& messages() const { | |
| 163 return messages_; | |
| 164 } | |
| 165 | |
| 166 // IPC::Sender implementation | |
| 167 bool Send(IPC::Message* message) override { | |
| 168 if (message->type() == ResourceMsg_SetDataBuffer::ID) | |
| 169 ConsumeHandle(*message); | |
| 170 messages_.push_back(base::WrapUnique(message)); | |
| 171 return true; | |
| 172 } | |
| 173 | |
| 174 private: | |
| 175 ~RecordingResourceMessageFilter() override {} | |
| 176 | |
| 177 void GetContexts(ResourceType resource_type, | |
| 178 int origin_pid, | |
| 179 ResourceContext** resource_context, | |
| 180 net::URLRequestContext** request_context) { | |
| 181 *resource_context = resource_context_; | |
| 182 *request_context = request_context_; | |
| 183 } | |
| 184 | |
| 185 // Unpickle the base::SharedMemoryHandle to avoid warnings about | |
| 186 // "MessageAttachmentSet destroyed with unconsumed descriptors". | |
| 187 void ConsumeHandle(const IPC::Message& message) { | |
| 188 IPC_BEGIN_MESSAGE_MAP(RecordingResourceMessageFilter, message) | |
| 189 IPC_MESSAGE_HANDLER(ResourceMsg_SetDataBuffer, OnSetDataBuffer) | |
| 190 IPC_MESSAGE_UNHANDLED(NOTREACHED()) | |
| 191 IPC_END_MESSAGE_MAP() | |
|
kinuko
2016/07/11 02:35:25
Isn't it more compact / easier to read if we just
Adam Rice
2016/07/11 05:16:36
I didn't think that would work. I should have trie
| |
| 192 } | |
| 193 | |
| 194 void OnSetDataBuffer(int request_id, | |
| 195 base::SharedMemoryHandle shm_handle, | |
| 196 int shm_size, | |
| 197 base::ProcessId renderer_pid) {} | |
| 198 | |
| 199 ResourceContext* const resource_context_; | |
| 200 net::URLRequestContext* const request_context_; | |
| 201 std::vector<std::unique_ptr<IPC::Message>> messages_; | |
| 202 }; | |
| 203 | |
| 204 class AsyncResourceHandlerTest : public ::testing::Test { | |
| 205 protected: | |
| 206 AsyncResourceHandlerTest() | |
| 207 : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), context_(true) {} | |
| 208 | |
| 209 void CreateRequest(const GURL& url) { | |
| 210 test_job_factory_.SetProtocolHandler( | |
| 211 "test", base::MakeUnique<TestJobProtocolHandler>()); | |
| 212 context_.set_job_factory(&test_job_factory_); | |
| 213 context_.Init(); | |
| 214 std::unique_ptr<net::URLRequest> request = | |
| 215 context_.CreateRequest(GURL(url), net::DEFAULT_PRIORITY, nullptr); | |
| 216 resource_context_ = base::MakeUnique<MockResourceContext>(&context_); | |
| 217 filter_ = make_scoped_refptr( | |
| 218 new RecordingResourceMessageFilter(resource_context_.get(), &context_)); | |
| 219 ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl( | |
|
Mike West
2016/07/08 13:14:37
Nit: Wrap this in `// clang format off` at the top
Adam Rice
2016/07/11 02:07:59
Actually, the comments on each line are sufficient
kinuko
2016/07/11 02:35:24
Note that ResourceRequestInfo::AllocateForTesting
Adam Rice
2016/07/11 05:16:36
ResourceRequestInfo::AllocateForTesting() only set
kinuko
2016/07/11 06:43:38
I see... then we probably need to live with this l
| |
| 220 PROCESS_TYPE_RENDERER, // process_type | |
| 221 0, // child_id | |
| 222 0, // route_id | |
| 223 -1, // frame_tree_node_id | |
| 224 0, // origin_pid | |
| 225 0, // request_id | |
| 226 0, // render_frame_id | |
| 227 false, // is_main_frame | |
| 228 false, // parent_is_main_frame | |
| 229 RESOURCE_TYPE_IMAGE, // resource_type | |
| 230 ui::PAGE_TRANSITION_LINK, // transition_type | |
| 231 false, // should_replace_current_entry | |
| 232 false, // is_download | |
| 233 false, // is_stream | |
| 234 false, // allow_download | |
| 235 false, // has_user_gesture | |
| 236 false, // enable load timing | |
| 237 false, // enable upload progress | |
| 238 false, // do_not_prompt_for_login | |
| 239 blink::WebReferrerPolicyDefault, // referrer_policy | |
| 240 blink::WebPageVisibilityStateVisible, // visibility_state | |
| 241 resource_context_.get(), // context | |
| 242 filter_->GetWeakPtr(), // filter | |
| 243 false, // report_raw_headers | |
| 244 true, // is_async | |
| 245 false, // is_using_lofi | |
| 246 std::string(), // original_headers | |
| 247 nullptr); // body | |
| 248 info->AssociateWithRequest(request.get()); | |
| 249 std::unique_ptr<AsyncResourceHandler> handler = | |
| 250 base::MakeUnique<AsyncResourceHandler>(request.get(), &rdh_); | |
| 251 loader_delegate_.reset(new TrivialResourceLoaderDelegate(this)); | |
| 252 loader_ = | |
| 253 base::MakeUnique<ResourceLoader>(std::move(request), std::move(handler), | |
| 254 nullptr, loader_delegate_.get()); | |
| 255 } | |
| 256 | |
| 257 void StartRequest() { loader_->StartRequest(); } | |
| 258 | |
| 259 void WaitForFinish() { finish_waiter_.Run(); } | |
|
kinuko
2016/07/11 02:35:25
StartRequest and WaitForFinish are called only fro
Adam Rice
2016/07/11 05:16:36
Done.
| |
| 260 | |
| 261 void CreateStartAndWait(const GURL& url) { | |
| 262 CreateRequest(url); | |
| 263 StartRequest(); | |
| 264 WaitForFinish(); | |
| 265 } | |
| 266 | |
| 267 TestBrowserThreadBundle thread_bundle_; | |
| 268 ResourceDispatcherHostImpl rdh_; | |
| 269 net::TestURLRequestContext context_; | |
| 270 net::URLRequestJobFactoryImpl test_job_factory_; | |
| 271 std::unique_ptr<MockResourceContext> resource_context_; | |
| 272 scoped_refptr<RecordingResourceMessageFilter> filter_; | |
| 273 std::unique_ptr<ResourceLoader> loader_; | |
| 274 base::RunLoop finish_waiter_; | |
|
kinuko
2016/07/11 02:35:24
Looks like all fields other than filter_ can be ac
Adam Rice
2016/07/11 05:16:36
Done.
| |
| 275 | |
| 276 private: | |
| 277 class TrivialResourceLoaderDelegate : public ResourceLoaderDelegate { | |
|
kinuko
2016/07/11 02:35:24
I think AsyncResourceHandlerTest can directly impl
Adam Rice
2016/07/11 05:16:36
Done.
| |
| 278 public: | |
| 279 explicit TrivialResourceLoaderDelegate(AsyncResourceHandlerTest* fixture) | |
| 280 : fixture_(fixture) {} | |
| 281 ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( | |
| 282 ResourceLoader* loader, | |
| 283 net::AuthChallengeInfo* auth_info) override { | |
| 284 return nullptr; | |
| 285 } | |
| 286 | |
| 287 bool HandleExternalProtocol(ResourceLoader* loader, | |
| 288 const GURL& url) override { | |
| 289 return false; | |
| 290 } | |
| 291 void DidStartRequest(ResourceLoader* loader) override {} | |
| 292 void DidReceiveRedirect(ResourceLoader* loader, | |
| 293 const GURL& new_url) override {} | |
| 294 void DidReceiveResponse(ResourceLoader* loader) override {} | |
| 295 void DidFinishLoading(ResourceLoader* loader) override { | |
| 296 fixture_->DidFinishLoading(); | |
| 297 } | |
| 298 | |
| 299 private: | |
| 300 AsyncResourceHandlerTest* fixture_; | |
| 301 }; | |
| 302 | |
| 303 void DidFinishLoading() { | |
| 304 loader_.reset(); | |
| 305 finish_waiter_.Quit(); | |
| 306 } | |
| 307 | |
| 308 std::unique_ptr<TrivialResourceLoaderDelegate> loader_delegate_; | |
| 309 }; | |
| 310 | |
| 311 std::unique_ptr<ResourceMsg_DataReceived::Param> UnpackDataReceivedIPC( | |
| 312 const IPC::Message* msg) { | |
| 313 if (ResourceMsg_DataReceived::ID != msg->type()) | |
| 314 return nullptr; | |
| 315 std::unique_ptr<ResourceMsg_DataReceived::Param> params = | |
| 316 base::MakeUnique<ResourceMsg_DataReceived::Param>(); | |
| 317 ResourceMsg_DataReceived::Read(msg, params.get()); | |
| 318 return params; | |
| 319 } | |
| 320 | |
| 321 std::unique_ptr<ResourceMsg_InlinedDataChunkReceived::Param> | |
| 322 UnpackInlinedDataChunkReceivedIPC(const IPC::Message* msg) { | |
| 323 if (ResourceMsg_InlinedDataChunkReceived::ID != msg->type()) | |
| 324 return nullptr; | |
| 325 std::unique_ptr<ResourceMsg_InlinedDataChunkReceived::Param> params = | |
| 326 base::MakeUnique<ResourceMsg_InlinedDataChunkReceived::Param>(); | |
| 327 ResourceMsg_InlinedDataChunkReceived::Read(msg, params.get()); | |
| 328 return params; | |
| 329 } | |
| 330 | |
| 331 TEST_F(AsyncResourceHandlerTest, Construct) { | |
| 332 CreateRequest(net::URLRequestTestJob::test_url_1()); | |
| 333 } | |
| 334 | |
| 335 TEST_F(AsyncResourceHandlerTest, OneChunkLengths) { | |
| 336 CreateStartAndWait(kTestURLMedium); | |
| 337 const auto& messages = filter_->messages(); | |
| 338 ASSERT_EQ(4u, messages.size()); | |
| 339 auto params = UnpackDataReceivedIPC(messages[2].get()); | |
| 340 ASSERT_TRUE(params); | |
|
kinuko
2016/07/11 02:35:24
I have a feeling that these two lines can be repla
Adam Rice
2016/07/11 05:16:36
Read() doesn't check the message type, so I had to
| |
| 341 | |
| 342 int encoded_data_length = std::get<3>(*params); | |
| 343 EXPECT_EQ(4163, encoded_data_length); | |
| 344 int encoded_body_length = std::get<4>(*params); | |
| 345 EXPECT_EQ(4096, encoded_body_length); | |
| 346 } | |
| 347 | |
| 348 TEST_F(AsyncResourceHandlerTest, InlinedChunkLengths) { | |
| 349 // TODO(ricea): Remove this Feature-enabling code once the feature is on by | |
| 350 // default. | |
| 351 auto feature_list = base::MakeUnique<base::FeatureList>(); | |
| 352 feature_list->InitializeFromCommandLine( | |
| 353 features::kOptimizeLoadingIPCForSmallResources.name, ""); | |
| 354 base::FeatureList::ClearInstanceForTesting(); | |
| 355 base::FeatureList::SetInstance(std::move(feature_list)); | |
| 356 | |
| 357 CreateStartAndWait(kTestURLShort); | |
| 358 const auto& messages = filter_->messages(); | |
| 359 ASSERT_EQ(3u, messages.size()); | |
| 360 auto params = UnpackInlinedDataChunkReceivedIPC(messages[1].get()); | |
| 361 ASSERT_TRUE(params); | |
| 362 | |
| 363 int encoded_data_length = std::get<2>(*params); | |
| 364 EXPECT_EQ(72, encoded_data_length); | |
| 365 int encoded_body_length = std::get<3>(*params); | |
| 366 EXPECT_EQ(8, encoded_body_length); | |
| 367 | |
| 368 base::FeatureList::ClearInstanceForTesting(); | |
| 369 base::FeatureList::SetInstance(base::MakeUnique<base::FeatureList>()); | |
| 370 } | |
| 371 | |
| 372 TEST_F(AsyncResourceHandlerTest, TwoChunksLengths) { | |
| 373 CreateStartAndWait(kTestURLLarge); | |
| 374 const auto& messages = filter_->messages(); | |
| 375 ASSERT_EQ(5u, messages.size()); | |
| 376 auto params = UnpackDataReceivedIPC(messages[2].get()); | |
| 377 ASSERT_TRUE(params); | |
| 378 | |
| 379 int encoded_data_length = std::get<3>(*params); | |
| 380 EXPECT_EQ(32836, encoded_data_length); | |
| 381 int encoded_body_length = std::get<4>(*params); | |
| 382 EXPECT_EQ(32768, encoded_body_length); | |
| 383 | |
| 384 params = UnpackDataReceivedIPC(messages[3].get()); | |
| 385 ASSERT_TRUE(params); | |
| 386 encoded_data_length = std::get<3>(*params); | |
| 387 EXPECT_EQ(32768, encoded_data_length); | |
| 388 encoded_body_length = std::get<4>(*params); | |
| 389 EXPECT_EQ(32768, encoded_body_length); | |
| 390 } | |
| 391 | |
| 392 } // namespace | |
| 393 | |
| 394 } // namespace content | |
| OLD | NEW |