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..020f0cb23dd102b64cf78a4b5e9b2cfea2aabc58 100644 |
| --- a/chrome/browser/captive_portal/captive_portal_browsertest.cc |
| +++ b/chrome/browser/captive_portal/captive_portal_browsertest.cc |
| @@ -19,8 +19,11 @@ |
| #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/ui/browser.h" |
| #include "chrome/browser/ui/browser_commands.h" |
| #include "chrome/browser/ui/browser_finder.h" |
| @@ -33,14 +36,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 +117,22 @@ const char* const kMockHttpsQuickTimeoutUrl = |
| // captive portal. |
| const char* const kInternetConnectedTitle = "Title Of Awesomeness"; |
| +// Wait until all <script> tags have executed, including jstemplate. |
| +// This isn't ideal, but the same trick is used in |
| +// SafeBrowsingBlockingPageBrowserTest to wait for the interstitials. |
| +bool WaitForPageReady(content::RenderViewHost* rvh) { |
|
mmenke
2014/11/26 18:57:48
Hrm...Do you know what this is needed? We embed t
meacer
2014/12/08 22:29:50
C++ side only waits for the navigation to complete
|
| + if (!rvh) |
| + return false; |
| + std::string ready_state; |
| + do { |
| + scoped_ptr<base::Value> value = content::ExecuteScriptAndGetValue( |
| + rvh->GetMainFrame(), "document.readyState"); |
| + if (!value.get() || !value->GetAsString(&ready_state)) |
| + return false; |
| + } while (ready_state != "complete"); |
| + return true; |
| +} |
| + |
| // 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. |
| @@ -689,8 +713,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); |
| @@ -816,6 +840,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 +924,11 @@ 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); |
| + void FastErrorBehindCaptivePortal( |
| + Browser* browser, |
| + bool expect_open_login_tab, |
| + const GURL& error_url, |
| + bool disable_portal_check_until_interstitial); |
| // 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 +945,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. |
| @@ -949,6 +984,9 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest { |
| void SetSlowSSLLoadTime(CaptivePortalTabReloader* tab_reloader, |
| base::TimeDelta slow_ssl_load_time); |
| + void SetSSLErrorDisplayDelay(CaptivePortalTabHelper* tab_helper, |
| + base::TimeDelta ssl_error_delay); |
| + |
| CaptivePortalTabReloader* GetTabReloader(WebContents* web_contents) const; |
| private: |
| @@ -995,6 +1033,7 @@ void CaptivePortalBrowserTest::SetUpCaptivePortalService(Profile* profile, |
| CaptivePortalService* captive_portal_service = |
| CaptivePortalServiceFactory::GetForProfile(profile); |
| captive_portal_service->set_test_url(test_url); |
| + captive_portal_service->SetPortalDetectionEnabledForTest(true); |
| // Don't use any non-zero timers. Timers are checked in unit tests. |
| CaptivePortalService::RecheckPolicy* recheck_policy = |
| @@ -1012,6 +1051,16 @@ bool CaptivePortalBrowserTest::CheckPending(Browser* browser) { |
| captive_portal_service->TimerRunning(); |
| } |
| +const void* CaptivePortalBrowserTest::GetInterstitialType( |
| + WebContents* contents) const { |
| + DCHECK(contents->ShowingInterstitialPage()); |
|
mmenke
2014/11/26 18:57:48
Rather than a DCHECK, seems like we should just as
meacer
2014/12/08 22:29:50
Done. Using NULL instead of nullptr to be consiste
|
| + SecurityInterstitialPage* blocking_page = |
| + static_cast<SecurityInterstitialPage*>( |
| + contents->GetInterstitialPage()->GetDelegateForTesting()); |
| + DCHECK(blocking_page); |
| + return blocking_page->GetTypeForTesting(); |
| +} |
| + |
| CaptivePortalTabReloader::State CaptivePortalBrowserTest::GetStateOfTabReloader( |
| WebContents* web_contents) const { |
| return GetTabReloader(web_contents)->state(); |
| @@ -1244,13 +1293,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 disable_portal_check_until_interstitial) { |
| 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 +1325,27 @@ void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal( |
| ++expected_broken_tabs; |
| } |
| + CaptivePortalService* captive_portal_service = |
| + CaptivePortalServiceFactory::GetForProfile(browser->profile()); |
| + if (disable_portal_check_until_interstitial) |
| + captive_portal_service->SetPortalDetectionEnabledForTest(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 (disable_portal_check_until_interstitial) { |
| + EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, |
| + GetStateOfTabReloaderAt(browser, initial_active_index)); |
| + // Once the interstitial is attached, probe for captive portal. |
| + WaitForInterstitialAttach(tab_strip_model->GetActiveWebContents()); |
| + captive_portal_service->SetPortalDetectionEnabledForTest(true); |
| + captive_portal_service->DetectCaptivePortal(); |
| + } |
| + |
| portal_observer.WaitForResults(1); |
| if (expect_open_login_tab) { |
| @@ -1407,6 +1473,43 @@ void CaptivePortalBrowserTest::Login(Browser* browser, |
| tab_strip_model->GetWebContentsAt(login_tab_index))); |
| } |
| +void CaptivePortalBrowserTest::LoginCertError(Browser* browser) { |
| + TabStripModel* tab_strip_model = browser->tab_strip_model(); |
| + URLRequestMockCaptivePortalJobFactory::SetBehindCaptivePortal(false); |
| + MultiNavigationObserver navigation_observer; |
| + CaptivePortalObserver portal_observer(browser->profile()); |
| + |
| + content::RenderFrameHost* render_frame_host = |
| + tab_strip_model->GetActiveWebContents()->GetMainFrame(); |
| + render_frame_host->ExecuteJavaScript(base::ASCIIToUTF16("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_FALSE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); |
| + EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, |
| + GetStateOfTabReloaderAt(browser, 1)); |
| + |
| + // Make sure only one navigation was for the login tab. |
| + EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( |
| + tab_strip_model->GetWebContentsAt(1))); |
| +} |
| + |
| void CaptivePortalBrowserTest::FailLoadsAfterLogin(Browser* browser, |
| int num_loading_tabs) { |
| ASSERT_EQ(num_loading_tabs, NumLoadingTabs()); |
| @@ -1546,6 +1649,12 @@ void CaptivePortalBrowserTest::SetSlowSSLLoadTime( |
| tab_reloader->set_slow_ssl_load_time(slow_ssl_load_time); |
| } |
| +void CaptivePortalBrowserTest::SetSSLErrorDisplayDelay( |
| + CaptivePortalTabHelper* tab_helper, |
| + base::TimeDelta ssl_error_delay) { |
| + tab_helper->SetSSLErrorDelayForTest(ssl_error_delay); |
| +} |
| + |
| CaptivePortalTabReloader* CaptivePortalBrowserTest::GetTabReloader( |
| WebContents* web_contents) const { |
| return CaptivePortalTabHelper::FromWebContents(web_contents)-> |
| @@ -1705,11 +1814,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 +1825,103 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SSLCertErrorLogin) { |
| base::FilePath(FILE_PATH_LITERAL("chrome/test/data"))); |
| ASSERT_TRUE(https_server.Start()); |
| + // 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. |
| + TabStripModel* tab_strip_model = browser()->tab_strip_model(); |
| + WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); |
| + SetSSLErrorDisplayDelay( |
| + CaptivePortalTabHelper::FromWebContents(broken_tab_contents), |
| + base::TimeDelta::FromHours(1)); |
| + |
| // 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); |
| - // 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); |
| - MultiNavigationObserver navigation_observer; |
| + EXPECT_EQ(CaptivePortalBlockingPage::kTypeForTesting, |
| + GetInterstitialType(broken_tab_contents)); |
| + |
| + int login_tab_index = tab_strip_model->active_index(); |
| + // Switch to the interstitial and click the "Connect" button. Should switch |
| + // active tab to the captive portal landing page. |
| + tab_strip_model->ActivateTabAt(cert_error_tab_index, false); |
| + |
| + content::RenderViewHost* rvh = |
| + broken_tab_contents->GetInterstitialPage()->GetRenderViewHostForTesting(); |
| + EXPECT_TRUE(WaitForPageReady(rvh)); |
| + EXPECT_TRUE( |
| + content::ExecuteScript( |
| + rvh->GetMainFrame(), |
| + "document.getElementById('primary-button').click();")); |
| + 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 a final captive portal check. 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)); |
| +} |
| - 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()")); |
| +// 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()); |
| - // 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); |
| + 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()); |
| - // 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()); |
| + // 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. |
| + TabStripModel* tab_strip_model = browser()->tab_strip_model(); |
| + WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents(); |
| + SetSSLErrorDisplayDelay( |
| + CaptivePortalTabHelper::FromWebContents(broken_tab_contents), |
| + base::TimeDelta()); |
| + |
| + // 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 disable_portal_check_until_interstitial = true; |
| - // 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_FALSE(IsLoginTab(tab_strip_model->GetWebContentsAt(1))); |
| - EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, |
| - GetStateOfTabReloaderAt(browser(), 1)); |
| + // The path does not matter. |
| + GURL cert_error_url = https_server.GetURL(kTestServerLoginPath); |
| + // The interstitial should trigger a captive portal check when it opens, just |
| + // like navigating to kMockHttpsQuickTimeoutUrl. |
| + FastErrorBehindCaptivePortal( |
| + browser(), |
| + true, |
| + cert_error_url, |
| + disable_portal_check_until_interstitial); |
| - // Make sure only one navigation was for the login tab. |
| - EXPECT_EQ(1, navigation_observer.NumNavigationsForTab( |
| - tab_strip_model->GetWebContentsAt(1))); |
| + EXPECT_EQ(SSLBlockingPage::kTypeForTesting, |
| + GetInterstitialType(broken_tab_contents)); |
| + |
| + LoginCertError(browser()); |
| } |
| // Tries navigating both the tab that encounters an SSL timeout and the |