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/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"
33 using content::ResourceType;
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 void Cancel() override { NOTREACHED(); }
90 void CancelAndIgnore() override { status_ = CANCELLED; }
91 void CancelWithError(int error_code) override { NOTREACHED(); }
92 void Resume() override {
93 DCHECK(status_ == UNKNOWN);
101 // TestIOThreadState ----------------------------------------------------------
104 REDIRECT_MODE_NO_REDIRECT,
108 class TestIOThreadState {
110 TestIOThreadState(const GURL& url,
111 int render_process_id,
113 const std::string& request_method,
114 RedirectMode redirect_mode,
115 MockInterceptCallbackReceiver* callback_receiver)
116 : resource_context_(&test_url_request_context_),
117 request_(resource_context_.GetRequestContext()->CreateRequest(
119 net::DEFAULT_PRIORITY,
121 NULL /* cookie_store */)) {
122 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
123 if (render_process_id != MSG_ROUTING_NONE &&
124 render_frame_id != MSG_ROUTING_NONE) {
125 content::ResourceRequestInfo::AllocateForTesting(
127 content::RESOURCE_TYPE_MAIN_FRAME,
134 throttle_.reset(new InterceptNavigationResourceThrottle(
136 base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation,
137 base::Unretained(callback_receiver))));
138 throttle_->set_controller_for_testing(&throttle_controller_);
139 request_->set_method(request_method);
141 if (redirect_mode == REDIRECT_MODE_302) {
142 net::HttpResponseInfo& response_info =
143 const_cast<net::HttpResponseInfo&>(request_->response_info());
144 response_info.headers = new net::HttpResponseHeaders(
145 "Status: 302 Found\0\0");
149 void ThrottleWillStartRequest(bool* defer) {
150 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
151 throttle_->WillStartRequest(defer);
154 void ThrottleWillRedirectRequest(const GURL& new_url, bool* defer) {
155 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
156 throttle_->WillRedirectRequest(new_url, defer);
159 bool request_resumed() const {
160 return throttle_controller_.status() ==
161 MockResourceController::RESUMED;
164 bool request_cancelled() const {
165 return throttle_controller_.status() ==
166 MockResourceController::CANCELLED;
170 net::TestURLRequestContext test_url_request_context_;
171 content::MockResourceContext resource_context_;
172 scoped_ptr<net::URLRequest> request_;
173 scoped_ptr<InterceptNavigationResourceThrottle> throttle_;
174 MockResourceController throttle_controller_;
177 // InterceptNavigationResourceThrottleTest ------------------------------------
179 class InterceptNavigationResourceThrottleTest
180 : public content::RenderViewHostTestHarness {
182 InterceptNavigationResourceThrottleTest()
183 : mock_callback_receiver_(new MockInterceptCallbackReceiver()),
184 io_thread_state_(NULL) {
187 void SetUp() override { RenderViewHostTestHarness::SetUp(); }
189 void TearDown() override {
191 web_contents()->SetDelegate(NULL);
193 content::BrowserThread::DeleteSoon(
194 content::BrowserThread::IO, FROM_HERE, io_thread_state_);
196 RenderViewHostTestHarness::TearDown();
199 void SetIOThreadState(TestIOThreadState* io_thread_state) {
200 io_thread_state_ = io_thread_state;
203 void RunThrottleWillStartRequestOnIOThread(
205 const std::string& request_method,
206 RedirectMode redirect_mode,
207 int render_process_id,
210 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
211 TestIOThreadState* io_thread_state =
212 new TestIOThreadState(url, render_process_id, render_frame_id,
213 request_method, redirect_mode,
214 mock_callback_receiver_.get());
216 SetIOThreadState(io_thread_state);
218 if (redirect_mode == REDIRECT_MODE_NO_REDIRECT)
219 io_thread_state->ThrottleWillStartRequest(defer);
221 io_thread_state->ThrottleWillRedirectRequest(url, defer);
225 enum ShouldIgnoreNavigationCallbackAction {
230 void SetUpWebContentsDelegateAndDrainRunLoop(
231 ShouldIgnoreNavigationCallbackAction callback_action,
233 ON_CALL(*mock_callback_receiver_, ShouldIgnoreNavigation(_, _))
234 .WillByDefault(Return(callback_action == IgnoreNavigation));
235 EXPECT_CALL(*mock_callback_receiver_,
236 ShouldIgnoreNavigation(web_contents(),
237 NavigationParamsUrlIsTest()))
240 content::BrowserThread::PostTask(
241 content::BrowserThread::IO,
244 &InterceptNavigationResourceThrottleTest::
245 RunThrottleWillStartRequestOnIOThread,
246 base::Unretained(this),
249 REDIRECT_MODE_NO_REDIRECT,
250 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
251 web_contents()->GetMainFrame()->GetRoutingID(),
252 base::Unretained(defer)));
254 // Wait for the request to finish processing.
255 base::RunLoop().RunUntilIdle();
258 void WaitForPreviouslyScheduledIoThreadWork() {
259 base::WaitableEvent io_thread_work_done(true, false);
260 content::BrowserThread::PostTask(
261 content::BrowserThread::IO,
264 &base::WaitableEvent::Signal,
265 base::Unretained(&io_thread_work_done)));
266 io_thread_work_done.Wait();
269 scoped_ptr<MockInterceptCallbackReceiver> mock_callback_receiver_;
270 TestIOThreadState* io_thread_state_;
273 TEST_F(InterceptNavigationResourceThrottleTest,
274 RequestDeferredAndResumedIfNavigationNotIgnored) {
276 SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation, &defer);
279 ASSERT_TRUE(io_thread_state_);
280 EXPECT_TRUE(io_thread_state_->request_resumed());
283 TEST_F(InterceptNavigationResourceThrottleTest,
284 RequestDeferredAndCancelledIfNavigationIgnored) {
286 SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation, &defer);
289 ASSERT_TRUE(io_thread_state_);
290 EXPECT_TRUE(io_thread_state_->request_cancelled());
293 TEST_F(InterceptNavigationResourceThrottleTest,
294 NoCallbackMadeIfContentsDeletedWhileThrottleRunning) {
297 // The tested scenario is when the WebContents is deleted after the
298 // ResourceThrottle has finished processing on the IO thread but before the
299 // UI thread callback has been processed. Since both threads in this test
300 // are serviced by one message loop, the post order is the execution order.
301 EXPECT_CALL(*mock_callback_receiver_,
302 ShouldIgnoreNavigation(_, _))
305 content::BrowserThread::PostTask(
306 content::BrowserThread::IO,
309 &InterceptNavigationResourceThrottleTest::
310 RunThrottleWillStartRequestOnIOThread,
311 base::Unretained(this),
314 REDIRECT_MODE_NO_REDIRECT,
315 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
316 web_contents()->GetMainFrame()->GetRoutingID(),
317 base::Unretained(&defer)));
319 content::BrowserThread::PostTask(
320 content::BrowserThread::UI,
323 &RenderViewHostTestHarness::DeleteContents,
324 base::Unretained(this)));
326 // The WebContents will now be deleted and only after that will the UI-thread
327 // callback posted by the ResourceThrottle be executed.
328 base::RunLoop().RunUntilIdle();
331 ASSERT_TRUE(io_thread_state_);
332 EXPECT_TRUE(io_thread_state_->request_resumed());
335 TEST_F(InterceptNavigationResourceThrottleTest,
336 RequestNotDeferredForRequestNotAssociatedWithARenderView) {
339 content::BrowserThread::PostTask(
340 content::BrowserThread::IO,
343 &InterceptNavigationResourceThrottleTest::
344 RunThrottleWillStartRequestOnIOThread,
345 base::Unretained(this),
348 REDIRECT_MODE_NO_REDIRECT,
351 base::Unretained(&defer)));
353 // Wait for the request to finish processing.
354 base::RunLoop().RunUntilIdle();
359 TEST_F(InterceptNavigationResourceThrottleTest,
360 CallbackCalledWithFilteredUrl) {
363 ON_CALL(*mock_callback_receiver_,
364 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe()))
365 .WillByDefault(Return(false));
366 EXPECT_CALL(*mock_callback_receiver_,
367 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe()))
370 content::BrowserThread::PostTask(
371 content::BrowserThread::IO,
374 &InterceptNavigationResourceThrottleTest::
375 RunThrottleWillStartRequestOnIOThread,
376 base::Unretained(this),
377 GURL(kUnsafeTestUrl),
379 REDIRECT_MODE_NO_REDIRECT,
380 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
381 web_contents()->GetMainFrame()->GetRoutingID(),
382 base::Unretained(&defer)));
384 // Wait for the request to finish processing.
385 base::RunLoop().RunUntilIdle();
388 TEST_F(InterceptNavigationResourceThrottleTest,
389 CallbackIsPostFalseForGet) {
392 EXPECT_CALL(*mock_callback_receiver_,
393 ShouldIgnoreNavigation(_, AllOf(
394 NavigationParamsUrlIsSafe(),
395 Property(&NavigationParams::is_post, Eq(false)))))
396 .WillOnce(Return(false));
398 content::BrowserThread::PostTask(
399 content::BrowserThread::IO,
402 &InterceptNavigationResourceThrottleTest::
403 RunThrottleWillStartRequestOnIOThread,
404 base::Unretained(this),
407 REDIRECT_MODE_NO_REDIRECT,
408 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
409 web_contents()->GetMainFrame()->GetRoutingID(),
410 base::Unretained(&defer)));
412 // Wait for the request to finish processing.
413 base::RunLoop().RunUntilIdle();
416 TEST_F(InterceptNavigationResourceThrottleTest,
417 CallbackIsPostTrueForPost) {
420 EXPECT_CALL(*mock_callback_receiver_,
421 ShouldIgnoreNavigation(_, AllOf(
422 NavigationParamsUrlIsSafe(),
423 Property(&NavigationParams::is_post, Eq(true)))))
424 .WillOnce(Return(false));
426 content::BrowserThread::PostTask(
427 content::BrowserThread::IO,
430 &InterceptNavigationResourceThrottleTest::
431 RunThrottleWillStartRequestOnIOThread,
432 base::Unretained(this),
435 REDIRECT_MODE_NO_REDIRECT,
436 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
437 web_contents()->GetMainFrame()->GetRoutingID(),
438 base::Unretained(&defer)));
440 // Wait for the request to finish processing.
441 base::RunLoop().RunUntilIdle();
444 TEST_F(InterceptNavigationResourceThrottleTest,
445 CallbackIsPostFalseForPostConvertedToGetBy302) {
448 EXPECT_CALL(*mock_callback_receiver_,
449 ShouldIgnoreNavigation(_, AllOf(
450 NavigationParamsUrlIsSafe(),
451 Property(&NavigationParams::is_post, Eq(false)))))
452 .WillOnce(Return(false));
454 content::BrowserThread::PostTask(
455 content::BrowserThread::IO,
458 &InterceptNavigationResourceThrottleTest::
459 RunThrottleWillStartRequestOnIOThread,
460 base::Unretained(this),
464 web_contents()->GetRenderViewHost()->GetProcess()->GetID(),
465 web_contents()->GetMainFrame()->GetRoutingID(),
466 base::Unretained(&defer)));
468 // Wait for the request to finish processing.
469 base::RunLoop().RunUntilIdle();
472 } // namespace navigation_interception