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/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