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..1fcb08c4ee4c0b51930d8a5b1a4682fcf33a6192 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::TimerStartedCallback callback_; |
+ |
+ scoped_refptr<content::MessageLoopRunner> 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::OnTimerFired, |
+ base::Unretained(this)); |
+ SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(&callback_); |
+} |
+ |
+SSLInterstitialTimerObserver::~SSLInterstitialTimerObserver() { |
+ SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(nullptr); |
+} |
+ |
+void SSLInterstitialTimerObserver::WaitForTimerFired() { |
+ 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,11 @@ 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::SetInterstitialDelayTypeForTest(SSLErrorHandler::LONG); |
} |
void CaptivePortalBrowserTest::TearDownOnMainThread() { |
@@ -990,6 +1123,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 +1159,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 +1401,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 +1433,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 +1489,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 +1530,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 +1576,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 +1603,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 +1946,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 +1957,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::SetInterstitialDelayTypeForTest(SSLErrorHandler::NONE); |
+ 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 +2701,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)); |
+} |