OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 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 "base/bind.h" | |
6 #include "base/bind_helpers.h" | |
7 #include "base/memory/scoped_ptr.h" | |
8 #include "base/run_loop.h" | |
9 #include "base/synchronization/waitable_event.h" | |
10 #include "components/navigation_interception/intercept_navigation_resource_throt
tle.h" | |
11 #include "components/navigation_interception/navigation_params.h" | |
12 #include "content/public/browser/browser_thread.h" | |
13 #include "content/public/browser/render_frame_host.h" | |
14 #include "content/public/browser/render_process_host.h" | |
15 #include "content/public/browser/resource_context.h" | |
16 #include "content/public/browser/resource_controller.h" | |
17 #include "content/public/browser/resource_dispatcher_host.h" | |
18 #include "content/public/browser/resource_dispatcher_host_delegate.h" | |
19 #include "content/public/browser/resource_request_info.h" | |
20 #include "content/public/browser/resource_throttle.h" | |
21 #include "content/public/browser/web_contents.h" | |
22 #include "content/public/browser/web_contents_delegate.h" | |
23 #include "content/public/test/mock_resource_context.h" | |
24 #include "content/public/test/test_renderer_host.h" | |
25 #include "net/base/request_priority.h" | |
26 #include "net/http/http_response_headers.h" | |
27 #include "net/http/http_response_info.h" | |
28 #include "net/url_request/redirect_info.h" | |
29 #include "net/url_request/url_request.h" | |
30 #include "net/url_request/url_request_test_util.h" | |
31 #include "testing/gmock/include/gmock/gmock.h" | |
32 #include "testing/gtest/include/gtest/gtest.h" | |
33 | |
34 using content::ResourceType; | |
35 using testing::_; | |
36 using testing::Eq; | |
37 using testing::Ne; | |
38 using testing::Property; | |
39 using testing::Return; | |
40 | |
41 namespace navigation_interception { | |
42 | |
43 namespace { | |
44 | |
45 const char kTestUrl[] = "http://www.test.com/"; | |
46 const char kUnsafeTestUrl[] = "about:crash"; | |
47 | |
48 // The MS C++ compiler complains about not being able to resolve which url() | |
49 // method (const or non-const) to use if we use the Property matcher to check | |
50 // the return value of the NavigationParams::url() method. | |
51 // It is possible to suppress the error by specifying the types directly but | |
52 // that results in very ugly syntax, which is why these custom matchers are | |
53 // used instead. | |
54 MATCHER(NavigationParamsUrlIsTest, "") { | |
55 return arg.url() == GURL(kTestUrl); | |
56 } | |
57 | |
58 MATCHER(NavigationParamsUrlIsSafe, "") { | |
59 return arg.url() != GURL(kUnsafeTestUrl); | |
60 } | |
61 | |
62 } // namespace | |
63 | |
64 | |
65 // MockInterceptCallbackReceiver ---------------------------------------------- | |
66 | |
67 class MockInterceptCallbackReceiver { | |
68 public: | |
69 MOCK_METHOD2(ShouldIgnoreNavigation, | |
70 bool(content::WebContents* source, | |
71 const NavigationParams& navigation_params)); | |
72 }; | |
73 | |
74 // MockResourceController ----------------------------------------------------- | |
75 class MockResourceController : public content::ResourceController { | |
76 public: | |
77 enum Status { | |
78 UNKNOWN, | |
79 RESUMED, | |
80 CANCELLED | |
81 }; | |
82 | |
83 MockResourceController() | |
84 : status_(UNKNOWN) { | |
85 } | |
86 | |
87 Status status() const { return status_; } | |
88 | |
89 // ResourceController: | |
90 void Cancel() override { NOTREACHED(); } | |
91 void CancelAndIgnore() override { status_ = CANCELLED; } | |
92 void CancelWithError(int error_code) override { NOTREACHED(); } | |
93 void Resume() override { | |
94 DCHECK(status_ == UNKNOWN); | |
95 status_ = RESUMED; | |
96 } | |
97 | |
98 private: | |
99 Status status_; | |
100 }; | |
101 | |
102 // TestIOThreadState ---------------------------------------------------------- | |
103 | |
104 enum RedirectMode { | |
105 REDIRECT_MODE_NO_REDIRECT, | |
106 REDIRECT_MODE_302, | |
107 }; | |
108 | |
109 class TestIOThreadState { | |
110 public: | |
111 TestIOThreadState(const GURL& url, | |
112 int render_process_id, | |
113 int render_frame_id, | |
114 const std::string& request_method, | |
115 RedirectMode redirect_mode, | |
116 MockInterceptCallbackReceiver* callback_receiver) | |
117 : resource_context_(&test_url_request_context_), | |
118 request_(resource_context_.GetRequestContext()->CreateRequest( | |
119 url, | |
120 net::DEFAULT_PRIORITY, | |
121 NULL /* delegate */)) { | |
122 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | |
123 if (render_process_id != MSG_ROUTING_NONE && | |
124 render_frame_id != MSG_ROUTING_NONE) { | |
125 content::ResourceRequestInfo::AllocateForTesting( | |
126 request_.get(), | |
127 content::RESOURCE_TYPE_MAIN_FRAME, | |
128 &resource_context_, | |
129 render_process_id, | |
130 MSG_ROUTING_NONE, | |
131 render_frame_id, | |
132 true, // is_main_frame | |
133 false, // parent_is_main_frame | |
134 true, // allow_download | |
135 false); // is_async | |
136 } | |
137 throttle_.reset(new InterceptNavigationResourceThrottle( | |
138 request_.get(), | |
139 base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation, | |
140 base::Unretained(callback_receiver)))); | |
141 throttle_->set_controller_for_testing(&throttle_controller_); | |
142 request_->set_method(request_method); | |
143 | |
144 if (redirect_mode == REDIRECT_MODE_302) { | |
145 net::HttpResponseInfo& response_info = | |
146 const_cast<net::HttpResponseInfo&>(request_->response_info()); | |
147 response_info.headers = new net::HttpResponseHeaders( | |
148 "Status: 302 Found\0\0"); | |
149 } | |
150 } | |
151 | |
152 void ThrottleWillStartRequest(bool* defer) { | |
153 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | |
154 throttle_->WillStartRequest(defer); | |
155 } | |
156 | |
157 void ThrottleWillRedirectRequest(const net::RedirectInfo& redirect_info, | |
158 bool* defer) { | |
159 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | |
160 throttle_->WillRedirectRequest(redirect_info, defer); | |
161 } | |
162 | |
163 bool request_resumed() const { | |
164 return throttle_controller_.status() == | |
165 MockResourceController::RESUMED; | |
166 } | |
167 | |
168 bool request_cancelled() const { | |
169 return throttle_controller_.status() == | |
170 MockResourceController::CANCELLED; | |
171 } | |
172 | |
173 private: | |
174 net::TestURLRequestContext test_url_request_context_; | |
175 content::MockResourceContext resource_context_; | |
176 scoped_ptr<net::URLRequest> request_; | |
177 scoped_ptr<InterceptNavigationResourceThrottle> throttle_; | |
178 MockResourceController throttle_controller_; | |
179 }; | |
180 | |
181 // InterceptNavigationResourceThrottleTest ------------------------------------ | |
182 | |
183 class InterceptNavigationResourceThrottleTest | |
184 : public content::RenderViewHostTestHarness { | |
185 public: | |
186 InterceptNavigationResourceThrottleTest() | |
187 : mock_callback_receiver_(new MockInterceptCallbackReceiver()), | |
188 io_thread_state_(NULL) { | |
189 } | |
190 | |
191 void SetUp() override { RenderViewHostTestHarness::SetUp(); } | |
192 | |
193 void TearDown() override { | |
194 if (web_contents()) | |
195 web_contents()->SetDelegate(NULL); | |
196 | |
197 content::BrowserThread::DeleteSoon( | |
198 content::BrowserThread::IO, FROM_HERE, io_thread_state_); | |
199 | |
200 RenderViewHostTestHarness::TearDown(); | |
201 } | |
202 | |
203 void SetIOThreadState(TestIOThreadState* io_thread_state) { | |
204 io_thread_state_ = io_thread_state; | |
205 } | |
206 | |
207 void RunThrottleWillStartRequestOnIOThread( | |
208 const GURL& url, | |
209 const std::string& request_method, | |
210 RedirectMode redirect_mode, | |
211 int render_process_id, | |
212 int render_frame_id, | |
213 bool* defer) { | |
214 DCHECK_CURRENTLY_ON(content::BrowserThread::IO); | |
215 TestIOThreadState* io_thread_state = | |
216 new TestIOThreadState(url, render_process_id, render_frame_id, | |
217 request_method, redirect_mode, | |
218 mock_callback_receiver_.get()); | |
219 | |
220 SetIOThreadState(io_thread_state); | |
221 | |
222 if (redirect_mode == REDIRECT_MODE_NO_REDIRECT) { | |
223 io_thread_state->ThrottleWillStartRequest(defer); | |
224 } else { | |
225 // 302 redirects convert POSTs to gets. | |
226 net::RedirectInfo redirect_info; | |
227 redirect_info.new_url = url; | |
228 redirect_info.new_method = "GET"; | |
229 io_thread_state->ThrottleWillRedirectRequest(redirect_info, defer); | |
230 } | |
231 } | |
232 | |
233 protected: | |
234 enum ShouldIgnoreNavigationCallbackAction { | |
235 IgnoreNavigation, | |
236 DontIgnoreNavigation | |
237 }; | |
238 | |
239 void SetUpWebContentsDelegateAndDrainRunLoop( | |
240 ShouldIgnoreNavigationCallbackAction callback_action, | |
241 bool* defer) { | |
242 ON_CALL(*mock_callback_receiver_, ShouldIgnoreNavigation(_, _)) | |
243 .WillByDefault(Return(callback_action == IgnoreNavigation)); | |
244 EXPECT_CALL(*mock_callback_receiver_, | |
245 ShouldIgnoreNavigation(web_contents(), | |
246 NavigationParamsUrlIsTest())) | |
247 .Times(1); | |
248 | |
249 content::BrowserThread::PostTask( | |
250 content::BrowserThread::IO, | |
251 FROM_HERE, | |
252 base::Bind( | |
253 &InterceptNavigationResourceThrottleTest:: | |
254 RunThrottleWillStartRequestOnIOThread, | |
255 base::Unretained(this), | |
256 GURL(kTestUrl), | |
257 "GET", | |
258 REDIRECT_MODE_NO_REDIRECT, | |
259 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), | |
260 web_contents()->GetMainFrame()->GetRoutingID(), | |
261 base::Unretained(defer))); | |
262 | |
263 // Wait for the request to finish processing. | |
264 base::RunLoop().RunUntilIdle(); | |
265 } | |
266 | |
267 void WaitForPreviouslyScheduledIoThreadWork() { | |
268 base::WaitableEvent io_thread_work_done(true, false); | |
269 content::BrowserThread::PostTask( | |
270 content::BrowserThread::IO, | |
271 FROM_HERE, | |
272 base::Bind( | |
273 &base::WaitableEvent::Signal, | |
274 base::Unretained(&io_thread_work_done))); | |
275 io_thread_work_done.Wait(); | |
276 } | |
277 | |
278 scoped_ptr<MockInterceptCallbackReceiver> mock_callback_receiver_; | |
279 TestIOThreadState* io_thread_state_; | |
280 }; | |
281 | |
282 TEST_F(InterceptNavigationResourceThrottleTest, | |
283 RequestDeferredAndResumedIfNavigationNotIgnored) { | |
284 bool defer = false; | |
285 SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation, &defer); | |
286 | |
287 EXPECT_TRUE(defer); | |
288 ASSERT_TRUE(io_thread_state_); | |
289 EXPECT_TRUE(io_thread_state_->request_resumed()); | |
290 } | |
291 | |
292 TEST_F(InterceptNavigationResourceThrottleTest, | |
293 RequestDeferredAndCancelledIfNavigationIgnored) { | |
294 bool defer = false; | |
295 SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation, &defer); | |
296 | |
297 EXPECT_TRUE(defer); | |
298 ASSERT_TRUE(io_thread_state_); | |
299 EXPECT_TRUE(io_thread_state_->request_cancelled()); | |
300 } | |
301 | |
302 TEST_F(InterceptNavigationResourceThrottleTest, | |
303 NoCallbackMadeIfContentsDeletedWhileThrottleRunning) { | |
304 bool defer = false; | |
305 | |
306 // The tested scenario is when the WebContents is deleted after the | |
307 // ResourceThrottle has finished processing on the IO thread but before the | |
308 // UI thread callback has been processed. Since both threads in this test | |
309 // are serviced by one message loop, the post order is the execution order. | |
310 EXPECT_CALL(*mock_callback_receiver_, | |
311 ShouldIgnoreNavigation(_, _)) | |
312 .Times(0); | |
313 | |
314 content::BrowserThread::PostTask( | |
315 content::BrowserThread::IO, | |
316 FROM_HERE, | |
317 base::Bind( | |
318 &InterceptNavigationResourceThrottleTest:: | |
319 RunThrottleWillStartRequestOnIOThread, | |
320 base::Unretained(this), | |
321 GURL(kTestUrl), | |
322 "GET", | |
323 REDIRECT_MODE_NO_REDIRECT, | |
324 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), | |
325 web_contents()->GetMainFrame()->GetRoutingID(), | |
326 base::Unretained(&defer))); | |
327 | |
328 content::BrowserThread::PostTask( | |
329 content::BrowserThread::UI, | |
330 FROM_HERE, | |
331 base::Bind( | |
332 &RenderViewHostTestHarness::DeleteContents, | |
333 base::Unretained(this))); | |
334 | |
335 // The WebContents will now be deleted and only after that will the UI-thread | |
336 // callback posted by the ResourceThrottle be executed. | |
337 base::RunLoop().RunUntilIdle(); | |
338 | |
339 EXPECT_TRUE(defer); | |
340 ASSERT_TRUE(io_thread_state_); | |
341 EXPECT_TRUE(io_thread_state_->request_resumed()); | |
342 } | |
343 | |
344 TEST_F(InterceptNavigationResourceThrottleTest, | |
345 RequestNotDeferredForRequestNotAssociatedWithARenderView) { | |
346 bool defer = false; | |
347 | |
348 content::BrowserThread::PostTask( | |
349 content::BrowserThread::IO, | |
350 FROM_HERE, | |
351 base::Bind( | |
352 &InterceptNavigationResourceThrottleTest:: | |
353 RunThrottleWillStartRequestOnIOThread, | |
354 base::Unretained(this), | |
355 GURL(kTestUrl), | |
356 "GET", | |
357 REDIRECT_MODE_NO_REDIRECT, | |
358 MSG_ROUTING_NONE, | |
359 MSG_ROUTING_NONE, | |
360 base::Unretained(&defer))); | |
361 | |
362 // Wait for the request to finish processing. | |
363 base::RunLoop().RunUntilIdle(); | |
364 | |
365 EXPECT_FALSE(defer); | |
366 } | |
367 | |
368 TEST_F(InterceptNavigationResourceThrottleTest, | |
369 CallbackCalledWithFilteredUrl) { | |
370 bool defer = false; | |
371 | |
372 ON_CALL(*mock_callback_receiver_, | |
373 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) | |
374 .WillByDefault(Return(false)); | |
375 EXPECT_CALL(*mock_callback_receiver_, | |
376 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) | |
377 .Times(1); | |
378 | |
379 content::BrowserThread::PostTask( | |
380 content::BrowserThread::IO, | |
381 FROM_HERE, | |
382 base::Bind( | |
383 &InterceptNavigationResourceThrottleTest:: | |
384 RunThrottleWillStartRequestOnIOThread, | |
385 base::Unretained(this), | |
386 GURL(kUnsafeTestUrl), | |
387 "GET", | |
388 REDIRECT_MODE_NO_REDIRECT, | |
389 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), | |
390 web_contents()->GetMainFrame()->GetRoutingID(), | |
391 base::Unretained(&defer))); | |
392 | |
393 // Wait for the request to finish processing. | |
394 base::RunLoop().RunUntilIdle(); | |
395 } | |
396 | |
397 TEST_F(InterceptNavigationResourceThrottleTest, | |
398 CallbackIsPostFalseForGet) { | |
399 bool defer = false; | |
400 | |
401 EXPECT_CALL(*mock_callback_receiver_, | |
402 ShouldIgnoreNavigation(_, AllOf( | |
403 NavigationParamsUrlIsSafe(), | |
404 Property(&NavigationParams::is_post, Eq(false))))) | |
405 .WillOnce(Return(false)); | |
406 | |
407 content::BrowserThread::PostTask( | |
408 content::BrowserThread::IO, | |
409 FROM_HERE, | |
410 base::Bind( | |
411 &InterceptNavigationResourceThrottleTest:: | |
412 RunThrottleWillStartRequestOnIOThread, | |
413 base::Unretained(this), | |
414 GURL(kTestUrl), | |
415 "GET", | |
416 REDIRECT_MODE_NO_REDIRECT, | |
417 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), | |
418 web_contents()->GetMainFrame()->GetRoutingID(), | |
419 base::Unretained(&defer))); | |
420 | |
421 // Wait for the request to finish processing. | |
422 base::RunLoop().RunUntilIdle(); | |
423 } | |
424 | |
425 TEST_F(InterceptNavigationResourceThrottleTest, | |
426 CallbackIsPostTrueForPost) { | |
427 bool defer = false; | |
428 | |
429 EXPECT_CALL(*mock_callback_receiver_, | |
430 ShouldIgnoreNavigation(_, AllOf( | |
431 NavigationParamsUrlIsSafe(), | |
432 Property(&NavigationParams::is_post, Eq(true))))) | |
433 .WillOnce(Return(false)); | |
434 | |
435 content::BrowserThread::PostTask( | |
436 content::BrowserThread::IO, | |
437 FROM_HERE, | |
438 base::Bind( | |
439 &InterceptNavigationResourceThrottleTest:: | |
440 RunThrottleWillStartRequestOnIOThread, | |
441 base::Unretained(this), | |
442 GURL(kTestUrl), | |
443 "POST", | |
444 REDIRECT_MODE_NO_REDIRECT, | |
445 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), | |
446 web_contents()->GetMainFrame()->GetRoutingID(), | |
447 base::Unretained(&defer))); | |
448 | |
449 // Wait for the request to finish processing. | |
450 base::RunLoop().RunUntilIdle(); | |
451 } | |
452 | |
453 TEST_F(InterceptNavigationResourceThrottleTest, | |
454 CallbackIsPostFalseForPostConvertedToGetBy302) { | |
455 bool defer = false; | |
456 | |
457 EXPECT_CALL(*mock_callback_receiver_, | |
458 ShouldIgnoreNavigation(_, AllOf( | |
459 NavigationParamsUrlIsSafe(), | |
460 Property(&NavigationParams::is_post, Eq(false))))) | |
461 .WillOnce(Return(false)); | |
462 | |
463 content::BrowserThread::PostTask( | |
464 content::BrowserThread::IO, | |
465 FROM_HERE, | |
466 base::Bind( | |
467 &InterceptNavigationResourceThrottleTest:: | |
468 RunThrottleWillStartRequestOnIOThread, | |
469 base::Unretained(this), | |
470 GURL(kTestUrl), | |
471 "POST", | |
472 REDIRECT_MODE_302, | |
473 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), | |
474 web_contents()->GetMainFrame()->GetRoutingID(), | |
475 base::Unretained(&defer))); | |
476 | |
477 // Wait for the request to finish processing. | |
478 base::RunLoop().RunUntilIdle(); | |
479 } | |
480 | |
481 } // namespace navigation_interception | |
OLD | NEW |