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