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