Commit 7fdea651 authored by dalyk's avatar dalyk Committed by Commit Bot

Trigger captive portal checks on secure DNS network failures.

These captive portal checks may occur on either HTTP or HTTPS
navigations. The CaptivePortalTabHelper no longer tracks navigations
that are renderer-initiated to avoid interference from link doctor and
renderer-initiated reload attempts (these navigations, which can be
triggered by DNS failures, may otherwise reset the tab state before a
captive portal probe result is received).

Bug: 10161646
Change-Id: Ia2ac3715d43e9b1be71df1dea7a1882f98033888
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1870013Reviewed-by: default avatarAvi Drissman <avi@chromium.org>
Reviewed-by: default avatarMatt Menke <mmenke@chromium.org>
Commit-Queue: Katharine Daly <dalyk@google.com>
Cr-Commit-Position: refs/heads/master@{#737843}
parent f5d536ed
......@@ -159,6 +159,10 @@ const char* const kMockHttpsConnectionUnexpectedErr =
"https://mock.captive.portal.quick.error/unexpected";
const char* const kMockHttpConnectionConnectionClosedErr =
"http://mock.captive.portal.quick.error/connection_closed";
const char* const kMockHttpConnectionSecureDnsErr =
"http://mock.captive.portal.quick.error/secure_dns";
const char* const kMockHttpsConnectionSecureDnsErr =
"https://mock.captive.portal.quick.error/secure_dns";
// Expected title of a tab once an HTTPS load completes, when not behind a
// captive portal.
......@@ -684,6 +688,7 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest {
// result in an error rather than hanging.
void FastErrorBehindCaptivePortal(Browser* browser,
bool expect_open_login_tab,
bool expect_new_login_browser,
const GURL& error_url);
// Navigates the active tab to an SSL error page which triggers an
......@@ -706,9 +711,11 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest {
// |captive-portal_browser| is the browser containing the login page.
// |num_loading_tabs| and |num_timed_out_tabs| are used as extra checks
// that nothing has gone wrong prior to the function call.
// |expected_portal_checks| allows client-side redirects to be tested.
void Login(Browser* captive_portal_browser,
int num_loading_tabs,
int num_timed_out_tabs);
int num_timed_out_tabs,
int expected_portal_checks);
// Simulates a login when the broken tab shows an SSL or captive portal
// interstitial. Can't use Login() in those cases because the interstitial
......@@ -791,22 +798,27 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest {
num_jobs_to_wait_for_ = num_jobs;
}
// Fails all active kMockHttps* requests with connection timeouts.
// Fails all active kMockHttps* requests with error code |error| and
// hostname resolution error info |resolve_error_info|.
// There are expected to be exactly |expected_num_jobs| waiting for
// failure. The only way to guarantee this is with an earlier call to
// WaitForJobs, so makes sure there has been a matching WaitForJobs call.
void FailJobs(int expected_num_jobs) {
void FailJobs(int expected_num_jobs,
int error,
net::ResolveErrorInfo resolve_error_info) {
if (!BrowserThread::CurrentlyOn(BrowserThread::UI)) {
base::PostTask(FROM_HERE, {BrowserThread::UI},
base::BindOnce(&CaptivePortalBrowserTest::FailJobs,
base::Unretained(this), expected_num_jobs));
base::Unretained(this), expected_num_jobs,
error, resolve_error_info));
return;
}
EXPECT_EQ(expected_num_jobs,
static_cast<int>(ongoing_mock_requests_.size()));
network::URLLoaderCompletionStatus status;
status.error_code = net::ERR_CONNECTION_TIMED_OUT;
status.error_code = error;
status.resolve_error_info = resolve_error_info;
for (auto& job : ongoing_mock_requests_)
job.client->OnComplete(status);
ongoing_mock_requests_.clear();
......@@ -957,17 +969,23 @@ bool CaptivePortalBrowserTest::OnIntercept(
}
auto url_string = params->url_request.url.spec();
net::Error error = net::OK;
network::URLLoaderCompletionStatus status;
status.error_code = net::OK;
if (url_string == kMockHttpConnectionTimeoutErr ||
url_string == kMockHttpsConnectionTimeoutErr) {
error = net::ERR_CONNECTION_TIMED_OUT;
status.error_code = net::ERR_CONNECTION_TIMED_OUT;
} else if (url_string == kMockHttpsConnectionUnexpectedErr) {
error = net::ERR_UNEXPECTED;
status.error_code = net::ERR_UNEXPECTED;
} else if (url_string == kMockHttpConnectionConnectionClosedErr) {
error = net::ERR_CONNECTION_CLOSED;
status.error_code = net::ERR_CONNECTION_CLOSED;
} else if (url_string == kMockHttpConnectionSecureDnsErr ||
url_string == kMockHttpsConnectionSecureDnsErr) {
status.error_code = net::ERR_NAME_NOT_RESOLVED;
status.resolve_error_info = net::ResolveErrorInfo(
net::ERR_CERT_COMMON_NAME_INVALID, true /* is_secure_network_error */);
}
if (error != net::OK) {
params->client->OnComplete(network::URLLoaderCompletionStatus(error));
if (status.error_code != net::OK) {
params->client->OnComplete(status);
return true;
}
......@@ -1204,7 +1222,7 @@ void CaptivePortalBrowserTest::SlowLoadNoCaptivePortal(
// Wait for the request to be issued, then time it out.
WaitForJobs(1);
FailJobs(1);
FailJobs(1, net::ERR_CONNECTION_TIMED_OUT, net::ResolveErrorInfo(net::OK));
navigation_observer.WaitForNavigations(1);
ASSERT_EQ(1, browser->tab_strip_model()->count());
......@@ -1374,12 +1392,14 @@ void CaptivePortalBrowserTest::FastTimeoutBehindCaptivePortal(
Browser* browser,
bool expect_open_login_tab) {
FastErrorBehindCaptivePortal(browser, expect_open_login_tab,
false /* expect_new_login_browser */,
GURL(kMockHttpsQuickTimeoutUrl));
}
void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal(
Browser* browser,
bool expect_open_login_tab,
bool expect_new_login_browser,
const GURL& error_url) {
TabStripModel* tab_strip_model = browser->tab_strip_model();
// Calling this on a tab that's waiting for a load to manually be timed out
......@@ -1399,6 +1419,7 @@ void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal(
int initial_active_index = tab_strip_model->active_index();
int initial_loading_tabs = NumLoadingTabs();
int expected_broken_tabs = NumBrokenTabs();
size_t initial_browser_count = browser_list_->size();
if (CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL !=
GetStateOfTabReloader(tab_strip_model->GetActiveWebContents())) {
++expected_broken_tabs;
......@@ -1414,19 +1435,40 @@ void CaptivePortalBrowserTest::FastErrorBehindCaptivePortal(
if (expect_open_login_tab) {
navigation_observer.WaitForNavigations(2);
ASSERT_EQ(initial_tab_count + 1, tab_strip_model->count());
EXPECT_EQ(initial_tab_count, tab_strip_model->active_index());
// Make sure that the originally active tab and the captive portal tab have
// each loaded once.
WebContents* login_tab;
if (expect_new_login_browser) {
ASSERT_EQ(initial_browser_count + 1, browser_list_->size());
// Check the original browser
ASSERT_EQ(initial_tab_count, tab_strip_model->count());
EXPECT_EQ(initial_tab_count - 1, tab_strip_model->active_index());
EXPECT_NE(browser_list_->get(initial_browser_count - 1),
browser_list_->GetLastActive());
// Check the new popup browser
Browser* popup_browser = browser_list_->get(initial_browser_count);
EXPECT_EQ(popup_browser, browser_list_->GetLastActive());
EXPECT_EQ(Browser::TYPE_POPUP, popup_browser->type());
login_tab = popup_browser->tab_strip_model()->GetWebContentsAt(0);
EXPECT_TRUE(CaptivePortalTabHelper::FromWebContents(login_tab)
->is_captive_portal_window());
} else {
ASSERT_EQ(initial_browser_count, browser_list_->size());
ASSERT_EQ(initial_tab_count + 1, tab_strip_model->count());
EXPECT_EQ(initial_tab_count, tab_strip_model->active_index());
login_tab = tab_strip_model->GetWebContentsAt(initial_tab_count);
}
EXPECT_EQ(1, navigation_observer.NumNavigationsForTab(
tab_strip_model->GetWebContentsAt(initial_active_index)));
EXPECT_EQ(1, navigation_observer.NumNavigationsForTab(
tab_strip_model->GetWebContentsAt(initial_tab_count)));
EXPECT_EQ(1, navigation_observer.NumNavigationsForTab(login_tab));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE,
GetStateOfTabReloaderAt(browser, 1));
EXPECT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1)));
GetStateOfTabReloader(login_tab));
EXPECT_TRUE(IsLoginTab(login_tab));
} else {
navigation_observer.WaitForNavigations(1);
ASSERT_EQ(initial_browser_count, browser_list_->size());
EXPECT_EQ(initial_active_index, tab_strip_model->active_index());
EXPECT_EQ(1, navigation_observer.NumNavigationsForTab(
tab_strip_model->GetWebContentsAt(initial_active_index)));
......@@ -1512,7 +1554,8 @@ void CaptivePortalBrowserTest::NavigateLoginTab(Browser* browser,
void CaptivePortalBrowserTest::Login(Browser* captive_portal_browser,
int num_loading_tabs,
int num_timed_out_tabs) {
int num_timed_out_tabs,
int expected_portal_checks) {
// Simulate logging in.
SetBehindCaptivePortal(false);
......@@ -1536,9 +1579,11 @@ void CaptivePortalBrowserTest::Login(Browser* captive_portal_browser,
"submitForm()");
portal_observer.WaitForResults(1);
// Wait for all the timed out tabs to reload.
// Wait for all the timed out tabs to reload and any new portal checks
// triggered by the reloads.
navigation_observer.WaitForNavigations(1 + num_timed_out_tabs);
EXPECT_EQ(1, portal_observer.num_results_received());
portal_observer.WaitForResults(expected_portal_checks);
EXPECT_EQ(expected_portal_checks, portal_observer.num_results_received());
// The tabs that were loading before should still be loading, and now be in
// STATE_NEEDS_RELOAD.
......@@ -1619,7 +1664,8 @@ void CaptivePortalBrowserTest::FailLoadsAfterLogin(Browser* browser,
// Connection(s) finally time out. There should have already been a call
// to wait for the requests to be issued before logging on.
WaitForJobs(num_loading_tabs);
FailJobs(num_loading_tabs);
FailJobs(num_loading_tabs, net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
fail_loads_observer.WaitForNavigations();
......@@ -1653,7 +1699,8 @@ void CaptivePortalBrowserTest::FailLoadsWithoutLogin(Browser* browser,
MultiNavigationObserver navigation_observer;
// Connection(s) finally time out. There should have already been a call
// to wait for the requests to be issued.
FailJobs(num_loading_tabs);
FailJobs(num_loading_tabs, net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
navigation_observer.WaitForNavigations(num_loading_tabs);
......@@ -1732,7 +1779,8 @@ void CaptivePortalBrowserTest::RunNavigateLoadingTabToTimeoutTest(
// Simulate logging in.
tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromDays(1));
Login(browser, 1, 0);
Login(browser, 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
// Timeout occurs, and page is automatically reloaded.
FailLoadsAfterLogin(browser, 1);
......@@ -1839,8 +1887,9 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, Login) {
// Load starts, detect captive portal and open up a login tab.
SlowLoadBehindCaptivePortal(browser(), true);
// Log in. One loading tab, no timed out ones.
Login(browser(), 1, 0);
// Log in.
Login(browser(), 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
// Timeout occurs, and page is automatically reloaded.
FailLoadsAfterLogin(browser(), 1);
......@@ -1868,7 +1917,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginIncognito) {
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE,
GetStateOfTabReloaderAt(browser(), 0));
Login(incognito_browser, 1, 0);
Login(incognito_browser, 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
FailLoadsAfterLogin(incognito_browser, 1);
EXPECT_EQ(1, tab_strip_model->count());
......@@ -1885,14 +1935,16 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginIncognito) {
IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginSlow) {
SlowLoadBehindCaptivePortal(browser(), true);
FailLoadsWithoutLogin(browser(), 1);
Login(browser(), 0, 1);
Login(browser(), 0 /* num_loading_tabs */, 1 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
}
// Checks the unlikely case that the tab times out before the timer triggers.
// This most likely won't happen, but should still work:
IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginFastTimeout) {
FastTimeoutBehindCaptivePortal(browser(), true);
Login(browser(), 0, 1);
Login(browser(), 0 /* num_loading_tabs */, 1 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
}
// Test that a navigation in a tab that is part of a captive portal windoow
......@@ -1951,7 +2003,9 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
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 /* expect_open_login_tab */,
false /* expect_new_login_browser */,
cert_error_url);
EXPECT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
GetInterstitialType(broken_tab_contents));
......@@ -2311,7 +2365,9 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SSLCertErrorLogin) {
// 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);
FastErrorBehindCaptivePortal(browser(), true /* expect_open_login_tab */,
false /* expect_new_login_browser */,
cert_error_url);
EXPECT_EQ(SSLBlockingPage::kTypeForTesting,
GetInterstitialType(broken_tab_contents));
......@@ -2335,7 +2391,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, LoginExtraNavigations) {
NavigateLoginTab(browser(), 0, 1);
// Simulate logging in.
Login(browser(), 0, 1);
Login(browser(), 0 /* num_loading_tabs */, 1 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
}
// After the first SSL timeout, closes the login tab and makes sure it's opened
......@@ -2350,7 +2407,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, CloseLoginTab) {
// Go through the standard slow load login, and make sure it still works.
SlowLoadBehindCaptivePortal(browser(), true);
Login(browser(), 1, 0);
Login(browser(), 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
FailLoadsAfterLogin(browser(), 1);
}
......@@ -2390,7 +2448,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, TwoBrokenTabs) {
SlowLoadBehindCaptivePortal(browser(), false);
tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
Login(browser(), 2, 0);
Login(browser(), 2 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
FailLoadsAfterLogin(browser(), 2);
}
......@@ -2417,7 +2476,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, AbortLoad) {
GetStateOfTabReloaderAt(browser(), 0));
tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
Login(browser(), 0, 0);
Login(browser(), 0 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
}
// Checks the case where the timed out tab is successfully navigated before
......@@ -2438,7 +2498,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, NavigateBrokenTab) {
// Simulate logging in.
tab_strip_model->ActivateTabAt(1, {TabStripModel::GestureType::kOther});
Login(browser(), 0, 0);
Login(browser(), 0 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
}
// Checks that captive portal detection triggers correctly when a same-site
......@@ -2562,7 +2623,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, GoBackToTimeout) {
EXPECT_EQ(1, NumLoadingTabs());
SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromDays(1));
Login(browser(), 1, 0);
Login(browser(), 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
FailLoadsAfterLogin(browser(), 1);
}
......@@ -2616,7 +2678,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, ReloadTimeout) {
EXPECT_EQ(1, NumLoadingTabs());
SetSlowSSLLoadTime(tab_reloader, base::TimeDelta::FromDays(1));
Login(browser(), 1, 0);
Login(browser(), 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
FailLoadsAfterLogin(browser(), 1);
}
......@@ -2696,7 +2759,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, DISABLED_TwoWindows) {
IsLoginTab(active_browser->tab_strip_model()->GetWebContentsAt(1)));
// Simulate logging in.
Login(active_browser, 0, 1);
Login(active_browser, 0 /* num_loading_tabs */, 1 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
}
// An HTTP page redirects to an HTTPS page loads slowly before timing out. A
......@@ -2708,7 +2772,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, HttpToHttpsRedirectLogin) {
browser(), true /* expect_open_login_tab */,
false /* expect_new_login_browser */,
embedded_test_server()->GetURL(kRedirectToMockHttpsPath), 1, 1);
Login(browser(), 1, 0);
Login(browser(), 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
FailLoadsAfterLogin(browser(), 1);
}
......@@ -2737,7 +2802,8 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, Status511) {
SlowLoadBehindCaptivePortal(browser(), true /* expect_open_login_tab */,
false /* expect_new_login_browser */,
GURL(kMockHttpsUrl), 2, 2);
Login(browser(), 1, 0);
Login(browser(), 1 /* num_loading_tabs */, 0 /* num_timed_out_tabs */,
1 /* expected_portal_checks */);
FailLoadsAfterLogin(browser(), 1);
}
......@@ -2821,5 +2887,105 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SecureDnsCaptivePortal) {
EXPECT_TRUE(browser_list_->get(1)->window()->IsVisible());
// Login to the captive portal.
Login(browser_list_->get(1), 2, 0);
Login(browser_list_->get(1), 2 /* num_loading_tabs */,
0 /* num_timed_out_tabs */, 1 /* expected_portal_checks */);
}
// An HTTP load results in a secure DNS error, which triggers a captive portal
// probe that fails. After logging in, the secure DNS error happens again,
// triggering a captive portal probe that now succeeds.
IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SecureDnsErrorTriggersCheck) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kDnsOverHttpsTemplates,
"https://bar.test/dns-query{?dns}");
local_state->SetString(prefs::kDnsOverHttpsMode,
chrome_browser_net::kDnsOverHttpsModeSecure);
TabStripModel* tab_strip_model = browser()->tab_strip_model();
WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents();
FastErrorBehindCaptivePortal(browser(), true /* expect_open_login_tab */,
true /* expect_new_login_browser */,
GURL(kMockHttpConnectionSecureDnsErr));
// The navigated tab should be displaying an error page.
EXPECT_TRUE(broken_tab_contents->GetController()
.GetLastCommittedEntry()
->GetPageType() == content::PAGE_TYPE_ERROR);
// Login to the captive portal. The captive portal tab navigation will trigger
// a captive portal check, and reloading the original tab will produce the
// same secure DNS error, triggering a second captive portal check.
Login(browser_list_->get(1), 0 /* num_loading_tabs */,
1 /* num_timed_out_tabs */, 2 /* expected_portal_checks */);
// The reload of the original page should have produced another DNS error
// page.
EXPECT_TRUE(broken_tab_contents->GetController()
.GetLastCommittedEntry()
->GetPageType() == content::PAGE_TYPE_ERROR);
}
// An HTTPS load happens slowly. The reloader triggers a captive portal check,
// which finds a captive portal. The HTTPS load finally completes with a secure
// DNS error, which does not trigger another captive portal check. Only one
// login tab should exist.
IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
SlowLoadSecureDnsErrorWithCaptivePortal) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kDnsOverHttpsTemplates,
"https://bar.test/dns-query{?dns}");
local_state->SetString(prefs::kDnsOverHttpsMode,
chrome_browser_net::kDnsOverHttpsModeSecure);
SlowLoadBehindCaptivePortal(browser(), true /* expect_open_login_tab */,
true /* expect_new_login_browser */);
// Connection finally hits a secure DNS error. No new captive portal check is
// triggered.
MultiNavigationObserver navigation_observer;
FailJobs(1, net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID, true));
navigation_observer.WaitForNavigations(1);
WebContents* tab =
browser_list_->get(0)->tab_strip_model()->GetWebContentsAt(0);
EXPECT_EQ(1, navigation_observer.NumNavigationsForTab(tab));
EXPECT_TRUE(tab->GetController().GetLastCommittedEntry()->GetPageType() ==
content::PAGE_TYPE_ERROR);
EXPECT_EQ(2u, browser_list_->size());
EXPECT_EQ(2, NumTabs());
}
// An HTTPS load happens slowly. The reloader triggers a captive portal check,
// which finds a captive portal. After logging in, the HTTPS load finally
// completes with a secure DNS error, which triggers another captive portal
// check that should succeed.
IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
SlowLoadSecureDnsErrorAfterLogin) {
PrefService* local_state = g_browser_process->local_state();
local_state->SetString(prefs::kDnsOverHttpsTemplates,
"https://bar.test/dns-query{?dns}");
local_state->SetString(prefs::kDnsOverHttpsMode,
chrome_browser_net::kDnsOverHttpsModeSecure);
SlowLoadBehindCaptivePortal(browser(), true /* expect_open_login_tab */,
true /* expect_new_login_browser */);
// Login to the captive portal.
Login(browser_list_->get(1), 1 /* num_loading_tabs */,
0 /* num_timed_out_tabs */, 1 /* expected_portal_checks */);
// Connection finally hits a secure DNS error. It should reload without
// sending a new captive portal check.
MultiNavigationObserver navigation_observer;
FailJobs(1, net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID, true));
navigation_observer.WaitForNavigations(1);
WebContents* tab =
browser_list_->get(0)->tab_strip_model()->GetWebContentsAt(0);
EXPECT_EQ(1, navigation_observer.NumNavigationsForTab(tab));
EXPECT_TRUE(tab->GetController().GetLastCommittedEntry()->GetPageType() ==
content::PAGE_TYPE_NORMAL);
EXPECT_EQ(2u, browser_list_->size());
EXPECT_EQ(2, NumTabs());
}
......@@ -172,7 +172,10 @@ bool ShouldUseFixUrlServiceForError(const error_page::Error& error,
*error_param = "http404";
return true;
}
if (IsNetDnsError(error)) {
// Don't use the link doctor for secure DNS network errors, since the
// additional navigation may interfere with the captive portal probe state.
if (IsNetDnsError(error) &&
!error.resolve_error_info().is_secure_network_error) {
*error_param = "dnserror";
return true;
}
......@@ -475,7 +478,13 @@ bool NetErrorHelperCore::IsReloadableError(
info.error.reason() != net::ERR_INVALID_AUTH_CREDENTIALS &&
// Don't auto-reload non-http/https schemas.
// https://crbug.com/471713
url.SchemeIsHTTPOrHTTPS();
url.SchemeIsHTTPOrHTTPS() &&
// Don't auto reload if the error was a secure DNS network error, since
// the reload may interfere with the captive portal probe state.
// TODO(crbug.com/1016164): Explore how to allow reloads for secure DNS
// network errors without interfering with the captive portal probe
// state.
!info.error.resolve_error_info().is_secure_network_error;
}
NetErrorHelperCore::NetErrorHelperCore(Delegate* delegate,
......
......@@ -122,7 +122,8 @@ void CaptivePortalTabHelper::DidFinishNavigation(
}
if (navigation_handle->HasCommitted()) {
tab_reloader_->OnLoadCommitted(navigation_handle->GetNetErrorCode());
tab_reloader_->OnLoadCommitted(navigation_handle->GetNetErrorCode(),
navigation_handle->GetResolveErrorInfo());
} else {
tab_reloader_->OnAbort();
}
......
......@@ -45,7 +45,7 @@ class MockCaptivePortalTabReloader : public CaptivePortalTabReloader {
: CaptivePortalTabReloader(nullptr, nullptr, base::Callback<void()>()) {}
MOCK_METHOD1(OnLoadStart, void(bool));
MOCK_METHOD1(OnLoadCommitted, void(int));
MOCK_METHOD2(OnLoadCommitted, void(int, net::ResolveErrorInfo));
MOCK_METHOD0(OnAbort, void());
MOCK_METHOD1(OnRedirect, void(bool));
MOCK_METHOD2(OnCaptivePortalResults,
......@@ -91,7 +91,9 @@ class CaptivePortalTabHelperTest : public content::RenderViewHostTestHarness {
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
url, web_contents());
navigation->Start();
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::OK)).Times(1);
EXPECT_CALL(mock_reloader(),
OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK)))
.Times(1);
navigation->Commit();
}
......@@ -102,7 +104,27 @@ class CaptivePortalTabHelperTest : public content::RenderViewHostTestHarness {
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
url, web_contents());
navigation->Fail(net::ERR_TIMED_OUT);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::ERR_TIMED_OUT)).Times(1);
EXPECT_CALL(
mock_reloader(),
OnLoadCommitted(net::ERR_TIMED_OUT, net::ResolveErrorInfo(net::OK)))
.Times(1);
navigation->CommitErrorPage();
}
// Simulates a secure DNS network error while requesting |url|.
void SimulateSecureDnsNetworkError(const GURL& url) {
EXPECT_CALL(mock_reloader(), OnLoadStart(url.SchemeIsCryptographic()))
.Times(1);
auto navigation = content::NavigationSimulator::CreateBrowserInitiated(
url, web_contents());
navigation->SetResolveErrorInfo({net::ERR_CERT_COMMON_NAME_INVALID,
true /* is_secure_network_error */});
navigation->Fail(net::ERR_NAME_NOT_RESOLVED);
EXPECT_CALL(mock_reloader(),
OnLoadCommitted(net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(
net::ERR_CERT_COMMON_NAME_INVALID, true)))
.Times(1);
navigation->CommitErrorPage();
}
......@@ -189,6 +211,13 @@ TEST_F(CaptivePortalTabHelperTest, HttpsTimeout) {
EXPECT_FALSE(tab_helper()->IsLoginTab());
}
TEST_F(CaptivePortalTabHelperTest, HttpsSecureDnsNetworkError) {
SimulateSecureDnsNetworkError(GURL(kHttpsUrl));
// Make sure no state was carried over from the secure DNS network error.
SimulateSuccess(GURL(kHttpsUrl));
EXPECT_FALSE(tab_helper()->IsLoginTab());
}
TEST_F(CaptivePortalTabHelperTest, HttpsAbort) {
SimulateAbort(GURL(kHttpsUrl));
// Make sure no state was carried over from the abort.
......@@ -261,7 +290,9 @@ TEST_F(CaptivePortalTabHelperTest, UnexpectedProvisionalLoad) {
// The cross-process navigation fails.
cross_process_navigation->Fail(net::ERR_FAILED);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::ERR_FAILED)).Times(1);
EXPECT_CALL(mock_reloader(),
OnLoadCommitted(net::ERR_FAILED, net::ResolveErrorInfo(net::OK)))
.Times(1);
cross_process_navigation->CommitErrorPage();
}
......@@ -298,7 +329,9 @@ TEST_F(CaptivePortalTabHelperTest, UnexpectedCommit) {
EXPECT_CALL(mock_reloader(),
OnLoadStart(same_site_url.SchemeIsCryptographic()))
.Times(1);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::OK)).Times(1);
EXPECT_CALL(mock_reloader(),
OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK)))
.Times(1);
same_site_navigation->Commit();
}
......@@ -355,7 +388,9 @@ TEST_F(CaptivePortalTabHelperTest, HttpsSubframeParallelError) {
// Error page load finishes.
subframe_navigation->CommitErrorPage();
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::ERR_UNEXPECTED)).Times(1);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::ERR_UNEXPECTED,
net::ResolveErrorInfo(net::OK)))
.Times(1);
main_frame_navigation->CommitErrorPage();
}
......@@ -373,7 +408,9 @@ TEST_F(CaptivePortalTabHelperTest, HttpToHttpsRedirectTimeout) {
navigation->Fail(net::ERR_TIMED_OUT);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::ERR_TIMED_OUT)).Times(1);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::ERR_TIMED_OUT,
net::ResolveErrorInfo(net::OK)))
.Times(1);
navigation->CommitErrorPage();
}
......@@ -391,7 +428,9 @@ TEST_F(CaptivePortalTabHelperTest, HttpsToHttpRedirect) {
.Times(1);
navigation->Redirect(http_url);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::OK)).Times(1);
EXPECT_CALL(mock_reloader(),
OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK)))
.Times(1);
navigation->Commit();
}
......@@ -408,7 +447,9 @@ TEST_F(CaptivePortalTabHelperTest, HttpToHttpRedirect) {
.Times(1);
navigation->Redirect(http_url);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::OK)).Times(1);
EXPECT_CALL(mock_reloader(),
OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK)))
.Times(1);
navigation->Commit();
}
......@@ -431,7 +472,9 @@ TEST_F(CaptivePortalTabHelperTest, SubframeRedirect) {
GURL https_url(kHttpsUrl);
subframe_navigation->Redirect(https_url);
EXPECT_CALL(mock_reloader(), OnLoadCommitted(net::OK)).Times(1);
EXPECT_CALL(mock_reloader(),
OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK)))
.Times(1);
main_frame_navigation->Commit();
}
......
......@@ -70,10 +70,18 @@ void CaptivePortalTabReloader::OnLoadStart(bool is_ssl) {
SetState(STATE_TIMER_RUNNING);
}
void CaptivePortalTabReloader::OnLoadCommitted(int net_error) {
void CaptivePortalTabReloader::OnLoadCommitted(
int net_error,
net::ResolveErrorInfo resolve_error_info) {
provisional_main_frame_load_ = false;
ssl_url_in_redirect_chain_ = false;
// There was a secure DNS network error, so maybe check for a captive portal.
if (resolve_error_info.is_secure_network_error) {
OnSecureDnsNetworkError();
return;
}
if (state_ == STATE_NONE)
return;
......@@ -181,6 +189,19 @@ void CaptivePortalTabReloader::OnSlowSSLConnect() {
SetState(STATE_MAYBE_BROKEN_BY_PORTAL);
}
void CaptivePortalTabReloader::OnSecureDnsNetworkError() {
if (state_ == STATE_NONE || state_ == STATE_TIMER_RUNNING) {
SetState(STATE_MAYBE_BROKEN_BY_PORTAL);
return;
}
if (state_ == STATE_NEEDS_RELOAD) {
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&CaptivePortalTabReloader::ReloadTabIfNeeded,
weak_factory_.GetWeakPtr()));
}
}
void CaptivePortalTabReloader::SetState(State new_state) {
// Stop the timer even when old and new states are the same.
if (state_ == STATE_TIMER_RUNNING) {
......@@ -192,7 +213,8 @@ void CaptivePortalTabReloader::SetState(State new_state) {
// Check for unexpected state transitions.
switch (state_) {
case STATE_NONE:
DCHECK(new_state == STATE_NONE || new_state == STATE_TIMER_RUNNING);
DCHECK(new_state == STATE_NONE || new_state == STATE_TIMER_RUNNING ||
new_state == STATE_MAYBE_BROKEN_BY_PORTAL);
break;
case STATE_TIMER_RUNNING:
DCHECK(new_state == STATE_NONE ||
......
......@@ -12,6 +12,7 @@
#include "base/time/time.h"
#include "base/timer/timer.h"
#include "components/captive_portal/content/captive_portal_service.h"
#include "net/dns/public/resolve_error_info.h"
namespace content {
class WebContents;
......@@ -85,15 +86,17 @@ class CaptivePortalTabReloader {
// loads and for error pages.
virtual void OnLoadStart(bool is_ssl);
// Called when the main frame is committed. |net_error| will be net::OK in
// the case of a successful load. For an errror page, the entire 3-step
// Called when the main frame is committed. |net_error| will be net::OK in
// the case of a successful load. |resolve_error_info| contains information
// about any hostname resolution error. For an error page, the entire 3-step
// process of getting the error, starting a new provisional load for the error
// page, and committing the error page is treated as a single commit.
//
// The Link Doctor page will typically be one OnLoadCommitted with an error
// code, followed by another OnLoadCommitted with net::OK for the Link Doctor
// page.
virtual void OnLoadCommitted(int net_error);
virtual void OnLoadCommitted(int net_error,
net::ResolveErrorInfo resolve_error_info);
// This is called when the current provisional main frame load is canceled.
// Sets state to STATE_NONE, unless this is a login tab.
......@@ -138,6 +141,9 @@ class CaptivePortalTabReloader {
// while to commit.
void OnSlowSSLConnect();
// Called when a main frame loads with a secure DNS network error.
void OnSecureDnsNetworkError();
// Reloads the tab if there's no provisional load going on and the current
// state is STATE_NEEDS_RELOAD. Not safe to call synchronously when called
// by from a WebContentsObserver function, since the WebContents is currently
......
......@@ -122,7 +122,7 @@ TEST_F(CaptivePortalTabReloaderTest, InternetConnected) {
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
EXPECT_FALSE(tab_reloader().TimerRunning());
tab_reloader().OnLoadCommitted(net::OK);
tab_reloader().OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
......@@ -138,7 +138,8 @@ TEST_F(CaptivePortalTabReloaderTest, InternetConnectedTimeout) {
EXPECT_TRUE(tab_reloader().TimerRunning());
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_FALSE(tab_reloader().TimerRunning());
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
......@@ -171,12 +172,12 @@ TEST_F(CaptivePortalTabReloaderTest, NoResponse) {
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
EXPECT_FALSE(tab_reloader().TimerRunning());
tab_reloader().OnLoadCommitted(net::OK);
tab_reloader().OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
// Simulates a slow HTTP load when behind a captive portal, that eventually.
// tiems out. Since it's HTTP, the TabReloader should do nothing.
// times out. Since it's HTTP, the TabReloader should do nothing.
TEST_F(CaptivePortalTabReloaderTest, DoesNothingOnHttp) {
tab_reloader().OnLoadStart(false);
EXPECT_FALSE(tab_reloader().TimerRunning());
......@@ -194,7 +195,8 @@ TEST_F(CaptivePortalTabReloaderTest, DoesNothingOnHttp) {
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
// The page times out.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
......@@ -227,7 +229,8 @@ TEST_F(CaptivePortalTabReloaderTest, Login) {
tab_reloader().state());
// The error page commits, which should start an asynchronous reload.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
tab_reloader().state());
......@@ -258,7 +261,8 @@ TEST_F(CaptivePortalTabReloaderTest, LoginLate) {
EXPECT_FALSE(tab_reloader().TimerRunning());
// The error page commits.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL,
tab_reloader().state());
......@@ -277,7 +281,8 @@ TEST_F(CaptivePortalTabReloaderTest, TimeoutFast) {
// The error page commits, which should trigger a captive portal check,
// since the timer's still running.
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
......@@ -299,6 +304,199 @@ TEST_F(CaptivePortalTabReloaderTest, TimeoutFast) {
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
// The secure DNS config is misconfigured. A secure DNS network error on a
// HTTP navigation triggers a captive portal probe. The probe does not find
// a captive portal.
TEST_F(CaptivePortalTabReloaderTest, HttpBadSecureDnsConfig) {
tab_reloader().OnLoadStart(false);
EXPECT_FALSE(tab_reloader().TimerRunning());
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
// The page encounters a secure DNS network error. The error page commits,
// which should trigger a captive portal check, even for HTTP pages.
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
tab_reloader().OnLoadCommitted(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID,
true /* is_secure_network_error */));
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
// If the only issue was the secure DNS config not being valid, the probes
// (which disable secure DNS) should indicate an internet connection.
tab_reloader().OnCaptivePortalResults(
captive_portal::RESULT_INTERNET_CONNECTED,
captive_portal::RESULT_INTERNET_CONNECTED);
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
// The secure DNS config is misconfigured. A secure DNS network error on a
// HTTPS navigation triggers a captive portal probe before the SSL timer
// triggers. The probe does not find a captive portal.
TEST_F(CaptivePortalTabReloaderTest,
HttpsBadSecureDnsConfigPageLoadsBeforeTimerTriggers) {
tab_reloader().OnLoadStart(true);
EXPECT_EQ(CaptivePortalTabReloader::STATE_TIMER_RUNNING,
tab_reloader().state());
EXPECT_TRUE(tab_reloader().TimerRunning());
// The page encounters a secure DNS network error. The error page commits,
// which should trigger a captive portal check. The SSL timer should be
// cancelled.
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
tab_reloader().OnLoadCommitted(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID,
true /* is_secure_network_error */));
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
EXPECT_FALSE(tab_reloader().TimerRunning());
// If the only issue was the secure DNS config not being valid, the probes
// (which disable secure DNS) should indicate an internet connection.
tab_reloader().OnCaptivePortalResults(
captive_portal::RESULT_INTERNET_CONNECTED,
captive_portal::RESULT_INTERNET_CONNECTED);
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
// The secure DNS config is misconfigured. The SSL timer triggers a captive
// portal probe, which does not complete before the page loads with a secure
// DNS network error. The probe does not find a captive portal.
TEST_F(CaptivePortalTabReloaderTest,
HttpsBadSecureDnsConfigPageLoadsBeforeTimerTriggeredResults) {
tab_reloader().OnLoadStart(true);
EXPECT_EQ(CaptivePortalTabReloader::STATE_TIMER_RUNNING,
tab_reloader().state());
EXPECT_TRUE(tab_reloader().TimerRunning());
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(tab_reloader().TimerRunning());
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
// The page encounters a secure DNS network error. The error page commits.
// Since a probe is already scheduled, we don't schedule another one.
tab_reloader().OnLoadCommitted(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID,
true /* is_secure_network_error */));
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
// If the only issue was the secure DNS config not being valid, the probes
// (which disable secure DNS) should indicate an internet connection.
tab_reloader().OnCaptivePortalResults(
captive_portal::RESULT_INTERNET_CONNECTED,
captive_portal::RESULT_INTERNET_CONNECTED);
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
// The secure DNS config is misconfigured. The SSL timer triggers a captive
// portal probe, which completes before the page loads with a secure DNS
// network error, which triggers another captive portal probe. The probe does
// not find a captive portal.
TEST_F(CaptivePortalTabReloaderTest,
HttpsBadSecureDnsConfigPageLoadsAfterTimerTriggeredResults) {
tab_reloader().OnLoadStart(true);
EXPECT_EQ(CaptivePortalTabReloader::STATE_TIMER_RUNNING,
tab_reloader().state());
EXPECT_TRUE(tab_reloader().TimerRunning());
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(2);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(tab_reloader().TimerRunning());
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
// If the only issue was the secure DNS config not being valid, the probes
// (which disable secure DNS) should indicate an internet connection.
tab_reloader().OnCaptivePortalResults(
captive_portal::RESULT_INTERNET_CONNECTED,
captive_portal::RESULT_INTERNET_CONNECTED);
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
// The page encounters a secure DNS network error. The error page commits,
// which triggers another captive portal check.
tab_reloader().OnLoadCommitted(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID,
true /* is_secure_network_error */));
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
}
// The secure DNS config is configured correctly. The SSL timer triggers a
// captive portal probe. This probe finds a captive portal and completes before
// the page loads with a secure DNS network error, which does not trigger
// another captive portal probe. The user then logs in, causing a page reload.
TEST_F(CaptivePortalTabReloaderTest,
HttpsSecureDnsConfigPageLoadsAfterTimerTriggeredResults) {
tab_reloader().OnLoadStart(true);
EXPECT_EQ(CaptivePortalTabReloader::STATE_TIMER_RUNNING,
tab_reloader().state());
EXPECT_TRUE(tab_reloader().TimerRunning());
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(tab_reloader().TimerRunning());
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
// The probe finds a captive portal and opens a login page.
EXPECT_CALL(tab_reloader(), MaybeOpenCaptivePortalLoginTab()).Times(1);
tab_reloader().OnCaptivePortalResults(
captive_portal::RESULT_INTERNET_CONNECTED,
captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL);
EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL,
tab_reloader().state());
EXPECT_FALSE(tab_reloader().TimerRunning());
// The original navigation encounters a secure DNS network error. The error
// page commits but does not trigger another captive portal check.
tab_reloader().OnLoadCommitted(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID,
true /* is_secure_network_error */));
EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL,
tab_reloader().state());
// The user logs on from another tab, and the page is reloaded.
EXPECT_CALL(tab_reloader(), ReloadTab()).Times(1);
tab_reloader().OnCaptivePortalResults(
captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL,
captive_portal::RESULT_INTERNET_CONNECTED);
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
// The user logs in in a different tab, before the page loads with a secure
// DNS network error. A reload should occur when the page commits.
TEST_F(CaptivePortalTabReloaderTest, HttpsSecureDnsConfigErrorAlreadyLoggedIn) {
tab_reloader().OnLoadStart(true);
// The user logs in from another tab before the tab errors out.
tab_reloader().OnCaptivePortalResults(
captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL,
captive_portal::RESULT_INTERNET_CONNECTED);
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
tab_reloader().state());
// The error page commits, which should trigger a reload.
EXPECT_CALL(tab_reloader(), ReloadTab()).Times(1);
tab_reloader().OnLoadCommitted(
net::ERR_NAME_NOT_RESOLVED,
net::ResolveErrorInfo(net::ERR_CERT_COMMON_NAME_INVALID,
true /* is_secure_network_error */));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
// An SSL protocol error triggers a captive portal check behind a captive
// portal. The user then logs in.
TEST_F(CaptivePortalTabReloaderTest, SSLProtocolError) {
......@@ -307,7 +505,8 @@ TEST_F(CaptivePortalTabReloaderTest, SSLProtocolError) {
// The error page commits, which should trigger a captive portal check,
// since the timer's still running.
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
tab_reloader().OnLoadCommitted(net::ERR_SSL_PROTOCOL_ERROR);
tab_reloader().OnLoadCommitted(net::ERR_SSL_PROTOCOL_ERROR,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
......@@ -338,7 +537,8 @@ TEST_F(CaptivePortalTabReloaderTest, SSLProtocolErrorFastLogin) {
// The error page commits, which should trigger a captive portal check,
// since the timer's still running.
EXPECT_CALL(tab_reloader(), CheckForCaptivePortal()).Times(1);
tab_reloader().OnLoadCommitted(net::ERR_SSL_PROTOCOL_ERROR);
tab_reloader().OnLoadCommitted(net::ERR_SSL_PROTOCOL_ERROR,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_MAYBE_BROKEN_BY_PORTAL,
tab_reloader().state());
......@@ -365,7 +565,8 @@ TEST_F(CaptivePortalTabReloaderTest, SSLProtocolErrorAlreadyLoggedIn) {
// The error page commits, which should trigger a reload.
EXPECT_CALL(tab_reloader(), ReloadTab()).Times(1);
tab_reloader().OnLoadCommitted(net::ERR_SSL_PROTOCOL_ERROR);
tab_reloader().OnLoadCommitted(net::ERR_SSL_PROTOCOL_ERROR,
net::ResolveErrorInfo(net::OK));
base::RunLoop().RunUntilIdle();
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
......@@ -391,7 +592,8 @@ TEST_F(CaptivePortalTabReloaderTest, AlreadyLoggedIn) {
tab_reloader().state());
// The error page commits, which should start an asynchronous reload.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
tab_reloader().state());
......@@ -416,7 +618,8 @@ TEST_F(CaptivePortalTabReloaderTest, AlreadyLoggedInBeforeTimerTriggers) {
EXPECT_FALSE(tab_reloader().TimerRunning());
// The error page commits, which should start an asynchronous reload.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
tab_reloader().state());
......@@ -441,7 +644,8 @@ TEST_F(CaptivePortalTabReloaderTest, LoginWhileTimerRunning) {
tab_reloader().state());
// The error page commits, which should start an asynchronous reload.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
tab_reloader().state());
......@@ -491,7 +695,8 @@ TEST_F(CaptivePortalTabReloaderTest, BehindPortalResultWhileTimerRunning) {
tab_reloader().state());
// The error page commits, which should start an asynchronous reload.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
tab_reloader().state());
......@@ -518,7 +723,7 @@ TEST_F(CaptivePortalTabReloaderTest, LogInWhileTimerRunningNoError) {
tab_reloader().state());
// The page successfully commits, so no reload is triggered.
tab_reloader().OnLoadCommitted(net::OK);
tab_reloader().OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
......@@ -579,7 +784,7 @@ TEST_F(CaptivePortalTabReloaderTest, HttpToHttpsRedirectInternetConnected) {
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
EXPECT_FALSE(tab_reloader().TimerRunning());
tab_reloader().OnLoadCommitted(net::OK);
tab_reloader().OnLoadCommitted(net::OK, net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE, tab_reloader().state());
}
......@@ -619,7 +824,8 @@ TEST_F(CaptivePortalTabReloaderTest, HttpToHttpsRedirectLogin) {
tab_reloader().state());
// The error page commits, which should start an asynchronous reload.
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT);
tab_reloader().OnLoadCommitted(net::ERR_CONNECTION_TIMED_OUT,
net::ResolveErrorInfo(net::OK));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NEEDS_RELOAD,
tab_reloader().state());
......
......@@ -12,6 +12,7 @@
#include "content/public/browser/navigation_throttle.h"
#include "content/public/browser/reload_type.h"
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/dns/public/resolve_error_info.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/referrer.mojom.h"
#include "ui/base/page_transition_types.h"
......@@ -296,6 +297,10 @@ class NavigationSimulator {
// in throttles deferring the navigation with a call to Wait().
virtual void SetAutoAdvance(bool auto_advance) = 0;
// Sets the ResolveErrorInfo to be set on the URLLoaderCompletionStatus.
virtual void SetResolveErrorInfo(
const net::ResolveErrorInfo& resolve_error_info) = 0;
// Sets the SSLInfo to be set on the response. This should be called before
// Commit().
virtual void SetSSLInfo(const net::SSLInfo& ssl_info) = 0;
......
......@@ -684,6 +684,7 @@ void NavigationSimulatorImpl::FailWithResponseHeaders(
static_cast<TestNavigationURLLoader*>(request_->loader_for_testing());
CHECK(url_loader);
network::URLLoaderCompletionStatus status(error_code);
status.resolve_error_info = resolve_error_info_;
status.ssl_info = ssl_info_;
url_loader->SimulateErrorWithStatus(status);
......@@ -916,6 +917,11 @@ void NavigationSimulatorImpl::SetAutoAdvance(bool auto_advance) {
auto_advance_ = auto_advance;
}
void NavigationSimulatorImpl::SetResolveErrorInfo(
const net::ResolveErrorInfo& resolve_error_info) {
resolve_error_info_ = resolve_error_info;
}
void NavigationSimulatorImpl::SetSSLInfo(const net::SSLInfo& ssl_info) {
ssl_info_ = ssl_info;
}
......
......@@ -20,6 +20,7 @@
#include "mojo/public/cpp/bindings/pending_receiver.h"
#include "net/base/host_port_pair.h"
#include "net/base/ip_endpoint.h"
#include "net/dns/public/resolve_error_info.h"
#include "services/service_manager/public/cpp/interface_provider.h"
#include "third_party/blink/public/mojom/referrer.mojom-forward.h"
#include "url/gurl.h"
......@@ -95,6 +96,8 @@ class NavigationSimulatorImpl : public NavigationSimulator,
override;
void SetContentsMimeType(const std::string& contents_mime_type) override;
void SetAutoAdvance(bool auto_advance) override;
void SetResolveErrorInfo(
const net::ResolveErrorInfo& resolve_error_info) override;
void SetSSLInfo(const net::SSLInfo& ssl_info) override;
NavigationThrottle::ThrottleCheckResult GetLastThrottleCheckResult() override;
......@@ -295,6 +298,7 @@ class NavigationSimulatorImpl : public NavigationSimulator,
network::mojom::CSPDisposition::CHECK;
net::HttpResponseInfo::ConnectionInfo http_connection_info_ =
net::HttpResponseInfo::CONNECTION_INFO_UNKNOWN;
net::ResolveErrorInfo resolve_error_info_ = net::ResolveErrorInfo(net::OK);
base::Optional<net::SSLInfo> ssl_info_;
base::Optional<PageState> page_state_;
base::Optional<url::Origin> origin_;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment