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/memory/scoped_vector.h" 9 #include "base/run_loop.h" 10 #include "base/synchronization/waitable_event.h" 11 #include "components/navigation_interception/intercept_navigation_resource_throttle.h" 12 #include "components/navigation_interception/navigation_params.h" 13 #include "content/public/browser/browser_thread.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/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 81 MockResourceController() 82 : status_(UNKNOWN) { 83 } 84 85 Status status() const { return status_; } 86 87 // ResourceController: 88 virtual void Cancel() OVERRIDE { 89 NOTREACHED(); 90 } 91 virtual void CancelAndIgnore() OVERRIDE { 92 status_ = CANCELLED; 93 } 94 virtual void CancelWithError(int error_code) OVERRIDE { 95 NOTREACHED(); 96 } 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: 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, NULL, resource_context_.GetRequestContext()) { 123 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 124 if (render_process_id != MSG_ROUTING_NONE && 125 render_view_id != MSG_ROUTING_NONE) { 126 content::ResourceRequestInfo::AllocateForTesting( 127 &request_, 128 ResourceType::MAIN_FRAME, 129 &resource_context_, 130 render_process_id, 131 render_view_id); 132 } 133 throttle_.reset(new InterceptNavigationResourceThrottle( 134 &request_, 135 base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation, 136 base::Unretained(callback_receiver)))); 137 throttle_->set_controller_for_testing(&throttle_controller_); 138 request_.set_method(request_method); 139 140 if (redirect_mode == REDIRECT_MODE_302) { 141 net::HttpResponseInfo& response_info = 142 const_cast<net::HttpResponseInfo&>(request_.response_info()); 143 response_info.headers = new net::HttpResponseHeaders( 144 "Status: 302 Found\0\0"); 145 } 146 } 147 148 void ThrottleWillStartRequest(bool* defer) { 149 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 150 throttle_->WillStartRequest(defer); 151 } 152 153 void ThrottleWillRedirectRequest(const GURL& new_url, bool* defer) { 154 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 155 throttle_->WillRedirectRequest(new_url, defer); 156 } 157 158 bool request_resumed() const { 159 return throttle_controller_.status() == 160 MockResourceController::RESUMED; 161 } 162 163 bool request_cancelled() const { 164 return throttle_controller_.status() == 165 MockResourceController::CANCELLED; 166 } 167 168 private: 169 net::TestURLRequestContext test_url_request_context_; 170 content::MockResourceContext resource_context_; 171 net::URLRequest request_; 172 scoped_ptr<InterceptNavigationResourceThrottle> throttle_; 173 MockResourceController throttle_controller_; 174 }; 175 176 // InterceptNavigationResourceThrottleTest ------------------------------------ 177 178 class InterceptNavigationResourceThrottleTest 179 : public content::RenderViewHostTestHarness { 180 public: 181 InterceptNavigationResourceThrottleTest() 182 : mock_callback_receiver_(new MockInterceptCallbackReceiver()), 183 io_thread_state_(NULL) { 184 } 185 186 virtual void SetUp() OVERRIDE { 187 RenderViewHostTestHarness::SetUp(); 188 } 189 190 virtual void TearDown() OVERRIDE { 191 if (web_contents()) 192 web_contents()->SetDelegate(NULL); 193 194 content::BrowserThread::PostTask( 195 content::BrowserThread::IO, 196 FROM_HERE, 197 base::Bind(&base::DeletePointer<TestIOThreadState>, io_thread_state_)); 198 199 RenderViewHostTestHarness::TearDown(); 200 } 201 202 void SetIOThreadState(TestIOThreadState* io_thread_state) { 203 io_thread_state_ = io_thread_state; 204 } 205 206 void RunThrottleWillStartRequestOnIOThread( 207 const GURL& url, 208 const std::string& request_method, 209 RedirectMode redirect_mode, 210 int render_process_id, 211 int render_view_id, 212 bool* defer) { 213 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 214 TestIOThreadState* io_thread_state = 215 new TestIOThreadState(url, render_process_id, render_view_id, 216 request_method, redirect_mode, 217 mock_callback_receiver_.get()); 218 219 SetIOThreadState(io_thread_state); 220 221 if (redirect_mode == REDIRECT_MODE_NO_REDIRECT) 222 io_thread_state->ThrottleWillStartRequest(defer); 223 else 224 io_thread_state->ThrottleWillRedirectRequest(url, defer); 225 } 226 227 protected: 228 enum ShouldIgnoreNavigationCallbackAction { 229 IgnoreNavigation, 230 DontIgnoreNavigation 231 }; 232 233 void SetUpWebContentsDelegateAndDrainRunLoop( 234 ShouldIgnoreNavigationCallbackAction callback_action, 235 bool* defer) { 236 237 ON_CALL(*mock_callback_receiver_, ShouldIgnoreNavigation(_, _)) 238 .WillByDefault(Return(callback_action == IgnoreNavigation)); 239 EXPECT_CALL(*mock_callback_receiver_, 240 ShouldIgnoreNavigation(rvh(), NavigationParamsUrlIsTest())) 241 .Times(1); 242 243 content::BrowserThread::PostTask( 244 content::BrowserThread::IO, 245 FROM_HERE, 246 base::Bind( 247 &InterceptNavigationResourceThrottleTest:: 248 RunThrottleWillStartRequestOnIOThread, 249 base::Unretained(this), 250 GURL(kTestUrl), 251 "GET", 252 REDIRECT_MODE_NO_REDIRECT, 253 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 254 web_contents()->GetRenderViewHost()->GetRoutingID(), 255 base::Unretained(defer))); 256 257 // Wait for the request to finish processing. 258 base::RunLoop().RunUntilIdle(); 259 } 260 261 void WaitForPreviouslyScheduledIoThreadWork() { 262 base::WaitableEvent io_thread_work_done(true, false); 263 content::BrowserThread::PostTask( 264 content::BrowserThread::IO, 265 FROM_HERE, 266 base::Bind( 267 &base::WaitableEvent::Signal, 268 base::Unretained(&io_thread_work_done))); 269 io_thread_work_done.Wait(); 270 } 271 272 scoped_ptr<MockInterceptCallbackReceiver> mock_callback_receiver_; 273 TestIOThreadState* io_thread_state_; 274 }; 275 276 TEST_F(InterceptNavigationResourceThrottleTest, 277 RequestDeferredAndResumedIfNavigationNotIgnored) { 278 bool defer = false; 279 SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation, &defer); 280 281 EXPECT_TRUE(defer); 282 EXPECT_TRUE(io_thread_state_); 283 EXPECT_TRUE(io_thread_state_->request_resumed()); 284 } 285 286 TEST_F(InterceptNavigationResourceThrottleTest, 287 RequestDeferredAndCancelledIfNavigationIgnored) { 288 bool defer = false; 289 SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation, &defer); 290 291 EXPECT_TRUE(defer); 292 EXPECT_TRUE(io_thread_state_); 293 EXPECT_TRUE(io_thread_state_->request_cancelled()); 294 } 295 296 TEST_F(InterceptNavigationResourceThrottleTest, 297 NoCallbackMadeIfContentsDeletedWhileThrottleRunning) { 298 bool defer = false; 299 300 // The tested scenario is when the WebContents is deleted after the 301 // ResourceThrottle has finished processing on the IO thread but before the 302 // UI thread callback has been processed. Since both threads in this test 303 // are serviced by one message loop, the post order is the execution order. 304 EXPECT_CALL(*mock_callback_receiver_, 305 ShouldIgnoreNavigation(_, _)) 306 .Times(0); 307 308 content::BrowserThread::PostTask( 309 content::BrowserThread::IO, 310 FROM_HERE, 311 base::Bind( 312 &InterceptNavigationResourceThrottleTest:: 313 RunThrottleWillStartRequestOnIOThread, 314 base::Unretained(this), 315 GURL(kTestUrl), 316 "GET", 317 REDIRECT_MODE_NO_REDIRECT, 318 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 319 web_contents()->GetRenderViewHost()->GetRoutingID(), 320 base::Unretained(&defer))); 321 322 content::BrowserThread::PostTask( 323 content::BrowserThread::UI, 324 FROM_HERE, 325 base::Bind( 326 &RenderViewHostTestHarness::DeleteContents, 327 base::Unretained(this))); 328 329 // The WebContents will now be deleted and only after that will the UI-thread 330 // callback posted by the ResourceThrottle be executed. 331 base::RunLoop().RunUntilIdle(); 332 333 EXPECT_TRUE(defer); 334 EXPECT_TRUE(io_thread_state_); 335 EXPECT_TRUE(io_thread_state_->request_resumed()); 336 } 337 338 TEST_F(InterceptNavigationResourceThrottleTest, 339 RequestNotDeferredForRequestNotAssociatedWithARenderView) { 340 bool defer = false; 341 342 content::BrowserThread::PostTask( 343 content::BrowserThread::IO, 344 FROM_HERE, 345 base::Bind( 346 &InterceptNavigationResourceThrottleTest:: 347 RunThrottleWillStartRequestOnIOThread, 348 base::Unretained(this), 349 GURL(kTestUrl), 350 "GET", 351 REDIRECT_MODE_NO_REDIRECT, 352 MSG_ROUTING_NONE, 353 MSG_ROUTING_NONE, 354 base::Unretained(&defer))); 355 356 // Wait for the request to finish processing. 357 base::RunLoop().RunUntilIdle(); 358 359 EXPECT_FALSE(defer); 360 } 361 362 TEST_F(InterceptNavigationResourceThrottleTest, 363 CallbackCalledWithFilteredUrl) { 364 bool defer = false; 365 366 ON_CALL(*mock_callback_receiver_, 367 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) 368 .WillByDefault(Return(false)); 369 EXPECT_CALL(*mock_callback_receiver_, 370 ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) 371 .Times(1); 372 373 content::BrowserThread::PostTask( 374 content::BrowserThread::IO, 375 FROM_HERE, 376 base::Bind( 377 &InterceptNavigationResourceThrottleTest:: 378 RunThrottleWillStartRequestOnIOThread, 379 base::Unretained(this), 380 GURL(kUnsafeTestUrl), 381 "GET", 382 REDIRECT_MODE_NO_REDIRECT, 383 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 384 web_contents()->GetRenderViewHost()->GetRoutingID(), 385 base::Unretained(&defer))); 386 387 // Wait for the request to finish processing. 388 base::RunLoop().RunUntilIdle(); 389 } 390 391 TEST_F(InterceptNavigationResourceThrottleTest, 392 CallbackIsPostFalseForGet) { 393 bool defer = false; 394 395 EXPECT_CALL(*mock_callback_receiver_, 396 ShouldIgnoreNavigation(_, AllOf( 397 NavigationParamsUrlIsSafe(), 398 Property(&NavigationParams::is_post, Eq(false))))) 399 .WillOnce(Return(false)); 400 401 content::BrowserThread::PostTask( 402 content::BrowserThread::IO, 403 FROM_HERE, 404 base::Bind( 405 &InterceptNavigationResourceThrottleTest:: 406 RunThrottleWillStartRequestOnIOThread, 407 base::Unretained(this), 408 GURL(kTestUrl), 409 "GET", 410 REDIRECT_MODE_NO_REDIRECT, 411 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 412 web_contents()->GetRenderViewHost()->GetRoutingID(), 413 base::Unretained(&defer))); 414 415 // Wait for the request to finish processing. 416 base::RunLoop().RunUntilIdle(); 417 } 418 419 TEST_F(InterceptNavigationResourceThrottleTest, 420 CallbackIsPostTrueForPost) { 421 bool defer = false; 422 423 EXPECT_CALL(*mock_callback_receiver_, 424 ShouldIgnoreNavigation(_, AllOf( 425 NavigationParamsUrlIsSafe(), 426 Property(&NavigationParams::is_post, Eq(true))))) 427 .WillOnce(Return(false)); 428 429 content::BrowserThread::PostTask( 430 content::BrowserThread::IO, 431 FROM_HERE, 432 base::Bind( 433 &InterceptNavigationResourceThrottleTest:: 434 RunThrottleWillStartRequestOnIOThread, 435 base::Unretained(this), 436 GURL(kTestUrl), 437 "POST", 438 REDIRECT_MODE_NO_REDIRECT, 439 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 440 web_contents()->GetRenderViewHost()->GetRoutingID(), 441 base::Unretained(&defer))); 442 443 // Wait for the request to finish processing. 444 base::RunLoop().RunUntilIdle(); 445 } 446 447 TEST_F(InterceptNavigationResourceThrottleTest, 448 CallbackIsPostFalseForPostConvertedToGetBy302) { 449 bool defer = false; 450 451 EXPECT_CALL(*mock_callback_receiver_, 452 ShouldIgnoreNavigation(_, AllOf( 453 NavigationParamsUrlIsSafe(), 454 Property(&NavigationParams::is_post, Eq(false))))) 455 .WillOnce(Return(false)); 456 457 content::BrowserThread::PostTask( 458 content::BrowserThread::IO, 459 FROM_HERE, 460 base::Bind( 461 &InterceptNavigationResourceThrottleTest:: 462 RunThrottleWillStartRequestOnIOThread, 463 base::Unretained(this), 464 GURL(kTestUrl), 465 "POST", 466 REDIRECT_MODE_302, 467 web_contents()->GetRenderViewHost()->GetProcess()->GetID(), 468 web_contents()->GetRenderViewHost()->GetRoutingID(), 469 base::Unretained(&defer))); 470 471 // Wait for the request to finish processing. 472 base::RunLoop().RunUntilIdle(); 473 } 474 475 } // namespace navigation_interception 476