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 // Should be larger than kInlinedLeadingChunkSize | |
kinuko
2016/07/08 05:57:05
nit: end sentence with period (here and below)
Adam Rice
2016/07/08 07:45:25
Done.
| |
59 const size_t kMediumLength = 4096; | |
60 | |
61 // Should be larger than kMaxAllocationSize | |
62 const size_t kLargeLength = 64 * 1024; | |
63 | |
64 const char kBaseData[] = "Hello. "; | |
65 | |
66 // URLRequestTestJob doesn't implement GetTotalReceivedBytes(), so subclass it. | |
67 class TestJob : public net::URLRequestTestJob { | |
68 public: | |
69 TestJob(net::URLRequest* request, net::NetworkDelegate* network_delegate) | |
70 : net::URLRequestTestJob(request, network_delegate, true) {} | |
71 | |
72 // All these URLs have a content-length header. | |
73 | |
74 // Response can be inlined into IPC. | |
75 static GURL test_url_short_length() { return GURL("test:short"); } | |
76 | |
77 // Response can be sent in a single chunk. | |
78 static GURL test_url_medium_length() { return GURL("test:medium"); } | |
79 | |
80 // Response requires multiple chunks. | |
81 static GURL test_url_large_length() { return GURL("test:large"); } | |
kinuko
2016/07/08 05:57:05
Not sure why these need to be static methods, just
Adam Rice
2016/07/08 07:45:24
Sorry, I was copying URLRequestTestJob without thi
| |
82 | |
83 // URLRequestJob implementation | |
84 int64_t GetTotalReceivedBytes() const override { | |
85 std::string http_headers = net::HttpUtil::ConvertHeadersBackToHTTPResponse( | |
86 response_headers_->raw_headers()); | |
87 return http_headers.size() + offset_; | |
88 } | |
89 | |
90 protected: | |
91 void StartAsync() override { | |
92 std::string spec = request_->url().spec(); | |
93 std::string data; | |
94 if (spec == test_url_short_length().spec()) { | |
kinuko
2016/07/08 05:57:05
nit: == operator just works for GURLs, no need to
Adam Rice
2016/07/08 07:45:25
Done.
| |
95 data = kBaseData; | |
96 } else if (spec == test_url_medium_length().spec()) { | |
97 data = RepeatData(kMediumLength); | |
98 } else if (spec == test_url_large_length().spec()) { | |
99 data = RepeatData(kLargeLength); | |
100 } | |
101 if (data.empty()) { | |
102 URLRequestTestJob::StartAsync(); | |
103 return; | |
104 } | |
105 | |
106 std::string headers = test_headers(); | |
107 response_headers_ = new net::HttpResponseHeaders( | |
108 net::HttpUtil::AssembleRawHeaders(headers.data(), headers.size())); | |
109 | |
110 response_data_ = data; | |
111 response_headers_->AddHeader("Content-Length: " + | |
112 base::SizeTToString(data.size())); | |
113 URLRequestTestJob::StartAsync(); | |
114 } | |
115 | |
116 private: | |
117 std::string RepeatData(size_t target_size) { | |
118 std::string data = kBaseData; | |
119 data.reserve(target_size); | |
120 while (data.size() < target_size) | |
121 data += data; | |
122 return data; | |
123 } | |
124 | |
125 DISALLOW_COPY_AND_ASSIGN(TestJob); | |
126 }; | |
127 | |
128 class TestJobProtocolHandler | |
kinuko
2016/07/08 05:57:05
While writing up all these test harness is nice an
Adam Rice
2016/07/08 07:45:25
A real URLRequest is needed to return non-zero val
| |
129 : public net::URLRequestJobFactory::ProtocolHandler { | |
130 public: | |
131 // URLRequestJobFactory::ProtocolHandler implementation: | |
132 net::URLRequestJob* MaybeCreateJob( | |
133 net::URLRequest* request, | |
134 net::NetworkDelegate* network_delegate) const override { | |
135 return new TestJob(request, network_delegate); | |
136 } | |
137 }; | |
138 | |
139 // A subclass of ResourceMessageFilter that records IPC messages that are sent. | |
140 class RecordingResourceMessageFilter : public ResourceMessageFilter { | |
141 public: | |
142 RecordingResourceMessageFilter(ResourceContext* resource_context, | |
143 net::URLRequestContext* request_context) | |
144 : ResourceMessageFilter( | |
145 0, | |
146 PROCESS_TYPE_RENDERER, | |
147 nullptr, | |
148 nullptr, | |
149 nullptr, | |
150 nullptr, | |
151 nullptr, | |
152 base::Bind(&RecordingResourceMessageFilter::GetContexts, | |
153 base::Unretained(this))), | |
154 resource_context_(resource_context), | |
155 request_context_(request_context) { | |
156 set_peer_process_for_testing(base::Process::Current()); | |
157 } | |
158 | |
159 const std::vector<std::unique_ptr<IPC::Message>>& messages() const { | |
160 return messages_; | |
161 } | |
162 | |
163 // IPC::Sender implementation | |
164 bool Send(IPC::Message* message) override { | |
165 if (message->type() == ResourceMsg_SetDataBuffer::ID) | |
166 ConsumeHandle(*message); | |
167 messages_.push_back(base::WrapUnique(message)); | |
168 return true; | |
169 } | |
170 | |
171 private: | |
172 ~RecordingResourceMessageFilter() override {} | |
173 | |
174 void GetContexts(ResourceType resource_type, | |
175 int origin_pid, | |
176 ResourceContext** resource_context, | |
177 net::URLRequestContext** request_context) { | |
178 *resource_context = resource_context_; | |
179 *request_context = request_context_; | |
180 } | |
181 | |
182 // Unpickle the base::SharedMemoryHandle to avoid warnings about | |
183 // "MessageAttachmentSet destroyed with unconsumed descriptors". | |
184 void ConsumeHandle(const IPC::Message& message) { | |
185 IPC_BEGIN_MESSAGE_MAP(RecordingResourceMessageFilter, message) | |
186 IPC_MESSAGE_HANDLER(ResourceMsg_SetDataBuffer, OnSetDataBuffer) | |
187 IPC_MESSAGE_UNHANDLED(NOTREACHED()) | |
188 IPC_END_MESSAGE_MAP() | |
189 } | |
190 | |
191 void OnSetDataBuffer(int request_id, | |
192 base::SharedMemoryHandle shm_handle, | |
193 int shm_size, | |
194 base::ProcessId renderer_pid) {} | |
195 | |
196 ResourceContext* const resource_context_; | |
197 net::URLRequestContext* const request_context_; | |
198 std::vector<std::unique_ptr<IPC::Message>> messages_; | |
199 }; | |
200 | |
201 class AsyncResourceHandlerTest : public ::testing::Test { | |
202 protected: | |
203 AsyncResourceHandlerTest() | |
204 : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), context_(true) {} | |
205 | |
206 void CreateRequest(const GURL& url) { | |
207 test_job_factory_.SetProtocolHandler( | |
208 "test", base::MakeUnique<TestJobProtocolHandler>()); | |
209 context_.set_job_factory(&test_job_factory_); | |
210 context_.Init(); | |
211 std::unique_ptr<net::URLRequest> request = | |
212 context_.CreateRequest(GURL(url), net::DEFAULT_PRIORITY, nullptr); | |
213 resource_context_ = base::MakeUnique<MockResourceContext>(&context_); | |
214 filter_ = make_scoped_refptr( | |
215 new RecordingResourceMessageFilter(resource_context_.get(), &context_)); | |
216 ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl( | |
kinuko
2016/07/08 05:57:05
Use ResourceRequestInfo::AllocateForTesting ?
Adam Rice
2016/07/08 07:45:25
Done.
Adam Rice
2016/07/08 11:21:10
I failed to spot that AllocateForTesting() doesn't
| |
217 PROCESS_TYPE_RENDERER, // process_type | |
218 0, // child_id | |
219 0, // route_id | |
220 -1, // frame_tree_node_id | |
221 0, // origin_pid | |
222 0, // request_id | |
223 0, // render_frame_id | |
224 false, // is_main_frame | |
225 false, // parent_is_main_frame | |
226 RESOURCE_TYPE_IMAGE, // resource_type | |
227 ui::PAGE_TRANSITION_LINK, // transition_type | |
228 false, // should_replace_current_entry | |
229 false, // is_download | |
230 false, // is_stream | |
231 false, // allow_download | |
232 false, // has_user_gesture | |
233 false, // enable load timing | |
234 false, // enable upload progress | |
235 false, // do_not_prompt_for_login | |
236 blink::WebReferrerPolicyDefault, // referrer_policy | |
237 blink::WebPageVisibilityStateVisible, // visibility_state | |
238 resource_context_.get(), // context | |
239 filter_->GetWeakPtr(), // filter | |
240 false, // report_raw_headers | |
241 true, // is_async | |
242 false, // is_using_lofi | |
243 std::string(), // original_headers | |
244 nullptr); // body | |
245 info->AssociateWithRequest(request.get()); | |
246 std::unique_ptr<AsyncResourceHandler> handler = | |
247 base::MakeUnique<AsyncResourceHandler>(request.get(), &rdh_); | |
248 loader_delegate_.reset(new TrivialResourceLoaderDelegate(this)); | |
249 loader_ = | |
250 base::MakeUnique<ResourceLoader>(std::move(request), std::move(handler), | |
251 nullptr, loader_delegate_.get()); | |
252 } | |
253 | |
254 void StartRequest() { loader_->StartRequest(); } | |
255 | |
256 void WaitForFinish() { finish_waiter_.Run(); } | |
257 | |
258 void CreateStartAndWait(const GURL& url) { | |
259 CreateRequest(url); | |
260 StartRequest(); | |
261 WaitForFinish(); | |
262 } | |
263 | |
264 TestBrowserThreadBundle thread_bundle_; | |
265 ResourceDispatcherHostImpl rdh_; | |
266 net::TestURLRequestContext context_; | |
267 net::URLRequestJobFactoryImpl test_job_factory_; | |
268 std::unique_ptr<MockResourceContext> resource_context_; | |
269 scoped_refptr<RecordingResourceMessageFilter> filter_; | |
270 std::unique_ptr<ResourceLoader> loader_; | |
271 base::RunLoop finish_waiter_; | |
272 | |
273 private: | |
274 class TrivialResourceLoaderDelegate : public ResourceLoaderDelegate { | |
275 public: | |
276 explicit TrivialResourceLoaderDelegate(AsyncResourceHandlerTest* fixture) | |
277 : fixture_(fixture) {} | |
278 ResourceDispatcherHostLoginDelegate* CreateLoginDelegate( | |
279 ResourceLoader* loader, | |
280 net::AuthChallengeInfo* auth_info) override { | |
281 return nullptr; | |
282 } | |
283 | |
284 bool HandleExternalProtocol(ResourceLoader* loader, | |
285 const GURL& url) override { | |
286 return false; | |
287 } | |
288 void DidStartRequest(ResourceLoader* loader) override {} | |
289 void DidReceiveRedirect(ResourceLoader* loader, | |
290 const GURL& new_url) override {} | |
291 void DidReceiveResponse(ResourceLoader* loader) override {} | |
292 void DidFinishLoading(ResourceLoader* loader) override { | |
293 fixture_->DidFinishLoading(); | |
294 } | |
295 | |
296 private: | |
297 AsyncResourceHandlerTest* fixture_; | |
298 }; | |
299 | |
300 void DidFinishLoading() { | |
301 loader_.reset(); | |
302 finish_waiter_.Quit(); | |
303 } | |
304 | |
305 std::unique_ptr<TrivialResourceLoaderDelegate> loader_delegate_; | |
306 }; | |
307 | |
308 std::unique_ptr<ResourceMsg_DataReceived::Param> UnpackDataReceivedIPC( | |
309 const IPC::Message* msg) { | |
310 if (ResourceMsg_DataReceived::ID != msg->type()) | |
311 return nullptr; | |
312 static_assert(std::tuple_size<ResourceMsg_DataReceived::Param>::value == 5u, | |
313 "ResourceMsg_DataReceived argument count has changed. Tests " | |
314 "must be updated."); | |
315 std::unique_ptr<ResourceMsg_DataReceived::Param> params = | |
316 base::MakeUnique<ResourceMsg_DataReceived::Param>(); | |
317 if (!ResourceMsg_DataReceived::Read(msg, params.get())) | |
318 return nullptr; | |
319 return params; | |
320 } | |
321 | |
322 std::unique_ptr<ResourceMsg_InlinedDataChunkReceived::Param> | |
323 UnpackInlinedDataChunkReceivedIPC(const IPC::Message* msg) { | |
324 if (ResourceMsg_InlinedDataChunkReceived::ID != msg->type()) | |
325 return nullptr; | |
326 static_assert( | |
327 std::tuple_size<ResourceMsg_InlinedDataChunkReceived::Param>::value == 4u, | |
328 "ResourceMsg_InlinedDataChunkReceived argument count has changed. Tests " | |
329 "must be updated."); | |
330 std::unique_ptr<ResourceMsg_InlinedDataChunkReceived::Param> params = | |
331 base::MakeUnique<ResourceMsg_InlinedDataChunkReceived::Param>(); | |
332 if (!ResourceMsg_InlinedDataChunkReceived::Read(msg, params.get())) | |
333 return nullptr; | |
334 return params; | |
335 } | |
336 | |
337 TEST_F(AsyncResourceHandlerTest, Construct) { | |
338 CreateRequest(net::URLRequestTestJob::test_url_1()); | |
339 } | |
340 | |
341 TEST_F(AsyncResourceHandlerTest, OneChunkLengths) { | |
342 CreateStartAndWait(TestJob::test_url_medium_length()); | |
343 const auto& messages = filter_->messages(); | |
344 ASSERT_EQ(4u, messages.size()); | |
345 auto params = UnpackDataReceivedIPC(messages[2].get()); | |
346 ASSERT_TRUE(params); | |
347 | |
348 int encoded_data_length = std::get<3>(*params); | |
349 EXPECT_EQ(4163, encoded_data_length); | |
350 int encoded_body_length = std::get<4>(*params); | |
351 EXPECT_EQ(4096, encoded_body_length); | |
352 } | |
353 | |
354 TEST_F(AsyncResourceHandlerTest, InlinedChunkLengths) { | |
355 // TODO(ricea): Remove this Feature-enabling code once the feature is on by | |
356 // default. | |
357 auto feature_list = base::MakeUnique<base::FeatureList>(); | |
358 feature_list->InitializeFromCommandLine( | |
359 features::kOptimizeLoadingIPCForSmallResources.name, ""); | |
360 base::FeatureList::ClearInstanceForTesting(); | |
361 base::FeatureList::SetInstance(std::move(feature_list)); | |
362 | |
363 CreateStartAndWait(TestJob::test_url_short_length()); | |
364 const auto& messages = filter_->messages(); | |
365 ASSERT_EQ(3u, messages.size()); | |
366 auto params = UnpackInlinedDataChunkReceivedIPC(messages[1].get()); | |
367 ASSERT_TRUE(params); | |
368 | |
369 int encoded_data_length = std::get<2>(*params); | |
370 EXPECT_EQ(72, encoded_data_length); | |
371 int encoded_body_length = std::get<3>(*params); | |
372 EXPECT_EQ(8, encoded_body_length); | |
373 | |
374 base::FeatureList::ClearInstanceForTesting(); | |
375 base::FeatureList::SetInstance(base::MakeUnique<base::FeatureList>()); | |
376 } | |
377 | |
378 TEST_F(AsyncResourceHandlerTest, TwoChunksLengths) { | |
379 CreateStartAndWait(TestJob::test_url_large_length()); | |
380 const auto& messages = filter_->messages(); | |
381 ASSERT_EQ(5u, messages.size()); | |
382 auto params = UnpackDataReceivedIPC(messages[2].get()); | |
383 ASSERT_TRUE(params); | |
384 | |
385 int encoded_data_length = std::get<3>(*params); | |
386 EXPECT_EQ(32836, encoded_data_length); | |
387 int encoded_body_length = std::get<4>(*params); | |
388 EXPECT_EQ(32768, encoded_body_length); | |
389 | |
390 params = UnpackDataReceivedIPC(messages[3].get()); | |
391 ASSERT_TRUE(params); | |
392 encoded_data_length = std::get<3>(*params); | |
393 EXPECT_EQ(32768, encoded_data_length); | |
394 encoded_body_length = std::get<4>(*params); | |
395 EXPECT_EQ(32768, encoded_body_length); | |
396 } | |
397 | |
398 } // namespace | |
399 | |
400 } // namespace content | |
OLD | NEW |