// 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 "base/basictypes.h" #include "base/bind.h" #include "base/callback.h" #include "base/command_line.h" #include "base/strings/utf_string_conversions.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/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/WebKit/public/platform/WebURLError.h" #include "third_party/WebKit/public/platform/WebURLRequest.h" #include "third_party/WebKit/public/web/WebFrame.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) {} virtual void GetNavigationErrorStrings( content::RenderView* render_view, blink::WebFrame* 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.staleCopyInCache; } 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(content::BrowserThread::CurrentlyOn(BrowserThread::IO)); net::HttpCache* cache( getter->GetURLRequestContext()->http_transaction_factory()->GetCache()); DCHECK(cache); scoped_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( factory.PassAs<net::HttpTransactionFactory>()); } 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(scoped_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(content::BrowserThread::CurrentlyOn(BrowserThread::IO)); net::HttpCache* cache( getter->GetURLRequestContext()->http_transaction_factory()->GetCache()); DCHECK(cache); scoped_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(backend.Pass()), 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. backend_callback.Run(net::OK); } } } // namespace class RenderViewBrowserTest : public ContentBrowserTest { public: RenderViewBrowserTest() {} virtual void SetUpCommandLine(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); } virtual 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_; }; IN_PROC_BROWSER_TEST_F(RenderViewBrowserTest, ConfirmCacheInformationPlumbed) { ASSERT_TRUE(test_server()->Start()); // Load URL 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. int renderer_id = shell()->web_contents()->GetMainFrame()->GetProcess()->GetID(); scoped_refptr<net::URLRequestContextGetter> url_request_context_getter = ShellContentBrowserClient::Get()->browser_context()-> GetRequestContextForRenderProcess(renderer_id); BrowserThread::PostTask( BrowserThread::IO, FROM_HERE, base::Bind(&InterceptNetworkTransactions, 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::Bind(&ClearCache, 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