Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(221)

Unified Diff: chrome/browser/captive_portal/captive_portal_browsertest.cc

Issue 318213002: Add custom interstitial for captive portals. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: mmenke comments, add login scenario to browser tests and fix race. Created 6 years, 1 month ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
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

Powered by Google App Engine
This is Rietveld 408576698