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