blob: b97f9b3ff55089539b247f0757e3f0ffd3cacff4 [file] [log] [blame]
// Copyright 2012 The Chromium Authors
// 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/check_op.h"
#include "base/command_line.h"
#include "base/feature_list.h"
#include "base/files/scoped_temp_dir.h"
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.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/threading/thread_restrictions.h"
#include "base/values.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process_platform_part.h"
#include "chrome/browser/browsing_data/chrome_browsing_data_remover_delegate.h"
#include "chrome/browser/net/net_error_diagnostics_dialog.h"
#include "chrome/browser/policy/profile_policy_connector_builder.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/browser_finder.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/browsing_data/content/browsing_data_helper.h"
#include "components/embedder_support/switches.h"
#include "components/error_page/content/browser/net_error_auto_reloader.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/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.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 "net/base/filename_util.h"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.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_test_job.h"
#include "services/network/public/cpp/features.h"
#include "ui/base/l10n/l10n_util.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "ash/constants/ash_features.h"
#include "chrome/browser/ash/system_web_apps/test_support/system_web_app_browsertest_base.h" // nogncheck
#include "chrome/browser/ui/ash/system_web_apps/system_web_app_ui_utils.h" // nogncheck
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
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.
[[nodiscard]] bool IsDisplayingText(content::RenderFrameHost* render_frame_host,
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;
isNodeVisible(node);
)", text.c_str());
// clang-format on
return content::EvalJs(render_frame_host, command).ExtractBool();
}
[[nodiscard]] bool IsDisplayingText(Browser* browser, const std::string& text) {
return IsDisplayingText(
browser->tab_strip_model()->GetActiveWebContents()->GetPrimaryMainFrame(),
text);
}
// Expands the more box on the currently displayed error page.
void ToggleHelpBox(Browser* browser) {
EXPECT_TRUE(
content::ExecJs(browser->tab_strip_model()->GetActiveWebContents(),
"document.getElementById('details-button').click();"));
}
// Returns true if the diagnostics link suggestion is displayed.
[[nodiscard]] bool IsDisplayingDiagnosticsLink(Browser* browser) {
std::string command = base::StringPrintf(
"var diagnose_link = document.getElementById('diagnose-link');"
"diagnose_link != null;");
return content::EvalJs(browser->tab_strip_model()->GetActiveWebContents(),
command)
.ExtractBool();
}
// Checks that the error page is being displayed with the specified error
// string.
void ExpectDisplayingErrorPage(Browser* browser,
const std::string& error_string) {
EXPECT_TRUE(IsDisplayingText(browser, error_string));
}
// Checks that the error page is being displayed with the specified error code.
void ExpectDisplayingErrorPage(Browser* browser, net::Error error_code) {
ExpectDisplayingErrorPage(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) {
return CanShowNetworkDiagnosticsDialog(web_contents);
}
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);
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
}
// Navigates to the given URL and waits for the title to change to
// |expected_title|.
void NavigateToURLAndWaitForTitle(const GURL& url,
const std::string& expected_title) {
content::TitleWatcher title_watcher(
browser()->tab_strip_model()->GetActiveWebContents(),
base::ASCIIToUTF16(expected_title));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ(base::ASCIIToUTF16(expected_title),
title_watcher.WaitAndGetTitle());
}
// Navigates back in the history and waits for the title to change to
// |expected_title|.
void GoBackAndWaitForTitle(const std::string& expected_title) {
NavigateHistoryAndWaitForTitle(expected_title,
HISTORY_NAVIGATE_BACK);
}
// Navigates forward in the history and waits for the title to change to
// |expected_title|.
void GoForwardAndWaitForTitle(const std::string& expected_title) {
NavigateHistoryAndWaitForTitle(expected_title,
HISTORY_NAVIGATE_FORWARD);
}
void GoBackAndWaitForNavigations() { NavigateHistory(HISTORY_NAVIGATE_BACK); }
void GoForwardAndWaitForNavigations() {
NavigateHistory(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,
HistoryNavigationDirection direction) {
content::TitleWatcher title_watcher(
browser()->tab_strip_model()->GetActiveWebContents(),
base::ASCIIToUTF16(expected_title));
NavigateHistory(direction);
EXPECT_EQ(title_watcher.WaitAndGetTitle(),
base::ASCIIToUTF16(expected_title));
}
void NavigateHistory(HistoryNavigationDirection direction) {
content::TestNavigationObserver test_navigation_observer(
browser()->tab_strip_model()->GetActiveWebContents(), 1);
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(const TestFailProvisionalLoadObserver&) =
delete;
TestFailProvisionalLoadObserver& operator=(
const TestFailProvisionalLoadObserver&) = delete;
~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_;
};
class DNSErrorPageTest : public ErrorPageTest {
public:
DNSErrorPageTest() {
// Inject a URLLoaderInterceptor. While the injected callback doesn't
// intercept any URLs itself, URLLoaderInterceptor automatically intercepts
// URLRequestFailedJob URLs, which these tests used to simulate network
// errors.
url_loader_interceptor_ =
std::make_unique<content::URLLoaderInterceptor>(base::BindRepeating(
[](content::URLLoaderInterceptor::RequestParams* params) {
return false;
}));
}
~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());
}
// Returns a GURL that results in a DNS error.
GURL GetDnsErrorURL() const {
return URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED);
}
private:
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"));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), non_existent_file_url));
ExpectDisplayingErrorPage(browser(), net::ERR_FILE_NOT_FOUND);
// Only errors on HTTP/HTTPS pages should display a diagnostics button.
EXPECT_FALSE(IsDisplayingDiagnosticsLink(browser()));
}
// Test that a DNS error occurring in the main frame displays an error page.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_Basic) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetDnsErrorURL()));
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
// Diagnostics button should be displayed, if available.
EXPECT_EQ(WebContentsCanShowDiagnosticsTool(
browser()->tab_strip_model()->GetActiveWebContents()),
IsDisplayingDiagnosticsLink(browser()));
}
// Test that a DNS error occurring in the main frame does not result in an
// additional session history entry.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack1) {
NavigateToFileURL("/title2.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetDnsErrorURL()));
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
GoBackAndWaitForTitle("Title Of Awesomeness");
}
// Test that a DNS error occurring in the main frame does not result in an
// additional session history entry.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2) {
NavigateToFileURL("/title2.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetDnsErrorURL()));
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
NavigateToFileURL("/title3.html");
GoBackAndWaitForNavigations();
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
}
// Test that a DNS error occurring in the main frame does not result in an
// additional session history entry.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2AndForward) {
NavigateToFileURL("/title2.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetDnsErrorURL()));
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
NavigateToFileURL("/title3.html");
GoBackAndWaitForNavigations();
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
GoBackAndWaitForTitle("Title Of Awesomeness");
GoForwardAndWaitForNavigations();
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
}
// Test that a DNS error occurring in the main frame does not result in an
// additional session history entry.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, DNSError_GoBack2Forward2) {
NavigateToFileURL("/title3.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetDnsErrorURL()));
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
NavigateToFileURL("/title2.html");
GoBackAndWaitForNavigations();
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
GoBackAndWaitForTitle("Title Of More Awesomeness");
GoForwardAndWaitForNavigations();
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
GoForwardAndWaitForTitle("Title Of Awesomeness");
}
// 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();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetDnsErrorURL()));
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Clicking the reload button should load the error page again.
content::TestNavigationObserver nav_observer(web_contents, 1);
// Can't use content::ExecJs 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->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
u"document.getElementById('reload-button').click();",
base::NullCallback());
nav_observer.Wait();
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
}
// 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) {
std::string url =
embedded_test_server()->GetURL("mock.http", "/title2.html").spec();
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), GetDnsErrorURL()));
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
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->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
u"document.location='#';", base::NullCallback());
content::WaitForLoadStop(web_contents);
// Page being displayed should not change.
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
// Clicking the reload button should load the error page again.
content::TestNavigationObserver nav_observer2(web_contents);
// Can't use content::ExecJs 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->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
u"document.getElementById('reload-button').click();",
base::NullCallback());
nav_observer2.Wait();
ExpectDisplayingErrorPage(browser(), net::ERR_NAME_NOT_RESOLVED);
}
// Test a DNS error occurring in an iframe.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, IFrameDNSError) {
NavigateToURLAndWaitForTitle(
embedded_test_server()->GetURL("/iframe_dns_error.html"), "Blah");
// There should be a child iframe with a DNS error.
content::RenderFrameHost* child_frame =
ChildFrameAt(browser()->tab_strip_model()->GetActiveWebContents(), 0);
ASSERT_TRUE(child_frame);
EXPECT_TRUE(IsDisplayingText(
child_frame, net::ErrorToShortString(net::ERR_NAME_NOT_RESOLVED)));
}
// This test fails regularly on win_rel trybots. See crbug.com/121540
#if BUILDFLAG(IS_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) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/title2.html")));
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/iframe_dns_error.html")));
GoBackAndWaitForTitle("Title Of Awesomeness");
}
// This test fails regularly on win_rel trybots. See crbug.com/121540
//
// This fails on linux_aura bringup: http://crbug.com/163931
#if BUILDFLAG(IS_WIN) || \
((BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS_LACROS)) && \
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");
GoForwardAndWaitForTitle("Blah");
}
// 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.
ASSERT_TRUE(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::LoadStopObserver load_observer(wc);
wc->GetPrimaryMainFrame()->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::LoadStopObserver load_observer(wc);
wc->GetPrimaryMainFrame()->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::LoadStopObserver load_observer(wc);
wc->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
base::ASCIIToUTF16(script), base::NullCallback());
load_observer.Wait();
EXPECT_EQ(fail_url, fail_observer.fail_url());
EXPECT_EQ(2, wc->GetController().GetEntryCount());
}
}
// Checks that the error page is not displayed when receiving an actual 404
// page.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Page404) {
NavigateToURLAndWaitForTitle(embedded_test_server()->GetURL("/page404.html"),
"SUCCESS");
// This depends on the non-internationalized error ID string in
// localized_error.cc.
EXPECT_FALSE(IsDisplayingText(browser(), "HTTP ERROR 404"));
}
// Checks that a local error page is shown in response to a 404 error page
// without a body.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Empty404) {
GURL url = embedded_test_server()->GetURL("/errorpage/empty404.html");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
// This depends on the non-internationalized error ID string in
// localized_error.cc.
ExpectDisplayingErrorPage(browser(), "HTTP ERROR 404");
}
// Check that the easter egg is present and initialised.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, CheckEasterEgg) {
ASSERT_TRUE(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;"
"hasDisableContainer;");
EXPECT_EQ(0, content::EvalJs(web_contents, command));
// Presence of the canvas container.
command = base::StringPrintf(
"var runnerCanvas = document.querySelectorAll('.runner-canvas').length;"
"runnerCanvas;");
EXPECT_EQ(1, content::EvalJs(web_contents, command));
}
// Test error page in incognito mode. The only difference is that no network
// diagnostic link is included, except on ChromeOS.
IN_PROC_BROWSER_TEST_F(DNSErrorPageTest, Incognito) {
Browser* incognito_browser = CreateIncognitoBrowser();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
incognito_browser,
URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED)));
// Verify that the expected error page is being displayed.
ExpectDisplayingErrorPage(incognito_browser, net::ERR_NAME_NOT_RESOLVED);
#if !BUILDFLAG(IS_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(embedder_support::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.host().find("googleapis.com") !=
std::string::npos) {
return false;
}
if (params->url_request.url.path() == "/searchdomaincheck")
return false;
if (params->url_request.url.path() == "/favicon.ico")
return false;
if (params->url_request.url.DeprecatedGetOriginAsURL() ==
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) {
content::TitleWatcher title_watcher(
browser()->tab_strip_model()->GetActiveWebContents(),
base::ASCIIToUTF16(expected_title));
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
EXPECT_EQ(base::ASCIIToUTF16(expected_title),
title_watcher.WaitAndGetTitle());
}
void NavigateAndWaitForFailureWithAutoReload(const GURL& url) {
content::WebContents* const web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
// Expect the first navigation to fail with a committed error page.
content::TestNavigationManager first_navigation(web_contents, url);
web_contents->GetController().LoadURL(url, content::Referrer(),
ui::PAGE_TRANSITION_TYPED,
/*extra_headers=*/std::string());
ASSERT_TRUE(first_navigation.WaitForNavigationFinished());
EXPECT_TRUE(first_navigation.was_committed());
EXPECT_FALSE(first_navigation.was_successful());
// Expect a second navigation to result from a failed auto-reload attempt.
// This should not be committed.
content::TestNavigationManager failed_auto_reload_navigation(web_contents,
url);
ASSERT_TRUE(failed_auto_reload_navigation.WaitForNavigationFinished());
EXPECT_FALSE(failed_auto_reload_navigation.was_committed());
}
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) && BUILDFLAG(IS_MAC)
#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");
// 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());
}
// TODO(crbug.com/40856405): Test is flaky.
IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest,
DISABLED_ManualReloadNotSuppressed) {
GURL test_url("http://error.page.auto.reload");
const int32_t kRequestsToFail = 3;
InstallInterceptor(test_url, kRequestsToFail);
// Wait for the error page and first autoreload.
NavigateAndWaitForFailureWithAutoReload(test_url);
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->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
u"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.
// TODO(crbug.com/40709227): Flaky.
IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest,
DISABLED_IgnoresSameDocumentNavigation) {
GURL test_url("http://error.page.auto.reload");
InstallInterceptor(test_url, 2);
// Wait for the error page and first autoreload.
NavigateAndWaitForFailureWithAutoReload(test_url);
EXPECT_EQ(2, interceptor_failures());
EXPECT_EQ(2, interceptor_requests());
content::WebContents* web_contents =
browser()->tab_strip_model()->GetActiveWebContents();
const std::u16string kExpectedTitle = u"Test One";
content::TitleWatcher title_watcher(web_contents, kExpectedTitle);
// Same-document navigation on an error page should not interrupt the
// scheduled auto-reload which should still be pending on the WebContents.
web_contents->GetPrimaryMainFrame()->ExecuteJavaScriptForTests(
u"document.location='#';", base::NullCallback());
// Wait for the second auto reload to happen. It will succeed and update the
// WebContents' title.
EXPECT_EQ(kExpectedTitle, title_watcher.WaitAndGetTitle());
EXPECT_EQ(2, interceptor_failures());
EXPECT_EQ(3, interceptor_requests());
}
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 {
// Sets up a mock policy provider for user and device policies.
policy_provider_.SetDefaultReturns(
/*is_initialization_complete_return=*/true,
/*is_first_policy_load_complete_return=*/true);
policy::PolicyMap policy_map;
if (set_allow_dinosaur_easter_egg_) {
policy_map.Set(policy::key::kAllowDinosaurEasterEgg,
policy::POLICY_LEVEL_MANDATORY, policy::POLICY_SCOPE_USER,
policy::POLICY_SOURCE_CLOUD,
base::Value(value_of_allow_dinosaur_easter_egg_), nullptr);
}
#if BUILDFLAG(IS_CHROMEOS)
SetEnterpriseUsersProfileDefaults(&policy_map);
#endif
policy_provider_.UpdateChromePolicy(policy_map);
policy::PushProfilePolicyConnectorProviderForTesting(&policy_provider_);
ErrorPageTest::SetUpInProcessBrowserTestFixture();
}
std::string NavigateToPageAndReadText() {
EXPECT_TRUE(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');"
"hasText ? hasText.innerText : '';");
return content::EvalJs(web_contents, command).ExtractString();
}
// 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_;
testing::NiceMock<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();
}
};
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 BUILDFLAG(IS_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
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) {
ASSERT_TRUE(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) {
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL("/echo-raw?spam")));
ExpectDisplayingErrorPage(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://:");
ASSERT_TRUE(ui_test_utils::NavigateToURL(browser(), url));
ExpectDisplayingErrorPage(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.
using ErrorPageSniffTest = 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());
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(), embedded_test_server()->GetURL(kErrorPath)));
ExpectDisplayingErrorPage(browser(), net::ERR_INVALID_RESPONSE);
}
#if BUILDFLAG(IS_CHROMEOS_ASH)
// For ChromeOS, launches appropriate diagnostics app.
void ClickDiagnosticsLink(Browser* browser) {
DCHECK(IsDisplayingDiagnosticsLink(browser));
EXPECT_TRUE(
content::ExecJs(browser->tab_strip_model()->GetActiveWebContents(),
"document.getElementById('diagnose-link').click();"));
}
// On ChromeOS "Running Connectivity Diagnostics" link on error page should
// launch chrome://diagnostics/?connectivity app by default. Not running test on
// LaCROS due to errors on Wayland initialization and to keep test to ChromeOS
// devices.
using ErrorPageOfflineAppLaunchTest = ash::SystemWebAppBrowserTestBase;
IN_PROC_BROWSER_TEST_F(ErrorPageOfflineAppLaunchTest, DiagnosticsConnectivity) {
WaitForTestSystemAppInstall();
ASSERT_TRUE(ui_test_utils::NavigateToURL(
browser(),
URLRequestFailedJob::GetMockHttpUrl(net::ERR_INTERNET_DISCONNECTED)));
const GURL expected_url = GURL("chrome://diagnostics/?connectivity");
content::TestNavigationObserver observer(expected_url);
observer.StartWatchingNewWebContents();
// Click to open diagnostics app.
ClickDiagnosticsLink(browser());
observer.Wait();
EXPECT_TRUE(observer.last_navigation_succeeded());
// The active screen should be Connectivity Diagnostics app.
content::WebContents* contents =
::chrome::FindLastActive()->tab_strip_model()->GetActiveWebContents();
EXPECT_EQ(expected_url, contents->GetVisibleURL());
}
#endif // BUILDFLAG(IS_CHROMEOS_ASH)
} // namespace