• 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_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 
MockResourceController()82   MockResourceController()
83       : status_(UNKNOWN) {
84   }
85 
status() const86   Status status() const { return status_; }
87 
88   // ResourceController:
Cancel()89   virtual void Cancel() OVERRIDE {
90     NOTREACHED();
91   }
CancelAndIgnore()92   virtual void CancelAndIgnore() OVERRIDE {
93     status_ = CANCELLED;
94   }
CancelWithError(int error_code)95   virtual void CancelWithError(int error_code) OVERRIDE {
96     NOTREACHED();
97   }
Resume()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:
TestIOThreadState(const GURL & url,int render_process_id,int render_frame_id,const std::string & request_method,RedirectMode redirect_mode,MockInterceptCallbackReceiver * callback_receiver)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 
ThrottleWillStartRequest(bool * defer)154   void ThrottleWillStartRequest(bool* defer) {
155     DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO));
156     throttle_->WillStartRequest(defer);
157   }
158 
ThrottleWillRedirectRequest(const GURL & new_url,bool * defer)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 
request_resumed() const164   bool request_resumed() const {
165     return throttle_controller_.status() ==
166         MockResourceController::RESUMED;
167   }
168 
request_cancelled() const169   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:
InterceptNavigationResourceThrottleTest()187   InterceptNavigationResourceThrottleTest()
188       : mock_callback_receiver_(new MockInterceptCallbackReceiver()),
189         io_thread_state_(NULL) {
190   }
191 
SetUp()192   virtual void SetUp() OVERRIDE {
193     RenderViewHostTestHarness::SetUp();
194   }
195 
TearDown()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 
SetIOThreadState(TestIOThreadState * io_thread_state)206   void SetIOThreadState(TestIOThreadState* io_thread_state) {
207     io_thread_state_ = io_thread_state;
208   }
209 
RunThrottleWillStartRequestOnIOThread(const GURL & url,const std::string & request_method,RedirectMode redirect_mode,int render_process_id,int render_frame_id,bool * defer)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 
SetUpWebContentsDelegateAndDrainRunLoop(ShouldIgnoreNavigationCallbackAction callback_action,bool * defer)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 
WaitForPreviouslyScheduledIoThreadWork()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 
TEST_F(InterceptNavigationResourceThrottleTest,RequestDeferredAndResumedIfNavigationNotIgnored)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 
TEST_F(InterceptNavigationResourceThrottleTest,RequestDeferredAndCancelledIfNavigationIgnored)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 
TEST_F(InterceptNavigationResourceThrottleTest,NoCallbackMadeIfContentsDeletedWhileThrottleRunning)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 
TEST_F(InterceptNavigationResourceThrottleTest,RequestNotDeferredForRequestNotAssociatedWithARenderView)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 
TEST_F(InterceptNavigationResourceThrottleTest,CallbackCalledWithFilteredUrl)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 
TEST_F(InterceptNavigationResourceThrottleTest,CallbackIsPostFalseForGet)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 
TEST_F(InterceptNavigationResourceThrottleTest,CallbackIsPostTrueForPost)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 
TEST_F(InterceptNavigationResourceThrottleTest,CallbackIsPostFalseForPostConvertedToGetBy302)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