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_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" 33 34 using testing::_; 35 using testing::Eq; 36 using testing::Ne; 37 using testing::Property; 38 using testing::Return; 39 40 namespace navigation_interception { 41 42 namespace { 43 44 const char kTestUrl[] = "http://www.test.com/"; 45 const char kUnsafeTestUrl[] = "about:crash"; 46 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 52 // used instead. 53 MATCHER(NavigationParamsUrlIsTest, "") { 54 return arg.url() == GURL(kTestUrl); 55 } 56 57 MATCHER(NavigationParamsUrlIsSafe, "") { 58 return arg.url() != GURL(kUnsafeTestUrl); 59 } 60 61 } // namespace 62 63 64 // MockInterceptCallbackReceiver ---------------------------------------------- 65 66 class MockInterceptCallbackReceiver { 67 public: 68 MOCK_METHOD2(ShouldIgnoreNavigation, 69 bool(content::WebContents* source, 70 const NavigationParams& navigation_params)); 71 }; 72 73 // MockResourceController ----------------------------------------------------- 74 class MockResourceController : public content::ResourceController { 75 public: 76 enum Status { 77 UNKNOWN, 78 RESUMED, 79 CANCELLED 80 }; 81 82 MockResourceController() 83 : status_(UNKNOWN) { 84 } 85 86 Status status() const { return status_; } 87 88 // ResourceController: 89 virtual void Cancel() OVERRIDE { 90 NOTREACHED(); 91 } 92 virtual void CancelAndIgnore() OVERRIDE { 93 status_ = CANCELLED; 94 } 95 virtual void CancelWithError(int error_code) OVERRIDE { 96 NOTREACHED(); 97 } 98 virtual void Resume() OVERRIDE { 99 DCHECK(status_ == UNKNOWN); 100 status_ = RESUMED; 101 } 102 103 private: 104 Status status_; 105 }; 106 107 // TestIOThreadState ---------------------------------------------------------- 108 109 enum RedirectMode { 110 REDIRECT_MODE_NO_REDIRECT, 111 REDIRECT_MODE_302, 112 }; 113 114 class TestIOThreadState { 115 public: 116 TestIOThreadState(const GURL& url, 117 int render_process_id, 118 int render_frame_id, 119 const std::string& request_method, 120 RedirectMode redirect_mode, 121 MockInterceptCallbackReceiver* callback_receiver) 122 : resource_context_(&test_url_request_context_), 123 request_(url, 124 net::DEFAULT_PRIORITY, 125 NULL, 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( 131 &request_, 132 ResourceType::MAIN_FRAME, 133 &resource_context_, 134 render_process_id, 135 MSG_ROUTING_NONE, 136 render_frame_id, 137 false); 138 } 139 throttle_.reset(new InterceptNavigationResourceThrottle( 140 &request_, 141 base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation, 142 base::Unretained(callback_receiver)))); 143 throttle_->set_controller_for_testing(&throttle_controller_); 144 request_.set_method(request_method); 145 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"); 151 } 152 } 153 154 void ThrottleWillStartRequest(bool* defer) { 155 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 156 throttle_->WillStartRequest(defer); 157 } 158 159 void ThrottleWillRedirectRequest(const GURL& new_url, bool* defer) { 160 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 161 throttle_->WillRedirectRequest(new_url, defer); 162 } 163 164 bool request_resumed() const { 165 return throttle_controller_.status() == 166 MockResourceController::RESUMED; 167 } 168 169 bool request_cancelled() const { 170 return throttle_controller_.status() == 171 MockResourceController::CANCELLED; 172 } 173 174 private: 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_; 180 }; 181 182 // InterceptNavigationResourceThrottleTest ------------------------------------ 183 184 class InterceptNavigationResourceThrottleTest 185 : public content::RenderViewHostTestHarness { 186 public: 187 InterceptNavigationResourceThrottleTest() 188 : mock_callback_receiver_(new MockInterceptCallbackReceiver()), 189 io_thread_state_(NULL) { 190 } 191 192 virtual void SetUp() OVERRIDE { 193 RenderViewHostTestHarness::SetUp(); 194 } 195 196 virtual void TearDown() OVERRIDE { 197 if (web_contents()) 198 web_contents()->SetDelegate(NULL); 199 200 content::BrowserThread::DeleteSoon( 201 content::BrowserThread::IO, FROM_HERE, io_thread_state_); 202 203 RenderViewHostTestHarness::TearDown(); 204 } 205 206 void SetIOThreadState(TestIOThreadState* io_thread_state) { 207 io_thread_state_ = io_thread_state; 208 } 209 210 void RunThrottleWillStartRequestOnIOThread( 211 const GURL& url, 212 const std::string& request_method, 213 RedirectMode redirect_mode, 214 int render_process_id, 215 int render_frame_id, 216 bool* defer) { 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()); 222 223 SetIOThreadState(io_thread_state); 224 225 if (redirect_mode == REDIRECT_MODE_NO_REDIRECT) 226 io_thread_state->ThrottleWillStartRequest(defer); 227 else 228 io_thread_state->ThrottleWillRedirectRequest(url, defer); 229 } 230 231 protected: 232 enum ShouldIgnoreNavigationCallbackAction { 233 IgnoreNavigation, 234 DontIgnoreNavigation 235 }; 236 237 void SetUpWebContentsDelegateAndDrainRunLoop( 238 ShouldIgnoreNavigationCallbackAction callback_action, 239 bool* defer) { 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())) 245 .Times(1); 246 247 content::BrowserThread::PostTask( 248 content::BrowserThread::IO, 249 FROM_HERE, 250 base::Bind( 251 &InterceptNavigationResourceThrottleTest:: 252 RunThrottleWillStartRequestOnIOThread, 253 base::Unretained(this), 254 GURL(kTestUrl), 255 "GET", 256 REDIRECT_MODE_NO_REDIRECT, 257 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 258 web_contents()->GetMainFrame()->GetRoutingID(), 259 base::Unretained(defer))); 260 261 // Wait for the request to finish processing. 262 base::RunLoop().RunUntilIdle(); 263 } 264 265 void WaitForPreviouslyScheduledIoThreadWork() { 266 base::WaitableEvent io_thread_work_done(true, false); 267 content::BrowserThread::PostTask( 268 content::BrowserThread::IO, 269 FROM_HERE, 270 base::Bind( 271 &base::WaitableEvent::Signal, 272 base::Unretained(&io_thread_work_done))); 273 io_thread_work_done.Wait(); 274 } 275 276 scoped_ptr<MockInterceptCallbackReceiver> mock_callback_receiver_; 277 TestIOThreadState* io_thread_state_; 278 }; 279 280 TEST_F(InterceptNavigationResourceThrottleTest, 281 RequestDeferredAndResumedIfNavigationNotIgnored) { 282 bool defer = false; 283 SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation, &defer); 284 285 EXPECT_TRUE(defer); 286 ASSERT_TRUE(io_thread_state_); 287 EXPECT_TRUE(io_thread_state_->request_resumed()); 288 } 289 290 TEST_F(InterceptNavigationResourceThrottleTest, 291 RequestDeferredAndCancelledIfNavigationIgnored) { 292 bool defer = false; 293 SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation, &defer); 294 295 EXPECT_TRUE(defer); 296 ASSERT_TRUE(io_thread_state_); 297 EXPECT_TRUE(io_thread_state_->request_cancelled()); 298 } 299 300 TEST_F(InterceptNavigationResourceThrottleTest, 301 NoCallbackMadeIfContentsDeletedWhileThrottleRunning) { 302 bool defer = false; 303 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(_, _)) 310 .Times(0); 311 312 content::BrowserThread::PostTask( 313 content::BrowserThread::IO, 314 FROM_HERE, 315 base::Bind( 316 &InterceptNavigationResourceThrottleTest:: 317 RunThrottleWillStartRequestOnIOThread, 318 base::Unretained(this), 319 GURL(kTestUrl), 320 "GET", 321 REDIRECT_MODE_NO_REDIRECT, 322 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 323 web_contents()->GetMainFrame()->GetRoutingID(), 324 base::Unretained(&defer))); 325 326 content::BrowserThread::PostTask( 327 content::BrowserThread::UI, 328 FROM_HERE, 329 base::Bind( 330 &RenderViewHostTestHarness::DeleteContents, 331 base::Unretained(this))); 332 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(); 336 337 EXPECT_TRUE(defer); 338 ASSERT_TRUE(io_thread_state_); 339 EXPECT_TRUE(io_thread_state_->request_resumed()); 340 } 341 342 TEST_F(InterceptNavigationResourceThrottleTest, 343 RequestNotDeferredForRequestNotAssociatedWithARenderView) { 344 bool defer = false; 345 346 content::BrowserThread::PostTask( 347 content::BrowserThread::IO, 348 FROM_HERE, 349 base::Bind( 350 &InterceptNavigationResourceThrottleTest:: 351 RunThrottleWillStartRequestOnIOThread, 352 base::Unretained(this), 353 GURL(kTestUrl), 354 "GET", 355 REDIRECT_MODE_NO_REDIRECT, 356 MSG_ROUTING_NONE, 357 MSG_ROUTING_NONE, 358 base::Unretained(&defer))); 359 360 // Wait for the request to finish processing. 361 base::RunLoop().RunUntilIdle(); 362 363 EXPECT_FALSE(defer); 364 } 365 366 TEST_F(InterceptNavigationResourceThrottleTest, 367 CallbackCalledWithFilteredUrl) { 368 bool defer = false; 369 370 ON_CALL(*mock_callback_receiver_, 371 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) 372 .WillByDefault(Return(false)); 373 EXPECT_CALL(*mock_callback_receiver_, 374 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) 375 .Times(1); 376 377 content::BrowserThread::PostTask( 378 content::BrowserThread::IO, 379 FROM_HERE, 380 base::Bind( 381 &InterceptNavigationResourceThrottleTest:: 382 RunThrottleWillStartRequestOnIOThread, 383 base::Unretained(this), 384 GURL(kUnsafeTestUrl), 385 "GET", 386 REDIRECT_MODE_NO_REDIRECT, 387 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 388 web_contents()->GetMainFrame()->GetRoutingID(), 389 base::Unretained(&defer))); 390 391 // Wait for the request to finish processing. 392 base::RunLoop().RunUntilIdle(); 393 } 394 395 TEST_F(InterceptNavigationResourceThrottleTest, 396 CallbackIsPostFalseForGet) { 397 bool defer = false; 398 399 EXPECT_CALL(*mock_callback_receiver_, 400 ShouldIgnoreNavigation(_, AllOf( 401 NavigationParamsUrlIsSafe(), 402 Property(&NavigationParams::is_post, Eq(false))))) 403 .WillOnce(Return(false)); 404 405 content::BrowserThread::PostTask( 406 content::BrowserThread::IO, 407 FROM_HERE, 408 base::Bind( 409 &InterceptNavigationResourceThrottleTest:: 410 RunThrottleWillStartRequestOnIOThread, 411 base::Unretained(this), 412 GURL(kTestUrl), 413 "GET", 414 REDIRECT_MODE_NO_REDIRECT, 415 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 416 web_contents()->GetMainFrame()->GetRoutingID(), 417 base::Unretained(&defer))); 418 419 // Wait for the request to finish processing. 420 base::RunLoop().RunUntilIdle(); 421 } 422 423 TEST_F(InterceptNavigationResourceThrottleTest, 424 CallbackIsPostTrueForPost) { 425 bool defer = false; 426 427 EXPECT_CALL(*mock_callback_receiver_, 428 ShouldIgnoreNavigation(_, AllOf( 429 NavigationParamsUrlIsSafe(), 430 Property(&NavigationParams::is_post, Eq(true))))) 431 .WillOnce(Return(false)); 432 433 content::BrowserThread::PostTask( 434 content::BrowserThread::IO, 435 FROM_HERE, 436 base::Bind( 437 &InterceptNavigationResourceThrottleTest:: 438 RunThrottleWillStartRequestOnIOThread, 439 base::Unretained(this), 440 GURL(kTestUrl), 441 "POST", 442 REDIRECT_MODE_NO_REDIRECT, 443 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 444 web_contents()->GetMainFrame()->GetRoutingID(), 445 base::Unretained(&defer))); 446 447 // Wait for the request to finish processing. 448 base::RunLoop().RunUntilIdle(); 449 } 450 451 TEST_F(InterceptNavigationResourceThrottleTest, 452 CallbackIsPostFalseForPostConvertedToGetBy302) { 453 bool defer = false; 454 455 EXPECT_CALL(*mock_callback_receiver_, 456 ShouldIgnoreNavigation(_, AllOf( 457 NavigationParamsUrlIsSafe(), 458 Property(&NavigationParams::is_post, Eq(false))))) 459 .WillOnce(Return(false)); 460 461 content::BrowserThread::PostTask( 462 content::BrowserThread::IO, 463 FROM_HERE, 464 base::Bind( 465 &InterceptNavigationResourceThrottleTest:: 466 RunThrottleWillStartRequestOnIOThread, 467 base::Unretained(this), 468 GURL(kTestUrl), 469 "POST", 470 REDIRECT_MODE_302, 471 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 472 web_contents()->GetMainFrame()->GetRoutingID(), 473 base::Unretained(&defer))); 474 475 // Wait for the request to finish processing. 476 base::RunLoop().RunUntilIdle(); 477 } 478 479 } // namespace navigation_interception 480