summaryrefslogtreecommitdiff
path: root/chromium/chrome/browser/net/errorpage_browsertest.cc
diff options
context:
space:
mode:
Diffstat (limited to 'chromium/chrome/browser/net/errorpage_browsertest.cc')
-rw-r--r--chromium/chrome/browser/net/errorpage_browsertest.cc1451
1 files changed, 1451 insertions, 0 deletions
diff --git a/chromium/chrome/browser/net/errorpage_browsertest.cc b/chromium/chrome/browser/net/errorpage_browsertest.cc
new file mode 100644
index 00000000000..b4531e853c5
--- /dev/null
+++ b/chromium/chrome/browser/net/errorpage_browsertest.cc
@@ -0,0 +1,1451 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+#include <algorithm>
+#include <memory>
+#include <utility>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/command_line.h"
+#include "base/compiler_specific.h"
+#include "base/feature_list.h"
+#include "base/files/scoped_temp_dir.h"
+#include "base/logging.h"
+#include "base/macros.h"
+#include "base/memory/ptr_util.h"
+#include "base/memory/weak_ptr.h"
+#include "base/path_service.h"
+#include "base/run_loop.h"
+#include "base/strings/stringprintf.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/synchronization/lock.h"
+#include "base/task/post_task.h"
+#include "base/threading/thread_restrictions.h"
+#include "base/values.h"
+#include "build/build_config.h"
+#include "chrome/browser/browser_process_platform_part.h"
+#include "chrome/browser/browsing_data/browsing_data_helper.h"
+#include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
+#include "chrome/browser/net/net_error_diagnostics_dialog.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/ui_thread_search_terms_data.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_commands.h"
+#include "chrome/browser/ui/tabs/tab_strip_model.h"
+#include "chrome/common/chrome_paths.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/in_process_browser_test.h"
+#include "chrome/test/base/ui_test_utils.h"
+#include "components/google/core/common/google_util.h"
+#include "components/language/core/browser/pref_names.h"
+#include "components/policy/core/browser/browser_policy_connector.h"
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+#include "components/policy/core/common/policy_map.h"
+#include "components/policy/policy_constants.h"
+#include "components/prefs/pref_member.h"
+#include "components/prefs/pref_service.h"
+#include "components/strings/grit/components_strings.h"
+#include "content/public/browser/browser_task_traits.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/browsing_data_remover.h"
+#include "content/public/browser/navigation_handle.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/storage_partition.h"
+#include "content/public/browser/web_contents.h"
+#include "content/public/browser/web_contents_observer.h"
+#include "content/public/test/browser_test_utils.h"
+#include "content/public/test/test_navigation_observer.h"
+#include "content/public/test/url_loader_interceptor.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "mojo/public/cpp/bindings/strong_binding.h"
+#include "net/base/filename_util.h"
+#include "net/base/net_errors.h"
+#include "net/dns/mock_host_resolver.h"
+#include "net/http/failing_http_transaction_factory.h"
+#include "net/http/http_cache.h"
+#include "net/test/embedded_test_server/embedded_test_server.h"
+#include "net/test/embedded_test_server/http_request.h"
+#include "net/test/embedded_test_server/http_response.h"
+#include "net/test/url_request/url_request_failed_job.h"
+#include "net/test/url_request/url_request_mock_data_job.h"
+#include "net/test/url_request/url_request_mock_http_job.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "net/url_request/url_request_job.h"
+#include "net/url_request/url_request_test_job.h"
+#include "net/url_request/url_request_test_util.h"
+#include "services/network/public/cpp/features.h"
+#include "ui/base/l10n/l10n_util.h"
+
+#if defined(OS_CHROMEOS)
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chromeos/chrome_browser_main_chromeos.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
+#include "chromeos/tpm/stub_install_attributes.h"
+#include "components/policy/core/common/policy_types.h"
+#else
+#include "chrome/browser/policy/profile_policy_connector_builder.h"
+#endif
+#include "components/policy/core/common/mock_configuration_policy_provider.h"
+
+using content::BrowserThread;
+using content::NavigationController;
+using net::URLRequestFailedJob;
+using net::URLRequestTestJob;
+
+namespace {
+
+// Searches for first node containing |text|, and if it finds one, searches
+// through all ancestors seeing if any of them is of class "hidden". Since it
+// relies on the hidden class used by network error pages, not suitable for
+// general use.
+bool WARN_UNUSED_RESULT IsDisplayingText(Browser* browser,
+ const std::string& text) {
+ // clang-format off
+ std::string command = base::StringPrintf(R"(
+ function isNodeVisible(node) {
+ if (!node || node.classList.contains('hidden'))
+ return false;
+ if (!node.parentElement)
+ return true;
+ // Otherwise, we must check all parent nodes
+ return isNodeVisible(node.parentElement);
+ }
+ var node = document.evaluate("//*[contains(text(),'%s')]", document,
+ null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
+ domAutomationController.send(isNodeVisible(node));
+ )", text.c_str());
+ // clang-format on
+ bool result = false;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser->tab_strip_model()->GetActiveWebContents(), command, &result));
+ return result;
+}
+
+// Expands the more box on the currently displayed error page.
+void ToggleHelpBox(Browser* browser) {
+ EXPECT_TRUE(content::ExecuteScript(
+ browser->tab_strip_model()->GetActiveWebContents(),
+ "document.getElementById('details-button').click();"));
+}
+
+// Returns true if the diagnostics link suggestion is displayed.
+bool WARN_UNUSED_RESULT IsDisplayingDiagnosticsLink(Browser* browser) {
+ std::string command = base::StringPrintf(
+ "var diagnose_link = document.getElementById('diagnose-link');"
+ "domAutomationController.send(diagnose_link != null);");
+ bool result = false;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
+ browser->tab_strip_model()->GetActiveWebContents(), command, &result));
+ return result;
+}
+
+// Checks that the local error page is being displayed, without remotely
+// retrieved navigation corrections, and with the specified error string.
+void ExpectDisplayingLocalErrorPage(const std::string& url,
+ Browser* browser,
+ const std::string& error_string) {
+ EXPECT_TRUE(IsDisplayingText(browser, error_string));
+
+ // Locally generated error pages should not have navigation corrections.
+ EXPECT_FALSE(IsDisplayingText(browser, url));
+
+ // Locally generated error pages should not have a link with search terms.
+ EXPECT_FALSE(IsDisplayingText(browser, "search query"));
+}
+
+// Checks that the local error page is being displayed, without remotely
+// retrieved navigation corrections, and with the specified error code.
+void ExpectDisplayingLocalErrorPage(const std::string& url,
+ Browser* browser,
+ net::Error error_code) {
+ ExpectDisplayingLocalErrorPage(url, browser,
+ net::ErrorToShortString(error_code));
+}
+
+// Checks that an error page with information retrieved from the navigation
+// correction service is being displayed, with the specified specified error
+// string.
+void ExpectDisplayingNavigationCorrections(Browser* browser,
+ const std::string& error_string) {
+ EXPECT_TRUE(IsDisplayingText(browser, error_string));
+
+ // Check that the mock navigation corrections are displayed.
+ EXPECT_TRUE(IsDisplayingText(browser, "http://mock.http/title2.html"));
+
+ // Check that the search terms are displayed as a link.
+ EXPECT_TRUE(IsDisplayingText(browser, "search query"));
+
+ // The diagnostics button isn't displayed when corrections were
+ // retrieved from a remote server.
+ EXPECT_FALSE(IsDisplayingDiagnosticsLink(browser));
+}
+
+// Checks that an error page with information retrieved from the navigation
+// correction service is being displayed, with the specified specified error
+// code.
+void ExpectDisplayingNavigationCorrections(Browser* browser,
+ net::Error error_code) {
+ ExpectDisplayingNavigationCorrections(browser,
+ net::ErrorToShortString(error_code));
+}
+
+// Returns true if the platform has support for a diagnostics tool, and it
+// can be launched from |web_contents|.
+bool WebContentsCanShowDiagnosticsTool(content::WebContents* web_contents) {
+#if defined(OS_CHROMEOS)
+ // ChromeOS uses an extension instead of a diagnostics dialog.
+ return true;
+#else
+ return CanShowNetworkDiagnosticsDialog(web_contents);
+#endif
+}
+
+class ErrorPageTest : public InProcessBrowserTest {
+ public:
+ enum HistoryNavigationDirection {
+ HISTORY_NAVIGATE_BACK,
+ HISTORY_NAVIGATE_FORWARD,
+ };
+
+ ErrorPageTest() = default;
+ ~ErrorPageTest() override = default;
+
+ // Navigates the active tab to a mock url created for the file at |path|.
+ void NavigateToFileURL(const std::string& path) {
+ GURL url = embedded_test_server()->GetURL(path);
+ ui_test_utils::NavigateToURL(browser(), url);
+ }
+
+ // Navigates to the given URL and waits for |num_navigations| to occur, and
+ // the title to change to |expected_title|.
+ void NavigateToURLAndWaitForTitle(const GURL& url,
+ const std::string& expected_title,
+ int32_t num_navigations) {
+ content::TitleWatcher title_watcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16(expected_title));
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), url, num_navigations);
+
+ EXPECT_EQ(base::ASCIIToUTF16(expected_title),
+ title_watcher.WaitAndGetTitle());
+ }
+
+ // Navigates back in the history and waits for |num_navigations| to occur, and
+ // the title to change to |expected_title|.
+ void GoBackAndWaitForTitle(const std::string& expected_title,
+ int32_t num_navigations) {
+ NavigateHistoryAndWaitForTitle(expected_title,
+ num_navigations,
+ HISTORY_NAVIGATE_BACK);
+ }
+
+ // Navigates forward in the history and waits for |num_navigations| to occur,
+ // and the title to change to |expected_title|.
+ void GoForwardAndWaitForTitle(const std::string& expected_title,
+ int32_t num_navigations) {
+ NavigateHistoryAndWaitForTitle(expected_title,
+ num_navigations,
+ HISTORY_NAVIGATE_FORWARD);
+ }
+
+ void GoBackAndWaitForNavigations(int32_t num_navigations) {
+ NavigateHistory(num_navigations, HISTORY_NAVIGATE_BACK);
+ }
+
+ void GoForwardAndWaitForNavigations(int32_t num_navigations) {
+ NavigateHistory(num_navigations, HISTORY_NAVIGATE_FORWARD);
+ }
+
+ // Navigates the browser the indicated direction in the history and waits for
+ // |num_navigations| to occur and the title to change to |expected_title|.
+ void NavigateHistoryAndWaitForTitle(const std::string& expected_title,
+ int32_t num_navigations,
+ HistoryNavigationDirection direction) {
+ content::TitleWatcher title_watcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16(expected_title));
+
+ NavigateHistory(num_navigations, direction);
+
+ EXPECT_EQ(title_watcher.WaitAndGetTitle(),
+ base::ASCIIToUTF16(expected_title));
+ }
+
+ void NavigateHistory(int32_t num_navigations,
+ HistoryNavigationDirection direction) {
+ content::TestNavigationObserver test_navigation_observer(
+ browser()->tab_strip_model()->GetActiveWebContents(), num_navigations);
+ if (direction == HISTORY_NAVIGATE_BACK) {
+ chrome::GoBack(browser(), WindowOpenDisposition::CURRENT_TAB);
+ } else if (direction == HISTORY_NAVIGATE_FORWARD) {
+ chrome::GoForward(browser(), WindowOpenDisposition::CURRENT_TAB);
+ } else {
+ FAIL();
+ }
+ test_navigation_observer.Wait();
+ }
+};
+
+class TestFailProvisionalLoadObserver : public content::WebContentsObserver {
+ public:
+ explicit TestFailProvisionalLoadObserver(content::WebContents* contents)
+ : content::WebContentsObserver(contents) {}
+ ~TestFailProvisionalLoadObserver() override {}
+
+ void DidFinishNavigation(
+ content::NavigationHandle* navigation_handle) override {
+ if (navigation_handle->IsErrorPage())
+ fail_url_ = navigation_handle->GetURL();
+ }
+
+ const GURL& fail_url() const { return fail_url_; }
+
+ private:
+ GURL fail_url_;
+
+ DISALLOW_COPY_AND_ASSIGN(TestFailProvisionalLoadObserver);
+};
+
+class DNSErrorPageTest : public ErrorPageTest {
+ public:
+ DNSErrorPageTest() {
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](DNSErrorPageTest* owner,
+ content::URLLoaderInterceptor::RequestParams* params) {
+ // Add an interceptor that serves LinkDoctor responses
+ if (google_util::LinkDoctorBaseURL() == params->url_request.url) {
+ // The origin header should exist and be opaque.
+ std::string origin;
+ bool has_origin = params->url_request.headers.GetHeader(
+ net::HttpRequestHeaders::kOrigin, &origin);
+ EXPECT_TRUE(has_origin);
+ EXPECT_EQ(origin, "null");
+ // Send RequestCreated so that anyone blocking on
+ // WaitForRequests can continue.
+ base::PostTask(FROM_HERE, {BrowserThread::UI},
+ base::BindOnce(&DNSErrorPageTest::RequestCreated,
+ base::Unretained(owner)));
+ content::URLLoaderInterceptor::WriteResponse(
+ "chrome/test/data/mock-link-doctor.json",
+ params->client.get());
+ return true;
+ }
+
+ // Referenced by mock Link Doctor page.
+ if (params->url_request.url.spec() ==
+ "http://mock.http/title2.html") {
+ content::URLLoaderInterceptor::WriteResponse(
+ "chrome/test/data/title2.html", params->client.get());
+ return true;
+ }
+
+ // Add an interceptor for the search engine the error page will
+ // use.
+ if (params->url_request.url.host() ==
+ owner->search_term_url_.host()) {
+ content::URLLoaderInterceptor::WriteResponse(
+ "chrome/test/data/title3.html", params->client.get());
+ return true;
+ }
+
+ return false;
+ },
+ this));
+ }
+
+ ~DNSErrorPageTest() override = default;
+
+ // When it sees a request for |path|, returns a 500 response with a body that
+ // will be sniffed as binary/octet-stream.
+ static std::unique_ptr<net::test_server::HttpResponse>
+ Return500WithBinaryBody(const std::string& path,
+ const net::test_server::HttpRequest& request) {
+ if (path != request.relative_url)
+ return nullptr;
+ return std::unique_ptr<net::test_server::HttpResponse>(
+ new net::test_server::RawHttpResponse("HTTP/1.1 500 Server Sad :(\n\n",
+ "\x01"));
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ void SetUpOnMainThread() override {
+ // All mock.http requests get served by the embedded test server.
+ host_resolver()->AddRule("mock.http", "127.0.0.1");
+
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ UIThreadSearchTermsData search_terms_data;
+ search_term_url_ = GURL(search_terms_data.GoogleBaseURLValue());
+ }
+
+ void WaitForRequests(int32_t requests_to_wait_for) {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ DCHECK_EQ(-1, requests_to_wait_for_);
+ DCHECK(!run_loop_);
+
+ if (requests_to_wait_for <= num_requests_)
+ return;
+
+ requests_to_wait_for_ = requests_to_wait_for;
+ run_loop_.reset(new base::RunLoop());
+ run_loop_->Run();
+ run_loop_.reset();
+ requests_to_wait_for_ = -1;
+ EXPECT_EQ(num_requests_, requests_to_wait_for);
+ }
+
+ // Returns the total number of requests handled thus far.
+ int32_t num_requests() const {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ return num_requests_;
+ }
+
+ void RequestCreated() {
+ DCHECK_CURRENTLY_ON(BrowserThread::UI);
+ num_requests_++;
+ if (num_requests_ == requests_to_wait_for_)
+ run_loop_->Quit();
+ }
+
+ // Returns a GURL that results in a DNS error.
+ GURL GetDnsErrorURL() const {
+ return URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED);
+ }
+
+ private:
+ // These are only used on the UI thread.
+ int32_t num_requests_ = 0;
+ int32_t requests_to_wait_for_ = -1;
+ GURL search_term_url_;
+ std::unique_ptr<base::RunLoop> run_loop_;
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+// Test an error with a file URL, and make sure it doesn't have a
+// button to launch a network diagnostics tool.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, FileNotFound) {
+ // Create an empty temp directory, to be sure there's no file in it.
+ base::ScopedAllowBlockingForTesting allow_blocking;
+ base::ScopedTempDir temp_dir;
+ ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
+ GURL non_existent_file_url =
+ net::FilePathToFileURL(temp_dir.GetPath().AppendASCII("marmoset"));
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), non_existent_file_url, 1);
+
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_FILE_NOT_FOUND);
+ // Should not request Link Doctor corrections for local errors.
+ EXPECT_EQ(0, num_requests());
+ // Only errors on HTTP/HTTPS pages should display a diagnostics button.
+ EXPECT_FALSE(IsDisplayingDiagnosticsLink(browser()));
+}
+
+// Check an network error page for ERR_FAILED. In particular, this should
+// not trigger a link doctor error page.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Failed) {
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), URLRequestFailedJob::GetMockHttpUrl(net::ERR_FAILED), 1);
+
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_FAILED);
+ // Should not request Link Doctor corrections for this error.
+ EXPECT_EQ(0, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame redirects to an error page.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_Basic) {
+ // The first navigation should fail and load a blank page, while it fetches
+ // the Link Doctor response. The second navigation is the Link Doctor.
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack1) {
+ NavigateToFileURL("/title2.html");
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(1, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2) {
+ NavigateToFileURL("/title2.html");
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ NavigateToFileURL("/title3.html");
+
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(2, num_requests());
+
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(2, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2AndForward) {
+ NavigateToFileURL("/title2.html");
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ NavigateToFileURL("/title3.html");
+
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(2, num_requests());
+
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+
+ GoForwardAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(3, num_requests());
+}
+
+// Test that a DNS error occuring in the main frame does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2Forward2) {
+ NavigateToFileURL("/title3.html");
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ NavigateToFileURL("/title2.html");
+
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(2, num_requests());
+
+ GoBackAndWaitForTitle("Title Of More Awesomeness", 1);
+
+ GoForwardAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(3, num_requests());
+
+ GoForwardAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(3, num_requests());
+}
+
+// Test that the search link on a DNS error page works.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoSearch) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Do a search and make sure the browser ends up at the right page.
+ content::TestNavigationObserver nav_observer(web_contents, 1);
+ content::TitleWatcher title_watcher(
+ web_contents,
+ base::ASCIIToUTF16("Title Of More Awesomeness"));
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('search-link').click();"),
+ base::NullCallback());
+ nav_observer.Wait();
+ EXPECT_EQ(base::ASCIIToUTF16("Title Of More Awesomeness"),
+ title_watcher.WaitAndGetTitle());
+
+ // There should have been another Link Doctor request, for tracking purposes.
+ // Have to wait for it, since the search page does not depend on having
+ // sent the tracking request.
+ WaitForRequests(2);
+
+ // Check the path and query string.
+ std::string url;
+ ASSERT_TRUE(content::ExecuteScriptAndExtractString(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ "domAutomationController.send(window.location.href);",
+ &url));
+ EXPECT_EQ("/search", GURL(url).path());
+ EXPECT_EQ("q=search%20query", GURL(url).query());
+
+ // Go back to the error page, to make sure the history is correct.
+ GoBackAndWaitForNavigations(2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(3, num_requests());
+}
+
+// Test that the reload button on a DNS error page works.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoReload) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ std::string url =
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Clicking the reload button should load the error page again, and there
+ // should be two commits, as before.
+ content::TestNavigationObserver nav_observer(web_contents, 2);
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('reload-button').click();"),
+ base::NullCallback());
+ nav_observer.Wait();
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+
+ // There should have been two more requests to the correction service: One
+ // for the new error page, and one for tracking purposes. Have to make sure
+ // to wait for the tracking request, since the new error page does not depend
+ // on it.
+ WaitForRequests(3);
+}
+
+// Test that the reload button on a DNS error page works after a same document
+// navigation on the error page. Error pages don't seem to do this, but some
+// traces indicate this may actually happen. This test may hang on regression.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest,
+ DNSError_DoReloadAfterSameDocumentNavigation) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ std::string url =
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Do a same-document navigation on the error page, which should not result
+ // in a new navigation.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.location='#';"), base::NullCallback());
+ content::WaitForLoadStop(web_contents);
+ // Page being displayed should not change.
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+ // No new requests should have been issued.
+ EXPECT_EQ(1, num_requests());
+
+ // Clicking the reload button should load the error page again, and there
+ // should be two commits, as before.
+ content::TestNavigationObserver nav_observer2(web_contents, 2);
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('reload-button').click();"),
+ base::NullCallback());
+ nav_observer2.Wait();
+ ExpectDisplayingNavigationCorrections(browser(), net::ERR_NAME_NOT_RESOLVED);
+
+ // There should have been two more requests to the correction service: One
+ // for the new error page, and one for tracking purposes. Have to make sure
+ // to wait for the tracking request, since the new error page does not depend
+ // on it.
+ WaitForRequests(3);
+}
+
+// Test that clicking links on a DNS error page works.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_DoClickLink) {
+ // The first navigation should fail, and the second one should be the error
+ // page.
+ GURL url = embedded_test_server()->GetURL("mock.http", "/title2.html");
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), GetDnsErrorURL(), 2);
+ ExpectDisplayingNavigationCorrections(
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+ EXPECT_EQ(1, num_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Simulate a click on a link.
+
+ content::TitleWatcher title_watcher(
+ web_contents,
+ base::ASCIIToUTF16("Title Of Awesomeness"));
+ std::string link_selector =
+ "document.querySelector('a[href=\"http://mock.http/title2.html\"]')";
+ // The tracking request is triggered by onmousedown, so it catches middle
+ // mouse button clicks, as well as left clicks.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16(link_selector + ".onmousedown();"),
+ base::NullCallback());
+ // Can't use content::ExecuteScript because it waits for scripts to send
+ // notification that they've run, and scripts that trigger a navigation may
+ // not send that notification.
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16(link_selector + ".click();"), base::NullCallback());
+ EXPECT_EQ(base::ASCIIToUTF16("Title Of Awesomeness"),
+ title_watcher.WaitAndGetTitle());
+
+ // There should have been a tracking request to the correction service. Have
+ // to make sure to wait the tracking request, since the new page does not
+ // depend on it.
+ WaitForRequests(2);
+}
+
+// Test that a DNS error occuring in an iframe does not result in showing
+// navigation corrections.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, IFrameDNSError_Basic) {
+ NavigateToURLAndWaitForTitle(
+ embedded_test_server()->GetURL("/iframe_dns_error.html"), "Blah", 1);
+ // We expect to have two history entries, since we started off with navigation
+ // to "about:blank" and then navigated to "iframe_dns_error.html".
+ EXPECT_EQ(2,
+ browser()->tab_strip_model()->GetActiveWebContents()->
+ GetController().GetEntryCount());
+ EXPECT_EQ(0, num_requests());
+}
+
+// This test fails regularly on win_rel trybots. See crbug.com/121540
+#if defined(OS_WIN)
+#define MAYBE_IFrameDNSError_GoBack DISABLED_IFrameDNSError_GoBack
+#else
+#define MAYBE_IFrameDNSError_GoBack IFrameDNSError_GoBack
+#endif
+// Test that a DNS error occuring in an iframe does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, MAYBE_IFrameDNSError_GoBack) {
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL("/title2.html"));
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/iframe_dns_error.html"));
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ EXPECT_EQ(0, num_requests());
+}
+
+// This test fails regularly on win_rel trybots. See crbug.com/121540
+//
+// This fails on linux_aura bringup: http://crbug.com/163931
+#if defined(OS_WIN) || (defined(OS_LINUX) && !defined(OS_CHROMEOS) && defined(USE_AURA))
+#define MAYBE_IFrameDNSError_GoBackAndForward DISABLED_IFrameDNSError_GoBackAndForward
+#else
+#define MAYBE_IFrameDNSError_GoBackAndForward IFrameDNSError_GoBackAndForward
+#endif
+// Test that a DNS error occuring in an iframe does not result in an
+// additional session history entry.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest,
+ MAYBE_IFrameDNSError_GoBackAndForward) {
+ NavigateToFileURL("/title2.html");
+ NavigateToFileURL("/iframe_dns_error.html");
+ GoBackAndWaitForTitle("Title Of Awesomeness", 1);
+ GoForwardAndWaitForTitle("Blah", 1);
+ EXPECT_EQ(0, num_requests());
+}
+
+// Test that a DNS error occuring in an iframe, once the main document is
+// completed loading, does not result in an additional session history entry.
+// To ensure that the main document has completed loading, JavaScript is used to
+// inject an iframe after loading is done.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, IFrameDNSError_JavaScript) {
+ content::WebContents* wc =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ GURL fail_url =
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED);
+
+ // Load a regular web page, in which we will inject an iframe.
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL("/title2.html"));
+
+ // We expect to have two history entries, since we started off with navigation
+ // to "about:blank" and then navigated to "title2.html".
+ EXPECT_EQ(2, wc->GetController().GetEntryCount());
+
+ std::string script = "var frame = document.createElement('iframe');"
+ "frame.src = '" + fail_url.spec() + "';"
+ "document.body.appendChild(frame);";
+ {
+ TestFailProvisionalLoadObserver fail_observer(wc);
+ content::WindowedNotificationObserver load_observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::Source<NavigationController>(&wc->GetController()));
+ wc->GetMainFrame()->ExecuteJavaScriptForTests(base::ASCIIToUTF16(script),
+ base::NullCallback());
+ load_observer.Wait();
+
+ // Ensure we saw the expected failure.
+ EXPECT_EQ(fail_url, fail_observer.fail_url());
+
+ // Failed initial navigation of an iframe shouldn't be adding any history
+ // entries.
+ EXPECT_EQ(2, wc->GetController().GetEntryCount());
+ }
+
+ // Do the same test, but with an iframe that doesn't have initial URL
+ // assigned.
+ script = "var frame = document.createElement('iframe');"
+ "frame.id = 'target_frame';"
+ "document.body.appendChild(frame);";
+ {
+ content::WindowedNotificationObserver load_observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::Source<NavigationController>(&wc->GetController()));
+ wc->GetMainFrame()->ExecuteJavaScriptForTests(base::ASCIIToUTF16(script),
+ base::NullCallback());
+ load_observer.Wait();
+ }
+
+ script = "var f = document.getElementById('target_frame');"
+ "f.src = '" + fail_url.spec() + "';";
+ {
+ TestFailProvisionalLoadObserver fail_observer(wc);
+ content::WindowedNotificationObserver load_observer(
+ content::NOTIFICATION_LOAD_STOP,
+ content::Source<NavigationController>(&wc->GetController()));
+ wc->GetMainFrame()->ExecuteJavaScriptForTests(base::ASCIIToUTF16(script),
+ base::NullCallback());
+ load_observer.Wait();
+
+ EXPECT_EQ(fail_url, fail_observer.fail_url());
+ EXPECT_EQ(2, wc->GetController().GetEntryCount());
+ }
+ EXPECT_EQ(0, num_requests());
+}
+
+// Checks that navigation corrections are not loaded when we receive an actual
+// 404 page.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Page404) {
+ NavigateToURLAndWaitForTitle(embedded_test_server()->GetURL("/page404.html"),
+ "SUCCESS", 1);
+ EXPECT_EQ(0, num_requests());
+}
+
+// Checks that navigation corrections are loaded in response to a 404 page with
+// no body.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Empty404) {
+ // The first navigation should fail and load a blank page, while it fetches
+ // the Link Doctor response. The second navigation is the Link Doctor.
+ GURL url = embedded_test_server()->GetURL("/errorpage/empty404.html");
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(browser(), url, 2);
+ // This depends on the non-internationalized error ID string in
+ // localized_error.cc.
+ ExpectDisplayingNavigationCorrections(
+ browser(), "HTTP ERROR 404");
+ EXPECT_EQ(1, num_requests());
+}
+
+// Checks that a local error page is shown in response to a 500 error page
+// without a body.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Empty500) {
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/errorpage/empty500.html"));
+ // This depends on the non-internationalized error ID string in
+ // localized_error.cc.
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), "HTTP ERROR 500");
+ EXPECT_EQ(0, num_requests());
+}
+
+// Check that the easter egg is present and initialised and is not disabled.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, CheckEasterEggIsNotDisabled) {
+ ui_test_utils::NavigateToURL(browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_INTERNET_DISCONNECTED));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ // Check for no disabled message container.
+ std::string command = base::StringPrintf(
+ "var hasDisableContainer = document.querySelectorAll('.snackbar').length;"
+ "domAutomationController.send(hasDisableContainer);");
+ int32_t result;
+ EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+ web_contents, command, &result));
+ EXPECT_EQ(0, result);
+
+ // Presence of the canvas container.
+ command = base::StringPrintf(
+ "var runnerCanvas = document.querySelectorAll('.runner-canvas').length;"
+ "domAutomationController.send(runnerCanvas);");
+ EXPECT_TRUE(content::ExecuteScriptAndExtractInt(
+ web_contents, command, &result));
+ EXPECT_EQ(1, result);
+}
+
+// Test error page in incognito mode. The two major things are that navigation
+// corrections are not fetched (Only one navigation, display local error page),
+// and that no network diagnostic link is included, except on ChromeOS.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Incognito) {
+ Browser* incognito_browser = CreateIncognitoBrowser();
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ incognito_browser,
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED), 1);
+
+ // Verify that the expected error page is being displayed.
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ incognito_browser, net::ERR_NAME_NOT_RESOLVED);
+
+#if !defined(OS_CHROMEOS)
+ // Can't currently show the diagnostics in incognito on any platform but
+ // ChromeOS.
+ EXPECT_FALSE(WebContentsCanShowDiagnosticsTool(
+ incognito_browser->tab_strip_model()->GetActiveWebContents()));
+#endif
+
+ // Diagnostics button should be displayed, if available.
+ EXPECT_EQ(WebContentsCanShowDiagnosticsTool(
+ incognito_browser->tab_strip_model()->GetActiveWebContents()),
+ IsDisplayingDiagnosticsLink(incognito_browser));
+}
+
+class ErrorPageAutoReloadTest : public InProcessBrowserTest {
+ public:
+ void SetUpCommandLine(base::CommandLine* command_line) override {
+ command_line->AppendSwitch(switches::kEnableAutoReload);
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ void InstallInterceptor(const GURL& url, int32_t requests_to_fail) {
+ requests_ = failures_ = 0;
+
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](int32_t requests_to_fail, int32_t* requests, int32_t* failures,
+ content::URLLoaderInterceptor::RequestParams* params) {
+ if (params->url_request.url.path() == "/searchdomaincheck")
+ return false;
+ if (params->url_request.url.path() == "/favicon.ico")
+ return false;
+ if (params->url_request.url.GetOrigin() ==
+ GaiaUrls::GetInstance()->gaia_url())
+ return false;
+ (*requests)++;
+ if (*failures < requests_to_fail) {
+ (*failures)++;
+ network::URLLoaderCompletionStatus status;
+ status.error_code = net::ERR_CONNECTION_RESET;
+ params->client->OnComplete(status);
+ return true;
+ }
+
+ std::string body = URLRequestTestJob::test_data_1();
+ content::URLLoaderInterceptor::WriteResponse(
+ URLRequestTestJob::test_headers(), body,
+ params->client.get());
+ return true;
+ },
+ requests_to_fail, &requests_, &failures_));
+ }
+
+ void NavigateToURLAndWaitForTitle(const GURL& url,
+ const std::string& expected_title,
+ int32_t num_navigations) {
+ content::TitleWatcher title_watcher(
+ browser()->tab_strip_model()->GetActiveWebContents(),
+ base::ASCIIToUTF16(expected_title));
+
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), url, num_navigations);
+
+ EXPECT_EQ(base::ASCIIToUTF16(expected_title),
+ title_watcher.WaitAndGetTitle());
+ }
+
+ int32_t interceptor_requests() const { return requests_; }
+ int32_t interceptor_failures() const { return failures_; }
+
+ private:
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+ int32_t requests_;
+ int32_t failures_;
+};
+
+// Fails on official mac_trunk build. See crbug.com/465789.
+#if defined(OFFICIAL_BUILD) && defined(OS_MACOSX)
+#define MAYBE_AutoReload DISABLED_AutoReload
+#else
+#define MAYBE_AutoReload AutoReload
+#endif
+IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest, MAYBE_AutoReload) {
+ GURL test_url("http://error.page.auto.reload");
+ const int32_t kRequestsToFail = 2;
+ InstallInterceptor(test_url, kRequestsToFail);
+ NavigateToURLAndWaitForTitle(test_url, "Test One", kRequestsToFail + 1);
+ // Note that the interceptor updates these variables on the IO thread,
+ // but this function reads them on the main thread. The requests have to be
+ // created (on the IO thread) before NavigateToURLAndWaitForTitle returns or
+ // this becomes racey.
+ EXPECT_EQ(kRequestsToFail, interceptor_failures());
+ EXPECT_EQ(kRequestsToFail + 1, interceptor_requests());
+}
+
+IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest, ManualReloadNotSuppressed) {
+ GURL test_url("http://error.page.auto.reload");
+ const int32_t kRequestsToFail = 3;
+ InstallInterceptor(test_url, kRequestsToFail);
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), test_url, 2);
+
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(2, interceptor_requests());
+
+ ToggleHelpBox(browser());
+ EXPECT_TRUE(IsDisplayingText(
+ browser(), l10n_util::GetStringUTF8(
+ IDS_ERRORPAGES_SUGGESTION_CHECK_CONNECTION_HEADER)));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::TestNavigationObserver nav_observer(web_contents, 1);
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.getElementById('reload-button').click();"),
+ base::NullCallback());
+ nav_observer.Wait();
+ EXPECT_FALSE(IsDisplayingText(
+ browser(), l10n_util::GetStringUTF8(
+ IDS_ERRORPAGES_SUGGESTION_CHECK_CONNECTION_HEADER)));
+}
+
+// Make sure that a same document navigation does not cause issues with the
+// auto-reload timer. Note that this test was added due to this case causing
+// a crash. On regression, this test may hang due to a crashed renderer.
+IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest, IgnoresSameDocumentNavigation) {
+ GURL test_url("http://error.page.auto.reload");
+ InstallInterceptor(test_url, 2);
+
+ // Wait for the error page and first autoreload, which happens immediately.
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(), test_url, 2);
+
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(2, interceptor_requests());
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ web_contents->GetMainFrame()->ExecuteJavaScriptForTests(
+ base::ASCIIToUTF16("document.location='#';"), base::NullCallback());
+ content::WaitForLoadStop(web_contents);
+
+ // Same-document navigation on an error page should not have resulted in a
+ // new navigation, so no new requests should have been issued.
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(2, interceptor_requests());
+
+ // Wait for the second auto reload, which succeeds.
+ content::TestNavigationObserver observer2(web_contents, 1);
+ observer2.Wait();
+
+ EXPECT_EQ(2, interceptor_failures());
+ EXPECT_EQ(3, interceptor_requests());
+}
+
+// A test fixture that returns ERR_ADDRESS_UNREACHABLE for all navigation
+// correction requests. ERR_NAME_NOT_RESOLVED is more typical, but need to use
+// a different error for the correction service and the original page to
+// validate the right page is being displayed.
+class ErrorPageNavigationCorrectionsFailTest : public ErrorPageTest {
+ public:
+ // InProcessBrowserTest:
+ void SetUpOnMainThread() override {
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](content::URLLoaderInterceptor::RequestParams* params) {
+ if (params->url_request.url != google_util::LinkDoctorBaseURL())
+ return false;
+
+ network::URLLoaderCompletionStatus status;
+ status.error_code = net::ERR_ADDRESS_UNREACHABLE;
+ params->client->OnComplete(status);
+ return true;
+ }));
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ private:
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+// Make sure that when corrections fail to load, the network error page is
+// successfully loaded and shows a link to the diagnostics too, if appropriate.
+IN_PROC_BROWSER_TEST_F(ErrorPageNavigationCorrectionsFailTest,
+ FetchCorrectionsFails) {
+ ASSERT_TRUE(embedded_test_server()->Start());
+ ui_test_utils::NavigateToURLBlockUntilNavigationsComplete(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED),
+ 2);
+
+ // Verify that the expected error page is being displayed.
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_NAME_NOT_RESOLVED);
+
+ // Diagnostics button should be displayed, if available.
+ EXPECT_EQ(WebContentsCanShowDiagnosticsTool(
+ browser()->tab_strip_model()->GetActiveWebContents()),
+ IsDisplayingDiagnosticsLink(browser()));
+}
+
+class ErrorPageOfflineTest : public ErrorPageTest {
+ void SetUpOnMainThread() override {
+ url_loader_interceptor_ =
+ std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
+ [](content::URLLoaderInterceptor::RequestParams* params) {
+ return false;
+ }));
+ }
+
+ void TearDownOnMainThread() override { url_loader_interceptor_.reset(); }
+
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+#if defined(OS_CHROMEOS)
+ if (enroll_) {
+ // Set up fake install attributes.
+ test_install_attributes_ =
+ std::make_unique<chromeos::ScopedStubInstallAttributes>(
+ chromeos::StubInstallAttributes::CreateCloudManaged("example.com",
+ "fake-id"));
+ }
+#endif
+
+ // Sets up a mock policy provider for user and device policies.
+ EXPECT_CALL(policy_provider_, IsInitializationComplete(testing::_))
+ .WillRepeatedly(testing::Return(true));
+
+ policy::PolicyMap policy_map;
+#if defined(OS_CHROMEOS)
+ if (enroll_)
+ SetEnterpriseUsersDefaults(&policy_map);
+#endif
+ if (set_allow_dinosaur_easter_egg_) {
+ policy_map.Set(
+ policy::key::kAllowDinosaurEasterEgg, policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER, policy::POLICY_SOURCE_CLOUD,
+ std::make_unique<base::Value>(value_of_allow_dinosaur_easter_egg_),
+ nullptr);
+ }
+ policy_provider_.UpdateChromePolicy(policy_map);
+
+#if defined(OS_CHROMEOS)
+ policy::BrowserPolicyConnector::SetPolicyProviderForTesting(
+ &policy_provider_);
+#else
+ policy::PushProfilePolicyConnectorProviderForTesting(&policy_provider_);
+#endif
+
+ ErrorPageTest::SetUpInProcessBrowserTestFixture();
+ }
+
+ std::string NavigateToPageAndReadText() {
+#if defined(OS_CHROMEOS)
+ // Check enterprise enrollment
+ policy::BrowserPolicyConnectorChromeOS* connector =
+ g_browser_process->platform_part()
+ ->browser_policy_connector_chromeos();
+ EXPECT_EQ(enroll_, connector->IsEnterpriseManaged());
+#endif
+
+ ui_test_utils::NavigateToURL(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrl(net::ERR_INTERNET_DISCONNECTED));
+
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+
+ std::string command = base::StringPrintf(
+ "var hasText = document.querySelector('.snackbar');"
+ "domAutomationController.send(hasText ? hasText.innerText : '');");
+
+ std::string result;
+ EXPECT_TRUE(
+ content::ExecuteScriptAndExtractString(web_contents, command, &result));
+
+ return result;
+ }
+
+ // Whether to set AllowDinosaurEasterEgg policy
+ bool set_allow_dinosaur_easter_egg_ = false;
+
+ // The value of AllowDinosaurEasterEgg policy we want to set
+ bool value_of_allow_dinosaur_easter_egg_;
+
+#if defined(OS_CHROMEOS)
+ // Whether to enroll this CrOS device
+ bool enroll_ = true;
+
+ std::unique_ptr<chromeos::ScopedStubInstallAttributes>
+ test_install_attributes_;
+#endif
+
+ // Mock policy provider for both user and device policies.
+ policy::MockConfigurationPolicyProvider policy_provider_;
+ std::unique_ptr<content::URLLoaderInterceptor> url_loader_interceptor_;
+};
+
+class ErrorPageOfflineTestWithAllowDinosaurTrue : public ErrorPageOfflineTest {
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ set_allow_dinosaur_easter_egg_ = true;
+ value_of_allow_dinosaur_easter_egg_ = true;
+ ErrorPageOfflineTest::SetUpInProcessBrowserTestFixture();
+ }
+};
+
+class ErrorPageOfflineTestWithAllowDinosaurFalse : public ErrorPageOfflineTest {
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ set_allow_dinosaur_easter_egg_ = true;
+ value_of_allow_dinosaur_easter_egg_ = false;
+ ErrorPageOfflineTest::SetUpInProcessBrowserTestFixture();
+ }
+};
+
+#if defined(OS_CHROMEOS)
+class ErrorPageOfflineTestUnEnrolledChromeOS : public ErrorPageOfflineTest {
+ protected:
+ void SetUpInProcessBrowserTestFixture() override {
+ set_allow_dinosaur_easter_egg_ = false;
+ enroll_ = false;
+ ErrorPageOfflineTest::SetUpInProcessBrowserTestFixture();
+ }
+};
+#endif
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurTrue,
+ CheckEasterEggIsAllowed) {
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+}
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurFalse,
+ CheckEasterEggIsDisabled) {
+ std::string result = NavigateToPageAndReadText();
+ std::string disabled_text =
+ l10n_util::GetStringUTF8(IDS_ERRORPAGE_FUN_DISABLED);
+ EXPECT_EQ(disabled_text, result);
+}
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTest, CheckEasterEggIsDisabled) {
+ std::string result = NavigateToPageAndReadText();
+ std::string disabled_text =
+ l10n_util::GetStringUTF8(IDS_ERRORPAGE_FUN_DISABLED);
+ EXPECT_EQ(disabled_text, result);
+}
+#else
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTest, CheckEasterEggIsAllowed) {
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+}
+#endif
+
+#if defined(OS_CHROMEOS)
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestUnEnrolledChromeOS,
+ CheckEasterEggIsAllowed) {
+ std::string result = NavigateToPageAndReadText();
+ std::string disabled_text =
+ l10n_util::GetStringUTF8(IDS_ERRORPAGE_FUN_DISABLED);
+ EXPECT_EQ("", result);
+}
+#endif
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurTrue,
+ CheckEasterEggHighScoreLoaded) {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::BrowserContext* browser_context = web_contents->GetBrowserContext();
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+
+ IntegerPrefMember easter_egg_high_score;
+ easter_egg_high_score.Init(prefs::kNetworkEasterEggHighScore,
+ profile->GetPrefs());
+
+ // Set a high score in the user's profile.
+ int high_score = 1000;
+ easter_egg_high_score.SetValue(high_score);
+
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+
+ content::EvalJsResult actual_high_score = content::EvalJs(
+ web_contents,
+ "new Promise((resolve) => {"
+ " window.initializeEasterEggHighScore = function(highscore) { "
+ " resolve(highscore);"
+ " };"
+ " /* Request the initial highscore from the browser. */"
+ " errorPageController.trackEasterEgg();"
+ "});");
+
+ EXPECT_EQ(high_score, actual_high_score);
+}
+
+IN_PROC_BROWSER_TEST_F(ErrorPageOfflineTestWithAllowDinosaurTrue,
+ CheckEasterEggHighScoreSaved) {
+ content::WebContents* web_contents =
+ browser()->tab_strip_model()->GetActiveWebContents();
+ content::BrowserContext* browser_context = web_contents->GetBrowserContext();
+ Profile* profile = Profile::FromBrowserContext(browser_context);
+
+ IntegerPrefMember easter_egg_high_score;
+ easter_egg_high_score.Init(prefs::kNetworkEasterEggHighScore,
+ profile->GetPrefs());
+
+ // The high score should be initialized to 0.
+ EXPECT_EQ(0, easter_egg_high_score.GetValue());
+
+ std::string result = NavigateToPageAndReadText();
+ EXPECT_EQ("", result);
+
+ {
+ base::RunLoop run_loop;
+ PrefChangeRegistrar change_observer;
+ change_observer.Init(profile->GetPrefs());
+ change_observer.Add(prefs::kNetworkEasterEggHighScore,
+ run_loop.QuitClosure());
+
+ // Save a new high score.
+ EXPECT_TRUE(content::ExecJs(
+ web_contents, "errorPageController.updateEasterEggHighScore(2000);"));
+
+ // Wait for preference change.
+ run_loop.Run();
+ EXPECT_EQ(2000, easter_egg_high_score.GetValue());
+ }
+
+ {
+ base::RunLoop run_loop;
+ PrefChangeRegistrar change_observer;
+ change_observer.Init(profile->GetPrefs());
+ change_observer.Add(prefs::kNetworkEasterEggHighScore,
+ run_loop.QuitClosure());
+
+ // Reset high score back to 0.
+ EXPECT_TRUE(content::ExecJs(
+ web_contents, "errorPageController.resetEasterEggHighScore();"));
+
+ // Wait for preference change.
+ run_loop.Run();
+ EXPECT_EQ(0, easter_egg_high_score.GetValue());
+ }
+}
+
+// A test fixture that simulates failing requests for an IDN domain name.
+class ErrorPageForIDNTest : public InProcessBrowserTest {
+ public:
+ // Target hostname in different forms.
+ static const char kHostname[];
+ static const char kHostnameJSUnicode[];
+
+ // InProcessBrowserTest:
+ void SetUpOnMainThread() override {
+ // Clear AcceptLanguages to force punycode decoding.
+ browser()->profile()->GetPrefs()->SetString(
+ language::prefs::kAcceptLanguages, std::string());
+ }
+};
+
+const char ErrorPageForIDNTest::kHostname[] =
+ "xn--d1abbgf6aiiy.xn--p1ai";
+const char ErrorPageForIDNTest::kHostnameJSUnicode[] =
+ "\\u043f\\u0440\\u0435\\u0437\\u0438\\u0434\\u0435\\u043d\\u0442."
+ "\\u0440\\u0444";
+
+// Make sure error page shows correct unicode for IDN.
+IN_PROC_BROWSER_TEST_F(ErrorPageForIDNTest, IDN) {
+ // ERR_UNSAFE_PORT will not trigger navigation corrections.
+ ui_test_utils::NavigateToURL(
+ browser(),
+ URLRequestFailedJob::GetMockHttpUrlForHostname(net::ERR_UNSAFE_PORT,
+ kHostname));
+ EXPECT_TRUE(IsDisplayingText(browser(), kHostnameJSUnicode));
+}
+
+// Make sure HTTP/0.9 is disabled on non-default ports by default.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Http09WeirdPort) {
+ ui_test_utils::NavigateToURL(
+ browser(), embedded_test_server()->GetURL("/echo-raw?spam"));
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_INVALID_HTTP_RESPONSE);
+}
+
+// Test that redirects to invalid URLs show an error. See
+// https://crbug.com/462272.
+IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, RedirectToInvalidURL) {
+ GURL url = embedded_test_server()->GetURL("/server-redirect?https://:");
+ ui_test_utils::NavigateToURL(browser(), url);
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_INVALID_REDIRECT);
+ // The error page should commit before the redirect, not after.
+ EXPECT_EQ(url, browser()
+ ->tab_strip_model()
+ ->GetActiveWebContents()
+ ->GetLastCommittedURL());
+}
+
+// Checks that when an HTTP error page is sniffed as a download, an error page
+// is displayed. This tests the particular case in which the response body
+// is small enough that the entire response must be read before its MIME type
+// can be determined.
+class ErrorPageSniffTest : public InProcessBrowserTest {};
+
+IN_PROC_BROWSER_TEST_F(ErrorPageSniffTest,
+ SniffSmallHttpErrorResponseAsDownload) {
+ const char kErrorPath[] = "/foo";
+ embedded_test_server()->RegisterRequestHandler(base::BindRepeating(
+ &DNSErrorPageTest::Return500WithBinaryBody, kErrorPath));
+ ASSERT_TRUE(embedded_test_server()->Start());
+
+ ui_test_utils::NavigateToURL(browser(),
+ embedded_test_server()->GetURL(kErrorPath));
+
+ ExpectDisplayingLocalErrorPage(
+ embedded_test_server()->GetURL("mock.http", "/title2.html").spec(),
+ browser(), net::ERR_INVALID_RESPONSE);
+}
+
+} // namespace