|  | // Copyright 2014 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. | 
|  |  | 
|  | // Browser tests targeted at the RenderView that run in browser context. | 
|  | // Note that these tests rely on single-process mode, and hence may be | 
|  | // disabled in some configurations (check gyp files). | 
|  |  | 
|  | #include <utility> | 
|  |  | 
|  | #include "base/bind.h" | 
|  | #include "base/callback.h" | 
|  | #include "base/command_line.h" | 
|  | #include "base/strings/utf_string_conversions.h" | 
|  | #include "build/build_config.h" | 
|  | #include "content/public/browser/browser_context.h" | 
|  | #include "content/public/browser/browser_thread.h" | 
|  | #include "content/public/browser/render_frame_host.h" | 
|  | #include "content/public/browser/render_process_host.h" | 
|  | #include "content/public/browser/storage_partition.h" | 
|  | #include "content/public/browser/web_contents.h" | 
|  | #include "content/public/common/content_switches.h" | 
|  | #include "content/public/renderer/render_view.h" | 
|  | #include "content/public/test/browser_test_utils.h" | 
|  | #include "content/public/test/content_browser_test.h" | 
|  | #include "content/public/test/content_browser_test_utils.h" | 
|  | #include "content/public/test/test_utils.h" | 
|  | #include "content/shell/browser/shell.h" | 
|  | #include "content/shell/browser/shell_browser_context.h" | 
|  | #include "content/shell/browser/shell_content_browser_client.h" | 
|  | #include "content/shell/common/shell_content_client.h" | 
|  | #include "content/shell/renderer/shell_content_renderer_client.h" | 
|  | #include "net/base/net_errors.h" | 
|  | #include "net/disk_cache/disk_cache.h" | 
|  | #include "net/http/failing_http_transaction_factory.h" | 
|  | #include "net/http/http_cache.h" | 
|  | #include "net/url_request/url_request_context.h" | 
|  | #include "net/url_request/url_request_context_getter.h" | 
|  | #include "testing/gtest/include/gtest/gtest.h" | 
|  | #include "third_party/blink/public/platform/web_url_error.h" | 
|  | #include "third_party/blink/public/platform/web_url_request.h" | 
|  | #include "third_party/blink/public/web/web_frame.h" | 
|  |  | 
|  | namespace content { | 
|  |  | 
|  | namespace { | 
|  |  | 
|  | class TestShellContentRendererClient : public ShellContentRendererClient { | 
|  | public: | 
|  | TestShellContentRendererClient() | 
|  | : latest_error_valid_(false), | 
|  | latest_error_reason_(0), | 
|  | latest_error_stale_copy_in_cache_(false) {} | 
|  |  | 
|  | void PrepareErrorPage(content::RenderFrame* render_frame, | 
|  | const blink::WebURLRequest& failed_request, | 
|  | const blink::WebURLError& error, | 
|  | std::string* error_html, | 
|  | base::string16* error_description) override { | 
|  | if (error_html) | 
|  | *error_html = "A suffusion of yellow."; | 
|  | latest_error_valid_ = true; | 
|  | latest_error_reason_ = error.reason(); | 
|  | latest_error_stale_copy_in_cache_ = error.has_copy_in_cache(); | 
|  | } | 
|  |  | 
|  | bool GetLatestError(int* error_code, bool* stale_cache_entry_present) { | 
|  | if (latest_error_valid_) { | 
|  | *error_code = latest_error_reason_; | 
|  | *stale_cache_entry_present = latest_error_stale_copy_in_cache_; | 
|  | } | 
|  | return latest_error_valid_; | 
|  | } | 
|  |  | 
|  | private: | 
|  | bool latest_error_valid_; | 
|  | int latest_error_reason_; | 
|  | bool latest_error_stale_copy_in_cache_; | 
|  | }; | 
|  |  | 
|  | // Must be called on IO thread. | 
|  | void InterceptNetworkTransactions(net::URLRequestContextGetter* getter, | 
|  | net::Error error) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | net::HttpCache* cache( | 
|  | getter->GetURLRequestContext()->http_transaction_factory()->GetCache()); | 
|  | DCHECK(cache); | 
|  | std::unique_ptr<net::FailingHttpTransactionFactory> factory( | 
|  | new net::FailingHttpTransactionFactory(cache->GetSession(), error)); | 
|  | // Throw away old version; since this is a browser test, there is no | 
|  | // need to restore the old state. | 
|  | cache->SetHttpNetworkTransactionFactoryForTesting(std::move(factory)); | 
|  | } | 
|  |  | 
|  | void CallOnUIThreadValidatingReturn(const base::Closure& callback, | 
|  | int rv) { | 
|  | DCHECK_EQ(net::OK, rv); | 
|  | BrowserThread::PostTask( | 
|  | BrowserThread::UI, FROM_HERE, callback); | 
|  | } | 
|  |  | 
|  | // Must be called on IO thread.  The callback will be called on | 
|  | // completion of cache clearing on the UI thread. | 
|  | void BackendClearCache(std::unique_ptr<disk_cache::Backend*> backend, | 
|  | const base::Closure& callback, | 
|  | int rv) { | 
|  | DCHECK(*backend); | 
|  | DCHECK_EQ(net::OK, rv); | 
|  | (*backend)->DoomAllEntries( | 
|  | base::Bind(&CallOnUIThreadValidatingReturn, callback)); | 
|  | } | 
|  |  | 
|  | // Must be called on IO thread.  The callback will be called on | 
|  | // completion of cache clearing on the UI thread. | 
|  | void ClearCache(net::URLRequestContextGetter* getter, | 
|  | const base::Closure& callback) { | 
|  | DCHECK_CURRENTLY_ON(BrowserThread::IO); | 
|  | net::HttpCache* cache( | 
|  | getter->GetURLRequestContext()->http_transaction_factory()->GetCache()); | 
|  | DCHECK(cache); | 
|  | std::unique_ptr<disk_cache::Backend*> backend(new disk_cache::Backend*); | 
|  | *backend = NULL; | 
|  | disk_cache::Backend** backend_ptr = backend.get(); | 
|  |  | 
|  | net::CompletionCallback backend_callback(base::Bind( | 
|  | &BackendClearCache, base::Passed(std::move(backend)), callback)); | 
|  |  | 
|  | // backend_ptr is valid until all copies of backend_callback go out | 
|  | // of scope. | 
|  | if (net::OK == cache->GetBackend(backend_ptr, backend_callback)) { | 
|  | // The call completed synchronously, so GetBackend didn't run the callback. | 
|  | std::move(backend_callback).Run(net::OK); | 
|  | } | 
|  | } | 
|  |  | 
|  | }  // namespace | 
|  |  | 
|  | class RenderViewBrowserTest : public ContentBrowserTest { | 
|  | public: | 
|  | RenderViewBrowserTest() {} | 
|  |  | 
|  | void SetUpCommandLine(base::CommandLine* command_line) override { | 
|  | // This method is needed to allow interaction with in-process renderer | 
|  | // and use of a test ContentRendererClient. | 
|  | command_line->AppendSwitch(switches::kSingleProcess); | 
|  | } | 
|  |  | 
|  | void SetUpOnMainThread() override { | 
|  | // Override setting of renderer client. | 
|  | renderer_client_ = new TestShellContentRendererClient(); | 
|  | // Explicitly leaks ownership; this object will remain alive | 
|  | // until process death.  We don't deleted the returned value, | 
|  | // since some contexts set the pointer to a non-heap address. | 
|  | SetRendererClientForTesting(renderer_client_); | 
|  | } | 
|  |  | 
|  | // 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( | 
|  | shell()->web_contents(), base::ASCIIToUTF16(expected_title)); | 
|  |  | 
|  | content::NavigateToURLBlockUntilNavigationsComplete( | 
|  | shell(), url, num_navigations); | 
|  |  | 
|  | EXPECT_EQ(base::ASCIIToUTF16(expected_title), | 
|  | title_watcher.WaitAndGetTitle()); | 
|  | } | 
|  |  | 
|  | // Returns true if there is a valid error stored; in this case | 
|  | // |*error_code| and |*stale_cache_entry_present| will be updated | 
|  | // appropriately. | 
|  | // Must be called after the renderer thread is created. | 
|  | bool GetLatestErrorFromRendererClient( | 
|  | int* error_code, bool* stale_cache_entry_present) { | 
|  | bool result = false; | 
|  |  | 
|  | PostTaskToInProcessRendererAndWait( | 
|  | base::Bind(&RenderViewBrowserTest::GetLatestErrorFromRendererClient0, | 
|  | renderer_client_, &result, error_code, | 
|  | stale_cache_entry_present)); | 
|  | return result; | 
|  | } | 
|  |  | 
|  | private: | 
|  | // Must be run on renderer thread. | 
|  | static void GetLatestErrorFromRendererClient0( | 
|  | TestShellContentRendererClient* renderer_client, | 
|  | bool* result, int* error_code, bool* stale_cache_entry_present) { | 
|  | *result = renderer_client->GetLatestError( | 
|  | error_code, stale_cache_entry_present); | 
|  | } | 
|  |  | 
|  | TestShellContentRendererClient* renderer_client_; | 
|  | }; | 
|  |  | 
|  | // https://crbug.com/788788 | 
|  | #if defined(OS_ANDROID) && defined(ADDRESS_SANITIZER) | 
|  | #define MAYBE_ConfirmCacheInformationPlumbed \ | 
|  | DISABLED_ConfirmCacheInformationPlumbed | 
|  | #else | 
|  | #define MAYBE_ConfirmCacheInformationPlumbed ConfirmCacheInformationPlumbed | 
|  | #endif  // defined(OS_ANDROID) && defined(ADDRESS_SANITIZER) | 
|  | IN_PROC_BROWSER_TEST_F(RenderViewBrowserTest, | 
|  | MAYBE_ConfirmCacheInformationPlumbed) { | 
|  | ASSERT_TRUE(embedded_test_server()->Start()); | 
|  |  | 
|  | // Load URL with "nocache" set, to create stale cache. | 
|  | GURL test_url(embedded_test_server()->GetURL("/nocache-with-etag.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<net::URLRequestContextGetter> url_request_context_getter = | 
|  | shell() | 
|  | ->web_contents() | 
|  | ->GetMainFrame() | 
|  | ->GetProcess() | 
|  | ->GetStoragePartition() | 
|  | ->GetURLRequestContext(); | 
|  | BrowserThread::PostTask( | 
|  | BrowserThread::IO, FROM_HERE, | 
|  | base::BindOnce(&InterceptNetworkTransactions, | 
|  | base::RetainedRef(url_request_context_getter), | 
|  | net::ERR_FAILED)); | 
|  |  | 
|  | // An error results in one completed navigation. | 
|  | NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); | 
|  | int error_code = net::OK; | 
|  | bool stale_cache_entry_present = false; | 
|  | ASSERT_TRUE(GetLatestErrorFromRendererClient( | 
|  | &error_code, &stale_cache_entry_present)); | 
|  | EXPECT_EQ(net::ERR_FAILED, error_code); | 
|  | EXPECT_TRUE(stale_cache_entry_present); | 
|  |  | 
|  | // Clear the cache and repeat; confirm lack of entry in cache reported. | 
|  | scoped_refptr<MessageLoopRunner> runner = new MessageLoopRunner; | 
|  | BrowserThread::PostTask( | 
|  | BrowserThread::IO, FROM_HERE, | 
|  | base::BindOnce(&ClearCache, base::RetainedRef(url_request_context_getter), | 
|  | runner->QuitClosure())); | 
|  | runner->Run(); | 
|  |  | 
|  | content::NavigateToURLBlockUntilNavigationsComplete(shell(), test_url, 1); | 
|  |  | 
|  | error_code = net::OK; | 
|  | stale_cache_entry_present = true; | 
|  | ASSERT_TRUE(GetLatestErrorFromRendererClient( | 
|  | &error_code, &stale_cache_entry_present)); | 
|  | EXPECT_EQ(net::ERR_FAILED, error_code); | 
|  | EXPECT_FALSE(stale_cache_entry_present); | 
|  | } | 
|  |  | 
|  | }  // namespace content |