// 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 "base/bind.h" #include "base/command_line.h" #include "base/compiler_specific.h" #include "base/logging.h" #include "base/memory/scoped_ptr.h" #include "base/prefs/pref_service.h" #include "base/strings/stringprintf.h" #include "base/strings/utf_string_conversions.h" #include "chrome/browser/browsing_data/browsing_data_helper.h" #include "chrome/browser/browsing_data/browsing_data_remover.h" #include "chrome/browser/google/google_util.h" #include "chrome/browser/net/url_request_mock_util.h" #include "chrome/browser/profiles/profile.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_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 "content/public/browser/browser_thread.h" #include "content/public/browser/notification_service.h" #include "content/public/browser/render_frame_host.h" #include "content/public/browser/render_view_host.h" #include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents_observer.h" #include "content/public/test/browser_test_utils.h" #include "content/public/test/test_navigation_observer.h" #include "content/test/net/url_request_failed_job.h" #include "content/test/net/url_request_mock_http_job.h" #include "net/base/net_errors.h" #include "net/base/net_util.h" #include "net/http/failing_http_transaction_factory.h" #include "net/http/http_cache.h" #include "net/test/spawned_test_server/spawned_test_server.h" #include "net/url_request/url_request_context.h" #include "net/url_request/url_request_context_getter.h" #include "net/url_request/url_request_filter.h" #include "net/url_request/url_request_job.h" #include "net/url_request/url_request_job_factory.h" #include "net/url_request/url_request_test_job.h" #include "net/url_request/url_request_test_util.h" using content::BrowserThread; using content::NavigationController; using content::URLRequestFailedJob; using net::URLRequestJobFactory; using net::URLRequestTestJob; namespace { // A protocol handler that fails a configurable number of requests, then // succeeds all requests after that, keeping count of failures and successes. class FailFirstNRequestsProtocolHandler : public URLRequestJobFactory::ProtocolHandler { public: FailFirstNRequestsProtocolHandler(const GURL& url, int requests_to_fail) : url_(url), requests_(0), failures_(0), requests_to_fail_(requests_to_fail) {} virtual ~FailFirstNRequestsProtocolHandler() {} // This method deliberately violates pointer ownership rules: // AddUrlProtocolHandler() takes a scoped_ptr, taking ownership of the // supplied ProtocolHandler (i.e., |this|), but also having the caller retain // a pointer to |this| so the caller can use the requests() and failures() // accessors. void AddUrlHandler() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); scoped_ptr scoped_handler(this); net::URLRequestFilter::GetInstance()->AddUrlProtocolHandler( url_, scoped_handler.Pass()); } // net::URLRequestJobFactory::ProtocolHandler implementation virtual net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const OVERRIDE { DCHECK_EQ(url_, request->url()); requests_++; if (failures_ < requests_to_fail_) { failures_++; // Note: net::ERR_CONNECTION_RESET does not summon the Link Doctor; see // NetErrorHelperCore::GetErrorPageURL. return new URLRequestFailedJob(request, network_delegate, net::ERR_CONNECTION_RESET); } else { return new URLRequestTestJob(request, network_delegate, URLRequestTestJob::test_headers(), URLRequestTestJob::test_data_1(), true); } } int requests() const { return requests_; } int failures() const { return failures_; } private: const GURL url_; // These are mutable because MaybeCreateJob is const but we want this state // for testing. mutable int requests_; mutable int failures_; int requests_to_fail_; }; class ErrorPageTest : public InProcessBrowserTest { public: enum HistoryNavigationDirection { HISTORY_NAVIGATE_BACK, HISTORY_NAVIGATE_FORWARD, }; // Navigates the active tab to a mock url created for the file at |file_path|. void NavigateToFileURL(const base::FilePath::StringType& file_path) { ui_test_utils::NavigateToURL( browser(), content::URLRequestMockHTTPJob::GetMockUrl(base::FilePath(file_path))); } // 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, int 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, int 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, int num_navigations) { NavigateHistoryAndWaitForTitle(expected_title, num_navigations, HISTORY_NAVIGATE_FORWARD); } // Confirms that the javascript variable indicating whether or not we have // a stale copy in the cache has been set to |expected|. bool ProbeStaleCopyValue(bool expected) { const char* js_cache_probe = "try {\n" " domAutomationController.send(\n" " templateData.staleCopyInCache ? 'yes' : 'no');\n" "} catch (e) {\n" " domAutomationController.send(e.message);\n" "}\n"; std::string result; bool ret = content::ExecuteScriptAndExtractString( browser()->tab_strip_model()->GetActiveWebContents(), js_cache_probe, &result); EXPECT_TRUE(ret); if (!ret) return false; EXPECT_EQ(expected ? "yes" : "no", result); return ((expected ? "yes" : "no") == result); } testing::AssertionResult ReloadStaleCopyFromCache() { const char* js_reload_script = "try {\n" " errorCacheLoad.reloadStaleInstance();\n" " domAutomationController.send('success');\n" "} catch (e) {\n" " domAutomationController.send(e.message);\n" "}\n"; std::string result; bool ret = content::ExecuteScriptAndExtractString( browser()->tab_strip_model()->GetActiveWebContents(), js_reload_script, &result); EXPECT_TRUE(ret); if (!ret) return testing::AssertionFailure(); return ("success" == result ? testing::AssertionSuccess() : (testing::AssertionFailure() << "Exception message is " << result)); } protected: virtual void SetUpOnMainThread() OVERRIDE { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&chrome_browser_net::SetUrlRequestMocksEnabled, true)); } // Returns a GURL that results in a DNS error. GURL GetDnsErrorURL() const { return URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED); } private: // 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, int num_navigations, HistoryNavigationDirection direction) { content::TitleWatcher title_watcher( browser()->tab_strip_model()->GetActiveWebContents(), base::ASCIIToUTF16(expected_title)); content::TestNavigationObserver test_navigation_observer( browser()->tab_strip_model()->GetActiveWebContents(), num_navigations); if (direction == HISTORY_NAVIGATE_BACK) { chrome::GoBack(browser(), CURRENT_TAB); } else if (direction == HISTORY_NAVIGATE_FORWARD) { chrome::GoForward(browser(), CURRENT_TAB); } else { FAIL(); } test_navigation_observer.Wait(); EXPECT_EQ(title_watcher.WaitAndGetTitle(), base::ASCIIToUTF16(expected_title)); } }; class TestFailProvisionalLoadObserver : public content::WebContentsObserver { public: explicit TestFailProvisionalLoadObserver(content::WebContents* contents) : content::WebContentsObserver(contents) {} virtual ~TestFailProvisionalLoadObserver() {} // This method is invoked when the provisional load failed. virtual void DidFailProvisionalLoad( int64 frame_id, const base::string16& frame_unique_name, bool is_main_frame, const GURL& validated_url, int error_code, const base::string16& error_description, content::RenderViewHost* render_view_host) OVERRIDE { fail_url_ = validated_url; } const GURL& fail_url() const { return fail_url_; } private: GURL fail_url_; DISALLOW_COPY_AND_ASSIGN(TestFailProvisionalLoadObserver); }; void InterceptNetworkTransactions(net::URLRequestContextGetter* getter, net::Error error) { DCHECK(content::BrowserThread::CurrentlyOn(BrowserThread::IO)); net::HttpCache* cache( getter->GetURLRequestContext()->http_transaction_factory()->GetCache()); DCHECK(cache); scoped_ptr factory( new net::FailingHttpTransactionFactory(cache->GetSession(), error)); // Throw away old version; since this is a a browser test, we don't // need to restore the old state. cache->SetHttpNetworkTransactionFactoryForTesting(factory.Pass()); } // See crbug.com/109669 #if defined(USE_AURA) #define MAYBE_DNSError_Basic DISABLED_DNSError_Basic #else #define MAYBE_DNSError_Basic DNSError_Basic #endif // Test that a DNS error occuring in the main frame redirects to an error page. IN_PROC_BROWSER_TEST_F(ErrorPageTest, MAYBE_DNSError_Basic) { // The first navigation should fail, and the second one should be the error // page. NavigateToURLAndWaitForTitle(GetDnsErrorURL(), "Mock Link Doctor", 2); } // See crbug.com/109669 #if defined(USE_AURA) #define MAYBE_DNSError_GoBack1 DISABLED_DNSError_GoBack1 #else #define MAYBE_DNSError_GoBack1 DNSError_GoBack1 #endif // Test that a DNS error occuring in the main frame does not result in an // additional session history entry. IN_PROC_BROWSER_TEST_F(ErrorPageTest, MAYBE_DNSError_GoBack1) { NavigateToFileURL(FILE_PATH_LITERAL("title2.html")); NavigateToURLAndWaitForTitle(GetDnsErrorURL(), "Mock Link Doctor", 2); GoBackAndWaitForTitle("Title Of Awesomeness", 1); } // See crbug.com/109669 #if defined(USE_AURA) #define MAYBE_DNSError_GoBack2 DISABLED_DNSError_GoBack2 #else #define MAYBE_DNSError_GoBack2 DNSError_GoBack2 #endif // Test that a DNS error occuring in the main frame does not result in an // additional session history entry. IN_PROC_BROWSER_TEST_F(ErrorPageTest, DNSError_GoBack2) { NavigateToFileURL(FILE_PATH_LITERAL("title2.html")); NavigateToURLAndWaitForTitle(GetDnsErrorURL(), "Mock Link Doctor", 2); NavigateToFileURL(FILE_PATH_LITERAL("title3.html")); GoBackAndWaitForTitle("Mock Link Doctor", 2); GoBackAndWaitForTitle("Title Of Awesomeness", 1); } // See crbug.com/109669 #if defined(USE_AURA) #define MAYBE_DNSError_GoBack2AndForward DISABLED_DNSError_GoBack2AndForward #else #define MAYBE_DNSError_GoBack2AndForward DNSError_GoBack2AndForward #endif // Test that a DNS error occuring in the main frame does not result in an // additional session history entry. IN_PROC_BROWSER_TEST_F(ErrorPageTest, DNSError_GoBack2AndForward) { NavigateToFileURL(FILE_PATH_LITERAL("title2.html")); NavigateToURLAndWaitForTitle(GetDnsErrorURL(), "Mock Link Doctor", 2); NavigateToFileURL(FILE_PATH_LITERAL("title3.html")); GoBackAndWaitForTitle("Mock Link Doctor", 2); GoBackAndWaitForTitle("Title Of Awesomeness", 1); GoForwardAndWaitForTitle("Mock Link Doctor", 2); } // See crbug.com/109669 #if defined(USE_AURA) #define MAYBE_DNSError_GoBack2Forward2 DISABLED_DNSError_GoBack2Forward2 #else #define MAYBE_DNSError_GoBack2Forward2 DNSError_GoBack2Forward2 #endif // Test that a DNS error occuring in the main frame does not result in an // additional session history entry. IN_PROC_BROWSER_TEST_F(ErrorPageTest, DNSError_GoBack2Forward2) { NavigateToFileURL(FILE_PATH_LITERAL("title3.html")); NavigateToURLAndWaitForTitle(GetDnsErrorURL(), "Mock Link Doctor", 2); NavigateToFileURL(FILE_PATH_LITERAL("title2.html")); GoBackAndWaitForTitle("Mock Link Doctor", 2); GoBackAndWaitForTitle("Title Of More Awesomeness", 1); GoForwardAndWaitForTitle("Mock Link Doctor", 2); GoForwardAndWaitForTitle("Title Of Awesomeness", 1); } // Test that a DNS error occuring in an iframe. IN_PROC_BROWSER_TEST_F(ErrorPageTest, IFrameDNSError_Basic) { NavigateToURLAndWaitForTitle( content::URLRequestMockHTTPJob::GetMockUrl( base::FilePath(FILE_PATH_LITERAL("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()); } // 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(ErrorPageTest, MAYBE_IFrameDNSError_GoBack) { NavigateToFileURL(FILE_PATH_LITERAL("title2.html")); NavigateToFileURL(FILE_PATH_LITERAL("iframe_dns_error.html")); GoBackAndWaitForTitle("Title Of Awesomeness", 1); } // 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(ErrorPageTest, MAYBE_IFrameDNSError_GoBackAndForward) { NavigateToFileURL(FILE_PATH_LITERAL("title2.html")); NavigateToFileURL(FILE_PATH_LITERAL("iframe_dns_error.html")); GoBackAndWaitForTitle("Title Of Awesomeness", 1); GoForwardAndWaitForTitle("Blah", 1); } // 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(ErrorPageTest, 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. NavigateToFileURL(FILE_PATH_LITERAL("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(&wc->GetController())); wc->GetMainFrame()->ExecuteJavaScript(base::ASCIIToUTF16(script)); 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(&wc->GetController())); wc->GetMainFrame()->ExecuteJavaScript(base::ASCIIToUTF16(script)); 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(&wc->GetController())); wc->GetMainFrame()->ExecuteJavaScript(base::ASCIIToUTF16(script)); load_observer.Wait(); EXPECT_EQ(fail_url, fail_observer.fail_url()); EXPECT_EQ(2, wc->GetController().GetEntryCount()); } } // Checks that the Link Doctor is not loaded when we receive an actual 404 page. IN_PROC_BROWSER_TEST_F(ErrorPageTest, Page404) { NavigateToURLAndWaitForTitle( content::URLRequestMockHTTPJob::GetMockUrl( base::FilePath(FILE_PATH_LITERAL("page404.html"))), "SUCCESS", 1); } // Checks that when an error occurs, the stale cache status of the page // is correctly transferred, and that stale cached copied can be loaded // from the javascript. IN_PROC_BROWSER_TEST_F(ErrorPageTest, StaleCacheStatus) { ASSERT_TRUE(test_server()->Start()); // Load cache with entry with "nocache" set, to create stale // cache. GURL test_url(test_server()->GetURL("files/nocache.html")); NavigateToURLAndWaitForTitle(test_url, "Nocache Test Page", 1); // Reload same URL after forcing an error from the the network layer; // confirm that the error page is told the cached copy exists. scoped_refptr url_request_context_getter = browser()->profile()->GetRequestContext(); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&InterceptNetworkTransactions, url_request_context_getter, // Note that we can't use an error that'll invoke the link // doctor. In normal network error conditions that would // work (because the link doctor fetch would also fail, // putting us back in the main offline path), but // SetUrlRequestMocksEnabled() has also specfied a link // doctor mock, which will be accessible because it // won't go through the network cache. net::ERR_FAILED)); ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( // With no link doctor load, there's only one navigation. browser(), test_url, 1); EXPECT_TRUE(ProbeStaleCopyValue(true)); EXPECT_NE(base::ASCIIToUTF16("Nocache Test Page"), browser()->tab_strip_model()->GetActiveWebContents()->GetTitle()); // Confirm that loading the stale copy from the cache works. content::TestNavigationObserver same_tab_observer( browser()->tab_strip_model()->GetActiveWebContents(), 1); ASSERT_TRUE(ReloadStaleCopyFromCache()); same_tab_observer.Wait(); EXPECT_EQ(base::ASCIIToUTF16("Nocache Test Page"), browser()->tab_strip_model()->GetActiveWebContents()->GetTitle()); // Clear the cache and reload the same URL; confirm the error page is told // that there is no cached copy. BrowsingDataRemover* remover = BrowsingDataRemover::CreateForUnboundedRange(browser()->profile()); remover->Remove(BrowsingDataRemover::REMOVE_CACHE, BrowsingDataHelper::UNPROTECTED_WEB); ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( browser(), test_url, 1); EXPECT_TRUE(ProbeStaleCopyValue(false)); } class ErrorPageAutoReloadTest : public InProcessBrowserTest { public: virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { command_line->AppendSwitch(switches::kEnableOfflineAutoReload); } void InstallProtocolHandler(const GURL& url, int requests_to_fail) { protocol_handler_ = new FailFirstNRequestsProtocolHandler( url, requests_to_fail); // Tests don't need to wait for this task to complete before using the // filter; any requests that might be affected by it will end up in the IO // thread's message loop after this posted task anyway. BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ErrorPageAutoReloadTest::AddFilters, base::Unretained(this))); } void NavigateToURLAndWaitForTitle(const GURL& url, const std::string& expected_title, int 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()); } FailFirstNRequestsProtocolHandler* protocol_handler() { return protocol_handler_; } private: void AddFilters() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); // Note: in theory, AddUrlHandler gives ownership of |protocol_handler_| to // URLRequestFilter. As soon as anything calls // URLRequestFilter::ClearHandlers(), |protocol_handler_| can become // invalid. protocol_handler_->AddUrlHandler(); } FailFirstNRequestsProtocolHandler* protocol_handler_; }; IN_PROC_BROWSER_TEST_F(ErrorPageAutoReloadTest, AutoReload) { GURL test_url("http://error.page.auto.reload"); const int kRequestsToFail = 2; InstallProtocolHandler(test_url, kRequestsToFail); NavigateToURLAndWaitForTitle(test_url, "Test One", kRequestsToFail + 1); // Note that the protocol handler 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, protocol_handler()->failures()); EXPECT_EQ(kRequestsToFail + 1, protocol_handler()->requests()); } // Returns Javascript code that executes plain text search for the page. // Pass into content::ExecuteScriptAndExtractBool as |script| parameter. std::string GetTextContentContainsStringScript( const std::string& value_to_search) { return base::StringPrintf( "var textContent = document.body.textContent;" "var hasError = textContent.indexOf('%s') >= 0;" "domAutomationController.send(hasError);", value_to_search.c_str()); } // Protocol handler that fails all requests with net::ERR_ADDRESS_UNREACHABLE. class AddressUnreachableProtocolHandler : public net::URLRequestJobFactory::ProtocolHandler { public: AddressUnreachableProtocolHandler() {} virtual ~AddressUnreachableProtocolHandler() {} // net::URLRequestJobFactory::ProtocolHandler: virtual net::URLRequestJob* MaybeCreateJob( net::URLRequest* request, net::NetworkDelegate* network_delegate) const OVERRIDE { return new URLRequestFailedJob(request, network_delegate, net::ERR_ADDRESS_UNREACHABLE); } private: DISALLOW_COPY_AND_ASSIGN(AddressUnreachableProtocolHandler); }; // A test fixture that returns ERR_ADDRESS_UNREACHABLE for all Link Doctor // requests. ERR_NAME_NOT_RESOLVED is more typical, but need to use a different // error for the Link Doctor and the original page to validate the right page // is being displayed. class ErrorPageLinkDoctorFailTest : public ErrorPageTest { public: // InProcessBrowserTest: virtual void SetUpOnMainThread() OVERRIDE { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ErrorPageLinkDoctorFailTest::AddFilters)); } virtual void CleanUpOnMainThread() OVERRIDE { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ErrorPageLinkDoctorFailTest::RemoveFilters)); } private: // Adds a filter that causes all requests for the Link Doctor's scheme and // host to fail with ERR_ADDRESS_UNREACHABLE. Since the Link Doctor adds // query strings, it's not enough to just fail exact matches. // // Also adds the content::URLRequestFailedJob filter. static void AddFilters() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); content::URLRequestFailedJob::AddUrlHandler(); net::URLRequestFilter::GetInstance()->AddHostnameProtocolHandler( google_util::LinkDoctorBaseURL().scheme(), google_util::LinkDoctorBaseURL().host(), scoped_ptr( new AddressUnreachableProtocolHandler())); } static void RemoveFilters() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); net::URLRequestFilter::GetInstance()->ClearHandlers(); } }; // Make sure that when the Link Doctor fails to load, the network error page is // successfully loaded. IN_PROC_BROWSER_TEST_F(ErrorPageLinkDoctorFailTest, LinkDoctorFail) { ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( browser(), URLRequestFailedJob::GetMockHttpUrl(net::ERR_NAME_NOT_RESOLVED), 2); // Verify that the expected error page is being displayed. Do this by making // sure the original error code (ERR_NAME_NOT_RESOLVED) is displayed. bool result = false; EXPECT_TRUE(content::ExecuteScriptAndExtractBool( browser()->tab_strip_model()->GetActiveWebContents(), GetTextContentContainsStringScript("ERR_NAME_NOT_RESOLVED"), &result)); EXPECT_TRUE(result); } // Checks that when an error occurs and a link doctor load fails, the stale // cache status of the page is correctly transferred, and we can load the // stale copy from the javascript. Most logic copied from StaleCacheStatus // above. IN_PROC_BROWSER_TEST_F(ErrorPageLinkDoctorFailTest, StaleCacheStatusFailedLinkDoctor) { ASSERT_TRUE(test_server()->Start()); // Load cache with entry with "nocache" set, to create stale // cache. GURL test_url(test_server()->GetURL("files/nocache.html")); NavigateToURLAndWaitForTitle(test_url, "Nocache Test Page", 1); // Reload same URL after forcing an error from the the network layer; // confirm that the error page is told the cached copy exists. scoped_refptr url_request_context_getter = browser()->profile()->GetRequestContext(); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&InterceptNetworkTransactions, url_request_context_getter, net::ERR_CONNECTION_FAILED)); ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( browser(), test_url, 2); ProbeStaleCopyValue(true); // Confirm that loading the stale copy from the cache works. content::TestNavigationObserver same_tab_observer( browser()->tab_strip_model()->GetActiveWebContents(), 1); ASSERT_TRUE(ReloadStaleCopyFromCache()); same_tab_observer.Wait(); EXPECT_EQ(base::ASCIIToUTF16("Nocache Test Page"), browser()->tab_strip_model()->GetActiveWebContents()->GetTitle()); // Clear the cache and reload the same URL; confirm the error page is told // that there is no cached copy. BrowsingDataRemover* remover = BrowsingDataRemover::CreateForUnboundedRange(browser()->profile()); remover->Remove(BrowsingDataRemover::REMOVE_CACHE, BrowsingDataHelper::UNPROTECTED_WEB); ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( browser(), test_url, 2); ProbeStaleCopyValue(false); } // 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: virtual void SetUpOnMainThread() OVERRIDE { // Clear AcceptLanguages to force punycode decoding. browser()->profile()->GetPrefs()->SetString(prefs::kAcceptLanguages, std::string()); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ErrorPageForIDNTest::AddFilters)); } virtual void CleanUpOnMainThread() OVERRIDE { BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&ErrorPageForIDNTest::RemoveFilters)); } private: static void AddFilters() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); content::URLRequestFailedJob::AddUrlHandlerForHostname(kHostname); } static void RemoveFilters() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); net::URLRequestFilter::GetInstance()->ClearHandlers(); } }; 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 the link doctor. ui_test_utils::NavigateToURLBlockUntilNavigationsComplete( browser(), URLRequestFailedJob::GetMockHttpUrlForHostname(net::ERR_UNSAFE_PORT, kHostname), 1); bool result = false; EXPECT_TRUE(content::ExecuteScriptAndExtractBool( browser()->tab_strip_model()->GetActiveWebContents(), GetTextContentContainsStringScript(kHostnameJSUnicode), &result)); EXPECT_TRUE(result); } } // namespace