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/public/renderer/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/time/time.h" 12 #include "base/timer/timer.h" 13 #include "content/public/common/content_switches.h" 14 #include "content/public/common/url_constants.h" 15 #include "content/public/renderer/render_view.h" 16 #include "content/public/test/content_browser_test.h" 17 #include "content/public/test/content_browser_test_utils.h" 18 #include "content/public/test/test_utils.h" 19 #include "content/shell/browser/shell.h" 20 #include "third_party/WebKit/public/platform/WebURLResponse.h" 21 #include "third_party/WebKit/public/web/WebFrame.h" 22 #include "third_party/WebKit/public/web/WebView.h" 23 24 using blink::WebFrame; 25 using blink::WebURLRequest; 26 using blink::WebURLResponse; 27 28 namespace { 29 30 // The first RenderFrame is routing ID 1, and the first RenderView is 2. 31 const int kRenderViewRoutingId = 2; 32 33 } 34 35 namespace content { 36 37 static const int kMaxWaitTimeMs = 5000; 38 39 class FetcherDelegate { 40 public: 41 FetcherDelegate() 42 : completed_(false), 43 timed_out_(false) { 44 // Start a repeating timer waiting for the download to complete. The 45 // callback has to be a static function, so we hold on to our instance. 46 FetcherDelegate::instance_ = this; 47 StartTimer(); 48 } 49 50 virtual ~FetcherDelegate() {} 51 52 ResourceFetcher::Callback NewCallback() { 53 return base::Bind(&FetcherDelegate::OnURLFetchComplete, 54 base::Unretained(this)); 55 } 56 57 virtual void OnURLFetchComplete(const WebURLResponse& response, 58 const std::string& data) { 59 response_ = response; 60 data_ = data; 61 completed_ = true; 62 timer_.Stop(); 63 if (!timed_out_) 64 quit_task_.Run(); 65 } 66 67 bool completed() const { return completed_; } 68 bool timed_out() const { return timed_out_; } 69 70 std::string data() const { return data_; } 71 const WebURLResponse& response() const { return response_; } 72 73 // Wait for the request to complete or timeout. 74 void WaitForResponse() { 75 scoped_refptr<MessageLoopRunner> runner = new MessageLoopRunner; 76 quit_task_ = runner->QuitClosure(); 77 runner->Run(); 78 } 79 80 void StartTimer() { 81 timer_.Start(FROM_HERE, 82 base::TimeDelta::FromMilliseconds(kMaxWaitTimeMs), 83 this, 84 &FetcherDelegate::TimerFired); 85 } 86 87 void TimerFired() { 88 ASSERT_FALSE(completed_); 89 90 timed_out_ = true; 91 if (!completed_) 92 quit_task_.Run(); 93 FAIL() << "fetch timed out"; 94 } 95 96 static FetcherDelegate* instance_; 97 98 private: 99 base::OneShotTimer<FetcherDelegate> timer_; 100 bool completed_; 101 bool timed_out_; 102 WebURLResponse response_; 103 std::string data_; 104 base::Closure quit_task_; 105 }; 106 107 FetcherDelegate* FetcherDelegate::instance_ = NULL; 108 109 class EvilFetcherDelegate : public FetcherDelegate { 110 public: 111 virtual ~EvilFetcherDelegate() {} 112 113 void SetFetcher(ResourceFetcher* fetcher) { 114 fetcher_.reset(fetcher); 115 } 116 117 virtual void OnURLFetchComplete(const WebURLResponse& response, 118 const std::string& data) OVERRIDE { 119 FetcherDelegate::OnURLFetchComplete(response, data); 120 121 // Destroy the ResourceFetcher here. We are testing that upon returning 122 // to the ResourceFetcher that it does not crash. This must be done after 123 // calling FetcherDelegate::OnURLFetchComplete, since deleting the fetcher 124 // invalidates |response| and |data|. 125 fetcher_.reset(); 126 } 127 128 private: 129 scoped_ptr<ResourceFetcher> fetcher_; 130 }; 131 132 class ResourceFetcherTests : public ContentBrowserTest { 133 public: 134 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 135 command_line->AppendSwitch(switches::kSingleProcess); 136 #if defined(OS_WIN) 137 // Don't want to try to create a GPU process. 138 command_line->AppendSwitch(switches::kDisableGpu); 139 #endif 140 } 141 142 RenderView* GetRenderView() { 143 // We could have the test on the UI thread get the WebContent's routing ID, 144 // but we know this will be the first RV so skip that and just hardcode it. 145 return RenderView::FromRoutingID(kRenderViewRoutingId); 146 } 147 148 void ResourceFetcherDownloadOnRenderer(const GURL& url) { 149 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 150 151 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 152 scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(url)); 153 fetcher->Start(frame, WebURLRequest::TargetIsMainFrame, 154 delegate->NewCallback()); 155 156 delegate->WaitForResponse(); 157 158 ASSERT_TRUE(delegate->completed()); 159 EXPECT_EQ(delegate->response().httpStatusCode(), 200); 160 std::string text = delegate->data(); 161 EXPECT_TRUE(text.find("Basic html test.") != std::string::npos); 162 } 163 164 void ResourceFetcher404OnRenderer(const GURL& url) { 165 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 166 167 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 168 scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(url)); 169 fetcher->Start(frame, WebURLRequest::TargetIsMainFrame, 170 delegate->NewCallback()); 171 172 delegate->WaitForResponse(); 173 174 ASSERT_TRUE(delegate->completed()); 175 EXPECT_EQ(delegate->response().httpStatusCode(), 404); 176 EXPECT_TRUE(delegate->data().find("Not Found.") != std::string::npos); 177 } 178 179 void ResourceFetcherDidFailOnRenderer() { 180 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 181 182 // Try to fetch a page on a site that doesn't exist. 183 GURL url("http://localhost:1339/doesnotexist"); 184 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 185 scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(url)); 186 fetcher->Start(frame, WebURLRequest::TargetIsMainFrame, 187 delegate->NewCallback()); 188 189 delegate->WaitForResponse(); 190 191 // When we fail, we still call the Delegate callback but we pass in empty 192 // values. 193 EXPECT_TRUE(delegate->completed()); 194 EXPECT_TRUE(delegate->response().isNull()); 195 EXPECT_EQ(delegate->data(), std::string()); 196 EXPECT_FALSE(delegate->timed_out()); 197 } 198 199 void ResourceFetcherTimeoutOnRenderer(const GURL& url) { 200 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 201 202 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 203 scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(url)); 204 fetcher->Start(frame, WebURLRequest::TargetIsMainFrame, 205 delegate->NewCallback()); 206 fetcher->SetTimeout(base::TimeDelta()); 207 208 delegate->WaitForResponse(); 209 210 // When we timeout, we still call the Delegate callback but we pass in empty 211 // values. 212 EXPECT_TRUE(delegate->completed()); 213 EXPECT_TRUE(delegate->response().isNull()); 214 EXPECT_EQ(delegate->data(), std::string()); 215 EXPECT_FALSE(delegate->timed_out()); 216 } 217 218 void ResourceFetcherDeletedInCallbackOnRenderer(const GURL& url) { 219 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 220 221 scoped_ptr<EvilFetcherDelegate> delegate(new EvilFetcherDelegate); 222 scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(url)); 223 fetcher->Start(frame, WebURLRequest::TargetIsMainFrame, 224 delegate->NewCallback()); 225 fetcher->SetTimeout(base::TimeDelta()); 226 delegate->SetFetcher(fetcher.release()); 227 228 delegate->WaitForResponse(); 229 EXPECT_FALSE(delegate->timed_out()); 230 } 231 232 void ResourceFetcherPost(const GURL& url) { 233 const char* kBody = "Really nifty POST body!"; 234 235 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 236 237 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 238 scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(url)); 239 fetcher->SetMethod("POST"); 240 fetcher->SetBody(kBody); 241 fetcher->Start(frame, WebURLRequest::TargetIsMainFrame, 242 delegate->NewCallback()); 243 244 delegate->WaitForResponse(); 245 ASSERT_TRUE(delegate->completed()); 246 EXPECT_EQ(delegate->response().httpStatusCode(), 200); 247 EXPECT_EQ(kBody, delegate->data()); 248 } 249 250 void ResourceFetcherSetHeader(const GURL& url) { 251 const char* kHeader = "Rather boring header."; 252 253 WebFrame* frame = GetRenderView()->GetWebView()->mainFrame(); 254 255 scoped_ptr<FetcherDelegate> delegate(new FetcherDelegate); 256 scoped_ptr<ResourceFetcher> fetcher(ResourceFetcher::Create(url)); 257 fetcher->SetHeader("header", kHeader); 258 fetcher->Start(frame, WebURLRequest::TargetIsMainFrame, 259 delegate->NewCallback()); 260 261 delegate->WaitForResponse(); 262 ASSERT_TRUE(delegate->completed()); 263 EXPECT_EQ(delegate->response().httpStatusCode(), 200); 264 EXPECT_EQ(kHeader, delegate->data()); 265 } 266 }; 267 268 #if defined(OS_ANDROID) 269 // Disable (http://crbug.com/248796). 270 #define MAYBE_ResourceFetcher404 DISABLED_ResourceFetcher404 271 #define MAYBE_ResourceFetcherDeletedInCallback \ 272 DISABLED_ResourceFetcherDeletedInCallback 273 #define MAYBE_ResourceFetcherTimeout DISABLED_ResourceFetcherTimeout 274 #define MAYBE_ResourceFetcherDownload DISABLED_ResourceFetcherDownload 275 // Disable (http://crbug.com/341142). 276 #define MAYBE_ResourceFetcherPost DISABLED_ResourceFetcherPost 277 #define MAYBE_ResourceFetcherSetHeader DISABLED_ResourceFetcherSetHeader 278 #else 279 #define MAYBE_ResourceFetcher404 ResourceFetcher404 280 #define MAYBE_ResourceFetcherDeletedInCallback ResourceFetcherDeletedInCallback 281 #define MAYBE_ResourceFetcherTimeout ResourceFetcherTimeout 282 #define MAYBE_ResourceFetcherDownload ResourceFetcherDownload 283 #define MAYBE_ResourceFetcherPost ResourceFetcherPost 284 #define MAYBE_ResourceFetcherSetHeader ResourceFetcherSetHeader 285 #endif 286 287 // Test a fetch from the test server. 288 // If this flakes, use http://crbug.com/51622. 289 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, MAYBE_ResourceFetcherDownload) { 290 // Need to spin up the renderer. 291 NavigateToURL(shell(), GURL(url::kAboutBlankURL)); 292 293 ASSERT_TRUE(test_server()->Start()); 294 GURL url(test_server()->GetURL("files/simple_page.html")); 295 296 PostTaskToInProcessRendererAndWait( 297 base::Bind(&ResourceFetcherTests::ResourceFetcherDownloadOnRenderer, 298 base::Unretained(this), url)); 299 } 300 301 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, MAYBE_ResourceFetcher404) { 302 // Need to spin up the renderer. 303 NavigateToURL(shell(), GURL(url::kAboutBlankURL)); 304 305 // Test 404 response. 306 ASSERT_TRUE(test_server()->Start()); 307 GURL url = test_server()->GetURL("files/thisfiledoesntexist.html"); 308 309 PostTaskToInProcessRendererAndWait( 310 base::Bind(&ResourceFetcherTests::ResourceFetcher404OnRenderer, 311 base::Unretained(this), url)); 312 } 313 314 // If this flakes, use http://crbug.com/51622. 315 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, ResourceFetcherDidFail) { 316 // Need to spin up the renderer. 317 NavigateToURL(shell(), GURL(url::kAboutBlankURL)); 318 319 PostTaskToInProcessRendererAndWait( 320 base::Bind(&ResourceFetcherTests::ResourceFetcherDidFailOnRenderer, 321 base::Unretained(this))); 322 } 323 324 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, MAYBE_ResourceFetcherTimeout) { 325 // Need to spin up the renderer. 326 NavigateToURL(shell(), GURL(url::kAboutBlankURL)); 327 328 // Grab a page that takes at least 1 sec to respond, but set the fetcher to 329 // timeout in 0 sec. 330 ASSERT_TRUE(test_server()->Start()); 331 GURL url(test_server()->GetURL("slow?1")); 332 333 PostTaskToInProcessRendererAndWait( 334 base::Bind(&ResourceFetcherTests::ResourceFetcherTimeoutOnRenderer, 335 base::Unretained(this), url)); 336 } 337 338 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, 339 MAYBE_ResourceFetcherDeletedInCallback) { 340 // Need to spin up the renderer. 341 NavigateToURL(shell(), GURL(url::kAboutBlankURL)); 342 343 // Grab a page that takes at least 1 sec to respond, but set the fetcher to 344 // timeout in 0 sec. 345 ASSERT_TRUE(test_server()->Start()); 346 GURL url(test_server()->GetURL("slow?1")); 347 348 PostTaskToInProcessRendererAndWait( 349 base::Bind( 350 &ResourceFetcherTests::ResourceFetcherDeletedInCallbackOnRenderer, 351 base::Unretained(this), url)); 352 } 353 354 355 356 // Test that ResourceFetchers can handle POSTs. 357 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, MAYBE_ResourceFetcherPost) { 358 // Need to spin up the renderer. 359 NavigateToURL(shell(), GURL(url::kAboutBlankURL)); 360 361 // Grab a page that echos the POST body. 362 ASSERT_TRUE(test_server()->Start()); 363 GURL url(test_server()->GetURL("echo")); 364 365 PostTaskToInProcessRendererAndWait( 366 base::Bind( 367 &ResourceFetcherTests::ResourceFetcherPost, 368 base::Unretained(this), url)); 369 } 370 371 // Test that ResourceFetchers can set headers. 372 IN_PROC_BROWSER_TEST_F(ResourceFetcherTests, MAYBE_ResourceFetcherSetHeader) { 373 // Need to spin up the renderer. 374 NavigateToURL(shell(), GURL(url::kAboutBlankURL)); 375 376 // Grab a page that echos the POST body. 377 ASSERT_TRUE(test_server()->Start()); 378 GURL url(test_server()->GetURL("echoheader?header")); 379 380 PostTaskToInProcessRendererAndWait( 381 base::Bind( 382 &ResourceFetcherTests::ResourceFetcherSetHeader, 383 base::Unretained(this), url)); 384 } 385 386 } // namespace content 387