// 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 #include #include #include #include #include "base/base_switches.h" #include "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/files/file_path.h" #include "base/macros.h" #include "base/memory/scoped_ptr.h" #include "base/message_loop/message_loop.h" #include "base/path_service.h" #include "base/strings/utf_string_conversions.h" #include "base/values.h" #include "build/build_config.h" #include "chrome/browser/captive_portal/captive_portal_service.h" #include "chrome/browser/captive_portal/captive_portal_service_factory.h" #include "chrome/browser/captive_portal/captive_portal_tab_helper.h" #include "chrome/browser/captive_portal/captive_portal_tab_reloader.h" #include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/interstitials/security_interstitial_page.h" #include "chrome/browser/net/url_request_mock_util.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/ssl/captive_portal_blocking_page.h" #include "chrome/browser/ssl/ssl_blocking_page.h" #include "chrome/browser/ssl/ssl_error_handler.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_commands.h" #include "chrome/browser/ui/browser_finder.h" #include "chrome/browser/ui/browser_navigator_params.h" #include "chrome/browser/ui/tab_contents/tab_contents_iterator.h" #include "chrome/browser/ui/tabs/tab_strip_model.h" #include "chrome/common/chrome_paths.h" #include "chrome/common/pref_names.h" #include "chrome/test/base/in_process_browser_test.h" #include "chrome/test/base/ui_test_utils.h" #include "components/prefs/pref_service.h" #include "content/public/browser/browser_thread.h" #include "content/public/browser/interstitial_page.h" #include "content/public/browser/interstitial_page_delegate.h" #include "content/public/browser/navigation_controller.h" #include "content/public/browser/navigation_entry.h" #include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/notification_types.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/web_contents.h" #include "content/public/common/url_constants.h" #include "content/public/test/browser_test_utils.h" #include "net/base/net_errors.h" #include "net/base/test_data_directory.h" #include "net/cert/x509_certificate.h" #include "net/http/transport_security_state.h" #include "net/test/cert_test_util.h" #include "net/test/embedded_test_server/embedded_test_server.h" #include "net/test/url_request/url_request_failed_job.h" #include "net/test/url_request/url_request_mock_http_job.h" #include "net/url_request/url_request.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_filter.h" #include "net/url_request/url_request_interceptor.h" #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_status.h" #include "testing/gtest/include/gtest/gtest.h" using captive_portal::CaptivePortalResult; using content::BrowserThread; using content::WebContents; using net::URLRequestFailedJob; using net::URLRequestMockHTTPJob; namespace { // Path of the fake login page, when using the TestServer. const char* const kTestServerLoginPath = "/captive_portal/login.html"; // Path of a page with an iframe that has a mock SSL timeout, when using the // TestServer. const char* const kTestServerIframeTimeoutPath = "/captive_portal/iframe_timeout.html"; // The following URLs each have two different behaviors, depending on whether // URLRequestMockCaptivePortalJobFactory is currently simulating the presence // of a captive portal or not. They use different domains so that HSTS can be // applied to them independently. // A mock URL for the CaptivePortalService's |test_url|. When behind a captive // portal, this URL returns a mock login page. When connected to the Internet, // it returns a 204 response. Uses the name of the login file so that reloading // it will not request a different URL. const char* const kMockCaptivePortalTestUrl = "http://mock.captive.portal.test/login.html"; // Another mock URL for the CaptivePortalService's |test_url|. When behind a // captive portal, this URL returns a 511 status code and an HTML page that // redirect to the above URL. When connected to the Internet, it returns a 204 // response. const char* const kMockCaptivePortal511Url = "http://mock.captive.portal.511/page511.html"; // When behind a captive portal, this URL hangs without committing until a call // to URLRequestTimeoutOnDemandJob::FailJobs. When that function is called, // the request will time out. // // When connected to the Internet, this URL returns a non-error page. const char* const kMockHttpsUrl = "https://mock.captive.portal.long.timeout/title2.html"; // Same as above, but different domain, so can be used to trigger cross-site // navigations. const char* const kMockHttpsUrl2 = "https://mock.captive.portal.long.timeout2/title2.html"; // Same as kMockHttpsUrl, except the timeout happens instantly. const char* const kMockHttpsQuickTimeoutUrl = "https://mock.captive.portal.quick.timeout/title2.html"; // Expected title of a tab once an HTTPS load completes, when not behind a // captive portal. const char* const kInternetConnectedTitle = "Title Of Awesomeness"; // A URL request job that hangs until FailJobs() is called. Started jobs // are stored in a static class variable containing a linked list so that // FailJobs() can locate them. class URLRequestTimeoutOnDemandJob : public net::URLRequestJob, public base::NonThreadSafe { public: // net::URLRequestJob: void Start() override; // All the public static methods below can be called on any thread. // Waits for exactly |num_jobs|. static void WaitForJobs(int num_jobs); // Fails all active URLRequestTimeoutOnDemandJobs with connection timeouts. // There are expected to be exactly |expected_num_jobs| waiting for // failure. The only way to guarantee this is with an earlier call to // WaitForJobs, so makes sure there has been a matching WaitForJobs call. static void FailJobs(int expected_num_jobs); // Fails all active URLRequestTimeoutOnDemandJobs with SSL cert errors. // |expected_num_jobs| behaves just as in FailJobs. static void FailJobsWithCertError(int expected_num_jobs, const net::SSLInfo& ssl_info); // Abandon all active URLRequestTimeoutOnDemandJobs. |expected_num_jobs| // behaves just as in FailJobs. static void AbandonJobs(int expected_num_jobs); private: friend class URLRequestMockCaptivePortalJobFactory; // Operation to perform on jobs when removing them from |job_list_|. enum EndJobOperation { FAIL_JOBS, ABANDON_JOBS, FAIL_JOBS_WITH_CERT_ERROR }; URLRequestTimeoutOnDemandJob(net::URLRequest* request, net::NetworkDelegate* network_delegate); ~URLRequestTimeoutOnDemandJob() override; // Attempts to removes |this| from |jobs_|. Returns true if it was removed // from the list. bool RemoveFromList(); static void WaitForJobsOnIOThread(int num_jobs); static void FailOrAbandonJobsOnIOThread(int expected_num_jobs, EndJobOperation end_job_operation, const net::SSLInfo& ssl_info); // Checks if there are at least |num_jobs_to_wait_for_| jobs in // |job_list_|. If so, exits the message loop on the UI thread, which // should be spinning in a call to WaitForJobs. Does nothing when // |num_jobs_to_wait_for_| is 0. static void MaybeStopWaitingForJobsOnIOThread(); // All class variables are only accessed on the IO thread. // Number of jobs currently being waited for, or 0 if not currently // waiting for jobs. static int num_jobs_to_wait_for_; // The last number of jobs that were waited for. When FailJobs or // AbandonJobs is called, this should match |expected_num_jobs|. static int last_num_jobs_to_wait_for_; // Number of jobs that have been started, but not yet waited for. If jobs // are deleted unexpectedly, they're still included in this count, even though // they've been removed from |job_list_|. Intended to reduce chance of stalls // on regressions. static int num_jobs_started_; // Head of linked list of jobs that have been started and are now waiting to // be timed out. static URLRequestTimeoutOnDemandJob* job_list_; // The next job that had been started but not yet timed out. URLRequestTimeoutOnDemandJob* next_job_; DISALLOW_COPY_AND_ASSIGN(URLRequestTimeoutOnDemandJob); }; int URLRequestTimeoutOnDemandJob::num_jobs_to_wait_for_ = 0; int URLRequestTimeoutOnDemandJob::last_num_jobs_to_wait_for_ = 0; int URLRequestTimeoutOnDemandJob::num_jobs_started_ = 0; URLRequestTimeoutOnDemandJob* URLRequestTimeoutOnDemandJob::job_list_ = NULL; void URLRequestTimeoutOnDemandJob::Start() { EXPECT_TRUE(CalledOnValidThread()); // Insert at start of the list. next_job_ = job_list_; job_list_ = this; ++num_jobs_started_; // Checks if there are at least |num_jobs_to_wait_for_| jobs in // |job_list_|. If so, exits the message loop on the UI thread, which // should be spinning in a call to WaitForJobs. Does nothing if // |num_jobs_to_wait_for_| is 0. MaybeStopWaitingForJobsOnIOThread(); } // static void URLRequestTimeoutOnDemandJob::WaitForJobs(int num_jobs) { content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&URLRequestTimeoutOnDemandJob::WaitForJobsOnIOThread, num_jobs)); content::RunMessageLoop(); } // static void URLRequestTimeoutOnDemandJob::FailJobs(int expected_num_jobs) { content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread, expected_num_jobs, FAIL_JOBS, net::SSLInfo())); } // static void URLRequestTimeoutOnDemandJob::FailJobsWithCertError( int expected_num_jobs, const net::SSLInfo& ssl_info) { content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread, expected_num_jobs, FAIL_JOBS_WITH_CERT_ERROR, ssl_info)); } // static void URLRequestTimeoutOnDemandJob::AbandonJobs(int expected_num_jobs) { content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread, expected_num_jobs, ABANDON_JOBS, net::SSLInfo())); } URLRequestTimeoutOnDemandJob::URLRequestTimeoutOnDemandJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) : net::URLRequestJob(request, network_delegate), next_job_(NULL) { } URLRequestTimeoutOnDemandJob::~URLRequestTimeoutOnDemandJob() { // All hanging jobs should have failed or been abandoned before being // destroyed. EXPECT_FALSE(RemoveFromList()); } bool URLRequestTimeoutOnDemandJob::RemoveFromList() { URLRequestTimeoutOnDemandJob** job = &job_list_; while (*job) { if (*job == this) { *job = next_job_; next_job_ = NULL; return true; } job = &next_job_; } // If the job wasn't in this list, |next_job_| should be NULL. EXPECT_FALSE(next_job_); return false; } // static void URLRequestTimeoutOnDemandJob::WaitForJobsOnIOThread(int num_jobs) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); ASSERT_EQ(0, num_jobs_to_wait_for_); ASSERT_LT(0, num_jobs); // Number of tabs being waited on should be strictly increasing. ASSERT_LE(last_num_jobs_to_wait_for_, num_jobs); num_jobs_to_wait_for_ = num_jobs; MaybeStopWaitingForJobsOnIOThread(); } // static void URLRequestTimeoutOnDemandJob::MaybeStopWaitingForJobsOnIOThread() { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); if (num_jobs_to_wait_for_ == 0) return; // There shouldn't be any extra jobs. EXPECT_LE(num_jobs_started_, num_jobs_to_wait_for_); // Should never be greater, but if it is, go ahead and exit the message loop // to try and avoid hanging. if (num_jobs_started_ >= num_jobs_to_wait_for_) { last_num_jobs_to_wait_for_ = num_jobs_to_wait_for_; num_jobs_to_wait_for_ = 0; BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, base::MessageLoop::QuitWhenIdleClosure()); } } // static void URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread( int expected_num_jobs, EndJobOperation end_job_operation, const net::SSLInfo& ssl_info) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); ASSERT_LT(0, expected_num_jobs); EXPECT_EQ(last_num_jobs_to_wait_for_, expected_num_jobs); last_num_jobs_to_wait_for_ = 0; int num_jobs = 0; while (job_list_) { ++num_jobs; URLRequestTimeoutOnDemandJob* job = job_list_; // Since the error notification may result in the job's destruction, remove // it from the job list before the error. EXPECT_TRUE(job->RemoveFromList()); if (end_job_operation == FAIL_JOBS) { job->NotifyStartError(net::URLRequestStatus( net::URLRequestStatus::FAILED, net::ERR_CONNECTION_TIMED_OUT)); } else if (end_job_operation == FAIL_JOBS_WITH_CERT_ERROR) { DCHECK(job->request()->url().SchemeIsCryptographic()); job->NotifySSLCertificateError(ssl_info, true); } } EXPECT_EQ(expected_num_jobs, num_jobs_started_); EXPECT_EQ(expected_num_jobs, num_jobs); num_jobs_started_ -= expected_num_jobs; } // URLRequestCaptivePortalJobFactory emulates captive portal behavior. // Initially, it emulates being behind a captive portal. When // SetBehindCaptivePortal(false) is called, it emulates behavior when not behind // a captive portal. The class itself is never instantiated. // // It handles requests for kMockCaptivePortalTestUrl, kMockHttpsUrl, and // kMockHttpsQuickTimeoutUrl. class URLRequestMockCaptivePortalJobFactory { public: URLRequestMockCaptivePortalJobFactory() : behind_captive_portal_(true) {} virtual ~URLRequestMockCaptivePortalJobFactory() {} // Adds the testing URLs to the net::URLRequestFilter. Should only be called // once. void AddUrlHandlers(); // Sets whether or not there is a captive portal. Outstanding requests are // not affected. void SetBehindCaptivePortal(bool behind_captive_portal); private: class Interceptor : public net::URLRequestInterceptor { public: explicit Interceptor(bool behind_captive_portal) : behind_captive_portal_(behind_captive_portal) {} ~Interceptor() override {} // Returns a URLRequestJob that reflects the current captive portal state // for the URLs: kMockCaptivePortalTestUrl, kMockHttpsUrl, and // kMockHttpsQuickTimeoutUrl. See documentation of individual URLs for // actual behavior. net::URLRequestJob* MaybeInterceptRequest( net::URLRequest* request, net::NetworkDelegate* network_delegate) const override; void SetBehindCaptivePortal(bool behind_captive_portal) { behind_captive_portal_ = behind_captive_portal; } private: bool behind_captive_portal_; DISALLOW_COPY_AND_ASSIGN(Interceptor); }; // These do all the work of the corresponding public functions, with the only // difference being that they must be called on the IO thread. void AddUrlHandlersOnIOThread(); void SetBehindCaptivePortalOnIOThread(bool behind_captive_portal); // Create a new Interceptor and add it to |interceptors_|, though it returns // ownership. scoped_ptr CreateInterceptor(); // These variables are only accessed on IO thread, though // URLRequestMockCaptivePortalJobFactory is created and // destroyed on the UI thread. // The Interceptors are owned by URLRequestFilter and live on the IO thread; // these are just references. std::vector interceptors_; bool behind_captive_portal_; DISALLOW_COPY_AND_ASSIGN(URLRequestMockCaptivePortalJobFactory); }; void URLRequestMockCaptivePortalJobFactory::AddUrlHandlers() { content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind( &URLRequestMockCaptivePortalJobFactory::AddUrlHandlersOnIOThread, base::Unretained(this))); } void URLRequestMockCaptivePortalJobFactory::SetBehindCaptivePortal( bool behind_captive_portal) { content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&URLRequestMockCaptivePortalJobFactory:: SetBehindCaptivePortalOnIOThread, base::Unretained(this), behind_captive_portal)); } scoped_ptr URLRequestMockCaptivePortalJobFactory::CreateInterceptor() { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); scoped_ptr interceptor(new Interceptor(behind_captive_portal_)); interceptors_.push_back(interceptor.get()); return std::move(interceptor); } void URLRequestMockCaptivePortalJobFactory::AddUrlHandlersOnIOThread() { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Handle only exact matches, so any related requests, such as those for // favicons, are not handled by the factory. net::URLRequestFilter* filter = net::URLRequestFilter::GetInstance(); filter->AddUrlInterceptor(GURL(kMockCaptivePortalTestUrl), CreateInterceptor()); filter->AddUrlInterceptor(GURL(kMockCaptivePortal511Url), CreateInterceptor()); filter->AddUrlInterceptor(GURL(kMockHttpsUrl), CreateInterceptor()); filter->AddUrlInterceptor(GURL(kMockHttpsUrl2), CreateInterceptor()); filter->AddUrlInterceptor(GURL(kMockHttpsQuickTimeoutUrl), CreateInterceptor()); } void URLRequestMockCaptivePortalJobFactory::SetBehindCaptivePortalOnIOThread( bool behind_captive_portal) { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); behind_captive_portal_ = behind_captive_portal; for (auto* interceptor : interceptors_) interceptor->SetBehindCaptivePortal(behind_captive_portal); } net::URLRequestJob* URLRequestMockCaptivePortalJobFactory::Interceptor::MaybeInterceptRequest( net::URLRequest* request, net::NetworkDelegate* network_delegate) const { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); // The PathService is threadsafe. base::FilePath root_http; PathService::Get(chrome::DIR_TEST_DATA, &root_http); if (request->url() == GURL(kMockHttpsUrl) || request->url() == GURL(kMockHttpsUrl2)) { if (behind_captive_portal_) return new URLRequestTimeoutOnDemandJob(request, network_delegate); // Once logged in to the portal, HTTPS requests return the page that was // actually requested. return new URLRequestMockHTTPJob( request, network_delegate, root_http.Append(FILE_PATH_LITERAL("title2.html")), BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); } else if (request->url() == GURL(kMockHttpsQuickTimeoutUrl)) { if (behind_captive_portal_) return new URLRequestFailedJob( request, network_delegate, net::ERR_CONNECTION_TIMED_OUT); // Once logged in to the portal, HTTPS requests return the page that was // actually requested. return new URLRequestMockHTTPJob( request, network_delegate, root_http.Append(FILE_PATH_LITERAL("title2.html")), BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); } else { // The URL should be the captive portal test URL. EXPECT_TRUE(GURL(kMockCaptivePortalTestUrl) == request->url() || GURL(kMockCaptivePortal511Url) == request->url()); if (behind_captive_portal_) { // Prior to logging in to the portal, the HTTP test URLs are intercepted // by the captive portal. if (GURL(kMockCaptivePortal511Url) == request->url()) { return new URLRequestMockHTTPJob( request, network_delegate, root_http.Append(FILE_PATH_LITERAL("captive_portal/page511.html")), BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); } return new URLRequestMockHTTPJob( request, network_delegate, root_http.Append(FILE_PATH_LITERAL("captive_portal/login.html")), BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); } // After logging in to the portal, the test URLs return a 204 response. return new URLRequestMockHTTPJob( request, network_delegate, root_http.Append(FILE_PATH_LITERAL("captive_portal/page204.html")), BrowserThread::GetBlockingPool()->GetTaskRunnerWithShutdownBehavior( base::SequencedWorkerPool::SKIP_ON_SHUTDOWN)); } } // Creates a server-side redirect for use with the TestServer. std::string CreateServerRedirect(const std::string& dest_url) { const char* const kServerRedirectBase = "/server-redirect?"; return kServerRedirectBase + dest_url; } // Returns the total number of loading tabs across all Browsers, for all // Profiles. int NumLoadingTabs() { int num_loading_tabs = 0; for (TabContentsIterator it; !it.done(); it.Next()) { if (it->IsLoading()) ++num_loading_tabs; } return num_loading_tabs; } bool IsLoginTab(WebContents* web_contents) { return CaptivePortalTabHelper::FromWebContents(web_contents)->IsLoginTab(); } // Tracks how many times each tab has been navigated since the Observer was // created. The standard TestNavigationObserver can only watch specific // pre-existing tabs or loads in serial for all tabs. class MultiNavigationObserver : public content::NotificationObserver { public: MultiNavigationObserver(); ~MultiNavigationObserver() override; // Waits for exactly |num_navigations_to_wait_for| LOAD_STOP // notifications to have occurred since the construction of |this|. More // navigations than expected occuring will trigger a expect failure. void WaitForNavigations(int num_navigations_to_wait_for); // Returns the number of LOAD_STOP events that have occurred for // |web_contents| since this was constructed. int NumNavigationsForTab(WebContents* web_contents) const; // The number of LOAD_STOP events since |this| was created. int num_navigations() const { return num_navigations_; } private: typedef std::map TabNavigationMap; // content::NotificationObserver: void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) override; int num_navigations_; // Map of how many times each tab has navigated since |this| was created. TabNavigationMap tab_navigation_map_; // Total number of navigations to wait for. Value only matters when // |waiting_for_navigation_| is true. int num_navigations_to_wait_for_; // True if WaitForNavigations has been called, until // |num_navigations_to_wait_for_| have been observed. bool waiting_for_navigation_; content::NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(MultiNavigationObserver); }; MultiNavigationObserver::MultiNavigationObserver() : num_navigations_(0), num_navigations_to_wait_for_(0), waiting_for_navigation_(false) { registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, content::NotificationService::AllSources()); } MultiNavigationObserver::~MultiNavigationObserver() { } void MultiNavigationObserver::WaitForNavigations( int num_navigations_to_wait_for) { // Shouldn't already be waiting for navigations. EXPECT_FALSE(waiting_for_navigation_); EXPECT_LT(0, num_navigations_to_wait_for); if (num_navigations_ < num_navigations_to_wait_for) { num_navigations_to_wait_for_ = num_navigations_to_wait_for; waiting_for_navigation_ = true; content::RunMessageLoop(); EXPECT_FALSE(waiting_for_navigation_); } EXPECT_EQ(num_navigations_, num_navigations_to_wait_for); } int MultiNavigationObserver::NumNavigationsForTab( WebContents* web_contents) const { TabNavigationMap::const_iterator tab_navigations = tab_navigation_map_.find(web_contents); if (tab_navigations == tab_navigation_map_.end()) return 0; return tab_navigations->second; } void MultiNavigationObserver::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { ASSERT_EQ(type, content::NOTIFICATION_LOAD_STOP); content::NavigationController* controller = content::Source(source).ptr(); ++num_navigations_; ++tab_navigation_map_[controller->GetWebContents()]; if (waiting_for_navigation_ && num_navigations_to_wait_for_ == num_navigations_) { waiting_for_navigation_ = false; base::MessageLoopForUI::current()->QuitWhenIdle(); } } // This observer creates a list of loading tabs, and then waits for them all // to stop loading and have the kInternetConnectedTitle. // // This is for the specific purpose of observing tabs time out after logging in // to a captive portal, which will then cause them to reload. // MultiNavigationObserver is insufficient for this because there may or may not // be a LOAD_STOP event between the timeout and the reload. // See bug http://crbug.com/133227 class FailLoadsAfterLoginObserver : public content::NotificationObserver { public: FailLoadsAfterLoginObserver(); ~FailLoadsAfterLoginObserver() override; void WaitForNavigations(); private: typedef std::set TabSet; // content::NotificationObserver: void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) override; // The set of tabs that need to be navigated. This is the set of loading // tabs when the observer is created. TabSet tabs_needing_navigation_; // Number of tabs that have stopped navigating with the expected title. These // are expected not to be navigated again. TabSet tabs_navigated_to_final_destination_; // True if WaitForNavigations has been called, until // |tabs_navigated_to_final_destination_| equals |tabs_needing_navigation_|. bool waiting_for_navigation_; content::NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(FailLoadsAfterLoginObserver); }; FailLoadsAfterLoginObserver::FailLoadsAfterLoginObserver() : waiting_for_navigation_(false) { registrar_.Add(this, content::NOTIFICATION_LOAD_STOP, content::NotificationService::AllSources()); for (TabContentsIterator it; !it.done(); it.Next()) { if (it->IsLoading()) tabs_needing_navigation_.insert(*it); } } FailLoadsAfterLoginObserver::~FailLoadsAfterLoginObserver() { } void FailLoadsAfterLoginObserver::WaitForNavigations() { // Shouldn't already be waiting for navigations. EXPECT_FALSE(waiting_for_navigation_); if (tabs_needing_navigation_.size() != tabs_navigated_to_final_destination_.size()) { waiting_for_navigation_ = true; content::RunMessageLoop(); EXPECT_FALSE(waiting_for_navigation_); } EXPECT_EQ(tabs_needing_navigation_.size(), tabs_navigated_to_final_destination_.size()); } void FailLoadsAfterLoginObserver::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { ASSERT_EQ(type, content::NOTIFICATION_LOAD_STOP); content::NavigationController* controller = content::Source(source).ptr(); WebContents* contents = controller->GetWebContents(); ASSERT_EQ(1u, tabs_needing_navigation_.count(contents)); ASSERT_EQ(0u, tabs_navigated_to_final_destination_.count(contents)); if (contents->GetTitle() != base::ASCIIToUTF16(kInternetConnectedTitle)) return; tabs_navigated_to_final_destination_.insert(contents); if (waiting_for_navigation_ && tabs_needing_navigation_.size() == tabs_navigated_to_final_destination_.size()) { waiting_for_navigation_ = false; base::MessageLoopForUI::current()->QuitWhenIdle(); } } // An observer for watching the CaptivePortalService. It tracks the last // received result and the total number of received results. class CaptivePortalObserver : public content::NotificationObserver { public: explicit CaptivePortalObserver(Profile* profile); // Runs the message loop until exactly |update_count| captive portal // results have been received, since the creation of |this|. Expects no // additional captive portal results. void WaitForResults(int num_results_to_wait_for); int num_results_received() const { return num_results_received_; } CaptivePortalResult captive_portal_result() const { return captive_portal_result_; } private: // Records results and exits the message loop, if needed. void Observe(int type, const content::NotificationSource& source, const content::NotificationDetails& details) override; // Number of times OnPortalResult has been called since construction. int num_results_received_; // If WaitForResults was called, the total number of updates for which to // wait. Value doesn't matter when |waiting_for_result_| is false. int num_results_to_wait_for_; bool waiting_for_result_; Profile* profile_; CaptivePortalService* captive_portal_service_; // Last result received. CaptivePortalResult captive_portal_result_; content::NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(CaptivePortalObserver); }; CaptivePortalObserver::CaptivePortalObserver(Profile* profile) : num_results_received_(0), num_results_to_wait_for_(0), waiting_for_result_(false), profile_(profile), captive_portal_service_( CaptivePortalServiceFactory::GetForProfile(profile)), captive_portal_result_( captive_portal_service_->last_detection_result()) { registrar_.Add(this, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT, content::Source(profile_)); } void CaptivePortalObserver::WaitForResults(int num_results_to_wait_for) { EXPECT_LT(0, num_results_to_wait_for); EXPECT_FALSE(waiting_for_result_); if (num_results_received_ < num_results_to_wait_for) { num_results_to_wait_for_ = num_results_to_wait_for; waiting_for_result_ = true; content::RunMessageLoop(); EXPECT_FALSE(waiting_for_result_); } EXPECT_EQ(num_results_to_wait_for, num_results_received_); } void CaptivePortalObserver::Observe( int type, const content::NotificationSource& source, const content::NotificationDetails& details) { ASSERT_EQ(type, chrome::NOTIFICATION_CAPTIVE_PORTAL_CHECK_RESULT); ASSERT_EQ(profile_, content::Source(source).ptr()); CaptivePortalService::Results* results = content::Details(details).ptr(); EXPECT_EQ(captive_portal_result_, results->previous_result); EXPECT_EQ(captive_portal_service_->last_detection_result(), results->result); captive_portal_result_ = results->result; ++num_results_received_; if (waiting_for_result_ && num_results_to_wait_for_ == num_results_received_) { waiting_for_result_ = false; base::MessageLoop::current()->QuitWhenIdle(); } } // This observer waits for the SSLErrorHandler to start an interstitial timer // for the given web contents. class SSLInterstitialTimerObserver { public: explicit SSLInterstitialTimerObserver(content::WebContents* web_contents); ~SSLInterstitialTimerObserver(); // Waits until the interstitial delay timer in SSLErrorHandler is started. void WaitForTimerStarted(); private: void OnTimerStarted(content::WebContents* web_contents); const content::WebContents* web_contents_; SSLErrorHandler::TimerStartedCallback callback_; scoped_refptr message_loop_runner_; DISALLOW_COPY_AND_ASSIGN(SSLInterstitialTimerObserver); }; SSLInterstitialTimerObserver::SSLInterstitialTimerObserver( content::WebContents* web_contents) : web_contents_(web_contents), message_loop_runner_(new content::MessageLoopRunner) { callback_ = base::Bind(&SSLInterstitialTimerObserver::OnTimerStarted, base::Unretained(this)); SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(&callback_); } SSLInterstitialTimerObserver::~SSLInterstitialTimerObserver() { SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(nullptr); } void SSLInterstitialTimerObserver::WaitForTimerStarted() { message_loop_runner_->Run(); } void SSLInterstitialTimerObserver::OnTimerStarted( content::WebContents* web_contents) { if (web_contents_ == web_contents && message_loop_runner_.get()) message_loop_runner_->Quit(); } // Adds an HSTS rule for |host|, so that all HTTP requests sent to it will // be switched to HTTPS requests. void AddHstsHost(net::URLRequestContextGetter* context_getter, const std::string& host) { ASSERT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::IO)); net::TransportSecurityState* transport_security_state = context_getter->GetURLRequestContext()->transport_security_state(); if (!transport_security_state) { FAIL(); return; } base::Time expiry = base::Time::Now() + base::TimeDelta::FromDays(1000); bool include_subdomains = false; transport_security_state->AddHSTS(host, expiry, include_subdomains); } } // namespace class CaptivePortalBrowserTest : public InProcessBrowserTest { public: CaptivePortalBrowserTest(); // InProcessBrowserTest: void SetUpOnMainThread() override; void TearDownOnMainThread() override; void SetUpCommandLine(base::CommandLine* command_line) override; // Sets the captive portal checking preference. Does not affect the command // line flag, which is set in SetUpCommandLine. void EnableCaptivePortalDetection(Profile* profile, bool enabled); // Enables or disables actual captive portal probes. Should only be called // after captive portal service setup is done. When disabled, probe requests // are silently ignored, never receiving a response. void RespondToProbeRequests(bool enabled); // Sets up the captive portal service for the given profile so that // all checks go to |test_url|. Also disables all timers. void SetUpCaptivePortalService(Profile* profile, const GURL& test_url); // Returns true if |browser|'s profile is currently running a captive portal // check. bool CheckPending(Browser* browser); // Returns the type of the interstitial being shown. content::InterstitialPageDelegate::TypeID GetInterstitialType( WebContents* contents) const; // Returns the CaptivePortalTabReloader::State of |web_contents|. CaptivePortalTabReloader::State GetStateOfTabReloader( WebContents* web_contents) const; // Returns the CaptivePortalTabReloader::State of the indicated tab. CaptivePortalTabReloader::State GetStateOfTabReloaderAt(Browser* browser, int index) const; // Returns the number of tabs with the given state, across all profiles. int NumTabsWithState(CaptivePortalTabReloader::State state) const; // Returns the number of tabs broken by captive portals, across all profiles. int NumBrokenTabs() const; // Returns the number of tabs that need to be reloaded due to having logged // in to a captive portal, across all profiles. int NumNeedReloadTabs() const; // Navigates |browser|'s active tab to |url| and expects no captive portal // test to be triggered. |expected_navigations| is the number of times the // active tab will end up being navigated. It should be 1, except for the // Link Doctor page, which acts like two navigations. void NavigateToPageExpectNoTest(Browser* browser, const GURL& url, int expected_navigations); // Navigates |browser|'s active tab to an SSL tab that takes a while to load, // triggering a captive portal check, which is expected to give the result // |expected_result|. The page finishes loading, with a timeout, after the // captive portal check. void SlowLoadNoCaptivePortal(Browser* browser, CaptivePortalResult expected_result); // Navigates |browser|'s active tab to an SSL timeout, expecting a captive // portal check to be triggered and return a result which will indicates // there's no detected captive portal. void FastTimeoutNoCaptivePortal(Browser* browser, CaptivePortalResult expected_result); // Navigates the active tab to a slow loading SSL page, which will then // trigger a captive portal test. The test is expected to find a captive // portal. The slow loading page will continue to load after the function // returns, until URLRequestTimeoutOnDemandJob::FailJobs() is called, // at which point it will timeout. // // When |expect_open_login_tab| is false, no login tab is expected to be // opened, because one already exists, and the function returns once the // captive portal test is complete. // // If |expect_open_login_tab| is true, a login tab is then expected to be // opened. It waits until both the login tab has finished loading, and two // captive portal tests complete. The second test is triggered by the load of // the captive portal tab completing. // // This function must not be called when the active tab is currently loading. // Waits for the hanging request to be issued, so other functions can rely // on URLRequestTimeoutOnDemandJob::WaitForJobs having been called. void SlowLoadBehindCaptivePortal(Browser* browser, bool expect_open_login_tab); // Same as above, but takes extra parameters. // // |hanging_url| should either be kMockHttpsUrl or redirect to kMockHttpsUrl. // // |expected_portal_checks| and |expected_login_tab_navigations| allow // client-side redirects to be tested. |expected_login_tab_navigations| is // ignored when |expect_open_login_tab| is false. void SlowLoadBehindCaptivePortal(Browser* browser, bool expect_open_login_tab, const GURL& hanging_url, int expected_portal_checks, int expected_login_tab_navigations); // Just like SlowLoadBehindCaptivePortal, except the navigated tab has // a connection timeout rather having its time trigger, and the function // waits until that timeout occurs. void FastTimeoutBehindCaptivePortal(Browser* browser, bool expect_open_login_tab); // Much as above, but accepts a URL parameter and can be used for errors that // trigger captive portal checks other than timeouts. |error_url| should // result in an error rather than hanging. // If |delay_portal_response_until_interstital| is true, captive portal probe // request are ignored until the interstitial is shown, at which point a // captive portal result is sent. This allows testing in conjunction with the // certificate error interstitial. void FastErrorBehindCaptivePortal( Browser* browser, bool expect_open_login_tab, const GURL& error_url, bool delay_portal_response_until_interstital); // Navigates the active tab to an SSL error page which triggers an // interstitial timer. Also disables captive portal checks indefinitely, so // the page appears to be hanging. void FastErrorWithInterstitialTimer(Browser* browser, const GURL& cert_error_url); // Navigates the login tab without logging in. The login tab must be the // specified browser's active tab. Expects no other tab to change state. // |num_loading_tabs| and |num_timed_out_tabs| are used as extra checks // that nothing has gone wrong prior to the function call. void NavigateLoginTab(Browser* browser, int num_loading_tabs, int num_timed_out_tabs); // Simulates a login by updating the URLRequestMockCaptivePortalJob's // behind captive portal state, and navigating the login tab. Waits for // all broken but not loading tabs to be reloaded. // |num_loading_tabs| and |num_timed_out_tabs| are used as extra checks // that nothing has gone wrong prior to the function call. void Login(Browser* browser, int num_loading_tabs, int num_timed_out_tabs); // Simulates a login when the broken tab shows an SSL or captive portal // interstitial. Can't use Login() in those cases because the interstitial // tab looks like a cross between a hung tab (Load was never committed) and a // tab at an error page (The load was stopped). void LoginCertError(Browser* browser); // Makes the slow SSL loads of all active tabs time out at once, and waits for // them to finish both that load and the automatic reload it should trigger. // There should be no timed out tabs when this is called. void FailLoadsAfterLogin(Browser* browser, int num_loading_tabs); // Makes the slow SSL loads of all active tabs time out at once, and waits for // them to finish displaying their error pages. The login tab should be the // active tab. There should be no timed out tabs when this is called. void FailLoadsWithoutLogin(Browser* browser, int num_loading_tabs); // Navigates |browser|'s active tab to |starting_url| while not behind a // captive portal. Then navigates to |interrupted_url|, which should create // a URLRequestTimeoutOnDemandJob, which is then abandoned. The load should // trigger a captive portal check, which finds a captive portal and opens a // tab. // // Then the navigation is interrupted by a navigation to |timeout_url|, which // should trigger a captive portal check, and finally the test simulates // logging in. // // The purpose of this test is to make sure the TabHelper triggers a captive // portal check when a load is interrupted by another load, particularly in // the case of cross-process navigations. void RunNavigateLoadingTabToTimeoutTest(Browser* browser, const GURL& starting_url, const GURL& interrupted_url, const GURL& timeout_url); // Sets the timeout used by a CaptivePortalTabReloader on slow SSL loads // before a captive portal check. void SetSlowSSLLoadTime(CaptivePortalTabReloader* tab_reloader, base::TimeDelta slow_ssl_load_time); CaptivePortalTabReloader* GetTabReloader(WebContents* web_contents) const; protected: URLRequestMockCaptivePortalJobFactory factory_; private: DISALLOW_COPY_AND_ASSIGN(CaptivePortalBrowserTest); }; CaptivePortalBrowserTest::CaptivePortalBrowserTest() { } void CaptivePortalBrowserTest::SetUpOnMainThread() { // Enable mock requests. content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); factory_.AddUrlHandlers(); // Double-check that the captive portal service isn't enabled by default for // browser tests. EXPECT_EQ(CaptivePortalService::DISABLED_FOR_TESTING, CaptivePortalService::get_state_for_testing()); CaptivePortalService::set_state_for_testing( CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); EnableCaptivePortalDetection(browser()->profile(), true); // Set the captive portal service to use URLRequestMockCaptivePortalJob's // mock URL, by default. SetUpCaptivePortalService(browser()->profile(), GURL(kMockCaptivePortalTestUrl)); // Set SSL interstitial delay long enough so that a captive portal result // is guaranteed to arrive during this window, and a captive portal // error page is displayed instead of an SSL interstitial. SSLErrorHandler::SetInterstitialDelayForTest(base::TimeDelta::FromHours(1)); } void CaptivePortalBrowserTest::TearDownOnMainThread() { // No test should have a captive portal check pending on quit. EXPECT_FALSE(CheckPending(browser())); } void CaptivePortalBrowserTest::SetUpCommandLine( base::CommandLine* command_line) { // Enable finch experiment for captive portal interstitials. command_line->AppendSwitchASCII( switches::kForceFieldTrials, "CaptivePortalInterstitial/Enabled/"); } void CaptivePortalBrowserTest::EnableCaptivePortalDetection( Profile* profile, bool enabled) { profile->GetPrefs()->SetBoolean(prefs::kAlternateErrorPagesEnabled, enabled); } void CaptivePortalBrowserTest::RespondToProbeRequests(bool enabled) { if (enabled) { EXPECT_EQ(CaptivePortalService::IGNORE_REQUESTS_FOR_TESTING, CaptivePortalService::get_state_for_testing()); CaptivePortalService::set_state_for_testing( CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING); } else { EXPECT_EQ(CaptivePortalService::SKIP_OS_CHECK_FOR_TESTING, CaptivePortalService::get_state_for_testing()); CaptivePortalService::set_state_for_testing( CaptivePortalService::IGNORE_REQUESTS_FOR_TESTING); } } void CaptivePortalBrowserTest::SetUpCaptivePortalService(Profile* profile, const GURL& test_url) { CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(profile); captive_portal_service->set_test_url(test_url); // Don't use any non-zero timers. Timers are checked in unit tests. CaptivePortalService::RecheckPolicy* recheck_policy = &captive_portal_service->recheck_policy(); recheck_policy->initial_backoff_no_portal_ms = 0; recheck_policy->initial_backoff_portal_ms = 0; recheck_policy->backoff_policy.maximum_backoff_ms = 0; } bool CaptivePortalBrowserTest::CheckPending(Browser* browser) { CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(browser->profile()); return captive_portal_service->DetectionInProgress() || captive_portal_service->TimerRunning(); } content::InterstitialPageDelegate::TypeID CaptivePortalBrowserTest::GetInterstitialType(WebContents* contents) const { if (!contents->ShowingInterstitialPage()) return nullptr; return contents->GetInterstitialPage() ->GetDelegateForTesting() ->GetTypeForTesting(); } CaptivePortalTabReloader::State CaptivePortalBrowserTest::GetStateOfTabReloader( WebContents* web_contents) const { return GetTabReloader(web_contents)->state(); } CaptivePortalTabReloader::State CaptivePortalBrowserTest::GetStateOfTabReloaderAt(Browser* browser, int index) const { return GetStateOfTabReloader( browser->tab_strip_model()->GetWebContentsAt(index)); } int CaptivePortalBrowserTest::NumTabsWithState( CaptivePortalTabReloader::State state) const { int num_tabs = 0; for (TabContentsIterator it; !it.done(); it.Next()) { if (GetStateOfTabReloader(*it) == state) ++num_tabs; } return num_tabs; } int CaptivePortalBrowserTest::NumBrokenTabs() const { return NumTabsWithState(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL); } int CaptivePortalBrowserTest::NumNeedReloadTabs() const { return NumTabsWithState(CaptivePortalTabReloader::STATE_NEEDS_RELOAD); } void CaptivePortalBrowserTest::NavigateToPageExpectNoTest( Browser* browser, const GURL& url, int expected_navigations) { MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( browser, url, expected_navigations); // No captive portal checks should have ocurred or be pending, and there // should be no new tabs. EXPECT_EQ(0, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(1, browser->tab_strip_model()->count()); EXPECT_EQ(expected_navigations, navigation_observer.num_navigations()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, 0)); } void CaptivePortalBrowserTest::SlowLoadNoCaptivePortal( Browser* browser, CaptivePortalResult expected_result) { CaptivePortalTabReloader* tab_reloader = GetTabReloader(browser->tab_strip_model()->GetActiveWebContents()); ASSERT_TRUE(tab_reloader); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta()); MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); ui_test_utils::NavigateToURLWithDisposition(browser, GURL(kMockHttpsUrl), CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE); portal_observer.WaitForResults(1); ASSERT_EQ(1, browser->tab_strip_model()->count()); EXPECT_EQ(expected_result, portal_observer.captive_portal_result()); EXPECT_EQ(1, portal_observer.num_results_received()); EXPECT_EQ(0, navigation_observer.num_navigations()); EXPECT_FALSE(CheckPending(browser)); // First tab should still be loading. EXPECT_EQ(1, NumLoadingTabs()); // Wait for the request to be issued, then time it out. URLRequestTimeoutOnDemandJob::WaitForJobs(1); URLRequestTimeoutOnDemandJob::FailJobs(1); navigation_observer.WaitForNavigations(1); ASSERT_EQ(1, browser->tab_strip_model()->count()); EXPECT_EQ(1, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(0, NumLoadingTabs()); // Set a slow SSL load time to prevent the timer from triggering. SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromDays(1)); } void CaptivePortalBrowserTest::FastTimeoutNoCaptivePortal( Browser* browser, CaptivePortalResult expected_result) { ASSERT_NE(expected_result, captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL); // Set the load time to be large, so the timer won't trigger. The value is // not restored at the end of the function. CaptivePortalTabReloader* tab_reloader = GetTabReloader(browser->tab_strip_model()->GetActiveWebContents()); ASSERT_TRUE(tab_reloader); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromHours(1)); MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); // Neither of these should be changed by the navigation. int active_index = browser->tab_strip_model()->active_index(); int expected_tab_count = browser->tab_strip_model()->count(); ui_test_utils::NavigateToURL( browser, URLRequestFailedJob::GetMockHttpsUrl(net::ERR_CONNECTION_TIMED_OUT)); // An attempt to detect a captive portal should have started by now. If not, // abort early to prevent hanging. ASSERT_TRUE(portal_observer.num_results_received() > 0 || CheckPending(browser)); portal_observer.WaitForResults(1); navigation_observer.WaitForNavigations(1); // Check the result. EXPECT_EQ(1, portal_observer.num_results_received()); EXPECT_EQ(expected_result, portal_observer.captive_portal_result()); // Check that the right tab was navigated, and there were no extra // navigations. EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( browser->tab_strip_model()->GetWebContentsAt(active_index))); EXPECT_EQ(0, NumLoadingTabs()); // Check the tab's state, and verify no captive portal check is pending. EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, 0)); EXPECT_FALSE(CheckPending(browser)); // Make sure no login tab was opened. EXPECT_EQ(expected_tab_count, browser->tab_strip_model()->count()); } void CaptivePortalBrowserTest::SlowLoadBehindCaptivePortal( Browser* browser, bool expect_open_login_tab) { SlowLoadBehindCaptivePortal(browser, expect_open_login_tab, GURL(kMockHttpsUrl), 1, 1); } void CaptivePortalBrowserTest::SlowLoadBehindCaptivePortal( Browser* browser, bool expect_open_login_tab, const GURL& hanging_url, int expected_portal_checks, int expected_login_tab_navigations) { ASSERT_GE(expected_portal_checks, 1); TabStripModel* tab_strip_model = browser->tab_strip_model(); // Calling this on a tab that's waiting for a load to manually be timed out // will result in a hang. ASSERT_FALSE(tab_strip_model->GetActiveWebContents()->IsLoading()); // Trigger a captive portal check quickly. CaptivePortalTabReloader* tab_reloader = GetTabReloader(tab_strip_model->GetActiveWebContents()); ASSERT_TRUE(tab_reloader); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta()); // Number of tabs expected to be open after the captive portal checks // have completed. int initial_tab_count = tab_strip_model->count(); int initial_active_index = tab_strip_model->active_index(); int initial_loading_tabs = NumLoadingTabs(); int expected_broken_tabs = NumBrokenTabs(); if (CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL != GetStateOfTabReloader(tab_strip_model->GetActiveWebContents())) { ++expected_broken_tabs; } MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); ui_test_utils::NavigateToURLWithDisposition(browser, hanging_url, CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE); portal_observer.WaitForResults(expected_portal_checks); if (expect_open_login_tab) { ASSERT_GE(expected_login_tab_navigations, 1); navigation_observer.WaitForNavigations(expected_login_tab_navigations); ASSERT_EQ(initial_tab_count + 1, tab_strip_model->count()); EXPECT_EQ(initial_tab_count, tab_strip_model->active_index()); EXPECT_EQ(expected_login_tab_navigations, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(initial_tab_count))); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, 1)); EXPECT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); } else { EXPECT_EQ(0, navigation_observer.num_navigations()); EXPECT_EQ(initial_active_index, tab_strip_model->active_index()); ASSERT_EQ(initial_tab_count, tab_strip_model->count()); EXPECT_EQ(initial_active_index, tab_strip_model->active_index()); } // Wait for all the expect resource loads to actually start, so subsequent // functions can rely on them having started. URLRequestTimeoutOnDemandJob::WaitForJobs(initial_loading_tabs + 1); EXPECT_EQ(initial_loading_tabs + 1, NumLoadingTabs()); EXPECT_EQ(expected_broken_tabs, NumBrokenTabs()); EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer.captive_portal_result()); EXPECT_EQ(expected_portal_checks, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(browser, initial_active_index)); // Reset the load time to be large, so the timer won't trigger on a reload. SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromHours(1)); } void CaptivePortalBrowserTest::FastTimeoutBehindCaptivePortal( Browser* browser, bool expect_open_login_tab) { FastErrorBehindCaptivePortal(browser, expect_open_login_tab, GURL(kMockHttpsQuickTimeoutUrl), false); } void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal( Browser* browser, bool expect_open_login_tab, const GURL& error_url, bool delay_portal_response_until_interstital) { TabStripModel* tab_strip_model = browser->tab_strip_model(); // Calling this on a tab that's waiting for a load to manually be timed out // will result in a hang. ASSERT_FALSE(tab_strip_model->GetActiveWebContents()->IsLoading()); // Set the load time to be large, so the timer won't trigger. The value is // not restored at the end of the function. CaptivePortalTabReloader* tab_reloader = GetTabReloader(tab_strip_model->GetActiveWebContents()); ASSERT_TRUE(tab_reloader); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromHours(1)); // Number of tabs expected to be open after the captive portal checks // have completed. int initial_tab_count = tab_strip_model->count(); int initial_active_index = tab_strip_model->active_index(); int initial_loading_tabs = NumLoadingTabs(); int expected_broken_tabs = NumBrokenTabs(); if (CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL != GetStateOfTabReloader(tab_strip_model->GetActiveWebContents())) { ++expected_broken_tabs; } CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(browser->profile()); if (delay_portal_response_until_interstital) RespondToProbeRequests(false); MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); ui_test_utils::NavigateToURLWithDisposition(browser, error_url, CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE); if (delay_portal_response_until_interstital) { EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, initial_active_index)); // Once the interstitial is attached, probe for captive portal. WaitForInterstitialAttach(tab_strip_model->GetActiveWebContents()); RespondToProbeRequests(true); captive_portal_service->DetectCaptivePortal(); } portal_observer.WaitForResults(1); if (expect_open_login_tab) { navigation_observer.WaitForNavigations(2); ASSERT_EQ(initial_tab_count + 1, tab_strip_model->count()); EXPECT_EQ(initial_tab_count, tab_strip_model->active_index()); // Make sure that the originally active tab and the captive portal tab have // each loaded once. EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(initial_active_index))); EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(initial_tab_count))); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, 1)); EXPECT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); } else { navigation_observer.WaitForNavigations(1); EXPECT_EQ(initial_active_index, tab_strip_model->active_index()); EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(initial_active_index))); ASSERT_EQ(initial_tab_count, tab_strip_model->count()); EXPECT_EQ(initial_active_index, tab_strip_model->active_index()); } EXPECT_EQ(initial_loading_tabs, NumLoadingTabs()); EXPECT_EQ(expected_broken_tabs, NumBrokenTabs()); EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer.captive_portal_result()); EXPECT_EQ(1, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(browser, initial_active_index)); } void CaptivePortalBrowserTest::FastErrorWithInterstitialTimer( Browser* browser, const GURL& cert_error_url) { TabStripModel* tab_strip_model = browser->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); // Disable captive portal checks indefinitely. RespondToProbeRequests(false); SSLInterstitialTimerObserver interstitial_timer_observer(broken_tab_contents); ui_test_utils::NavigateToURLWithDisposition(browser, cert_error_url, CURRENT_TAB, ui_test_utils::BROWSER_TEST_NONE); interstitial_timer_observer.WaitForTimerStarted(); // The tab should be in loading state, waiting for the interstitial timer to // expire or a captive portal result to arrive. Since captive portal checks // are disabled and timer set to expire after a very long time, the tab should // hang indefinitely. EXPECT_TRUE(broken_tab_contents->IsLoading()); EXPECT_EQ(1, NumLoadingTabs()); } void CaptivePortalBrowserTest::NavigateLoginTab(Browser* browser, int num_loading_tabs, int num_timed_out_tabs) { MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); TabStripModel* tab_strip_model = browser->tab_strip_model(); int initial_tab_count = tab_strip_model->count(); EXPECT_EQ(num_loading_tabs, NumLoadingTabs()); EXPECT_EQ(num_timed_out_tabs, NumBrokenTabs() - NumLoadingTabs()); int login_tab_index = tab_strip_model->active_index(); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloader(tab_strip_model->GetActiveWebContents())); ASSERT_TRUE(IsLoginTab(browser->tab_strip_model()->GetActiveWebContents())); // Do the navigation. EXPECT_TRUE(content::ExecuteScript(tab_strip_model->GetActiveWebContents(), "submitForm()")); portal_observer.WaitForResults(1); navigation_observer.WaitForNavigations(1); // Check the captive portal result. EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer.captive_portal_result()); EXPECT_EQ(1, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); // Make sure not much has changed. EXPECT_EQ(initial_tab_count, tab_strip_model->count()); EXPECT_EQ(num_loading_tabs, NumLoadingTabs()); EXPECT_EQ(num_loading_tabs + num_timed_out_tabs, NumBrokenTabs()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, login_tab_index)); EXPECT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(login_tab_index))); // Make sure there were no unexpected navigations. EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(login_tab_index))); } void CaptivePortalBrowserTest::Login(Browser* browser, int num_loading_tabs, int num_timed_out_tabs) { // Simulate logging in. factory_.SetBehindCaptivePortal(false); MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); TabStripModel* tab_strip_model = browser->tab_strip_model(); int initial_tab_count = tab_strip_model->count(); ASSERT_EQ(num_loading_tabs, NumLoadingTabs()); EXPECT_EQ(num_timed_out_tabs, NumBrokenTabs() - NumLoadingTabs()); // Verify that the login page is on top. int login_tab_index = tab_strip_model->active_index(); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, login_tab_index)); ASSERT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(login_tab_index))); // Trigger a navigation. EXPECT_TRUE(content::ExecuteScript(tab_strip_model->GetActiveWebContents(), "submitForm()")); portal_observer.WaitForResults(1); // Wait for all the timed out tabs to reload. navigation_observer.WaitForNavigations(1 + num_timed_out_tabs); EXPECT_EQ(1, portal_observer.num_results_received()); // The tabs that were loading before should still be loading, and now be in // STATE_NEEDS_RELOAD. EXPECT_EQ(0, NumBrokenTabs()); EXPECT_EQ(num_loading_tabs, NumLoadingTabs()); EXPECT_EQ(num_loading_tabs, NumNeedReloadTabs()); // Make sure that the broken tabs have reloaded, and there's no more // captive portal tab. EXPECT_EQ(initial_tab_count, tab_strip_model->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, login_tab_index)); EXPECT_FALSE(IsLoginTab(tab_strip_model->GetWebContentsAt(login_tab_index))); // Make sure there were no unexpected navigations of the login tab. EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(login_tab_index))); } void CaptivePortalBrowserTest::LoginCertError(Browser* browser) { factory_.SetBehindCaptivePortal(false); MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser->profile()); TabStripModel* tab_strip_model = browser->tab_strip_model(); // Verify that the login page is on top. int login_tab_index = tab_strip_model->active_index(); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, login_tab_index)); ASSERT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(login_tab_index))); // Trigger a navigation. EXPECT_TRUE(content::ExecuteScript(tab_strip_model->GetActiveWebContents(), "submitForm()")); // The captive portal tab navigation will trigger a captive portal check, // and reloading the original tab will bring up the interstitial page again, // triggering a second captive portal check. portal_observer.WaitForResults(2); // Wait for both tabs to finish loading. navigation_observer.WaitForNavigations(2); EXPECT_EQ(2, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED, portal_observer.captive_portal_result()); // Check state of tabs. While the first tab is still displaying an // interstitial page, since no portal was found, it should be in STATE_NONE, // as should the login tab. ASSERT_EQ(2, tab_strip_model->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, login_tab_index)); EXPECT_FALSE(IsLoginTab(tab_strip_model->GetWebContentsAt(login_tab_index))); // Make sure only one navigation was for the login tab. EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(login_tab_index))); } void CaptivePortalBrowserTest::FailLoadsAfterLogin(Browser* browser, int num_loading_tabs) { ASSERT_EQ(num_loading_tabs, NumLoadingTabs()); ASSERT_EQ(num_loading_tabs, NumNeedReloadTabs()); EXPECT_EQ(0, NumBrokenTabs()); TabStripModel* tab_strip_model = browser->tab_strip_model(); int initial_num_tabs = tab_strip_model->count(); int initial_active_tab = tab_strip_model->active_index(); CaptivePortalObserver portal_observer(browser->profile()); FailLoadsAfterLoginObserver fail_loads_observer; // Connection(s) finally time out. There should have already been a call // to wait for the requests to be issued before logging on. URLRequestTimeoutOnDemandJob::WaitForJobs(num_loading_tabs); URLRequestTimeoutOnDemandJob::FailJobs(num_loading_tabs); fail_loads_observer.WaitForNavigations(); // No captive portal checks should have ocurred or be pending, and there // should be no new tabs. EXPECT_EQ(0, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(initial_num_tabs, tab_strip_model->count()); EXPECT_EQ(initial_active_tab, tab_strip_model->active_index()); EXPECT_EQ(0, NumNeedReloadTabs()); EXPECT_EQ(0, NumLoadingTabs()); } void CaptivePortalBrowserTest::FailLoadsWithoutLogin(Browser* browser, int num_loading_tabs) { ASSERT_EQ(num_loading_tabs, NumLoadingTabs()); ASSERT_EQ(0, NumNeedReloadTabs()); EXPECT_EQ(num_loading_tabs, NumBrokenTabs()); TabStripModel* tab_strip_model = browser->tab_strip_model(); int initial_num_tabs = tab_strip_model->count(); int login_tab = tab_strip_model->active_index(); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloader(tab_strip_model->GetActiveWebContents())); ASSERT_TRUE(IsLoginTab(tab_strip_model->GetActiveWebContents())); CaptivePortalObserver portal_observer(browser->profile()); MultiNavigationObserver navigation_observer; // Connection(s) finally time out. There should have already been a call // to wait for the requests to be issued. URLRequestTimeoutOnDemandJob::FailJobs(num_loading_tabs); navigation_observer.WaitForNavigations(num_loading_tabs); // No captive portal checks should have ocurred or be pending, and there // should be no new tabs. EXPECT_EQ(0, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(initial_num_tabs, tab_strip_model->count()); EXPECT_EQ(0, NumNeedReloadTabs()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_EQ(num_loading_tabs, NumBrokenTabs()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloader(tab_strip_model->GetActiveWebContents())); EXPECT_TRUE(IsLoginTab(tab_strip_model->GetActiveWebContents())); EXPECT_EQ(login_tab, tab_strip_model->active_index()); EXPECT_EQ(0, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(login_tab))); } void CaptivePortalBrowserTest::RunNavigateLoadingTabToTimeoutTest( Browser* browser, const GURL& starting_url, const GURL& hanging_url, const GURL& timeout_url) { // Temporarily disable the captive portal and navigate to the starting // URL, which may be a URL that will hang when behind a captive portal. factory_.SetBehindCaptivePortal(false); NavigateToPageExpectNoTest(browser, starting_url, 1); factory_.SetBehindCaptivePortal(true); // Go to the first hanging url. SlowLoadBehindCaptivePortal(browser, true, hanging_url, 1, 1); // Abandon the request. URLRequestTimeoutOnDemandJob::WaitForJobs(1); URLRequestTimeoutOnDemandJob::AbandonJobs(1); TabStripModel* tab_strip_model = browser->tab_strip_model(); CaptivePortalTabReloader* tab_reloader = GetTabReloader(tab_strip_model->GetWebContentsAt(0)); ASSERT_TRUE(tab_reloader); // A non-zero delay makes it more likely that CaptivePortalTabHelper will // be confused by events relating to canceling the old navigation. SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromSeconds(2)); CaptivePortalObserver portal_observer(browser->profile()); // Navigate the error tab to another slow loading page. Can't have // ui_test_utils do the navigation because it will wait for loading tabs to // stop loading before navigating. // // This may result in either 0 or 1 DidStopLoading events. If there is one, // it must happen before the CaptivePortalService sends out its test request, // so waiting for PortalObserver to see that request prevents it from // confusing the MultiNavigationObservers used later. tab_strip_model->ActivateTabAt(0, true); browser->OpenURL(content::OpenURLParams(timeout_url, content::Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false)); portal_observer.WaitForResults(1); EXPECT_FALSE(CheckPending(browser)); EXPECT_EQ(1, NumLoadingTabs()); EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(browser, 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser, 1)); ASSERT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); // Need to make sure the request has been issued before logging in. URLRequestTimeoutOnDemandJob::WaitForJobs(1); // Simulate logging in. tab_strip_model->ActivateTabAt(1, true); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromDays(1)); Login(browser, 1, 0); // Timeout occurs, and page is automatically reloaded. FailLoadsAfterLogin(browser, 1); } void CaptivePortalBrowserTest::SetSlowSSLLoadTime( CaptivePortalTabReloader* tab_reloader, base::TimeDelta slow_ssl_load_time) { tab_reloader->set_slow_ssl_load_time(slow_ssl_load_time); } CaptivePortalTabReloader* CaptivePortalBrowserTest::GetTabReloader( WebContents* web_contents) const { return CaptivePortalTabHelper::FromWebContents(web_contents)-> GetTabReloaderForTest(); } // Make sure there's no test for a captive portal on HTTP timeouts. This will // also trigger the link doctor page, which results in the load of a second // error page. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HttpTimeout) { GURL url = URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_TIMED_OUT); NavigateToPageExpectNoTest(browser(), url, 2); } // Make sure there's no check for a captive portal on HTTPS errors other than // timeouts, when they preempt the slow load timer. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HttpsNonTimeoutError) { GURL url = URLRequestFailedJob::GetMockHttpsUrl(net::ERR_UNEXPECTED); NavigateToPageExpectNoTest(browser(), url, 1); } // Make sure no captive portal test triggers on HTTPS timeouts of iframes. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HttpsIframeTimeout) { // Use an HTTPS server for the top level page. net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); GURL url = https_server.GetURL(kTestServerIframeTimeoutPath); NavigateToPageExpectNoTest(browser(), url, 1); } // Check the captive portal result when the test request reports a network // error. The check is triggered by a slow loading page, and the page // errors out only after getting a captive portal result. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, RequestFails) { SetUpCaptivePortalService( browser()->profile(), URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_CLOSED)); SlowLoadNoCaptivePortal(browser(), captive_portal::RESULT_NO_RESPONSE); } // Same as above, but for the rather unlikely case that the connection times out // before the timer triggers. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, RequestFailsFastTimout) { SetUpCaptivePortalService( browser()->profile(), URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_CLOSED)); FastTimeoutNoCaptivePortal(browser(), captive_portal::RESULT_NO_RESPONSE); } // Checks the case that captive portal detection is disabled. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, Disabled) { EnableCaptivePortalDetection(browser()->profile(), false); SlowLoadNoCaptivePortal(browser(), captive_portal::RESULT_INTERNET_CONNECTED); } // Checks that we look for a captive portal on HTTPS timeouts and don't reload // the error tab when the captive portal probe gets a 204 response, indicating // there is no captive portal. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, InternetConnected) { // Can't just use SetBehindCaptivePortal(false), since then there wouldn't // be a timeout. ASSERT_TRUE(embedded_test_server()->Start()); SetUpCaptivePortalService(browser()->profile(), embedded_test_server()->GetURL("/nocontent")); SlowLoadNoCaptivePortal(browser(), captive_portal::RESULT_INTERNET_CONNECTED); } // Checks that no login page is opened when the HTTP test URL redirects to an // SSL certificate error. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, RedirectSSLCertError) { // Need an HTTP TestServer to handle a dynamically created server redirect. ASSERT_TRUE(embedded_test_server()->Start()); net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); GURL ssl_login_url = https_server.GetURL(kTestServerLoginPath); CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(browser()->profile()); ASSERT_TRUE(captive_portal_service); SetUpCaptivePortalService(browser()->profile(), embedded_test_server()->GetURL( CreateServerRedirect(ssl_login_url.spec()))); SlowLoadNoCaptivePortal(browser(), captive_portal::RESULT_NO_RESPONSE); } // A slow SSL load triggers a captive portal check. The user logs on before // the SSL page times out. We wait for the timeout and subsequent reload. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, Login) { // Load starts, detect captive portal and open up a login tab. SlowLoadBehindCaptivePortal(browser(), true); // Log in. One loading tab, no timed out ones. Login(browser(), 1, 0); // Timeout occurs, and page is automatically reloaded. FailLoadsAfterLogin(browser(), 1); } // Same as above, except we make sure everything works with an incognito // profile. Main issues it tests for are that the incognito has its own // non-NULL captive portal service, and we open the tab in the correct // window. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginIncognito) { // This will watch tabs for both profiles, but only used to make sure no // navigations occur for the non-incognito profile. MultiNavigationObserver navigation_observer; CaptivePortalObserver non_incognito_portal_observer(browser()->profile()); Browser* incognito_browser = CreateIncognitoBrowser(); EnableCaptivePortalDetection(incognito_browser->profile(), true); SetUpCaptivePortalService(incognito_browser->profile(), GURL(kMockCaptivePortalTestUrl)); SlowLoadBehindCaptivePortal(incognito_browser, true); TabStripModel* tab_strip_model = browser()->tab_strip_model(); EXPECT_EQ(1, tab_strip_model->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); Login(incognito_browser, 1, 0); FailLoadsAfterLogin(incognito_browser, 1); EXPECT_EQ(1, tab_strip_model->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); EXPECT_EQ(0, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(0))); EXPECT_EQ(0, non_incognito_portal_observer.num_results_received()); } // The captive portal page is opened before the SSL page times out, // but the user logs in only after the page times out. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginSlow) { SlowLoadBehindCaptivePortal(browser(), true); FailLoadsWithoutLogin(browser(), 1); Login(browser(), 0, 1); } // Checks the unlikely case that the tab times out before the timer triggers. // This most likely won't happen, but should still work: IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginFastTimeout) { FastTimeoutBehindCaptivePortal(browser(), true); Login(browser(), 0, 1); } // A cert error triggers a captive portal check and results in opening a login // tab. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, ShowCaptivePortalInterstitialOnCertError) { net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); TabStripModel* tab_strip_model = browser()->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); // The path does not matter. GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); int cert_error_tab_index = tab_strip_model->active_index(); // The interstitial should trigger a captive portal check when it opens, just // like navigating to kMockHttpsQuickTimeoutUrl. FastErrorBehindCaptivePortal(browser(), true, cert_error_url, false); EXPECT_EQ(CaptivePortalBlockingPage::kTypeForTesting, GetInterstitialType(broken_tab_contents)); // Switch to the interstitial and click the |Connect| button. Should switch // active tab to the captive portal landing page. int login_tab_index = tab_strip_model->active_index(); tab_strip_model->ActivateTabAt(cert_error_tab_index, false); // Wait for the interstitial to load all the JavaScript code. Otherwise, // trying to click on a button will fail. content::RenderFrameHost* rfh = broken_tab_contents->GetInterstitialPage()->GetMainFrame(); EXPECT_TRUE(WaitForRenderFrameReady(rfh)); const char kClickConnectButtonJS[] = "document.getElementById('primary-button').click();"; EXPECT_TRUE( content::ExecuteScript(rfh, kClickConnectButtonJS)); EXPECT_EQ(login_tab_index, tab_strip_model->active_index()); // For completeness, close the login tab and try clicking |Connect| again. // A new login tab should open. EXPECT_EQ(1, login_tab_index); content::WebContentsDestroyedWatcher destroyed_watcher( tab_strip_model->GetActiveWebContents()); EXPECT_TRUE( tab_strip_model->CloseWebContentsAt(tab_strip_model->active_index(), 0)); destroyed_watcher.Wait(); MultiNavigationObserver navigation_observer; EXPECT_TRUE( content::ExecuteScript(rfh, kClickConnectButtonJS)); navigation_observer.WaitForNavigations(1); EXPECT_EQ(login_tab_index, tab_strip_model->active_index()); LoginCertError(browser()); // Once logged in, broken tab should reload and display the SSL interstitial. WaitForInterstitialAttach(broken_tab_contents); tab_strip_model->ActivateTabAt(cert_error_tab_index, false); EXPECT_EQ(SSLBlockingPage::kTypeForTesting, GetInterstitialType(tab_strip_model->GetActiveWebContents())); // Trigger another captive portal check while the SSL interstitial is showing. // At this point the user is logged in to the captive portal, so the captive // portal interstitial shouldn't get recreated. CaptivePortalObserver portal_observer(browser()->profile()); CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(browser()->profile()); captive_portal_service->DetectCaptivePortal(); portal_observer.WaitForResults(1); EXPECT_EQ(SSLBlockingPage::kTypeForTesting, GetInterstitialType(broken_tab_contents)); // A captive portal appears. Trigger a final captive portal check. The // captive portal interstitial should still not get recreated. factory_.SetBehindCaptivePortal(true); CaptivePortalObserver final_portal_observer(browser()->profile()); captive_portal_service->DetectCaptivePortal(); final_portal_observer.WaitForResults(1); EXPECT_EQ(SSLBlockingPage::kTypeForTesting, GetInterstitialType(broken_tab_contents)); } // Tests this scenario: // - Portal probe requests are ignored, so that no captive portal result can // arrive. // - A cert error triggers an interstitial timer with a very long timeout. // - No captive portal results arrive, causing the tab to appear as loading // indefinitely (because probe requests are ignored). // - Stopping the page load shouldn't result in any interstitials. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, InterstitialTimerStopNavigationWhileLoading) { net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); // The path does not matter. GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); TabStripModel* tab_strip_model = browser()->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); CaptivePortalObserver portal_observer1(browser()->profile()); FastErrorWithInterstitialTimer(browser(), cert_error_url); // Page appears loading. Stop the navigation. There should be no interstitial. MultiNavigationObserver test_navigation_observer; broken_tab_contents->Stop(); test_navigation_observer.WaitForNavigations(1); // Make sure that the |ssl_error_handler| is deleted if page load is stopped. EXPECT_TRUE(nullptr == SSLErrorHandler::FromWebContents(broken_tab_contents)); EXPECT_FALSE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(0, portal_observer1.num_results_received()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(1, browser()->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); // Re-enable captive portal checks and fire one. The result should be ignored. RespondToProbeRequests(true); CaptivePortalObserver portal_observer2(browser()->profile()); CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(browser()->profile()); captive_portal_service->DetectCaptivePortal(); portal_observer2.WaitForResults(1); EXPECT_FALSE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(1, portal_observer2.num_results_received()); EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer2.captive_portal_result()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(1, browser()->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); } // Same as above, but instead of stopping, the loading page is reloaded. The end // result is the same. (i.e. page load stops, no interstitials shown) IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, InterstitialTimerReloadWhileLoading) { net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); // The path does not matter. GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); TabStripModel* tab_strip_model = browser()->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); CaptivePortalObserver portal_observer(browser()->profile()); FastErrorWithInterstitialTimer(browser(), cert_error_url); // Page appears loading. Reloading it cancels the page load. Since the load is // stopped, no cert error occurs and SSLErrorHandler isn't instantiated. MultiNavigationObserver test_navigation_observer; chrome::Reload(browser(), CURRENT_TAB); test_navigation_observer.WaitForNavigations(2); // Make sure that the |ssl_error_handler| is deleted. EXPECT_TRUE(nullptr == SSLErrorHandler::FromWebContents(broken_tab_contents)); EXPECT_FALSE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(0, portal_observer.num_results_received()); EXPECT_EQ(2, test_navigation_observer.num_navigations()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(1, browser()->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); // Re-enable captive portal checks and fire one. The result should be ignored. RespondToProbeRequests(true); CaptivePortalObserver portal_observer2(browser()->profile()); CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(browser()->profile()); captive_portal_service->DetectCaptivePortal(); portal_observer2.WaitForResults(1); EXPECT_FALSE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(1, portal_observer2.num_results_received()); EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer2.captive_portal_result()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(1, browser()->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); } // Same as |InterstitialTimerReloadWhileLoading_NoSSLError|, but instead of // reloading, the page is navigated away. The new page should load, and no // interstitials should be shown. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, InterstitialTimerNavigateAwayWhileLoading) { net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); // The path does not matter. GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); TabStripModel* tab_strip_model = browser()->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); CaptivePortalObserver portal_observer(browser()->profile()); FastErrorWithInterstitialTimer(browser(), cert_error_url); // Page appears loading. Navigating away shouldn't result in any interstitial. // Can't use ui_test_utils::NavigateToURLWithDisposition because it waits for // a load stop notification before starting a new navigation. MultiNavigationObserver test_navigation_observer; browser()->OpenURL(content::OpenURLParams( URLRequestMockHTTPJob::GetMockUrl("title2.html"), content::Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false)); // Expect two navigations: First one for stopping the hanging page, second one // for completing the load of the above navigation. test_navigation_observer.WaitForNavigations(2); // Make sure that the |ssl_error_handler| is deleted. EXPECT_TRUE(nullptr == SSLErrorHandler::FromWebContents(broken_tab_contents)); EXPECT_FALSE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(0, portal_observer.num_results_received()); EXPECT_EQ(2, test_navigation_observer.num_navigations()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(1, browser()->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); // Re-enable captive portal checks and fire one. The result should be ignored. RespondToProbeRequests(true); CaptivePortalObserver portal_observer2(browser()->profile()); CaptivePortalService* captive_portal_service = CaptivePortalServiceFactory::GetForProfile(browser()->profile()); captive_portal_service->DetectCaptivePortal(); portal_observer2.WaitForResults(1); EXPECT_FALSE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(1, portal_observer2.num_results_received()); EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer2.captive_portal_result()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(1, browser()->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); } // Same as above, but the hanging load is interrupted by a navigation to the // same page, this time committing the navigation. This should end up with an // SSL interstitial when not behind a captive portal. This ensures that a new // |SSLErrorHandler| is created on a new navigation, even though the tab's // WebContents doesn't change. IN_PROC_BROWSER_TEST_F( CaptivePortalBrowserTest, InterstitialTimerNavigateWhileLoading_EndWithSSLInterstitial) { net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); // The path does not matter. GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); factory_.SetBehindCaptivePortal(false); TabStripModel* tab_strip_model = browser()->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); FastErrorWithInterstitialTimer(browser(), cert_error_url); // Page appears loading. Turn on response to probe request again, and navigate // to the same page. This should result in a cert error which should // instantiate an |SSLErrorHandler| and end up showing an SSL interstitial. RespondToProbeRequests(true); // Can't have ui_test_utils do the navigation because it will wait for loading // tabs to stop loading before navigating. CaptivePortalObserver portal_observer(browser()->profile()); MultiNavigationObserver test_navigation_observer; browser()->OpenURL(content::OpenURLParams(cert_error_url, content::Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false)); // Expect two navigations: First one for stopping the hanging page, second one // for completing the load of the above navigation. test_navigation_observer.WaitForNavigations(2); // Should end up with an SSL interstitial. WaitForInterstitialAttach(broken_tab_contents); ASSERT_TRUE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_EQ(SSLBlockingPage::kTypeForTesting, broken_tab_contents->GetInterstitialPage() ->GetDelegateForTesting() ->GetTypeForTesting()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(1, portal_observer.num_results_received()); EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED, portal_observer.captive_portal_result()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(1, browser()->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); } // Same as above, but this time behind a captive portal. IN_PROC_BROWSER_TEST_F( CaptivePortalBrowserTest, InterstitialTimerNavigateWhileLoading_EndWithCaptivePortalInterstitial) { net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); // The path does not matter. GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); factory_.SetBehindCaptivePortal(true); TabStripModel* tab_strip_model = browser()->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); int initial_tab_count = tab_strip_model->count(); FastErrorWithInterstitialTimer(browser(), cert_error_url); // Page appears loading. Turn on response to probe request again, and navigate // to the same page. This should result in a cert error which should // instantiate an |SSLErrorHandler| and end up showing an SSL. RespondToProbeRequests(true); // Can't have ui_test_utils do the navigation because it will wait for loading // tabs to stop loading before navigating. CaptivePortalObserver portal_observer(browser()->profile()); MultiNavigationObserver test_navigation_observer; browser()->OpenURL(content::OpenURLParams(cert_error_url, content::Referrer(), CURRENT_TAB, ui::PAGE_TRANSITION_TYPED, false)); // Expect three navigations: // 1- For stopping the hanging page. // 2- For completing the load of the above navigation. // 3- For completing the load of the login tab. test_navigation_observer.WaitForNavigations(3); // Should end up with a captive portal interstitial and a new login tab. WaitForInterstitialAttach(broken_tab_contents); ASSERT_TRUE(broken_tab_contents->ShowingInterstitialPage()); EXPECT_EQ(CaptivePortalBlockingPage::kTypeForTesting, broken_tab_contents->GetInterstitialPage() ->GetDelegateForTesting() ->GetTypeForTesting()); ASSERT_EQ(initial_tab_count + 1, tab_strip_model->count()); EXPECT_EQ(initial_tab_count, tab_strip_model->active_index()); EXPECT_FALSE(broken_tab_contents->IsLoading()); EXPECT_EQ(1, portal_observer.num_results_received()); EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer.captive_portal_result()); EXPECT_EQ(0, NumLoadingTabs()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(browser(), 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 1)); EXPECT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); } // A cert error triggers a captive portal check and results in opening a login // tab. The user then logs in and the page with the error is reloaded. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SSLCertErrorLogin) { // Need an HTTP TestServer to handle a dynamically created server redirect. ASSERT_TRUE(embedded_test_server()->Start()); net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.SetSSLConfig(net::EmbeddedTestServer::CERT_MISMATCHED_NAME); https_server.ServeFilesFromSourceDirectory("chrome/test/data"); ASSERT_TRUE(https_server.Start()); // Set SSL interstitial delay to zero so that a captive portal result can not // arrive during this window, so an SSL interstitial is displayed instead // of a captive portal error page. SSLErrorHandler::SetInterstitialDelayForTest(base::TimeDelta()); TabStripModel* tab_strip_model = browser()->tab_strip_model(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); // Setting the delay to zero above has a race condition: A captive portal // result triggered by a cert error can arrive before the SSL interstitial // display timer is fired, even though it's set to zero. // To avoid this, disable captive portal checks until the SSL interstitial is // displayed. Once it's displayed, enable portal checks and fire one. bool delay_portal_response_until_interstital = true; // The path does not matter. GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); // A captive portal check is triggered in FastErrorBehindCaptivePortal. FastErrorBehindCaptivePortal( browser(), true, cert_error_url, delay_portal_response_until_interstital); EXPECT_EQ(SSLBlockingPage::kTypeForTesting, GetInterstitialType(broken_tab_contents)); LoginCertError(browser()); } // Tries navigating both the tab that encounters an SSL timeout and the // login tab twice, only logging in the second time. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginExtraNavigations) { FastTimeoutBehindCaptivePortal(browser(), true); // Activate the timed out tab and navigate it to a timeout again. TabStripModel* tab_strip_model = browser()->tab_strip_model(); tab_strip_model->ActivateTabAt(0, true); FastTimeoutBehindCaptivePortal(browser(), false); // Activate and navigate the captive portal tab. This should not trigger a // reload of the tab with the error. tab_strip_model->ActivateTabAt(1, true); NavigateLoginTab(browser(), 0, 1); // Simulate logging in. Login(browser(), 0, 1); } // After the first SSL timeout, closes the login tab and makes sure it's opened // it again on a second timeout. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, CloseLoginTab) { // First load starts, opens a login tab, and then times out. SlowLoadBehindCaptivePortal(browser(), true); FailLoadsWithoutLogin(browser(), 1); // Close login tab. chrome::CloseTab(browser()); // Go through the standard slow load login, and make sure it still works. SlowLoadBehindCaptivePortal(browser(), true); Login(browser(), 1, 0); FailLoadsAfterLogin(browser(), 1); } // Checks that two tabs with SSL timeouts in the same window work. Both // tabs only timeout after logging in. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, TwoBrokenTabs) { SlowLoadBehindCaptivePortal(browser(), true); // Can't set the TabReloader HTTPS timeout on a new tab without doing some // acrobatics, so open a new tab at a normal page, and then navigate it to a // timeout. MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser()->profile()); ui_test_utils::NavigateToURLWithDisposition( browser(), URLRequestMockHTTPJob::GetMockUrl("title2.html"), NEW_FOREGROUND_TAB, ui_test_utils::BROWSER_TEST_WAIT_FOR_NAVIGATION); TabStripModel* tab_strip_model = browser()->tab_strip_model(); ASSERT_EQ(3, tab_strip_model->count()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(0, portal_observer.num_results_received()); EXPECT_EQ(1, NumLoadingTabs()); EXPECT_EQ(1, navigation_observer.num_navigations()); EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(2))); ASSERT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(browser(), 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 1)); ASSERT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); ASSERT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 2)); ASSERT_EQ(2, tab_strip_model->active_index()); SlowLoadBehindCaptivePortal(browser(), false); tab_strip_model->ActivateTabAt(1, true); Login(browser(), 2, 0); FailLoadsAfterLogin(browser(), 2); } IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, AbortLoad) { SlowLoadBehindCaptivePortal(browser(), true); // Abandon the request. URLRequestTimeoutOnDemandJob::WaitForJobs(1); URLRequestTimeoutOnDemandJob::AbandonJobs(1); CaptivePortalObserver portal_observer(browser()->profile()); MultiNavigationObserver navigation_observer; // Switch back to the hung tab from the login tab, and abort the navigation. TabStripModel* tab_strip_model = browser()->tab_strip_model(); tab_strip_model->ActivateTabAt(0, true); chrome::Stop(browser()); navigation_observer.WaitForNavigations(1); EXPECT_EQ(0, NumBrokenTabs()); EXPECT_EQ(0, portal_observer.num_results_received()); EXPECT_FALSE(CheckPending(browser())); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); tab_strip_model->ActivateTabAt(1, true); Login(browser(), 0, 0); } // Checks the case where the timed out tab is successfully navigated before // logging in. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, NavigateBrokenTab) { // Go to the error page. SlowLoadBehindCaptivePortal(browser(), true); FailLoadsWithoutLogin(browser(), 1); // Navigate the error tab to a non-error page. TabStripModel* tab_strip_model = browser()->tab_strip_model(); tab_strip_model->ActivateTabAt(0, true); ui_test_utils::NavigateToURL( browser(), URLRequestMockHTTPJob::GetMockUrl("title2.html")); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); // Simulate logging in. tab_strip_model->ActivateTabAt(1, true); Login(browser(), 0, 0); } // Checks that captive portal detection triggers correctly when a same-site // navigation is cancelled by a navigation to the same site. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, NavigateLoadingTabToTimeoutSingleSite) { RunNavigateLoadingTabToTimeoutTest( browser(), GURL(kMockHttpsUrl), GURL(kMockHttpsUrl), GURL(kMockHttpsUrl)); } // Fails on Windows only, mostly on Win7. http://crbug.com/170033 #if defined(OS_WIN) #define MAYBE_NavigateLoadingTabToTimeoutTwoSites \ DISABLED_NavigateLoadingTabToTimeoutTwoSites #else #define MAYBE_NavigateLoadingTabToTimeoutTwoSites \ NavigateLoadingTabToTimeoutTwoSites #endif // Checks that captive portal detection triggers correctly when a same-site // navigation is cancelled by a navigation to another site. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, MAYBE_NavigateLoadingTabToTimeoutTwoSites) { RunNavigateLoadingTabToTimeoutTest( browser(), GURL(kMockHttpsUrl), GURL(kMockHttpsUrl), GURL(kMockHttpsUrl2)); } // Checks that captive portal detection triggers correctly when a cross-site // navigation is cancelled by a navigation to yet another site. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, NavigateLoadingTabToTimeoutThreeSites) { RunNavigateLoadingTabToTimeoutTest( browser(), URLRequestMockHTTPJob::GetMockUrl("title.html"), GURL(kMockHttpsUrl), GURL(kMockHttpsUrl2)); } // Checks that navigating a timed out tab back clears its state. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, GoBack) { // Navigate to a working page. ui_test_utils::NavigateToURL( browser(), URLRequestMockHTTPJob::GetMockUrl("title2.html")); // Go to the error page. SlowLoadBehindCaptivePortal(browser(), true); FailLoadsWithoutLogin(browser(), 1); CaptivePortalObserver portal_observer(browser()->profile()); MultiNavigationObserver navigation_observer; // Activate the error page tab again and go back. TabStripModel* tab_strip_model = browser()->tab_strip_model(); tab_strip_model->ActivateTabAt(0, true); chrome::GoBack(browser(), CURRENT_TAB); navigation_observer.WaitForNavigations(1); EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(0))); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); EXPECT_EQ(0, portal_observer.num_results_received()); } // Checks that navigating back to a timeout triggers captive portal detection. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, GoBackToTimeout) { // Disable captive portal detection so the first navigation doesn't open a // login tab. EnableCaptivePortalDetection(browser()->profile(), false); SlowLoadNoCaptivePortal(browser(), captive_portal::RESULT_INTERNET_CONNECTED); // Navigate to a working page. ui_test_utils::NavigateToURL( browser(), URLRequestMockHTTPJob::GetMockUrl("title2.html")); ASSERT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 0)); EnableCaptivePortalDetection(browser()->profile(), true); TabStripModel* tab_strip_model = browser()->tab_strip_model(); CaptivePortalTabReloader* tab_reloader = GetTabReloader(tab_strip_model->GetActiveWebContents()); ASSERT_TRUE(tab_reloader); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta()); // Go to the error page. MultiNavigationObserver navigation_observer; CaptivePortalObserver portal_observer(browser()->profile()); chrome::GoBack(browser(), CURRENT_TAB); // Wait for the check triggered by the broken tab and for the login tab to // stop loading. portal_observer.WaitForResults(1); navigation_observer.WaitForNavigations(1); // Make sure the request has been issued. URLRequestTimeoutOnDemandJob::WaitForJobs(1); EXPECT_EQ(1, portal_observer.num_results_received()); ASSERT_FALSE(CheckPending(browser())); ASSERT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer.captive_portal_result()); ASSERT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(browser(), 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 1)); ASSERT_TRUE(IsLoginTab(browser()->tab_strip_model()->GetWebContentsAt(1))); ASSERT_EQ(2, tab_strip_model->count()); EXPECT_EQ(1, tab_strip_model->active_index()); EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(1))); EXPECT_EQ(1, NumLoadingTabs()); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromDays(1)); Login(browser(), 1, 0); FailLoadsAfterLogin(browser(), 1); } // Checks that reloading a timeout triggers captive portal detection. // Much like the last test, though the captive portal is disabled before // the inital navigation, rather than captive portal detection. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, ReloadTimeout) { factory_.SetBehindCaptivePortal(false); // Do the first navigation while not behind a captive portal. TabStripModel* tab_strip_model = browser()->tab_strip_model(); CaptivePortalObserver portal_observer(browser()->profile()); ui_test_utils::NavigateToURL(browser(), GURL(kMockHttpsUrl)); ASSERT_EQ(0, portal_observer.num_results_received()); ASSERT_EQ(1, tab_strip_model->count()); // A captive portal spontaneously appears. factory_.SetBehindCaptivePortal(true); CaptivePortalTabReloader* tab_reloader = GetTabReloader(tab_strip_model->GetActiveWebContents()); ASSERT_TRUE(tab_reloader); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta()); MultiNavigationObserver navigation_observer; tab_strip_model->GetActiveWebContents()->GetController().Reload(true); // Wait for the check triggered by the broken tab and for the login tab to // stop loading. portal_observer.WaitForResults(1); navigation_observer.WaitForNavigations(1); // Make sure the request has been issued. URLRequestTimeoutOnDemandJob::WaitForJobs(1); ASSERT_EQ(1, portal_observer.num_results_received()); ASSERT_FALSE(CheckPending(browser())); ASSERT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer.captive_portal_result()); ASSERT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(browser(), 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(browser(), 1)); ASSERT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); ASSERT_EQ(2, tab_strip_model->count()); EXPECT_EQ(1, tab_strip_model->active_index()); EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( tab_strip_model->GetWebContentsAt(1))); EXPECT_EQ(1, NumLoadingTabs()); SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromDays(1)); Login(browser(), 1, 0); FailLoadsAfterLogin(browser(), 1); } // Checks the case where there are two windows, and there's an SSL timeout in // the background one. // Disabled: http://crbug.com/134357 IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, DISABLED_TwoWindows) { Browser* browser2 = new Browser(Browser::CreateParams(browser()->profile())); // Navigate the new browser window so it'll be shown and we can pick the // active window. ui_test_utils::NavigateToURL(browser2, GURL(url::kAboutBlankURL)); // Generally, |browser2| will be the active window. However, if the // original browser window lost focus before creating the new one, such as // when running multiple tests at once, the original browser window may // remain the profile's active window. Browser* active_browser = chrome::FindTabbedBrowser(browser()->profile(), true); Browser* inactive_browser; if (active_browser == browser2) { // When only one test is running at a time, the new browser will probably be // on top, but when multiple tests are running at once, this is not // guaranteed. inactive_browser = browser(); } else { ASSERT_EQ(active_browser, browser()); inactive_browser = browser2; } CaptivePortalObserver portal_observer(browser()->profile()); MultiNavigationObserver navigation_observer; // Navigate the tab in the inactive browser to an SSL timeout. Have to use // chrome::NavigateParams and NEW_BACKGROUND_TAB to avoid activating the // window. chrome::NavigateParams params(inactive_browser, GURL(kMockHttpsQuickTimeoutUrl), ui::PAGE_TRANSITION_TYPED); params.disposition = NEW_BACKGROUND_TAB; params.window_action = chrome::NavigateParams::NO_ACTION; ui_test_utils::NavigateToURL(¶ms); navigation_observer.WaitForNavigations(2); // Make sure the active window hasn't changed, and its new tab is // active. ASSERT_EQ(active_browser, chrome::FindTabbedBrowser(browser()->profile(), true)); ASSERT_EQ(1, active_browser->tab_strip_model()->active_index()); // Check that the only two navigated tabs were the new error tab in the // backround windows, and the login tab in the active window. EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( inactive_browser->tab_strip_model()->GetWebContentsAt(1))); EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( active_browser->tab_strip_model()->GetWebContentsAt(1))); EXPECT_EQ(0, NumLoadingTabs()); // Check captive portal test results. portal_observer.WaitForResults(1); ASSERT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL, portal_observer.captive_portal_result()); EXPECT_EQ(1, portal_observer.num_results_received()); // Check the inactive browser. EXPECT_EQ(2, inactive_browser->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(inactive_browser, 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL, GetStateOfTabReloaderAt(inactive_browser, 1)); // Check the active browser. ASSERT_EQ(2, active_browser->tab_strip_model()->count()); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(active_browser, 0)); EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, GetStateOfTabReloaderAt(active_browser, 1)); EXPECT_TRUE( IsLoginTab(active_browser->tab_strip_model()->GetWebContentsAt(1))); // Simulate logging in. Login(active_browser, 0, 1); } // An HTTP page redirects to an HTTPS page loads slowly before timing out. A // captive portal is found, and then the user logs in before the original page // times out. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HttpToHttpsRedirectLogin) { ASSERT_TRUE(embedded_test_server()->Start()); SlowLoadBehindCaptivePortal( browser(), true, embedded_test_server()->GetURL(CreateServerRedirect(kMockHttpsUrl)), 1, 1); Login(browser(), 1, 0); FailLoadsAfterLogin(browser(), 1); } // An HTTPS page redirects to an HTTP page. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HttpsToHttpRedirect) { // Use an HTTPS server for the top level page. net::EmbeddedTestServer https_server(net::EmbeddedTestServer::TYPE_HTTPS); https_server.AddDefaultHandlers( base::FilePath(FILE_PATH_LITERAL("chrome/test/data"))); ASSERT_TRUE(https_server.Start()); GURL http_timeout_url = URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_TIMED_OUT); // 2 navigations due to the Link Doctor. NavigateToPageExpectNoTest( browser(), https_server.GetURL(CreateServerRedirect(http_timeout_url.spec())), 2); } // Tests the 511 response code, along with an HTML redirect to a login page. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, Status511) { SetUpCaptivePortalService(browser()->profile(), GURL(kMockCaptivePortal511Url)); SlowLoadBehindCaptivePortal(browser(), true, GURL(kMockHttpsUrl), 2, 2); Login(browser(), 1, 0); FailLoadsAfterLogin(browser(), 1); } // HSTS redirects an HTTP request to HTTPS, and the request then times out. // A captive portal is then detected, and a login tab opened, before logging // in. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HstsLogin) { GURL::Replacements replacements; replacements.SetSchemeStr("http"); GURL http_timeout_url = GURL(kMockHttpsUrl).ReplaceComponents(replacements); URLRequestFailedJob::GetMockHttpUrl(net::ERR_CONNECTION_TIMED_OUT); content::BrowserThread::PostTask( content::BrowserThread::IO, FROM_HERE, base::Bind(&AddHstsHost, make_scoped_refptr(browser()->profile()->GetRequestContext()), http_timeout_url.host())); SlowLoadBehindCaptivePortal(browser(), true, http_timeout_url, 1, 1); Login(browser(), 1, 0); FailLoadsAfterLogin(browser(), 1); } // A slow SSL load starts. The reloader triggers a captive portal check, finds a // captive portal. The SSL commits with a cert error, triggering another captive // portal check. // The second check finds no captive portal. The reloader triggers a reload at // the same time SSL error handler tries to show an interstitial. Should result // in an SSL interstitial. IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, InterstitialTimerCertErrorAfterSlowLoad) { // Use a url that triggers a slow load, instead of creating an https server. GURL cert_error_url = GURL(kMockHttpsUrl); TabStripModel* tab_strip_model = browser()->tab_strip_model(); int broken_tab_index = tab_strip_model->active_index(); WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); SlowLoadBehindCaptivePortal(browser(), true, cert_error_url, 1, 1); // No longer behind a captive portal. Committing the SSL page should trigger // an SSL interstitial which triggers a new captive portal check. Since there // is no captive portal anymore, should end up with an SSL interstitial. factory_.SetBehindCaptivePortal(false); CaptivePortalObserver portal_observer(browser()->profile()); MultiNavigationObserver navigation_observer; net::SSLInfo info; info.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID; info.cert = net::ImportCertFromFile(net::GetTestCertsDirectory(), "ok_cert.pem"); URLRequestTimeoutOnDemandJob::FailJobsWithCertError(1, info); navigation_observer.WaitForNavigations(1); EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD, GetStateOfTabReloaderAt(browser(), broken_tab_index)); WaitForInterstitialAttach(broken_tab_contents); portal_observer.WaitForResults(1); EXPECT_EQ(SSLBlockingPage::kTypeForTesting, GetInterstitialType(broken_tab_contents)); }