Chromium Code Reviews| Index: chrome/browser/captive_portal/captive_portal_browsertest.cc |
| diff --git a/chrome/browser/captive_portal/captive_portal_browsertest.cc b/chrome/browser/captive_portal/captive_portal_browsertest.cc |
| index 2a8749ec8fd09f1f53dd64776238fa4d0ede991b..1f64cb1847edec3761748792ea2d63f0556c9b5d 100644 |
| --- a/chrome/browser/captive_portal/captive_portal_browsertest.cc |
| +++ b/chrome/browser/captive_portal/captive_portal_browsertest.cc |
| @@ -4,23 +4,30 @@ |
| #include <map> |
| #include <set> |
| +#include <string> |
| #include "base/basictypes.h" |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/compiler_specific.h" |
| #include "base/files/file_path.h" |
| +#include "base/memory/scoped_ptr.h" |
| #include "base/message_loop/message_loop.h" |
| #include "base/path_service.h" |
| #include "base/prefs/pref_service.h" |
| #include "base/strings/utf_string_conversions.h" |
| +#include "base/values.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" |
| @@ -33,14 +40,19 @@ |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.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/render_view_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/http/transport_security_state.h" |
| #include "net/test/url_request/url_request_failed_job.h" |
| @@ -109,6 +121,30 @@ const char* const kMockHttpsQuickTimeoutUrl = |
| // captive portal. |
| const char* const kInternetConnectedTitle = "Title Of Awesomeness"; |
| +// Wait until all resources have loaded in an interstitial page. |
| +bool WaitForInterstitialReady(content::InterstitialPage* interstitial) { |
| + content::RenderViewHost* rvh = interstitial->GetRenderViewHostForTesting(); |
| + if (!rvh) |
| + return false; |
| + bool load_complete = false; |
| + EXPECT_TRUE( |
| + content::ExecuteScriptAndExtractBool( |
| + rvh->GetMainFrame(), |
| + "(function() {" |
| + " var done = false;" |
| + " function checkState() {" |
| + " if (!done && document.readyState == 'complete') {" |
| + " done = true;" |
| + " window.domAutomationController.send(true);" |
| + " }" |
| + " }" |
| + " checkState();" |
| + " document.addEventListener('readystatechange', checkState);" |
| + "})();", |
| + &load_complete)); |
| + return load_complete; |
| +} |
| + |
| // 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. |
| @@ -125,10 +161,14 @@ class URLRequestTimeoutOnDemandJob : public net::URLRequestJob, |
| // Fails all active URLRequestTimeoutOnDemandJobs with connection timeouts. |
| // There are expected to be exactly |expected_num_jobs| waiting for |
| - // failure. The only way to gaurantee this is with an earlier call to |
| + // 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); |
| + |
| // Abandon all active URLRequestTimeoutOnDemandJobs. |expected_num_jobs| |
| // behaves just as in FailJobs. |
| static void AbandonJobs(int expected_num_jobs); |
| @@ -140,6 +180,7 @@ class URLRequestTimeoutOnDemandJob : public net::URLRequestJob, |
| enum EndJobOperation { |
| FAIL_JOBS, |
| ABANDON_JOBS, |
| + FAIL_JOBS_WITH_CERT_ERROR |
| }; |
| URLRequestTimeoutOnDemandJob(net::URLRequest* request, |
| @@ -226,6 +267,16 @@ void URLRequestTimeoutOnDemandJob::FailJobs(int expected_num_jobs) { |
| } |
| // static |
| +void URLRequestTimeoutOnDemandJob::FailJobsWithCertError( |
| + int expected_num_jobs) { |
| + content::BrowserThread::PostTask( |
| + content::BrowserThread::IO, FROM_HERE, |
| + base::Bind(&URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread, |
| + expected_num_jobs, |
| + FAIL_JOBS_WITH_CERT_ERROR)); |
| +} |
| + |
| +// static |
| void URLRequestTimeoutOnDemandJob::AbandonJobs(int expected_num_jobs) { |
| content::BrowserThread::PostTask( |
| content::BrowserThread::IO, FROM_HERE, |
| @@ -313,6 +364,13 @@ void URLRequestTimeoutOnDemandJob::FailOrAbandonJobsOnIOThread( |
| job->NotifyStartError(net::URLRequestStatus( |
| net::URLRequestStatus::FAILED, |
| net::ERR_CONNECTION_TIMED_OUT)); |
| + } else if (end_job_operation == FAIL_JOBS_WITH_CERT_ERROR) { |
| + ASSERT_TRUE(job->request()->url().SchemeIs(url::kHttpsScheme)); |
| + net::SSLInfo info; |
| + info.cert_status = net::CERT_STATUS_COMMON_NAME_INVALID; |
| + info.cert = new net::X509Certificate( |
| + "bad.host", "CA", base::Time::Max(), base::Time::Max()); |
| + job->NotifySSLCertificateError(info, true); |
| } |
| } |
| @@ -689,8 +747,8 @@ class CaptivePortalObserver : public content::NotificationObserver { |
| public: |
| explicit CaptivePortalObserver(Profile* profile); |
| - // Runs the message loop until until at exactly |update_count| capitive portal |
| - // results have been received, since this creation of |this|. Expects no |
| + // 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); |
| @@ -777,6 +835,50 @@ void CaptivePortalObserver::Observe( |
| } |
| } |
| +// This observer waits for the SSLErrorHandler to fire 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 fired. |
| + void WaitForTimerFired(); |
| + |
| + private: |
| + void OnTimerFired(content::WebContents* web_contents); |
| + |
| + const content::WebContents* web_contents_; |
| + SSLErrorHandler::TimerFiredCallback callback_; |
| + |
| + scoped_refptr<content::MessageLoopRunner> message_loop_runner_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(SSLInterstitialTimerObserver); |
| +}; |
| + |
| +SSLInterstitialTimerObserver::SSLInterstitialTimerObserver( |
| + content::WebContents* web_contents) |
| + : web_contents_(web_contents) { |
| + callback_ = base::Bind(&SSLInterstitialTimerObserver::OnTimerFired, |
| + base::Unretained(this)); |
| + SSLErrorHandler::SetInterstitialTimerFiredCallbackForTest(&callback_); |
| +} |
| + |
| +SSLInterstitialTimerObserver::~SSLInterstitialTimerObserver() { |
| + SSLErrorHandler::SetInterstitialTimerFiredCallbackForTest(nullptr); |
| +} |
| + |
| +void SSLInterstitialTimerObserver::WaitForTimerFired() { |
| + message_loop_runner_ = new content::MessageLoopRunner; |
|
Bernhard Bauer
2014/12/19 10:14:25
You may want to initialize the message loop runner
meacer
2014/12/19 19:04:23
Done.
|
| + message_loop_runner_->Run(); |
| +} |
| + |
| +void SSLInterstitialTimerObserver::OnTimerFired( |
| + 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, |
| @@ -808,6 +910,11 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest { |
| // 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); |
| @@ -816,6 +923,9 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest { |
| // check. |
| bool CheckPending(Browser* browser); |
| + // Returns the type of the interstitial being shown. |
| + const void* GetInterstitialType(WebContents* contents) const; |
| + |
| // Returns the CaptivePortalTabReloader::State of |web_contents|. |
| CaptivePortalTabReloader::State GetStateOfTabReloader( |
| WebContents* web_contents) const; |
| @@ -897,9 +1007,21 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest { |
| // 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. |
| - void FastErrorBehindCaptivePortal(Browser* browser, |
| - bool expect_open_login_tab, |
| - const GURL& error_url); |
| + // 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. |
| @@ -916,6 +1038,12 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest { |
| // 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. |
| @@ -978,6 +1106,12 @@ void CaptivePortalBrowserTest::SetUpOnMainThread() { |
| // 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::SetInterstitialDisplayDelayForTest( |
| + base::TimeDelta::FromHours(1)); |
| } |
| void CaptivePortalBrowserTest::TearDownOnMainThread() { |
| @@ -990,6 +1124,20 @@ void CaptivePortalBrowserTest::EnableCaptivePortalDetection( |
| 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 = |
| @@ -1012,6 +1160,16 @@ bool CaptivePortalBrowserTest::CheckPending(Browser* browser) { |
| captive_portal_service->TimerRunning(); |
| } |
| +const void* CaptivePortalBrowserTest::GetInterstitialType( |
| + WebContents* contents) const { |
| + if (!contents->ShowingInterstitialPage()) |
| + return nullptr; |
| + SecurityInterstitialPage* blocking_page = |
| + static_cast<SecurityInterstitialPage*>( |
| + contents->GetInterstitialPage()->GetDelegateForTesting()); |
| + return blocking_page->GetTypeForTesting(); |
| +} |
| + |
| CaptivePortalTabReloader::State CaptivePortalBrowserTest::GetStateOfTabReloader( |
| WebContents* web_contents) const { |
| return GetTabReloader(web_contents)->state(); |
| @@ -1244,13 +1402,15 @@ void CaptivePortalBrowserTest::FastTimeoutBehindCaptivePortal( |
| bool expect_open_login_tab) { |
| FastErrorBehindCaptivePortal(browser, |
| expect_open_login_tab, |
| - GURL(kMockHttpsQuickTimeoutUrl)); |
| + GURL(kMockHttpsQuickTimeoutUrl), |
| + false); |
| } |
| void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal( |
| Browser* browser, |
| bool expect_open_login_tab, |
| - const GURL& error_url) { |
| + 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. |
| @@ -1274,12 +1434,27 @@ void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal( |
| ++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) { |
| @@ -1315,6 +1490,30 @@ void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal( |
| 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.WaitForTimerFired(); |
| + |
| + // 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) { |
| @@ -1332,9 +1531,8 @@ void CaptivePortalBrowserTest::NavigateLoginTab(Browser* browser, |
| ASSERT_TRUE(IsLoginTab(browser->tab_strip_model()->GetActiveWebContents())); |
| // Do the navigation. |
| - content::RenderFrameHost* render_frame_host = |
| - tab_strip_model->GetActiveWebContents()->GetMainFrame(); |
| - render_frame_host->ExecuteJavaScript(base::ASCIIToUTF16("submitForm()")); |
| + EXPECT_TRUE(content::ExecuteScript(tab_strip_model->GetActiveWebContents(), |
| + "submitForm()")); |
| portal_observer.WaitForResults(1); |
| navigation_observer.WaitForNavigations(1); |
| @@ -1379,9 +1577,8 @@ void CaptivePortalBrowserTest::Login(Browser* browser, |
| ASSERT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(login_tab_index))); |
| // Trigger a navigation. |
| - content::RenderFrameHost* render_frame_host = |
| - tab_strip_model->GetActiveWebContents()->GetMainFrame(); |
| - render_frame_host->ExecuteJavaScript(base::ASCIIToUTF16("submitForm()")); |
| + EXPECT_TRUE(content::ExecuteScript(tab_strip_model->GetActiveWebContents(), |
| + "submitForm()")); |
| portal_observer.WaitForResults(1); |
| @@ -1407,6 +1604,51 @@ void CaptivePortalBrowserTest::Login(Browser* browser, |
| tab_strip_model->GetWebContentsAt(login_tab_index))); |
| } |
| +void CaptivePortalBrowserTest::LoginCertError(Browser* browser) { |
| + URLRequestMockCaptivePortalJobFactory::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()); |
| @@ -1705,11 +1947,9 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginFastTimeout) { |
| } |
| // 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(test_server()->Start()); |
| - |
| +// tab. |
| +IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, |
| + ShowCaptivePortalInterstitialOnCertError) { |
| net::SpawnedTestServer::SSLOptions https_options; |
| https_options.server_certificate = |
| net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME; |
| @@ -1718,49 +1958,298 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SSLCertErrorLogin) { |
| base::FilePath(FILE_PATH_LITERAL("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); |
| + FastErrorBehindCaptivePortal(browser(), true, cert_error_url, false); |
| + EXPECT_EQ(CaptivePortalBlockingPage::kTypeForTesting, |
| + GetInterstitialType(broken_tab_contents)); |
| - // Simulate logging in. Can't use Login() 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). |
| - URLRequestMockCaptivePortalJobFactory::SetBehindCaptivePortal(false); |
| + // 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. |
| + EXPECT_TRUE(WaitForInterstitialReady( |
| + broken_tab_contents->GetInterstitialPage())); |
| + content::RenderViewHost* rvh = |
| + broken_tab_contents->GetInterstitialPage()->GetRenderViewHostForTesting(); |
| + const char kClickConnectButtonJS[] = |
| + "document.getElementById('primary-button').click();"; |
| + EXPECT_TRUE( |
| + content::ExecuteScript(rvh->GetMainFrame(), 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(rvh->GetMainFrame(), 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. |
| + URLRequestMockCaptivePortalJobFactory::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::SpawnedTestServer::SSLOptions https_options; |
| + https_options.server_certificate = |
| + net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME; |
| + net::SpawnedTestServer https_server( |
| + net::SpawnedTestServer::TYPE_HTTPS, https_options, |
| + base::FilePath(FILE_PATH_LITERAL("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(); |
| - content::RenderFrameHost* render_frame_host = |
| - tab_strip_model->GetActiveWebContents()->GetMainFrame(); |
| - render_frame_host->ExecuteJavaScript(base::ASCIIToUTF16("submitForm()")); |
| + WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); |
| - // 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); |
| + CaptivePortalObserver portal_observer1(browser()->profile()); |
| + FastErrorWithInterstitialTimer(browser(), cert_error_url); |
| - // Wait for both tabs to finish loading. |
| - navigation_observer.WaitForNavigations(2); |
| - EXPECT_EQ(2, portal_observer.num_results_received()); |
| + // Page appears loading. Stop the navigation. There should be no interstitial. |
| + MultiNavigationObserver test_navigation_observer; |
| + broken_tab_contents->Stop(); |
| + test_navigation_observer.WaitForNavigations(1); |
| + |
| + 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(captive_portal::RESULT_INTERNET_CONNECTED, |
| - portal_observer.captive_portal_result()); |
| + EXPECT_EQ(1, browser()->tab_strip_model()->count()); |
| + EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, |
| + GetStateOfTabReloaderAt(browser(), 0)); |
| - // 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()); |
| + // 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)); |
| - EXPECT_FALSE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); |
| +} |
| + |
| +// 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::SpawnedTestServer::SSLOptions https_options; |
| + https_options.server_certificate = |
| + net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME; |
| + net::SpawnedTestServer https_server( |
| + net::SpawnedTestServer::TYPE_HTTPS, https_options, |
| + base::FilePath(FILE_PATH_LITERAL("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); |
| + |
| + 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(), 1)); |
| + GetStateOfTabReloaderAt(browser(), 0)); |
| - // Make sure only one navigation was for the login tab. |
| - EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( |
| - tab_strip_model->GetWebContentsAt(1))); |
| + // 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 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::SpawnedTestServer::SSLOptions https_options; |
| + https_options.server_certificate = |
| + net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME; |
| + net::SpawnedTestServer https_server( |
| + net::SpawnedTestServer::TYPE_HTTPS, https_options, |
| + base::FilePath(FILE_PATH_LITERAL("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( |
| + base::FilePath(FILE_PATH_LITERAL("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); |
| + |
| + 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)); |
| +} |
| + |
| +// 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(test_server()->Start()); |
| + |
| + net::SpawnedTestServer::SSLOptions https_options; |
| + https_options.server_certificate = |
| + net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME; |
| + net::SpawnedTestServer https_server( |
| + net::SpawnedTestServer::TYPE_HTTPS, https_options, |
| + base::FilePath(FILE_PATH_LITERAL("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::SetInterstitialDisplayDelayForTest(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 |
| @@ -2213,3 +2702,39 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HstsLogin) { |
| 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. |
| + URLRequestMockCaptivePortalJobFactory::SetBehindCaptivePortal(false); |
| + |
| + CaptivePortalObserver portal_observer(browser()->profile()); |
| + MultiNavigationObserver navigation_observer; |
| + URLRequestTimeoutOnDemandJob::FailJobsWithCertError(1); |
| + 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)); |
| +} |