Home | History | Annotate | Download | only in navigation_interception
      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