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