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.
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_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/common/page_transition_types.h"
24 #include "content/public/test/mock_resource_context.h"
25 #include "content/public/test/test_renderer_host.h"
26 #include "net/base/request_priority.h"
27 #include "net/http/http_response_headers.h"
28 #include "net/http/http_response_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"
34 using content::ResourceType
;
38 using testing::Property
;
39 using testing::Return
;
41 namespace navigation_interception
{
45 const char kTestUrl
[] = "http://www.test.com/";
46 const char kUnsafeTestUrl
[] = "about:crash";
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
54 MATCHER(NavigationParamsUrlIsTest
, "") {
55 return arg
.url() == GURL(kTestUrl
);
58 MATCHER(NavigationParamsUrlIsSafe
, "") {
59 return arg
.url() != GURL(kUnsafeTestUrl
);
65 // MockInterceptCallbackReceiver ----------------------------------------------
67 class MockInterceptCallbackReceiver
{
69 MOCK_METHOD2(ShouldIgnoreNavigation
,
70 bool(content::WebContents
* source
,
71 const NavigationParams
& navigation_params
));
74 // MockResourceController -----------------------------------------------------
75 class MockResourceController
: public content::ResourceController
{
83 MockResourceController()
87 Status
status() const { return status_
; }
89 // ResourceController:
90 virtual void Cancel() OVERRIDE
{
93 virtual void CancelAndIgnore() OVERRIDE
{
96 virtual void CancelWithError(int error_code
) OVERRIDE
{
99 virtual void Resume() OVERRIDE
{
100 DCHECK(status_
== UNKNOWN
);
108 // TestIOThreadState ----------------------------------------------------------
111 REDIRECT_MODE_NO_REDIRECT
,
115 class TestIOThreadState
{
117 TestIOThreadState(const GURL
& url
,
118 int render_process_id
,
120 const std::string
& request_method
,
121 RedirectMode redirect_mode
,
122 MockInterceptCallbackReceiver
* callback_receiver
)
123 : resource_context_(&test_url_request_context_
),
124 request_(resource_context_
.GetRequestContext()->CreateRequest(
126 net::DEFAULT_PRIORITY
,
128 NULL
/* cookie_store */)) {
129 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
130 if (render_process_id
!= MSG_ROUTING_NONE
&&
131 render_frame_id
!= MSG_ROUTING_NONE
) {
132 content::ResourceRequestInfo::AllocateForTesting(
134 content::RESOURCE_TYPE_MAIN_FRAME
,
141 throttle_
.reset(new InterceptNavigationResourceThrottle(
143 base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation
,
144 base::Unretained(callback_receiver
))));
145 throttle_
->set_controller_for_testing(&throttle_controller_
);
146 request_
->set_method(request_method
);
148 if (redirect_mode
== REDIRECT_MODE_302
) {
149 net::HttpResponseInfo
& response_info
=
150 const_cast<net::HttpResponseInfo
&>(request_
->response_info());
151 response_info
.headers
= new net::HttpResponseHeaders(
152 "Status: 302 Found\0\0");
156 void ThrottleWillStartRequest(bool* defer
) {
157 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
158 throttle_
->WillStartRequest(defer
);
161 void ThrottleWillRedirectRequest(const GURL
& new_url
, bool* defer
) {
162 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
163 throttle_
->WillRedirectRequest(new_url
, defer
);
166 bool request_resumed() const {
167 return throttle_controller_
.status() ==
168 MockResourceController::RESUMED
;
171 bool request_cancelled() const {
172 return throttle_controller_
.status() ==
173 MockResourceController::CANCELLED
;
177 net::TestURLRequestContext test_url_request_context_
;
178 content::MockResourceContext resource_context_
;
179 scoped_ptr
<net::URLRequest
> request_
;
180 scoped_ptr
<InterceptNavigationResourceThrottle
> throttle_
;
181 MockResourceController throttle_controller_
;
184 // InterceptNavigationResourceThrottleTest ------------------------------------
186 class InterceptNavigationResourceThrottleTest
187 : public content::RenderViewHostTestHarness
{
189 InterceptNavigationResourceThrottleTest()
190 : mock_callback_receiver_(new MockInterceptCallbackReceiver()),
191 io_thread_state_(NULL
) {
194 virtual void SetUp() OVERRIDE
{
195 RenderViewHostTestHarness::SetUp();
198 virtual void TearDown() OVERRIDE
{
200 web_contents()->SetDelegate(NULL
);
202 content::BrowserThread::DeleteSoon(
203 content::BrowserThread::IO
, FROM_HERE
, io_thread_state_
);
205 RenderViewHostTestHarness::TearDown();
208 void SetIOThreadState(TestIOThreadState
* io_thread_state
) {
209 io_thread_state_
= io_thread_state
;
212 void RunThrottleWillStartRequestOnIOThread(
214 const std::string
& request_method
,
215 RedirectMode redirect_mode
,
216 int render_process_id
,
219 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
220 TestIOThreadState
* io_thread_state
=
221 new TestIOThreadState(url
, render_process_id
, render_frame_id
,
222 request_method
, redirect_mode
,
223 mock_callback_receiver_
.get());
225 SetIOThreadState(io_thread_state
);
227 if (redirect_mode
== REDIRECT_MODE_NO_REDIRECT
)
228 io_thread_state
->ThrottleWillStartRequest(defer
);
230 io_thread_state
->ThrottleWillRedirectRequest(url
, defer
);
234 enum ShouldIgnoreNavigationCallbackAction
{
239 void SetUpWebContentsDelegateAndDrainRunLoop(
240 ShouldIgnoreNavigationCallbackAction callback_action
,
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()))
249 content::BrowserThread::PostTask(
250 content::BrowserThread::IO
,
253 &InterceptNavigationResourceThrottleTest::
254 RunThrottleWillStartRequestOnIOThread
,
255 base::Unretained(this),
258 REDIRECT_MODE_NO_REDIRECT
,
259 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
260 web_contents()->GetMainFrame()->GetRoutingID(),
261 base::Unretained(defer
)));
263 // Wait for the request to finish processing.
264 base::RunLoop().RunUntilIdle();
267 void WaitForPreviouslyScheduledIoThreadWork() {
268 base::WaitableEvent
io_thread_work_done(true, false);
269 content::BrowserThread::PostTask(
270 content::BrowserThread::IO
,
273 &base::WaitableEvent::Signal
,
274 base::Unretained(&io_thread_work_done
)));
275 io_thread_work_done
.Wait();
278 scoped_ptr
<MockInterceptCallbackReceiver
> mock_callback_receiver_
;
279 TestIOThreadState
* io_thread_state_
;
282 TEST_F(InterceptNavigationResourceThrottleTest
,
283 RequestDeferredAndResumedIfNavigationNotIgnored
) {
285 SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation
, &defer
);
288 ASSERT_TRUE(io_thread_state_
);
289 EXPECT_TRUE(io_thread_state_
->request_resumed());
292 TEST_F(InterceptNavigationResourceThrottleTest
,
293 RequestDeferredAndCancelledIfNavigationIgnored
) {
295 SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation
, &defer
);
298 ASSERT_TRUE(io_thread_state_
);
299 EXPECT_TRUE(io_thread_state_
->request_cancelled());
302 TEST_F(InterceptNavigationResourceThrottleTest
,
303 NoCallbackMadeIfContentsDeletedWhileThrottleRunning
) {
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(_
, _
))
314 content::BrowserThread::PostTask(
315 content::BrowserThread::IO
,
318 &InterceptNavigationResourceThrottleTest::
319 RunThrottleWillStartRequestOnIOThread
,
320 base::Unretained(this),
323 REDIRECT_MODE_NO_REDIRECT
,
324 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
325 web_contents()->GetMainFrame()->GetRoutingID(),
326 base::Unretained(&defer
)));
328 content::BrowserThread::PostTask(
329 content::BrowserThread::UI
,
332 &RenderViewHostTestHarness::DeleteContents
,
333 base::Unretained(this)));
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();
340 ASSERT_TRUE(io_thread_state_
);
341 EXPECT_TRUE(io_thread_state_
->request_resumed());
344 TEST_F(InterceptNavigationResourceThrottleTest
,
345 RequestNotDeferredForRequestNotAssociatedWithARenderView
) {
348 content::BrowserThread::PostTask(
349 content::BrowserThread::IO
,
352 &InterceptNavigationResourceThrottleTest::
353 RunThrottleWillStartRequestOnIOThread
,
354 base::Unretained(this),
357 REDIRECT_MODE_NO_REDIRECT
,
360 base::Unretained(&defer
)));
362 // Wait for the request to finish processing.
363 base::RunLoop().RunUntilIdle();
368 TEST_F(InterceptNavigationResourceThrottleTest
,
369 CallbackCalledWithFilteredUrl
) {
372 ON_CALL(*mock_callback_receiver_
,
373 ShouldIgnoreNavigation(_
, NavigationParamsUrlIsSafe()))
374 .WillByDefault(Return(false));
375 EXPECT_CALL(*mock_callback_receiver_
,
376 ShouldIgnoreNavigation(_
, NavigationParamsUrlIsSafe()))
379 content::BrowserThread::PostTask(
380 content::BrowserThread::IO
,
383 &InterceptNavigationResourceThrottleTest::
384 RunThrottleWillStartRequestOnIOThread
,
385 base::Unretained(this),
386 GURL(kUnsafeTestUrl
),
388 REDIRECT_MODE_NO_REDIRECT
,
389 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
390 web_contents()->GetMainFrame()->GetRoutingID(),
391 base::Unretained(&defer
)));
393 // Wait for the request to finish processing.
394 base::RunLoop().RunUntilIdle();
397 TEST_F(InterceptNavigationResourceThrottleTest
,
398 CallbackIsPostFalseForGet
) {
401 EXPECT_CALL(*mock_callback_receiver_
,
402 ShouldIgnoreNavigation(_
, AllOf(
403 NavigationParamsUrlIsSafe(),
404 Property(&NavigationParams::is_post
, Eq(false)))))
405 .WillOnce(Return(false));
407 content::BrowserThread::PostTask(
408 content::BrowserThread::IO
,
411 &InterceptNavigationResourceThrottleTest::
412 RunThrottleWillStartRequestOnIOThread
,
413 base::Unretained(this),
416 REDIRECT_MODE_NO_REDIRECT
,
417 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
418 web_contents()->GetMainFrame()->GetRoutingID(),
419 base::Unretained(&defer
)));
421 // Wait for the request to finish processing.
422 base::RunLoop().RunUntilIdle();
425 TEST_F(InterceptNavigationResourceThrottleTest
,
426 CallbackIsPostTrueForPost
) {
429 EXPECT_CALL(*mock_callback_receiver_
,
430 ShouldIgnoreNavigation(_
, AllOf(
431 NavigationParamsUrlIsSafe(),
432 Property(&NavigationParams::is_post
, Eq(true)))))
433 .WillOnce(Return(false));
435 content::BrowserThread::PostTask(
436 content::BrowserThread::IO
,
439 &InterceptNavigationResourceThrottleTest::
440 RunThrottleWillStartRequestOnIOThread
,
441 base::Unretained(this),
444 REDIRECT_MODE_NO_REDIRECT
,
445 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
446 web_contents()->GetMainFrame()->GetRoutingID(),
447 base::Unretained(&defer
)));
449 // Wait for the request to finish processing.
450 base::RunLoop().RunUntilIdle();
453 TEST_F(InterceptNavigationResourceThrottleTest
,
454 CallbackIsPostFalseForPostConvertedToGetBy302
) {
457 EXPECT_CALL(*mock_callback_receiver_
,
458 ShouldIgnoreNavigation(_
, AllOf(
459 NavigationParamsUrlIsSafe(),
460 Property(&NavigationParams::is_post
, Eq(false)))))
461 .WillOnce(Return(false));
463 content::BrowserThread::PostTask(
464 content::BrowserThread::IO
,
467 &InterceptNavigationResourceThrottleTest::
468 RunThrottleWillStartRequestOnIOThread
,
469 base::Unretained(this),
473 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
474 web_contents()->GetMainFrame()->GetRoutingID(),
475 base::Unretained(&defer
)));
477 // Wait for the request to finish processing.
478 base::RunLoop().RunUntilIdle();
481 } // namespace navigation_interception