1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "content/renderer/fetchers/resource_fetcher.h" 6 7 #include "base/bind.h" 8 #include "base/bind_helpers.h" 9 #include "base/command_line.h" 10 #include "base/message_loop/message_loop.h" 11 #include "base/timer/timer.h" 12 #include "content/public/common/content_switches.h" 13 #include "content/public/common/url_constants.h" 14 #include "content/public/renderer/render_view.h" 15 #include "content/public/test/test_utils.h" 16 #include "content/shell/shell.h" 17 #include "content/test/content_browser_test.h" 18 #include "content/test/content_browser_test_utils.h" 19 #include "third_party/WebKit/public/platform/WebURLResponse.h" 20 #include "third_party/WebKit/public/web/WebFrame.h" 21 #include "third_party/WebKit/public/web/WebView.h" 22 23 using WebKit::WebFrame; 24 using WebKit::WebURLRequest; 25 using WebKit::WebURLResponse; 26 27 namespace content { 28 29 static const int kMaxWaitTimeMs = 5000; 30 31 class FetcherDelegate { 32 public: 33 FetcherDelegate() 34 : completed_(false), 35 timed_out_(false) { 36 // Start a repeating timer waiting for the download to complete. The 37 // callback has to be a static function, so we hold on to our instance. 38 FetcherDelegate::instance_ = this; 39 StartTimer(); 40 } 41 42 virtual ~FetcherDelegate() {} 43 44 ResourceFetcher::Callback NewCallback() { 45 return base::Bind(&FetcherDelegate::OnURLFetchComplete, 46 base::Unretained(this)); 47 } 48 49 virtual void OnURLFetchComplete(const WebURLResponse& response, 50 const std::string& data) { 51 response_ = response; 52 data_ = data; 53 completed_ = true; 54 timer_.Stop(); 55 if (!timed_out_) 56 quit_task_.Run(); 57 } 58 59 bool completed() const { return completed_; } 60 bool timed_out() const { return timed_out_; } 61 62 std::string data() const { return data_; } 63 const WebURLResponse& response() const { return response_; } 64 65 // Wait for the request to complete or timeout. 66 void WaitForResponse() { 67 scoped_refptr<MessageLoopRunner> runner = new MessageLoopRunner; 68 quit_task_ = runner->QuitClosure(); 69 runner->Run(); 70 } 71 72 void StartTimer() { 73 timer_.Start(FROM_HERE, 74 base::TimeDelta::FromMilliseconds(kMaxWaitTimeMs), 75 this, 76 &FetcherDelegate::TimerFired); 77 } 78 79 void TimerFired() { 80 ASSERT_FALSE(completed_); 81 82 timed_out_ = true; 83 if (!completed_) 84 quit_task_.Run(); 85 FAIL() << "fetch timed out"; 86 } 87 88 static FetcherDelegate* instance_; 89 90 private: 91 base::OneShotTimer<FetcherDelegate> timer_; 92 bool completed_; 93 bool timed_out_; 94 WebURLResponse response_; 95 std::string data_; 96 base::Closure quit_task_; 97 }; 98 99 FetcherDelegate* FetcherDelegate::instance_ = NULL; 100 101 class EvilFetcherDelegate : public FetcherDelegate { 102 public: 103 virtual ~EvilFetcherDelegate() {} 104 105 void SetFetcher(ResourceFetcher* fetcher) { 106 fetcher_.reset(fetcher); 107 } 108 109 virtual void OnURLFetchComplete(const WebURLResponse& response, 110 const std::string& data) OVERRIDE { 111 // Destroy the ResourceFetcher here. We are testing that upon returning 112 // to the ResourceFetcher that it does not crash. 113 fetcher_.reset(); 114 FetcherDelegate::OnURLFetchComplete(response, data); 115 } 116 117 private: 118 scoped_ptr<ResourceFetcher> fetcher_; 119 }; 120 121 class ResourceFetcherTests : public ContentBrowserTest { 122 public: 123 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 124 command_line->AppendSwitch(switches::kSingleProcess); 125 #if defined(OS_WIN) && defined(USE_AURA) 126 // Don't want to try to create a GPU process. 127 command_line->AppendSwitch(switches::kDisableAcceleratedCompositing); 128 #endif 129 } 130 131 RenderView* GetRenderView() { 132 // We could have the test on the UI thread get the WebContent's routing ID, 133 // but we know this will be the first RV so skip that and just hardcode it. 134 return RenderView::FromRoutingID(1); 135 } 136 137 void ResourceFetcherDownloadOnRenderer(const GURL& url) { 138 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 139 140 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 141 scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcher( 142 url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback())); 143 144 delegate->WaitForResponse(); 145 146 ASSERT_TRUE(delegate->completed()); 147 EXPECT_EQ(delegate->response().httpStatusCode(), 200); 148 std::string text = delegate->data(); 149 EXPECT_TRUE(text.find("Basic html test.") != std::string::npos); 150 } 151 152 void ResourceFetcher404OnRenderer(const GURL& url) { 153 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 154 155 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 156 scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcher( 157 url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback())); 158 159 delegate->WaitForResponse(); 160 161 ASSERT_TRUE(delegate->completed()); 162 EXPECT_EQ(delegate->response().httpStatusCode(), 404); 163 EXPECT_TRUE(delegate->data().find("Not Found.") != std::string::npos); 164 } 165 166 void ResourceFetcherDidFailOnRenderer() { 167 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 168 169 // Try to fetch a page on a site that doesn't exist. 170 GURL url("http://localhost:1339/doesnotexist"); 171 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 172 scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcher( 173 url, frame, WebURLRequest::TargetIsMainFrame, delegate->NewCallback())); 174 175 delegate->WaitForResponse(); 176 177 // When we fail, we still call the Delegate callback but we pass in empty 178 // values. 179 EXPECT_TRUE(delegate->completed()); 180 EXPECT_TRUE(delegate->response().isNull()); 181 EXPECT_EQ(delegate->data(), std::string()); 182 EXPECT_FALSE(delegate->timed_out()); 183 } 184 185 void ResourceFetcherTimeoutOnRenderer(const GURL& url) { 186 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 187 188 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 189 scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcherWithTimeout( 190 url, frame, WebURLRequest::TargetIsMainFrame, 191 0, delegate->NewCallback())); 192 193 delegate->WaitForResponse(); 194 195 // When we timeout, we still call the Delegate callback but we pass in empty 196 // values. 197 EXPECT_TRUE(delegate->completed()); 198 EXPECT_TRUE(delegate->response().isNull()); 199 EXPECT_EQ(delegate->data(), std::string()); 200 EXPECT_FALSE(delegate->timed_out()); 201 } 202 203 void ResourceFetcherDeletedInCallbackOnRenderer(const GURL& url) { 204 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 205 206 scoped_ptr<EvilFetcherDelegate> delegate(new EvilFetcherDelegate); 207 scoped_ptr<ResourceFetcher> fetcher(new ResourceFetcherWithTimeout( 208 url, frame, WebURLRequest::TargetIsMainFrame, 209 0, delegate->NewCallback())); 210 delegate->SetFetcher(fetcher.release()); 211 212 delegate->WaitForResponse(); 213 EXPECT_FALSE(delegate->timed_out()); 214 } 215 }; 216 217 // Test a fetch from the test server. 218 // If this flakes, use http://crbug.com/51622. 219 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDownload) { 220 // Need to spin up the renderer. 221 NavigateToURL(shell(), GURL(kAboutBlankURL)); 222 223 ASSERT_TRUE(test_server()->Start()); 224 GURL url(test_server()->GetURL("files/simple_page.html")); 225 226 PostTaskToInProcessRendererAndWait( 227 base::Bind(&ResourceFetcherTests::ResourceFetcherDownloadOnRenderer, 228 base::Unretained(this), url)); 229 } 230 231 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcher404) { 232 // Need to spin up the renderer. 233 NavigateToURL(shell(), GURL(kAboutBlankURL)); 234 235 // Test 404 response. 236 ASSERT_TRUE(test_server()->Start()); 237 GURL url = test_server()->GetURL("files/thisfiledoesntexist.html"); 238 239 PostTaskToInProcessRendererAndWait( 240 base::Bind(&ResourceFetcherTests::ResourceFetcher404OnRenderer, 241 base::Unretained(this), url)); 242 } 243 244 // If this flakes, use http://crbug.com/51622. 245 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDidFail) { 246 // Need to spin up the renderer. 247 NavigateToURL(shell(), GURL(kAboutBlankURL)); 248 249 PostTaskToInProcessRendererAndWait( 250 base::Bind(&ResourceFetcherTests::ResourceFetcherDidFailOnRenderer, 251 base::Unretained(this))); 252 } 253 254 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherTimeout) { 255 // Need to spin up the renderer. 256 NavigateToURL(shell(), GURL(kAboutBlankURL)); 257 258 // Grab a page that takes at least 1 sec to respond, but set the fetcher to 259 // timeout in 0 sec. 260 ASSERT_TRUE(test_server()->Start()); 261 GURL url(test_server()->GetURL("slow?1")); 262 263 PostTaskToInProcessRendererAndWait( 264 base::Bind(&ResourceFetcherTests::ResourceFetcherTimeoutOnRenderer, 265 base::Unretained(this), url)); 266 } 267 268 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDeletedInCallback) { 269 // Need to spin up the renderer. 270 NavigateToURL(shell(), GURL(kAboutBlankURL)); 271 272 // Grab a page that takes at least 1 sec to respond, but set the fetcher to 273 // timeout in 0 sec. 274 ASSERT_TRUE(test_server()->Start()); 275 GURL url(test_server()->GetURL("slow?1")); 276 277 PostTaskToInProcessRendererAndWait( 278 base::Bind( 279 &ResourceFetcherTests::ResourceFetcherDeletedInCallbackOnRenderer, 280 base::Unretained(this), url)); 281 } 282 283 } // namespace content 284