Commit d2c91b4a authored by meacer's avatar meacer Committed by Commit bot

Destroy SSLErrorHandler on new navigations so that it can properly be recreated.

Since |SSLErrorHandler| was a |WebContentsUserData| tied to the lifetime of a
|WebContents|, it wasn't properly deleted when the |WebContents| was reloaded
or navigated to another page. This CL removes the error handler explicitly and
adds browser tests to assert reload/navigate behavior.

BUG=453875

Review URL: https://codereview.chromium.org/1004283004

Cr-Commit-Position: refs/heads/master@{#321603}
parent c306fb9a
...@@ -810,18 +810,18 @@ void CaptivePortalObserver::Observe( ...@@ -810,18 +810,18 @@ void CaptivePortalObserver::Observe(
} }
} }
// This observer waits for the SSLErrorHandler to fire an interstitial timer for // This observer waits for the SSLErrorHandler to start an interstitial timer
// the given web contents. // for the given web contents.
class SSLInterstitialTimerObserver { class SSLInterstitialTimerObserver {
public: public:
explicit SSLInterstitialTimerObserver(content::WebContents* web_contents); explicit SSLInterstitialTimerObserver(content::WebContents* web_contents);
~SSLInterstitialTimerObserver(); ~SSLInterstitialTimerObserver();
// Waits until the interstitial delay timer in SSLErrorHandler is fired. // Waits until the interstitial delay timer in SSLErrorHandler is started.
void WaitForTimerFired(); void WaitForTimerStarted();
private: private:
void OnTimerFired(content::WebContents* web_contents); void OnTimerStarted(content::WebContents* web_contents);
const content::WebContents* web_contents_; const content::WebContents* web_contents_;
SSLErrorHandler::TimerStartedCallback callback_; SSLErrorHandler::TimerStartedCallback callback_;
...@@ -835,7 +835,7 @@ SSLInterstitialTimerObserver::SSLInterstitialTimerObserver( ...@@ -835,7 +835,7 @@ SSLInterstitialTimerObserver::SSLInterstitialTimerObserver(
content::WebContents* web_contents) content::WebContents* web_contents)
: web_contents_(web_contents), : web_contents_(web_contents),
message_loop_runner_(new content::MessageLoopRunner) { message_loop_runner_(new content::MessageLoopRunner) {
callback_ = base::Bind(&SSLInterstitialTimerObserver::OnTimerFired, callback_ = base::Bind(&SSLInterstitialTimerObserver::OnTimerStarted,
base::Unretained(this)); base::Unretained(this));
SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(&callback_); SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(&callback_);
} }
...@@ -844,11 +844,11 @@ SSLInterstitialTimerObserver::~SSLInterstitialTimerObserver() { ...@@ -844,11 +844,11 @@ SSLInterstitialTimerObserver::~SSLInterstitialTimerObserver() {
SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(nullptr); SSLErrorHandler::SetInterstitialTimerStartedCallbackForTest(nullptr);
} }
void SSLInterstitialTimerObserver::WaitForTimerFired() { void SSLInterstitialTimerObserver::WaitForTimerStarted() {
message_loop_runner_->Run(); message_loop_runner_->Run();
} }
void SSLInterstitialTimerObserver::OnTimerFired( void SSLInterstitialTimerObserver::OnTimerStarted(
content::WebContents* web_contents) { content::WebContents* web_contents) {
if (web_contents_ == web_contents && message_loop_runner_.get()) if (web_contents_ == web_contents && message_loop_runner_.get())
message_loop_runner_->Quit(); message_loop_runner_->Quit();
...@@ -948,19 +948,20 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest { ...@@ -948,19 +948,20 @@ class CaptivePortalBrowserTest : public InProcessBrowserTest {
// returns, until URLRequestTimeoutOnDemandJob::FailJobs() is called, // returns, until URLRequestTimeoutOnDemandJob::FailJobs() is called,
// at which point it will timeout. // at which point it will timeout.
// //
// When |expect_login_tab| is false, no login tab is expected to be opened, // When |expect_open_login_tab| is false, no login tab is expected to be
// because one already exists, and the function returns once the captive // opened, because one already exists, and the function returns once the
// portal test is complete. // captive portal test is complete.
// //
// If |expect_login_tab| is true, a login tab is then expected to be opened. // If |expect_open_login_tab| is true, a login tab is then expected to be
// It waits until both the login tab has finished loading, and two captive // opened. It waits until both the login tab has finished loading, and two
// portal tests complete. The second test is triggered by the load of the // captive portal tests complete. The second test is triggered by the load of
// captive portal tab completing. // the captive portal tab completing.
// //
// This function must not be called when the active tab is currently loading. // This function must not be called when the active tab is currently loading.
// Waits for the hanging request to be issued, so other functions can rely // Waits for the hanging request to be issued, so other functions can rely
// on URLRequestTimeoutOnDemandJob::WaitForJobs having been called. // on URLRequestTimeoutOnDemandJob::WaitForJobs having been called.
void SlowLoadBehindCaptivePortal(Browser* browser, bool expect_login_tab); void SlowLoadBehindCaptivePortal(Browser* browser,
bool expect_open_login_tab);
// Same as above, but takes extra parameters. // Same as above, but takes extra parameters.
// //
...@@ -1486,7 +1487,7 @@ void CaptivePortalBrowserTest::FastErrorWithInterstitialTimer( ...@@ -1486,7 +1487,7 @@ void CaptivePortalBrowserTest::FastErrorWithInterstitialTimer(
cert_error_url, cert_error_url,
CURRENT_TAB, CURRENT_TAB,
ui_test_utils::BROWSER_TEST_NONE); ui_test_utils::BROWSER_TEST_NONE);
interstitial_timer_observer.WaitForTimerFired(); interstitial_timer_observer.WaitForTimerStarted();
// The tab should be in loading state, waiting for the interstitial timer to // 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 // expire or a captive portal result to arrive. Since captive portal checks
...@@ -2070,19 +2071,10 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, ...@@ -2070,19 +2071,10 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
GetStateOfTabReloaderAt(browser(), 0)); GetStateOfTabReloaderAt(browser(), 0));
} }
// This test is very flaky on Linux and is disabled.
// https://crbug.com/453875
#if defined(OS_LINUX)
#define MAYBE_InterstitialTimerReloadWhileLoading \
DISABLED_InterstitialTimerReloadWhileLoading
#else
#define MAYBE_InterstitialTimerReloadWhileLoading \
InterstitialTimerReloadWhileLoading
#endif
// Same as above, but instead of stopping, the loading page is reloaded. The end // 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) // result is the same. (i.e. page load stops, no interstitials shown)
IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
MAYBE_InterstitialTimerReloadWhileLoading) { InterstitialTimerReloadWhileLoading) {
net::SpawnedTestServer::SSLOptions https_options; net::SpawnedTestServer::SSLOptions https_options;
https_options.server_certificate = https_options.server_certificate =
net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME; net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME;
...@@ -2135,20 +2127,11 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, ...@@ -2135,20 +2127,11 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
GetStateOfTabReloaderAt(browser(), 0)); GetStateOfTabReloaderAt(browser(), 0));
} }
// This test is very flaky on Linux and is disabled. // Same as |InterstitialTimerReloadWhileLoading_NoSSLError|, but instead of
// https://crbug.com/453875 // reloading, the page is navigated away. The new page should load, and no
#if defined(OS_LINUX) // interstitials should be shown.
#define MAYBE_InterstitialTimerNavigateAwayWhileLoading \
DISABLED_InterstitialTimerNavigateAwayWhileLoading
#else
#define MAYBE_InterstitialTimerNavigateAwayWhileLoading \
InterstitialTimerNavigateAwayWhileLoading
#endif
// 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, IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
MAYBE_InterstitialTimerNavigateAwayWhileLoading) { InterstitialTimerNavigateAwayWhileLoading) {
net::SpawnedTestServer::SSLOptions https_options; net::SpawnedTestServer::SSLOptions https_options;
https_options.server_certificate = https_options.server_certificate =
net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME; net::SpawnedTestServer::SSLOptions::CERT_MISMATCHED_NAME;
...@@ -2209,6 +2192,119 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, ...@@ -2209,6 +2192,119 @@ IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest,
GetStateOfTabReloaderAt(browser(), 0)); GetStateOfTabReloaderAt(browser(), 0));
} }
// Same as above, but the hanging load is interrupted by a navigation to the
// same page, this time committing the navigation. This should end up with an
// SSL interstitial when not behind a captive portal. This ensures that a new
// |SSLErrorHandler| is created on a new navigation, even though the tab's
// WebContents doesn't change.
IN_PROC_BROWSER_TEST_F(
CaptivePortalBrowserTest,
InterstitialTimerNavigateWhileLoading_EndWithSSLInterstitial) {
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);
URLRequestMockCaptivePortalJobFactory::SetBehindCaptivePortal(false);
TabStripModel* tab_strip_model = browser()->tab_strip_model();
WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents();
FastErrorWithInterstitialTimer(browser(), cert_error_url);
// Page appears loading. Turn on response to probe request again, and navigate
// to the same page. This should result in a cert error which should
// instantiate an |SSLErrorHandler| and end up showing an SSL interstitial.
RespondToProbeRequests(true);
// Can't have ui_test_utils do the navigation because it will wait for loading
// tabs to stop loading before navigating.
CaptivePortalObserver portal_observer(browser()->profile());
MultiNavigationObserver test_navigation_observer;
browser()->OpenURL(content::OpenURLParams(cert_error_url, 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);
// Should end up with an SSL interstitial.
WaitForInterstitialAttach(broken_tab_contents);
ASSERT_TRUE(broken_tab_contents->ShowingInterstitialPage());
EXPECT_EQ(SSLBlockingPage::kTypeForTesting,
broken_tab_contents->GetInterstitialPage()
->GetDelegateForTesting()
->GetTypeForTesting());
EXPECT_FALSE(broken_tab_contents->IsLoading());
EXPECT_EQ(1, portal_observer.num_results_received());
EXPECT_EQ(captive_portal::RESULT_INTERNET_CONNECTED,
portal_observer.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 this time behind a captive portal.
IN_PROC_BROWSER_TEST_F(
CaptivePortalBrowserTest,
InterstitialTimerNavigateWhileLoading_EndWithCaptivePortalInterstitial) {
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);
URLRequestMockCaptivePortalJobFactory::SetBehindCaptivePortal(true);
TabStripModel* tab_strip_model = browser()->tab_strip_model();
WebContents* broken_tab_contents = tab_strip_model->GetActiveWebContents();
int initial_tab_count = tab_strip_model->count();
FastErrorWithInterstitialTimer(browser(), cert_error_url);
// Page appears loading. Turn on response to probe request again, and navigate
// to the same page. This should result in a cert error which should
// instantiate an |SSLErrorHandler| and end up showing an SSL.
RespondToProbeRequests(true);
// Can't have ui_test_utils do the navigation because it will wait for loading
// tabs to stop loading before navigating.
CaptivePortalObserver portal_observer(browser()->profile());
MultiNavigationObserver test_navigation_observer;
browser()->OpenURL(content::OpenURLParams(cert_error_url, content::Referrer(),
CURRENT_TAB,
ui::PAGE_TRANSITION_TYPED, false));
// Expect three navigations:
// 1- For stopping the hanging page.
// 2- For completing the load of the above navigation.
// 3- For completing the load of the login tab.
test_navigation_observer.WaitForNavigations(3);
// Should end up with a captive portal interstitial and a new login tab.
WaitForInterstitialAttach(broken_tab_contents);
ASSERT_TRUE(broken_tab_contents->ShowingInterstitialPage());
EXPECT_EQ(CaptivePortalBlockingPage::kTypeForTesting,
broken_tab_contents->GetInterstitialPage()
->GetDelegateForTesting()
->GetTypeForTesting());
ASSERT_EQ(initial_tab_count + 1, tab_strip_model->count());
EXPECT_EQ(initial_tab_count, tab_strip_model->active_index());
EXPECT_FALSE(broken_tab_contents->IsLoading());
EXPECT_EQ(1, portal_observer.num_results_received());
EXPECT_EQ(captive_portal::RESULT_BEHIND_CAPTIVE_PORTAL,
portal_observer.captive_portal_result());
EXPECT_EQ(0, NumLoadingTabs());
EXPECT_FALSE(CheckPending(browser()));
EXPECT_EQ(CaptivePortalTabReloader::STATE_BROKEN_BY_PORTAL,
GetStateOfTabReloaderAt(browser(), 0));
EXPECT_EQ(CaptivePortalTabReloader::STATE_NONE,
GetStateOfTabReloaderAt(browser(), 1));
EXPECT_TRUE(IsLoginTab(tab_strip_model->GetWebContentsAt(1)));
}
// A cert error triggers a captive portal check and results in opening a login // 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. // tab. The user then logs in and the page with the error is reloaded.
IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SSLCertErrorLogin) { IN_PROC_BROWSER_TEST_F(CaptivePortalBrowserTest, SSLCertErrorLogin) {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
#include "chrome/browser/ssl/ssl_error_handler.h" #include "chrome/browser/ssl/ssl_error_handler.h"
#include "base/callback_helpers.h"
#include "base/metrics/field_trial.h" #include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h" #include "base/metrics/histogram.h"
#include "base/time/time.h" #include "base/time/time.h"
...@@ -126,7 +127,8 @@ SSLErrorHandler::SSLErrorHandler(content::WebContents* web_contents, ...@@ -126,7 +127,8 @@ SSLErrorHandler::SSLErrorHandler(content::WebContents* web_contents,
const GURL& request_url, const GURL& request_url,
int options_mask, int options_mask,
const base::Callback<void(bool)>& callback) const base::Callback<void(bool)>& callback)
: web_contents_(web_contents), : content::WebContentsObserver(web_contents),
web_contents_(web_contents),
cert_error_(cert_error), cert_error_(cert_error),
ssl_info_(ssl_info), ssl_info_(ssl_info),
request_url_(request_url), request_url_(request_url),
...@@ -223,3 +225,17 @@ void SSLErrorHandler::Observe( ...@@ -223,3 +225,17 @@ void SSLErrorHandler::Observe(
} }
#endif #endif
} }
// Destroy the error handler on all new navigations. This ensures that the
// handler is properly recreated when a hanging page is navigated to an SSL
// error, even when the tab's WebContents doesn't change.
void SSLErrorHandler::DidStartNavigationToPendingEntry(
const GURL& url,
content::NavigationController::ReloadType reload_type) {
// Need to explicity deny the certificate via the callback, otherwise memory
// is leaked.
if (!callback_.is_null()) {
base::ResetAndReturn(&callback_).Run(false);
}
web_contents_->RemoveUserData(UserDataKey());
}
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chrome_notification_types.h"
#include "content/public/browser/notification_observer.h" #include "content/public/browser/notification_observer.h"
#include "content/public/browser/notification_registrar.h" #include "content/public/browser/notification_registrar.h"
#include "content/public/browser/web_contents_observer.h"
#include "content/public/browser/web_contents_user_data.h" #include "content/public/browser/web_contents_user_data.h"
#include "net/ssl/ssl_info.h" #include "net/ssl/ssl_info.h"
#include "url/gurl.h" #include "url/gurl.h"
...@@ -35,6 +36,7 @@ class WebContents; ...@@ -35,6 +36,7 @@ class WebContents;
// captive_portal::CaptivePortalService which can only be accessed on the UI // captive_portal::CaptivePortalService which can only be accessed on the UI
// thread. // thread.
class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>, class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>,
public content::WebContentsObserver,
public content::NotificationObserver { public content::NotificationObserver {
public: public:
// Type of the delay to display the SSL interstitial. // Type of the delay to display the SSL interstitial.
...@@ -91,12 +93,17 @@ class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>, ...@@ -91,12 +93,17 @@ class SSLErrorHandler : public content::WebContentsUserData<SSLErrorHandler>,
const content::NotificationSource& source, const content::NotificationSource& source,
const content::NotificationDetails& details) override; const content::NotificationDetails& details) override;
// content::WebContentsObserver:
void DidStartNavigationToPendingEntry(
const GURL& url,
content::NavigationController::ReloadType reload_type) override;
content::WebContents* web_contents_; content::WebContents* web_contents_;
const int cert_error_; const int cert_error_;
const net::SSLInfo ssl_info_; const net::SSLInfo ssl_info_;
const GURL request_url_; const GURL request_url_;
const int options_mask_; const int options_mask_;
const base::Callback<void(bool)> callback_; base::Callback<void(bool)> callback_;
content::NotificationRegistrar registrar_; content::NotificationRegistrar registrar_;
base::OneShotTimer<SSLErrorHandler> timer_; base::OneShotTimer<SSLErrorHandler> timer_;
......
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