Index: components/navigation_interception/intercept_navigation_resource_throttle_unittest.cc |
diff --git a/components/navigation_interception/intercept_navigation_resource_throttle_unittest.cc b/components/navigation_interception/intercept_navigation_resource_throttle_unittest.cc |
new file mode 100644 |
index 0000000000000000000000000000000000000000..8855f4248f7ad6bf26893932ffb49441f7de77bc |
--- /dev/null |
+++ b/components/navigation_interception/intercept_navigation_resource_throttle_unittest.cc |
@@ -0,0 +1,481 @@ |
+// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
+// Use of this source code is governed by a BSD-style license that can be |
+// found in the LICENSE file. |
+ |
+#include "base/bind.h" |
+#include "base/bind_helpers.h" |
+#include "base/memory/scoped_ptr.h" |
+#include "base/run_loop.h" |
+#include "base/synchronization/waitable_event.h" |
+#include "components/navigation_interception/intercept_navigation_resource_throttle.h" |
+#include "components/navigation_interception/navigation_params.h" |
+#include "content/public/browser/browser_thread.h" |
+#include "content/public/browser/render_frame_host.h" |
+#include "content/public/browser/render_process_host.h" |
+#include "content/public/browser/resource_context.h" |
+#include "content/public/browser/resource_controller.h" |
+#include "content/public/browser/resource_dispatcher_host.h" |
+#include "content/public/browser/resource_dispatcher_host_delegate.h" |
+#include "content/public/browser/resource_request_info.h" |
+#include "content/public/browser/resource_throttle.h" |
+#include "content/public/browser/web_contents.h" |
+#include "content/public/browser/web_contents_delegate.h" |
+#include "content/public/test/mock_resource_context.h" |
+#include "content/public/test/test_renderer_host.h" |
+#include "net/base/request_priority.h" |
+#include "net/http/http_response_headers.h" |
+#include "net/http/http_response_info.h" |
+#include "net/url_request/redirect_info.h" |
+#include "net/url_request/url_request.h" |
+#include "net/url_request/url_request_test_util.h" |
+#include "testing/gmock/include/gmock/gmock.h" |
+#include "testing/gtest/include/gtest/gtest.h" |
+ |
+using content::ResourceType; |
+using testing::_; |
+using testing::Eq; |
+using testing::Ne; |
+using testing::Property; |
+using testing::Return; |
+ |
+namespace navigation_interception { |
+ |
+namespace { |
+ |
+const char kTestUrl[] = "http://www.test.com/"; |
+const char kUnsafeTestUrl[] = "about:crash"; |
+ |
+// The MS C++ compiler complains about not being able to resolve which url() |
+// method (const or non-const) to use if we use the Property matcher to check |
+// the return value of the NavigationParams::url() method. |
+// It is possible to suppress the error by specifying the types directly but |
+// that results in very ugly syntax, which is why these custom matchers are |
+// used instead. |
+MATCHER(NavigationParamsUrlIsTest, "") { |
+ return arg.url() == GURL(kTestUrl); |
+} |
+ |
+MATCHER(NavigationParamsUrlIsSafe, "") { |
+ return arg.url() != GURL(kUnsafeTestUrl); |
+} |
+ |
+} // namespace |
+ |
+ |
+// MockInterceptCallbackReceiver ---------------------------------------------- |
+ |
+class MockInterceptCallbackReceiver { |
+ public: |
+ MOCK_METHOD2(ShouldIgnoreNavigation, |
+ bool(content::WebContents* source, |
+ const NavigationParams& navigation_params)); |
+}; |
+ |
+// MockResourceController ----------------------------------------------------- |
+class MockResourceController : public content::ResourceController { |
+ public: |
+ enum Status { |
+ UNKNOWN, |
+ RESUMED, |
+ CANCELLED |
+ }; |
+ |
+ MockResourceController() |
+ : status_(UNKNOWN) { |
+ } |
+ |
+ Status status() const { return status_; } |
+ |
+ // ResourceController: |
+ void Cancel() override { NOTREACHED(); } |
+ void CancelAndIgnore() override { status_ = CANCELLED; } |
+ void CancelWithError(int error_code) override { NOTREACHED(); } |
+ void Resume() override { |
+ DCHECK(status_ == UNKNOWN); |
+ status_ = RESUMED; |
+ } |
+ |
+ private: |
+ Status status_; |
+}; |
+ |
+// TestIOThreadState ---------------------------------------------------------- |
+ |
+enum RedirectMode { |
+ REDIRECT_MODE_NO_REDIRECT, |
+ REDIRECT_MODE_302, |
+}; |
+ |
+class TestIOThreadState { |
+ public: |
+ TestIOThreadState(const GURL& url, |
+ int render_process_id, |
+ int render_frame_id, |
+ const std::string& request_method, |
+ RedirectMode redirect_mode, |
+ MockInterceptCallbackReceiver* callback_receiver) |
+ : resource_context_(&test_url_request_context_), |
+ request_(resource_context_.GetRequestContext()->CreateRequest( |
+ url, |
+ net::DEFAULT_PRIORITY, |
+ NULL /* delegate */)) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
+ if (render_process_id != MSG_ROUTING_NONE && |
+ render_frame_id != MSG_ROUTING_NONE) { |
+ content::ResourceRequestInfo::AllocateForTesting( |
+ request_.get(), |
+ content::RESOURCE_TYPE_MAIN_FRAME, |
+ &resource_context_, |
+ render_process_id, |
+ MSG_ROUTING_NONE, |
+ render_frame_id, |
+ true, // is_main_frame |
+ false, // parent_is_main_frame |
+ true, // allow_download |
+ false); // is_async |
+ } |
+ throttle_.reset(new InterceptNavigationResourceThrottle( |
+ request_.get(), |
+ base::Bind(&MockInterceptCallbackReceiver::ShouldIgnoreNavigation, |
+ base::Unretained(callback_receiver)))); |
+ throttle_->set_controller_for_testing(&throttle_controller_); |
+ request_->set_method(request_method); |
+ |
+ if (redirect_mode == REDIRECT_MODE_302) { |
+ net::HttpResponseInfo& response_info = |
+ const_cast<net::HttpResponseInfo&>(request_->response_info()); |
+ response_info.headers = new net::HttpResponseHeaders( |
+ "Status: 302 Found\0\0"); |
+ } |
+ } |
+ |
+ void ThrottleWillStartRequest(bool* defer) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
+ throttle_->WillStartRequest(defer); |
+ } |
+ |
+ void ThrottleWillRedirectRequest(const net::RedirectInfo& redirect_info, |
+ bool* defer) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
+ throttle_->WillRedirectRequest(redirect_info, defer); |
+ } |
+ |
+ bool request_resumed() const { |
+ return throttle_controller_.status() == |
+ MockResourceController::RESUMED; |
+ } |
+ |
+ bool request_cancelled() const { |
+ return throttle_controller_.status() == |
+ MockResourceController::CANCELLED; |
+ } |
+ |
+ private: |
+ net::TestURLRequestContext test_url_request_context_; |
+ content::MockResourceContext resource_context_; |
+ scoped_ptr<net::URLRequest> request_; |
+ scoped_ptr<InterceptNavigationResourceThrottle> throttle_; |
+ MockResourceController throttle_controller_; |
+}; |
+ |
+// InterceptNavigationResourceThrottleTest ------------------------------------ |
+ |
+class InterceptNavigationResourceThrottleTest |
+ : public content::RenderViewHostTestHarness { |
+ public: |
+ InterceptNavigationResourceThrottleTest() |
+ : mock_callback_receiver_(new MockInterceptCallbackReceiver()), |
+ io_thread_state_(NULL) { |
+ } |
+ |
+ void SetUp() override { RenderViewHostTestHarness::SetUp(); } |
+ |
+ void TearDown() override { |
+ if (web_contents()) |
+ web_contents()->SetDelegate(NULL); |
+ |
+ content::BrowserThread::DeleteSoon( |
+ content::BrowserThread::IO, FROM_HERE, io_thread_state_); |
+ |
+ RenderViewHostTestHarness::TearDown(); |
+ } |
+ |
+ void SetIOThreadState(TestIOThreadState* io_thread_state) { |
+ io_thread_state_ = io_thread_state; |
+ } |
+ |
+ void RunThrottleWillStartRequestOnIOThread( |
+ const GURL& url, |
+ const std::string& request_method, |
+ RedirectMode redirect_mode, |
+ int render_process_id, |
+ int render_frame_id, |
+ bool* defer) { |
+ DCHECK_CURRENTLY_ON(content::BrowserThread::IO); |
+ TestIOThreadState* io_thread_state = |
+ new TestIOThreadState(url, render_process_id, render_frame_id, |
+ request_method, redirect_mode, |
+ mock_callback_receiver_.get()); |
+ |
+ SetIOThreadState(io_thread_state); |
+ |
+ if (redirect_mode == REDIRECT_MODE_NO_REDIRECT) { |
+ io_thread_state->ThrottleWillStartRequest(defer); |
+ } else { |
+ // 302 redirects convert POSTs to gets. |
+ net::RedirectInfo redirect_info; |
+ redirect_info.new_url = url; |
+ redirect_info.new_method = "GET"; |
+ io_thread_state->ThrottleWillRedirectRequest(redirect_info, defer); |
+ } |
+ } |
+ |
+ protected: |
+ enum ShouldIgnoreNavigationCallbackAction { |
+ IgnoreNavigation, |
+ DontIgnoreNavigation |
+ }; |
+ |
+ void SetUpWebContentsDelegateAndDrainRunLoop( |
+ ShouldIgnoreNavigationCallbackAction callback_action, |
+ bool* defer) { |
+ ON_CALL(*mock_callback_receiver_, ShouldIgnoreNavigation(_, _)) |
+ .WillByDefault(Return(callback_action == IgnoreNavigation)); |
+ EXPECT_CALL(*mock_callback_receiver_, |
+ ShouldIgnoreNavigation(web_contents(), |
+ NavigationParamsUrlIsTest())) |
+ .Times(1); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &InterceptNavigationResourceThrottleTest:: |
+ RunThrottleWillStartRequestOnIOThread, |
+ base::Unretained(this), |
+ GURL(kTestUrl), |
+ "GET", |
+ REDIRECT_MODE_NO_REDIRECT, |
+ web_contents()->GetRenderViewHost()->GetProcess()->GetID(), |
+ web_contents()->GetMainFrame()->GetRoutingID(), |
+ base::Unretained(defer))); |
+ |
+ // Wait for the request to finish processing. |
+ base::RunLoop().RunUntilIdle(); |
+ } |
+ |
+ void WaitForPreviouslyScheduledIoThreadWork() { |
+ base::WaitableEvent io_thread_work_done(true, false); |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &base::WaitableEvent::Signal, |
+ base::Unretained(&io_thread_work_done))); |
+ io_thread_work_done.Wait(); |
+ } |
+ |
+ scoped_ptr<MockInterceptCallbackReceiver> mock_callback_receiver_; |
+ TestIOThreadState* io_thread_state_; |
+}; |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ RequestDeferredAndResumedIfNavigationNotIgnored) { |
+ bool defer = false; |
+ SetUpWebContentsDelegateAndDrainRunLoop(DontIgnoreNavigation, &defer); |
+ |
+ EXPECT_TRUE(defer); |
+ ASSERT_TRUE(io_thread_state_); |
+ EXPECT_TRUE(io_thread_state_->request_resumed()); |
+} |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ RequestDeferredAndCancelledIfNavigationIgnored) { |
+ bool defer = false; |
+ SetUpWebContentsDelegateAndDrainRunLoop(IgnoreNavigation, &defer); |
+ |
+ EXPECT_TRUE(defer); |
+ ASSERT_TRUE(io_thread_state_); |
+ EXPECT_TRUE(io_thread_state_->request_cancelled()); |
+} |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ NoCallbackMadeIfContentsDeletedWhileThrottleRunning) { |
+ bool defer = false; |
+ |
+ // The tested scenario is when the WebContents is deleted after the |
+ // ResourceThrottle has finished processing on the IO thread but before the |
+ // UI thread callback has been processed. Since both threads in this test |
+ // are serviced by one message loop, the post order is the execution order. |
+ EXPECT_CALL(*mock_callback_receiver_, |
+ ShouldIgnoreNavigation(_, _)) |
+ .Times(0); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &InterceptNavigationResourceThrottleTest:: |
+ RunThrottleWillStartRequestOnIOThread, |
+ base::Unretained(this), |
+ GURL(kTestUrl), |
+ "GET", |
+ REDIRECT_MODE_NO_REDIRECT, |
+ web_contents()->GetRenderViewHost()->GetProcess()->GetID(), |
+ web_contents()->GetMainFrame()->GetRoutingID(), |
+ base::Unretained(&defer))); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::UI, |
+ FROM_HERE, |
+ base::Bind( |
+ &RenderViewHostTestHarness::DeleteContents, |
+ base::Unretained(this))); |
+ |
+ // The WebContents will now be deleted and only after that will the UI-thread |
+ // callback posted by the ResourceThrottle be executed. |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ EXPECT_TRUE(defer); |
+ ASSERT_TRUE(io_thread_state_); |
+ EXPECT_TRUE(io_thread_state_->request_resumed()); |
+} |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ RequestNotDeferredForRequestNotAssociatedWithARenderView) { |
+ bool defer = false; |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &InterceptNavigationResourceThrottleTest:: |
+ RunThrottleWillStartRequestOnIOThread, |
+ base::Unretained(this), |
+ GURL(kTestUrl), |
+ "GET", |
+ REDIRECT_MODE_NO_REDIRECT, |
+ MSG_ROUTING_NONE, |
+ MSG_ROUTING_NONE, |
+ base::Unretained(&defer))); |
+ |
+ // Wait for the request to finish processing. |
+ base::RunLoop().RunUntilIdle(); |
+ |
+ EXPECT_FALSE(defer); |
+} |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ CallbackCalledWithFilteredUrl) { |
+ bool defer = false; |
+ |
+ ON_CALL(*mock_callback_receiver_, |
+ ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) |
+ .WillByDefault(Return(false)); |
+ EXPECT_CALL(*mock_callback_receiver_, |
+ ShouldIgnoreNavigation(_, NavigationParamsUrlIsSafe())) |
+ .Times(1); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &InterceptNavigationResourceThrottleTest:: |
+ RunThrottleWillStartRequestOnIOThread, |
+ base::Unretained(this), |
+ GURL(kUnsafeTestUrl), |
+ "GET", |
+ REDIRECT_MODE_NO_REDIRECT, |
+ web_contents()->GetRenderViewHost()->GetProcess()->GetID(), |
+ web_contents()->GetMainFrame()->GetRoutingID(), |
+ base::Unretained(&defer))); |
+ |
+ // Wait for the request to finish processing. |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ CallbackIsPostFalseForGet) { |
+ bool defer = false; |
+ |
+ EXPECT_CALL(*mock_callback_receiver_, |
+ ShouldIgnoreNavigation(_, AllOf( |
+ NavigationParamsUrlIsSafe(), |
+ Property(&NavigationParams::is_post, Eq(false))))) |
+ .WillOnce(Return(false)); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &InterceptNavigationResourceThrottleTest:: |
+ RunThrottleWillStartRequestOnIOThread, |
+ base::Unretained(this), |
+ GURL(kTestUrl), |
+ "GET", |
+ REDIRECT_MODE_NO_REDIRECT, |
+ web_contents()->GetRenderViewHost()->GetProcess()->GetID(), |
+ web_contents()->GetMainFrame()->GetRoutingID(), |
+ base::Unretained(&defer))); |
+ |
+ // Wait for the request to finish processing. |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ CallbackIsPostTrueForPost) { |
+ bool defer = false; |
+ |
+ EXPECT_CALL(*mock_callback_receiver_, |
+ ShouldIgnoreNavigation(_, AllOf( |
+ NavigationParamsUrlIsSafe(), |
+ Property(&NavigationParams::is_post, Eq(true))))) |
+ .WillOnce(Return(false)); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &InterceptNavigationResourceThrottleTest:: |
+ RunThrottleWillStartRequestOnIOThread, |
+ base::Unretained(this), |
+ GURL(kTestUrl), |
+ "POST", |
+ REDIRECT_MODE_NO_REDIRECT, |
+ web_contents()->GetRenderViewHost()->GetProcess()->GetID(), |
+ web_contents()->GetMainFrame()->GetRoutingID(), |
+ base::Unretained(&defer))); |
+ |
+ // Wait for the request to finish processing. |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+TEST_F(InterceptNavigationResourceThrottleTest, |
+ CallbackIsPostFalseForPostConvertedToGetBy302) { |
+ bool defer = false; |
+ |
+ EXPECT_CALL(*mock_callback_receiver_, |
+ ShouldIgnoreNavigation(_, AllOf( |
+ NavigationParamsUrlIsSafe(), |
+ Property(&NavigationParams::is_post, Eq(false))))) |
+ .WillOnce(Return(false)); |
+ |
+ content::BrowserThread::PostTask( |
+ content::BrowserThread::IO, |
+ FROM_HERE, |
+ base::Bind( |
+ &InterceptNavigationResourceThrottleTest:: |
+ RunThrottleWillStartRequestOnIOThread, |
+ base::Unretained(this), |
+ GURL(kTestUrl), |
+ "POST", |
+ REDIRECT_MODE_302, |
+ web_contents()->GetRenderViewHost()->GetProcess()->GetID(), |
+ web_contents()->GetMainFrame()->GetRoutingID(), |
+ base::Unretained(&defer))); |
+ |
+ // Wait for the request to finish processing. |
+ base::RunLoop().RunUntilIdle(); |
+} |
+ |
+} // namespace navigation_interception |