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"
37 using testing::Property
;
38 using testing::Return
;
40 namespace navigation_interception
{
44 const char kTestUrl
[] = "http://www.test.com/";
45 const char kUnsafeTestUrl
[] = "about:crash";
47 // The MS C++ compiler complains about not being able to resolve which url()
48 // method (const or non-const) to use if we use the Property matcher to check
49 // the return value of the NavigationParams::url() method.
50 // It is possible to suppress the error by specifying the types directly but
51 // that results in very ugly syntax, which is why these custom matchers are
53 MATCHER(NavigationParamsUrlIsTest
, "") {
54 return arg
.url() == GURL(kTestUrl
);
57 MATCHER(NavigationParamsUrlIsSafe
, "") {
58 return arg
.url() != GURL(kUnsafeTestUrl
);
64 // MockInterceptCallbackReceiver ----------------------------------------------
66 class MockInterceptCallbackReceiver
{
68 MOCK_METHOD2(ShouldIgnoreNavigation
,
69 bool(content::WebContents
* source
,
70 const NavigationParams
& navigation_params
));
73 // MockResourceController -----------------------------------------------------
74 class MockResourceController
: public content::ResourceController
{
82 MockResourceController()
86 Status
status() const { return status_
; }
88 // ResourceController:
89 virtual void Cancel() OVERRIDE
{
92 virtual void CancelAndIgnore() OVERRIDE
{
95 virtual void CancelWithError(int error_code
) OVERRIDE
{
98 virtual void Resume() OVERRIDE
{
99 DCHECK(status_
== UNKNOWN
);
107 // TestIOThreadState ----------------------------------------------------------
110 REDIRECT_MODE_NO_REDIRECT
,
114 class TestIOThreadState
{
116 TestIOThreadState(const GURL
& url
,
117 int render_process_id
,
119 const std::string
& request_method
,
120 RedirectMode redirect_mode
,
121 MockInterceptCallbackReceiver
* callback_receiver
)
122 : resource_context_(&test_url_request_context_
),
124 net::DEFAULT_PRIORITY
,
126 resource_context_
.GetRequestContext()) {
127 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
128 if (render_process_id
!= MSG_ROUTING_NONE
&&
129 render_frame_id
!= MSG_ROUTING_NONE
) {
130 content::ResourceRequestInfo::AllocateForTesting(
132 ResourceType::MAIN_FRAME
,
139 throttle_
.reset(new InterceptNavigationResourceThrottle(
141 base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation
,
142 base::Unretained(callback_receiver
))));
143 throttle_
->set_controller_for_testing(&throttle_controller_
);
144 request_
.set_method(request_method
);
146 if (redirect_mode
== REDIRECT_MODE_302
) {
147 net::HttpResponseInfo
& response_info
=
148 const_cast<net::HttpResponseInfo
&>(request_
.response_info());
149 response_info
.headers
= new net::HttpResponseHeaders(
150 "Status: 302 Found\0\0");
154 void ThrottleWillStartRequest(bool* defer
) {
155 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
156 throttle_
->WillStartRequest(defer
);
159 void ThrottleWillRedirectRequest(const GURL
& new_url
, bool* defer
) {
160 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
161 throttle_
->WillRedirectRequest(new_url
, defer
);
164 bool request_resumed() const {
165 return throttle_controller_
.status() ==
166 MockResourceController::RESUMED
;
169 bool request_cancelled() const {
170 return throttle_controller_
.status() ==
171 MockResourceController::CANCELLED
;
175 net::TestURLRequestContext test_url_request_context_
;
176 content::MockResourceContext resource_context_
;
177 net::URLRequest request_
;
178 scoped_ptr
<InterceptNavigationResourceThrottle
> throttle_
;
179 MockResourceController throttle_controller_
;
182 // InterceptNavigationResourceThrottleTest ------------------------------------
184 class InterceptNavigationResourceThrottleTest
185 : public content::RenderViewHostTestHarness
{
187 InterceptNavigationResourceThrottleTest()
188 : mock_callback_receiver_(new MockInterceptCallbackReceiver()),
189 io_thread_state_(NULL
) {
192 virtual void SetUp() OVERRIDE
{
193 RenderViewHostTestHarness::SetUp();
196 virtual void TearDown() OVERRIDE
{
198 web_contents()->SetDelegate(NULL
);
200 content::BrowserThread::DeleteSoon(
201 content::BrowserThread::IO
, FROM_HERE
, io_thread_state_
);
203 RenderViewHostTestHarness::TearDown();
206 void SetIOThreadState(TestIOThreadState
* io_thread_state
) {
207 io_thread_state_
= io_thread_state
;
210 void RunThrottleWillStartRequestOnIOThread(
212 const std::string
& request_method
,
213 RedirectMode redirect_mode
,
214 int render_process_id
,
217 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO
));
218 TestIOThreadState
* io_thread_state
=
219 new TestIOThreadState(url
, render_process_id
, render_frame_id
,
220 request_method
, redirect_mode
,
221 mock_callback_receiver_
.get());
223 SetIOThreadState(io_thread_state
);
225 if (redirect_mode
== REDIRECT_MODE_NO_REDIRECT
)
226 io_thread_state
->ThrottleWillStartRequest(defer
);
228 io_thread_state
->ThrottleWillRedirectRequest(url
, defer
);
232 enum ShouldIgnoreNavigationCallbackAction
{
237 void SetUpWebContentsDelegateAndDrainRunLoop(
238 ShouldIgnoreNavigationCallbackAction callback_action
,
240 ON_CALL(*mock_callback_receiver_
, ShouldIgnoreNavigation(_
, _
))
241 .WillByDefault(Return(callback_action
== IgnoreNavigation
));
242 EXPECT_CALL(*mock_callback_receiver_
,
243 ShouldIgnoreNavigation(web_contents(),
244 NavigationParamsUrlIsTest()))
247 content::BrowserThread::PostTask(
248 content::BrowserThread::IO
,
251 &InterceptNavigationResourceThrottleTest::
252 RunThrottleWillStartRequestOnIOThread
,
253 base::Unretained(this),
256 REDIRECT_MODE_NO_REDIRECT
,
257 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
258 web_contents()->GetMainFrame()->GetRoutingID(),
259 base::Unretained(defer
)));
261 // Wait for the request to finish processing.
262 base::RunLoop().RunUntilIdle();
265 void WaitForPreviouslyScheduledIoThreadWork() {
266 base::WaitableEvent
io_thread_work_done(true, false);
267 content::BrowserThread::PostTask(
268 content::BrowserThread::IO
,
271 &base::WaitableEvent::Signal
,
272 base::Unretained(&io_thread_work_done
)));
273 io_thread_work_done
.Wait();
276 scoped_ptr
<MockInterceptCallbackReceiver
> mock_callback_receiver_
;
277 TestIOThreadState
* io_thread_state_
;
280 TEST_F(InterceptNavigationResourceThrottleTest
,
281 RequestDeferredAndResumedIfNavigationNotIgnored
) {
283 SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation
, &defer
);
286 ASSERT_TRUE(io_thread_state_
);
287 EXPECT_TRUE(io_thread_state_
->request_resumed());
290 TEST_F(InterceptNavigationResourceThrottleTest
,
291 RequestDeferredAndCancelledIfNavigationIgnored
) {
293 SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation
, &defer
);
296 ASSERT_TRUE(io_thread_state_
);
297 EXPECT_TRUE(io_thread_state_
->request_cancelled());
300 TEST_F(InterceptNavigationResourceThrottleTest
,
301 NoCallbackMadeIfContentsDeletedWhileThrottleRunning
) {
304 // The tested scenario is when the WebContents is deleted after the
305 // ResourceThrottle has finished processing on the IO thread but before the
306 // UI thread callback has been processed. Since both threads in this test
307 // are serviced by one message loop, the post order is the execution order.
308 EXPECT_CALL(*mock_callback_receiver_
,
309 ShouldIgnoreNavigation(_
, _
))
312 content::BrowserThread::PostTask(
313 content::BrowserThread::IO
,
316 &InterceptNavigationResourceThrottleTest::
317 RunThrottleWillStartRequestOnIOThread
,
318 base::Unretained(this),
321 REDIRECT_MODE_NO_REDIRECT
,
322 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
323 web_contents()->GetMainFrame()->GetRoutingID(),
324 base::Unretained(&defer
)));
326 content::BrowserThread::PostTask(
327 content::BrowserThread::UI
,
330 &RenderViewHostTestHarness::DeleteContents
,
331 base::Unretained(this)));
333 // The WebContents will now be deleted and only after that will the UI-thread
334 // callback posted by the ResourceThrottle be executed.
335 base::RunLoop().RunUntilIdle();
338 ASSERT_TRUE(io_thread_state_
);
339 EXPECT_TRUE(io_thread_state_
->request_resumed());
342 TEST_F(InterceptNavigationResourceThrottleTest
,
343 RequestNotDeferredForRequestNotAssociatedWithARenderView
) {
346 content::BrowserThread::PostTask(
347 content::BrowserThread::IO
,
350 &InterceptNavigationResourceThrottleTest::
351 RunThrottleWillStartRequestOnIOThread
,
352 base::Unretained(this),
355 REDIRECT_MODE_NO_REDIRECT
,
358 base::Unretained(&defer
)));
360 // Wait for the request to finish processing.
361 base::RunLoop().RunUntilIdle();
366 TEST_F(InterceptNavigationResourceThrottleTest
,
367 CallbackCalledWithFilteredUrl
) {
370 ON_CALL(*mock_callback_receiver_
,
371 ShouldIgnoreNavigation(_
, NavigationParamsUrlIsSafe()))
372 .WillByDefault(Return(false));
373 EXPECT_CALL(*mock_callback_receiver_
,
374 ShouldIgnoreNavigation(_
, NavigationParamsUrlIsSafe()))
377 content::BrowserThread::PostTask(
378 content::BrowserThread::IO
,
381 &InterceptNavigationResourceThrottleTest::
382 RunThrottleWillStartRequestOnIOThread
,
383 base::Unretained(this),
384 GURL(kUnsafeTestUrl
),
386 REDIRECT_MODE_NO_REDIRECT
,
387 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
388 web_contents()->GetMainFrame()->GetRoutingID(),
389 base::Unretained(&defer
)));
391 // Wait for the request to finish processing.
392 base::RunLoop().RunUntilIdle();
395 TEST_F(InterceptNavigationResourceThrottleTest
,
396 CallbackIsPostFalseForGet
) {
399 EXPECT_CALL(*mock_callback_receiver_
,
400 ShouldIgnoreNavigation(_
, AllOf(
401 NavigationParamsUrlIsSafe(),
402 Property(&NavigationParams::is_post
, Eq(false)))))
403 .WillOnce(Return(false));
405 content::BrowserThread::PostTask(
406 content::BrowserThread::IO
,
409 &InterceptNavigationResourceThrottleTest::
410 RunThrottleWillStartRequestOnIOThread
,
411 base::Unretained(this),
414 REDIRECT_MODE_NO_REDIRECT
,
415 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
416 web_contents()->GetMainFrame()->GetRoutingID(),
417 base::Unretained(&defer
)));
419 // Wait for the request to finish processing.
420 base::RunLoop().RunUntilIdle();
423 TEST_F(InterceptNavigationResourceThrottleTest
,
424 CallbackIsPostTrueForPost
) {
427 EXPECT_CALL(*mock_callback_receiver_
,
428 ShouldIgnoreNavigation(_
, AllOf(
429 NavigationParamsUrlIsSafe(),
430 Property(&NavigationParams::is_post
, Eq(true)))))
431 .WillOnce(Return(false));
433 content::BrowserThread::PostTask(
434 content::BrowserThread::IO
,
437 &InterceptNavigationResourceThrottleTest::
438 RunThrottleWillStartRequestOnIOThread
,
439 base::Unretained(this),
442 REDIRECT_MODE_NO_REDIRECT
,
443 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
444 web_contents()->GetMainFrame()->GetRoutingID(),
445 base::Unretained(&defer
)));
447 // Wait for the request to finish processing.
448 base::RunLoop().RunUntilIdle();
451 TEST_F(InterceptNavigationResourceThrottleTest
,
452 CallbackIsPostFalseForPostConvertedToGetBy302
) {
455 EXPECT_CALL(*mock_callback_receiver_
,
456 ShouldIgnoreNavigation(_
, AllOf(
457 NavigationParamsUrlIsSafe(),
458 Property(&NavigationParams::is_post
, Eq(false)))))
459 .WillOnce(Return(false));
461 content::BrowserThread::PostTask(
462 content::BrowserThread::IO
,
465 &InterceptNavigationResourceThrottleTest::
466 RunThrottleWillStartRequestOnIOThread
,
467 base::Unretained(this),
471 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
472 web_contents()->GetMainFrame()->GetRoutingID(),
473 base::Unretained(&defer
)));
475 // Wait for the request to finish processing.
476 base::RunLoop().RunUntilIdle();
479 } // namespace navigation_interception