| 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..c819644716af298ffd1951de2691b2e8c08b6862 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::SHORT);
|
| + 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));
|
| +}
|
|
|