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