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/browser/loader/resource_scheduler.h" 6 7 #include "base/memory/scoped_vector.h" 8 #include "base/message_loop/message_loop.h" 9 #include "base/strings/string_number_conversions.h" 10 #include "content/browser/browser_thread_impl.h" 11 #include "content/browser/loader/resource_dispatcher_host_impl.h" 12 #include "content/browser/loader/resource_message_filter.h" 13 #include "content/browser/loader/resource_request_info_impl.h" 14 #include "content/common/resource_messages.h" 15 #include "content/public/browser/resource_context.h" 16 #include "content/public/browser/resource_controller.h" 17 #include "content/public/browser/resource_throttle.h" 18 #include "content/public/common/process_type.h" 19 #include "net/base/host_port_pair.h" 20 #include "net/base/request_priority.h" 21 #include "net/http/http_server_properties_impl.h" 22 #include "net/url_request/url_request.h" 23 #include "net/url_request/url_request_test_util.h" 24 #include "testing/gtest/include/gtest/gtest.h" 25 #include "webkit/common/resource_type.h" 26 27 namespace content { 28 29 namespace { 30 31 class TestRequestFactory; 32 33 const int kChildId = 30; 34 const int kRouteId = 75; 35 36 class TestRequest : public ResourceController { 37 public: 38 TestRequest(scoped_ptr<ResourceThrottle> throttle, 39 scoped_ptr<net::URLRequest> url_request) 40 : started_(false), 41 throttle_(throttle.Pass()), 42 url_request_(url_request.Pass()) { 43 throttle_->set_controller_for_testing(this); 44 } 45 46 bool started() const { return started_; } 47 48 void Start() { 49 bool deferred = false; 50 throttle_->WillStartRequest(&deferred); 51 started_ = !deferred; 52 } 53 54 const net::URLRequest* url_request() const { return url_request_.get(); } 55 56 protected: 57 // ResourceController interface: 58 virtual void Cancel() OVERRIDE {} 59 virtual void CancelAndIgnore() OVERRIDE {} 60 virtual void CancelWithError(int error_code) OVERRIDE {} 61 virtual void Resume() OVERRIDE { started_ = true; } 62 63 private: 64 bool started_; 65 scoped_ptr<ResourceThrottle> throttle_; 66 scoped_ptr<net::URLRequest> url_request_; 67 }; 68 69 class CancelingTestRequest : public TestRequest { 70 public: 71 CancelingTestRequest(scoped_ptr<ResourceThrottle> throttle, 72 scoped_ptr<net::URLRequest> url_request) 73 : TestRequest(throttle.Pass(), url_request.Pass()) { 74 } 75 76 void set_request_to_cancel(scoped_ptr<TestRequest> request_to_cancel) { 77 request_to_cancel_ = request_to_cancel.Pass(); 78 } 79 80 private: 81 virtual void Resume() OVERRIDE { 82 TestRequest::Resume(); 83 request_to_cancel_.reset(); 84 } 85 86 scoped_ptr<TestRequest> request_to_cancel_; 87 }; 88 89 class FakeResourceContext : public ResourceContext { 90 private: 91 virtual net::HostResolver* GetHostResolver() OVERRIDE { return NULL; } 92 virtual net::URLRequestContext* GetRequestContext() OVERRIDE { return NULL; } 93 virtual bool AllowMicAccess(const GURL& origin) OVERRIDE { return false; } 94 virtual bool AllowCameraAccess(const GURL& origin) OVERRIDE { return false; } 95 }; 96 97 class FakeResourceMessageFilter : public ResourceMessageFilter { 98 public: 99 FakeResourceMessageFilter(int child_id) 100 : ResourceMessageFilter( 101 child_id, 102 PROCESS_TYPE_RENDERER, 103 NULL /* appcache_service */, 104 NULL /* blob_storage_context */, 105 NULL /* file_system_context */, 106 base::Bind(&FakeResourceMessageFilter::GetContexts, 107 base::Unretained(this))) { 108 } 109 110 private: 111 virtual ~FakeResourceMessageFilter() {} 112 113 void GetContexts(const ResourceHostMsg_Request& request, 114 ResourceContext** resource_context, 115 net::URLRequestContext** request_context) { 116 *resource_context = &context_; 117 *request_context = NULL; 118 } 119 120 FakeResourceContext context_; 121 }; 122 123 class ResourceSchedulerTest : public testing::Test { 124 protected: 125 ResourceSchedulerTest() 126 : next_request_id_(0), 127 message_loop_(base::MessageLoop::TYPE_IO), 128 ui_thread_(BrowserThread::UI, &message_loop_), 129 io_thread_(BrowserThread::IO, &message_loop_) { 130 scheduler_.OnClientCreated(kChildId, kRouteId); 131 context_.set_http_server_properties(http_server_properties_.GetWeakPtr()); 132 } 133 134 virtual ~ResourceSchedulerTest() { 135 scheduler_.OnClientDeleted(kChildId, kRouteId); 136 } 137 138 scoped_ptr<net::URLRequest> NewURLRequestWithRoute( 139 const char* url, 140 net::RequestPriority priority, 141 int route_id) { 142 scoped_ptr<net::URLRequest> url_request( 143 context_.CreateRequest(GURL(url), priority, NULL)); 144 ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl( 145 PROCESS_TYPE_RENDERER, // process_type 146 kChildId, // child_id 147 route_id, // route_id 148 0, // origin_pid 149 ++next_request_id_, // request_id 150 MSG_ROUTING_NONE, // render_frame_id 151 false, // is_main_frame 152 0, // frame_id 153 false, // parent_is_main_frame 154 0, // parent_frame_id 155 ResourceType::SUB_RESOURCE, // resource_type 156 PAGE_TRANSITION_LINK, // transition_type 157 false, // should_replace_current_entry 158 false, // is_download 159 false, // is_stream 160 true, // allow_download 161 false, // has_user_gesture 162 blink::WebReferrerPolicyDefault, // referrer_policy 163 NULL, // context 164 base::WeakPtr<ResourceMessageFilter>(), // filter 165 true); // is_async 166 info->AssociateWithRequest(url_request.get()); 167 return url_request.Pass(); 168 } 169 170 scoped_ptr<net::URLRequest> NewURLRequest(const char* url, 171 net::RequestPriority priority) { 172 return NewURLRequestWithRoute(url, priority, kRouteId); 173 } 174 175 TestRequest* NewRequestWithRoute(const char* url, 176 net::RequestPriority priority, 177 int route_id) { 178 scoped_ptr<net::URLRequest> url_request( 179 NewURLRequestWithRoute(url, priority, route_id)); 180 scoped_ptr<ResourceThrottle> throttle(scheduler_.ScheduleRequest( 181 kChildId, route_id, url_request.get())); 182 TestRequest* request = new TestRequest(throttle.Pass(), url_request.Pass()); 183 request->Start(); 184 return request; 185 } 186 187 TestRequest* NewRequest(const char* url, net::RequestPriority priority) { 188 return NewRequestWithRoute(url, priority, kRouteId); 189 } 190 191 void ChangeRequestPriority(TestRequest* request, 192 net::RequestPriority new_priority) { 193 scoped_refptr<FakeResourceMessageFilter> filter( 194 new FakeResourceMessageFilter(kChildId)); 195 const ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest( 196 request->url_request()); 197 const GlobalRequestID& id = info->GetGlobalRequestID(); 198 ResourceHostMsg_DidChangePriority msg(id.request_id, new_priority); 199 bool ok = false; 200 rdh_.OnMessageReceived(msg, filter.get(), &ok); 201 EXPECT_TRUE(ok); 202 } 203 204 int next_request_id_; 205 base::MessageLoop message_loop_; 206 BrowserThreadImpl ui_thread_; 207 BrowserThreadImpl io_thread_; 208 ResourceDispatcherHostImpl rdh_; 209 ResourceScheduler scheduler_; 210 net::HttpServerPropertiesImpl http_server_properties_; 211 net::TestURLRequestContext context_; 212 }; 213 214 TEST_F(ResourceSchedulerTest, OneIsolatedLowRequest) { 215 scoped_ptr<TestRequest> request(NewRequest("http://host/1", net::LOWEST)); 216 EXPECT_TRUE(request->started()); 217 } 218 219 TEST_F(ResourceSchedulerTest, OneLowLoadsUntilIdle) { 220 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 221 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); 222 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); 223 EXPECT_TRUE(high->started()); 224 EXPECT_TRUE(low->started()); 225 EXPECT_FALSE(low2->started()); 226 high.reset(); 227 EXPECT_TRUE(low2->started()); 228 } 229 230 TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInserted) { 231 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 232 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); 233 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); 234 EXPECT_TRUE(high->started()); 235 EXPECT_TRUE(low->started()); 236 EXPECT_FALSE(low2->started()); 237 scheduler_.OnWillInsertBody(kChildId, kRouteId); 238 EXPECT_TRUE(low2->started()); 239 } 240 241 TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInsertedExceptSpdy) { 242 http_server_properties_.SetSupportsSpdy( 243 net::HostPortPair("spdyhost", 443), true); 244 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 245 scoped_ptr<TestRequest> low_spdy( 246 NewRequest("https://spdyhost/high", net::LOWEST)); 247 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); 248 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); 249 EXPECT_TRUE(high->started()); 250 EXPECT_TRUE(low_spdy->started()); 251 EXPECT_TRUE(low->started()); 252 EXPECT_FALSE(low2->started()); 253 scheduler_.OnWillInsertBody(kChildId, kRouteId); 254 EXPECT_TRUE(low2->started()); 255 } 256 257 TEST_F(ResourceSchedulerTest, NavigationResetsState) { 258 scheduler_.OnWillInsertBody(kChildId, kRouteId); 259 scheduler_.OnNavigate(kChildId, kRouteId); 260 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 261 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); 262 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); 263 EXPECT_TRUE(high->started()); 264 EXPECT_TRUE(low->started()); 265 EXPECT_FALSE(low2->started()); 266 } 267 268 TEST_F(ResourceSchedulerTest, BackgroundRequestStartsImmediately) { 269 const int route_id = 0; // Indicates a background request. 270 scoped_ptr<TestRequest> request(NewRequestWithRoute("http://host/1", 271 net::LOWEST, route_id)); 272 EXPECT_TRUE(request->started()); 273 } 274 275 TEST_F(ResourceSchedulerTest, StartMultipleLowRequestsWhenIdle) { 276 scoped_ptr<TestRequest> high1(NewRequest("http://host/high1", net::HIGHEST)); 277 scoped_ptr<TestRequest> high2(NewRequest("http://host/high2", net::HIGHEST)); 278 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); 279 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST)); 280 EXPECT_TRUE(high1->started()); 281 EXPECT_TRUE(high2->started()); 282 EXPECT_TRUE(low->started()); 283 EXPECT_FALSE(low2->started()); 284 high1.reset(); 285 EXPECT_FALSE(low2->started()); 286 high2.reset(); 287 EXPECT_TRUE(low2->started()); 288 } 289 290 TEST_F(ResourceSchedulerTest, CancelOtherRequestsWhileResuming) { 291 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 292 scoped_ptr<TestRequest> low1(NewRequest("http://host/low1", net::LOWEST)); 293 294 scoped_ptr<net::URLRequest> url_request( 295 NewURLRequest("http://host/low2", net::LOWEST)); 296 scoped_ptr<ResourceThrottle> throttle(scheduler_.ScheduleRequest( 297 kChildId, kRouteId, url_request.get())); 298 scoped_ptr<CancelingTestRequest> low2(new CancelingTestRequest( 299 throttle.Pass(), url_request.Pass())); 300 low2->Start(); 301 302 scoped_ptr<TestRequest> low3(NewRequest("http://host/low3", net::LOWEST)); 303 low2->set_request_to_cancel(low3.Pass()); 304 scoped_ptr<TestRequest> low4(NewRequest("http://host/low4", net::LOWEST)); 305 306 EXPECT_TRUE(high->started()); 307 EXPECT_FALSE(low2->started()); 308 high.reset(); 309 EXPECT_TRUE(low1->started()); 310 EXPECT_TRUE(low2->started()); 311 EXPECT_TRUE(low4->started()); 312 } 313 314 TEST_F(ResourceSchedulerTest, LimitedNumberOfDelayableRequestsInFlight) { 315 // We only load low priority resources if there's a body. 316 scheduler_.OnWillInsertBody(kChildId, kRouteId); 317 318 // Throw in one high priority request to make sure that's not a factor. 319 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 320 EXPECT_TRUE(high->started()); 321 322 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. 323 const int kMaxNumDelayableRequestsPerHost = 6; 324 ScopedVector<TestRequest> lows_singlehost; 325 // Queue up to the per-host limit (we subtract the current high-pri request). 326 for (int i = 0; i < kMaxNumDelayableRequestsPerHost - 1; ++i) { 327 string url = "http://host/low" + base::IntToString(i); 328 lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST)); 329 EXPECT_TRUE(lows_singlehost[i]->started()); 330 } 331 332 scoped_ptr<TestRequest> second_last_singlehost(NewRequest("http://host/last", 333 net::LOWEST)); 334 scoped_ptr<TestRequest> last_singlehost(NewRequest("http://host/s_last", 335 net::LOWEST)); 336 337 EXPECT_FALSE(second_last_singlehost->started()); 338 high.reset(); 339 EXPECT_TRUE(second_last_singlehost->started()); 340 EXPECT_FALSE(last_singlehost->started()); 341 lows_singlehost.erase(lows_singlehost.begin()); 342 EXPECT_TRUE(last_singlehost->started()); 343 344 // Queue more requests from different hosts until we reach the total limit. 345 int expected_slots_left = 346 kMaxNumDelayableRequestsPerClient - kMaxNumDelayableRequestsPerHost; 347 EXPECT_GT(expected_slots_left, 0); 348 ScopedVector<TestRequest> lows_differenthosts; 349 for (int i = 0; i < expected_slots_left; ++i) { 350 string url = "http://host" + base::IntToString(i) + "/low"; 351 lows_differenthosts.push_back(NewRequest(url.c_str(), net::LOWEST)); 352 EXPECT_TRUE(lows_differenthosts[i]->started()); 353 } 354 355 scoped_ptr<TestRequest> last_differenthost(NewRequest("http://host_new/last", 356 net::LOWEST)); 357 EXPECT_FALSE(last_differenthost->started()); 358 } 359 360 TEST_F(ResourceSchedulerTest, RaisePriorityAndStart) { 361 // Dummies to enforce scheduling. 362 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 363 scoped_ptr<TestRequest> low(NewRequest("http://host/req", net::LOWEST)); 364 365 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST)); 366 EXPECT_FALSE(request->started()); 367 368 ChangeRequestPriority(request.get(), net::HIGHEST); 369 EXPECT_TRUE(request->started()); 370 } 371 372 TEST_F(ResourceSchedulerTest, RaisePriorityInQueue) { 373 // Dummies to enforce scheduling. 374 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 375 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); 376 377 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::IDLE)); 378 scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE)); 379 EXPECT_FALSE(request->started()); 380 EXPECT_FALSE(idle->started()); 381 382 ChangeRequestPriority(request.get(), net::LOWEST); 383 EXPECT_FALSE(request->started()); 384 EXPECT_FALSE(idle->started()); 385 386 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. 387 ScopedVector<TestRequest> lows; 388 for (int i = 0; i < kMaxNumDelayableRequestsPerClient - 1; ++i) { 389 string url = "http://host/low" + base::IntToString(i); 390 lows.push_back(NewRequest(url.c_str(), net::LOWEST)); 391 } 392 393 scheduler_.OnWillInsertBody(kChildId, kRouteId); 394 EXPECT_TRUE(request->started()); 395 EXPECT_FALSE(idle->started()); 396 } 397 398 TEST_F(ResourceSchedulerTest, LowerPriority) { 399 // Dummies to enforce scheduling. 400 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 401 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST)); 402 403 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST)); 404 scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE)); 405 EXPECT_FALSE(request->started()); 406 EXPECT_FALSE(idle->started()); 407 408 ChangeRequestPriority(request.get(), net::IDLE); 409 EXPECT_FALSE(request->started()); 410 EXPECT_FALSE(idle->started()); 411 412 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. 413 // 2 fewer filler requests: 1 for the "low" dummy at the start, and 1 for the 414 // one at the end, which will be tested. 415 const int kNumFillerRequests = kMaxNumDelayableRequestsPerClient - 2; 416 ScopedVector<TestRequest> lows; 417 for (int i = 0; i < kNumFillerRequests; ++i) { 418 string url = "http://host" + base::IntToString(i) + "/low"; 419 lows.push_back(NewRequest(url.c_str(), net::LOWEST)); 420 } 421 422 scheduler_.OnWillInsertBody(kChildId, kRouteId); 423 EXPECT_FALSE(request->started()); 424 EXPECT_TRUE(idle->started()); 425 } 426 427 TEST_F(ResourceSchedulerTest, ReprioritizedRequestGoesToBackOfQueue) { 428 // Dummies to enforce scheduling. 429 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 430 scoped_ptr<TestRequest> low(NewRequest("http://host/high", net::LOWEST)); 431 432 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST)); 433 scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE)); 434 EXPECT_FALSE(request->started()); 435 EXPECT_FALSE(idle->started()); 436 437 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc. 438 ScopedVector<TestRequest> lows; 439 for (int i = 0; i < kMaxNumDelayableRequestsPerClient; ++i) { 440 string url = "http://host/low" + base::IntToString(i); 441 lows.push_back(NewRequest(url.c_str(), net::LOWEST)); 442 } 443 444 ChangeRequestPriority(request.get(), net::IDLE); 445 EXPECT_FALSE(request->started()); 446 EXPECT_FALSE(idle->started()); 447 448 ChangeRequestPriority(request.get(), net::LOWEST); 449 EXPECT_FALSE(request->started()); 450 EXPECT_FALSE(idle->started()); 451 452 scheduler_.OnWillInsertBody(kChildId, kRouteId); 453 EXPECT_FALSE(request->started()); 454 EXPECT_FALSE(idle->started()); 455 } 456 457 TEST_F(ResourceSchedulerTest, NonHTTPSchedulesImmediately) { 458 // Dummies to enforce scheduling. 459 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST)); 460 scoped_ptr<TestRequest> low(NewRequest("http://host/high", net::LOWEST)); 461 462 scoped_ptr<TestRequest> request( 463 NewRequest("chrome-extension://req", net::LOWEST)); 464 EXPECT_TRUE(request->started()); 465 } 466 467 } // unnamed namespace 468 469 } // namespace content 470