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.
5 #include "content/browser/loader/resource_scheduler.h"
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"
31 class TestRequestFactory;
33 const int kChildId = 30;
34 const int kRouteId = 75;
36 class TestRequest : public ResourceController {
38 TestRequest(scoped_ptr<ResourceThrottle> throttle,
39 scoped_ptr<net::URLRequest> url_request)
41 throttle_(throttle.Pass()),
42 url_request_(url_request.Pass()) {
43 throttle_->set_controller_for_testing(this);
46 bool started() const { return started_; }
49 bool deferred = false;
50 throttle_->WillStartRequest(&deferred);
54 const net::URLRequest* url_request() const { return url_request_.get(); }
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; }
65 scoped_ptr<ResourceThrottle> throttle_;
66 scoped_ptr<net::URLRequest> url_request_;
69 class CancelingTestRequest : public TestRequest {
71 CancelingTestRequest(scoped_ptr<ResourceThrottle> throttle,
72 scoped_ptr<net::URLRequest> url_request)
73 : TestRequest(throttle.Pass(), url_request.Pass()) {
76 void set_request_to_cancel(scoped_ptr<TestRequest> request_to_cancel) {
77 request_to_cancel_ = request_to_cancel.Pass();
81 virtual void Resume() OVERRIDE {
82 TestRequest::Resume();
83 request_to_cancel_.reset();
86 scoped_ptr<TestRequest> request_to_cancel_;
89 class FakeResourceContext : public ResourceContext {
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; }
97 class FakeResourceMessageFilter : public ResourceMessageFilter {
99 FakeResourceMessageFilter(int child_id)
100 : ResourceMessageFilter(
102 PROCESS_TYPE_RENDERER,
103 NULL /* appcache_service */,
104 NULL /* blob_storage_context */,
105 NULL /* file_system_context */,
106 NULL /* service_worker_context */,
107 base::Bind(&FakeResourceMessageFilter::GetContexts,
108 base::Unretained(this))) {
112 virtual ~FakeResourceMessageFilter() {}
114 void GetContexts(const ResourceHostMsg_Request& request,
115 ResourceContext** resource_context,
116 net::URLRequestContext** request_context) {
117 *resource_context = &context_;
118 *request_context = NULL;
121 FakeResourceContext context_;
124 class ResourceSchedulerTest : public testing::Test {
126 ResourceSchedulerTest()
127 : next_request_id_(0),
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());
134 virtual ~ResourceSchedulerTest() {
135 scheduler_.OnClientDeleted(kChildId, kRouteId);
138 scoped_ptr<net::URLRequest> NewURLRequestWithRoute(
140 net::RequestPriority priority,
142 scoped_ptr<net::URLRequest> url_request(
143 context_.CreateRequest(GURL(url), priority, NULL, NULL));
144 ResourceRequestInfoImpl* info = new ResourceRequestInfoImpl(
145 PROCESS_TYPE_RENDERER, // process_type
146 kChildId, // child_id
147 route_id, // route_id
149 ++next_request_id_, // request_id
150 MSG_ROUTING_NONE, // render_frame_id
151 false, // is_main_frame
152 false, // parent_is_main_frame
153 0, // parent_render_frame_id
154 ResourceType::SUB_RESOURCE, // resource_type
155 PAGE_TRANSITION_LINK, // transition_type
156 false, // should_replace_current_entry
157 false, // is_download
159 true, // allow_download
160 false, // has_user_gesture
161 blink::WebReferrerPolicyDefault, // referrer_policy
162 blink::WebPageVisibilityStateVisible, // visibility_state
164 base::WeakPtr<ResourceMessageFilter>(), // filter
166 info->AssociateWithRequest(url_request.get());
167 return url_request.Pass();
170 scoped_ptr<net::URLRequest> NewURLRequest(const char* url,
171 net::RequestPriority priority) {
172 return NewURLRequestWithRoute(url, priority, kRouteId);
175 TestRequest* NewRequestWithRoute(const char* url,
176 net::RequestPriority priority,
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());
187 TestRequest* NewRequest(const char* url, net::RequestPriority priority) {
188 return NewRequestWithRoute(url, priority, kRouteId);
191 void ChangeRequestPriority(TestRequest* request,
192 net::RequestPriority new_priority,
193 int intra_priority = 0) {
194 scoped_refptr<FakeResourceMessageFilter> filter(
195 new FakeResourceMessageFilter(kChildId));
196 const ResourceRequestInfoImpl* info = ResourceRequestInfoImpl::ForRequest(
197 request->url_request());
198 const GlobalRequestID& id = info->GetGlobalRequestID();
199 ResourceHostMsg_DidChangePriority msg(id.request_id, new_priority,
202 rdh_.OnMessageReceived(msg, filter.get(), &ok);
206 int next_request_id_;
207 base::MessageLoopForIO message_loop_;
208 BrowserThreadImpl ui_thread_;
209 BrowserThreadImpl io_thread_;
210 ResourceDispatcherHostImpl rdh_;
211 ResourceScheduler scheduler_;
212 net::HttpServerPropertiesImpl http_server_properties_;
213 net::TestURLRequestContext context_;
216 TEST_F(ResourceSchedulerTest, OneIsolatedLowRequest) {
217 scoped_ptr<TestRequest> request(NewRequest("http://host/1", net::LOWEST));
218 EXPECT_TRUE(request->started());
221 TEST_F(ResourceSchedulerTest, OneLowLoadsUntilIdle) {
222 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
223 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
224 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
225 EXPECT_TRUE(high->started());
226 EXPECT_TRUE(low->started());
227 EXPECT_FALSE(low2->started());
229 EXPECT_TRUE(low2->started());
232 TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInserted) {
233 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
234 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
235 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
236 EXPECT_TRUE(high->started());
237 EXPECT_TRUE(low->started());
238 EXPECT_FALSE(low2->started());
239 scheduler_.OnWillInsertBody(kChildId, kRouteId);
240 EXPECT_TRUE(low2->started());
243 TEST_F(ResourceSchedulerTest, OneLowLoadsUntilBodyInsertedExceptSpdy) {
244 http_server_properties_.SetSupportsSpdy(
245 net::HostPortPair("spdyhost", 443), true);
246 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
247 scoped_ptr<TestRequest> low_spdy(
248 NewRequest("https://spdyhost/high", net::LOWEST));
249 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
250 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
251 EXPECT_TRUE(high->started());
252 EXPECT_TRUE(low_spdy->started());
253 EXPECT_TRUE(low->started());
254 EXPECT_FALSE(low2->started());
255 scheduler_.OnWillInsertBody(kChildId, kRouteId);
256 EXPECT_TRUE(low2->started());
259 TEST_F(ResourceSchedulerTest, NavigationResetsState) {
260 scheduler_.OnWillInsertBody(kChildId, kRouteId);
261 scheduler_.OnNavigate(kChildId, kRouteId);
262 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
263 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
264 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
265 EXPECT_TRUE(high->started());
266 EXPECT_TRUE(low->started());
267 EXPECT_FALSE(low2->started());
270 TEST_F(ResourceSchedulerTest, BackgroundRequestStartsImmediately) {
271 const int route_id = 0; // Indicates a background request.
272 scoped_ptr<TestRequest> request(NewRequestWithRoute("http://host/1",
273 net::LOWEST, route_id));
274 EXPECT_TRUE(request->started());
277 TEST_F(ResourceSchedulerTest, StartMultipleLowRequestsWhenIdle) {
278 scoped_ptr<TestRequest> high1(NewRequest("http://host/high1", net::HIGHEST));
279 scoped_ptr<TestRequest> high2(NewRequest("http://host/high2", net::HIGHEST));
280 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
281 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
282 EXPECT_TRUE(high1->started());
283 EXPECT_TRUE(high2->started());
284 EXPECT_TRUE(low->started());
285 EXPECT_FALSE(low2->started());
287 EXPECT_FALSE(low2->started());
289 EXPECT_TRUE(low2->started());
292 TEST_F(ResourceSchedulerTest, CancelOtherRequestsWhileResuming) {
293 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
294 scoped_ptr<TestRequest> low1(NewRequest("http://host/low1", net::LOWEST));
296 scoped_ptr<net::URLRequest> url_request(
297 NewURLRequest("http://host/low2", net::LOWEST));
298 scoped_ptr<ResourceThrottle> throttle(scheduler_.ScheduleRequest(
299 kChildId, kRouteId, url_request.get()));
300 scoped_ptr<CancelingTestRequest> low2(new CancelingTestRequest(
301 throttle.Pass(), url_request.Pass()));
304 scoped_ptr<TestRequest> low3(NewRequest("http://host/low3", net::LOWEST));
305 low2->set_request_to_cancel(low3.Pass());
306 scoped_ptr<TestRequest> low4(NewRequest("http://host/low4", net::LOWEST));
308 EXPECT_TRUE(high->started());
309 EXPECT_FALSE(low2->started());
311 EXPECT_TRUE(low1->started());
312 EXPECT_TRUE(low2->started());
313 EXPECT_TRUE(low4->started());
316 TEST_F(ResourceSchedulerTest, LimitedNumberOfDelayableRequestsInFlight) {
317 // We only load low priority resources if there's a body.
318 scheduler_.OnWillInsertBody(kChildId, kRouteId);
320 // Throw in one high priority request to make sure that's not a factor.
321 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
322 EXPECT_TRUE(high->started());
324 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
325 const int kMaxNumDelayableRequestsPerHost = 6;
326 ScopedVector<TestRequest> lows_singlehost;
327 // Queue up to the per-host limit (we subtract the current high-pri request).
328 for (int i = 0; i < kMaxNumDelayableRequestsPerHost - 1; ++i) {
329 string url = "http://host/low" + base::IntToString(i);
330 lows_singlehost.push_back(NewRequest(url.c_str(), net::LOWEST));
331 EXPECT_TRUE(lows_singlehost[i]->started());
334 scoped_ptr<TestRequest> second_last_singlehost(NewRequest("http://host/last",
336 scoped_ptr<TestRequest> last_singlehost(NewRequest("http://host/s_last",
339 EXPECT_FALSE(second_last_singlehost->started());
341 EXPECT_TRUE(second_last_singlehost->started());
342 EXPECT_FALSE(last_singlehost->started());
343 lows_singlehost.erase(lows_singlehost.begin());
344 EXPECT_TRUE(last_singlehost->started());
346 // Queue more requests from different hosts until we reach the total limit.
347 int expected_slots_left =
348 kMaxNumDelayableRequestsPerClient - kMaxNumDelayableRequestsPerHost;
349 EXPECT_GT(expected_slots_left, 0);
350 ScopedVector<TestRequest> lows_differenthosts;
351 for (int i = 0; i < expected_slots_left; ++i) {
352 string url = "http://host" + base::IntToString(i) + "/low";
353 lows_differenthosts.push_back(NewRequest(url.c_str(), net::LOWEST));
354 EXPECT_TRUE(lows_differenthosts[i]->started());
357 scoped_ptr<TestRequest> last_differenthost(NewRequest("http://host_new/last",
359 EXPECT_FALSE(last_differenthost->started());
362 TEST_F(ResourceSchedulerTest, RaisePriorityAndStart) {
363 // Dummies to enforce scheduling.
364 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
365 scoped_ptr<TestRequest> low(NewRequest("http://host/req", net::LOWEST));
367 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST));
368 EXPECT_FALSE(request->started());
370 ChangeRequestPriority(request.get(), net::HIGHEST);
371 EXPECT_TRUE(request->started());
374 TEST_F(ResourceSchedulerTest, RaisePriorityInQueue) {
375 // Dummies to enforce scheduling.
376 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
377 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
379 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::IDLE));
380 scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE));
381 EXPECT_FALSE(request->started());
382 EXPECT_FALSE(idle->started());
384 ChangeRequestPriority(request.get(), net::LOWEST);
385 EXPECT_FALSE(request->started());
386 EXPECT_FALSE(idle->started());
388 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
389 ScopedVector<TestRequest> lows;
390 for (int i = 0; i < kMaxNumDelayableRequestsPerClient - 1; ++i) {
391 string url = "http://host/low" + base::IntToString(i);
392 lows.push_back(NewRequest(url.c_str(), net::LOWEST));
395 scheduler_.OnWillInsertBody(kChildId, kRouteId);
396 EXPECT_TRUE(request->started());
397 EXPECT_FALSE(idle->started());
400 TEST_F(ResourceSchedulerTest, LowerPriority) {
401 // Dummies to enforce scheduling.
402 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
403 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
405 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST));
406 scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE));
407 EXPECT_FALSE(request->started());
408 EXPECT_FALSE(idle->started());
410 ChangeRequestPriority(request.get(), net::IDLE);
411 EXPECT_FALSE(request->started());
412 EXPECT_FALSE(idle->started());
414 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
415 // 2 fewer filler requests: 1 for the "low" dummy at the start, and 1 for the
416 // one at the end, which will be tested.
417 const int kNumFillerRequests = kMaxNumDelayableRequestsPerClient - 2;
418 ScopedVector<TestRequest> lows;
419 for (int i = 0; i < kNumFillerRequests; ++i) {
420 string url = "http://host" + base::IntToString(i) + "/low";
421 lows.push_back(NewRequest(url.c_str(), net::LOWEST));
424 scheduler_.OnWillInsertBody(kChildId, kRouteId);
425 EXPECT_FALSE(request->started());
426 EXPECT_TRUE(idle->started());
429 TEST_F(ResourceSchedulerTest, ReprioritizedRequestGoesToBackOfQueue) {
430 // Dummies to enforce scheduling.
431 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
432 scoped_ptr<TestRequest> low(NewRequest("http://host/high", net::LOWEST));
434 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::LOWEST));
435 scoped_ptr<TestRequest> idle(NewRequest("http://host/idle", net::IDLE));
436 EXPECT_FALSE(request->started());
437 EXPECT_FALSE(idle->started());
439 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
440 ScopedVector<TestRequest> lows;
441 for (int i = 0; i < kMaxNumDelayableRequestsPerClient; ++i) {
442 string url = "http://host/low" + base::IntToString(i);
443 lows.push_back(NewRequest(url.c_str(), net::LOWEST));
446 ChangeRequestPriority(request.get(), net::IDLE);
447 EXPECT_FALSE(request->started());
448 EXPECT_FALSE(idle->started());
450 ChangeRequestPriority(request.get(), net::LOWEST);
451 EXPECT_FALSE(request->started());
452 EXPECT_FALSE(idle->started());
454 scheduler_.OnWillInsertBody(kChildId, kRouteId);
455 EXPECT_FALSE(request->started());
456 EXPECT_FALSE(idle->started());
459 TEST_F(ResourceSchedulerTest, HigherIntraPriorityGoesToFrontOfQueue) {
460 // Dummies to enforce scheduling.
461 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
462 scoped_ptr<TestRequest> low(NewRequest("http://host/high", net::LOWEST));
464 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
465 ScopedVector<TestRequest> lows;
466 for (int i = 0; i < kMaxNumDelayableRequestsPerClient; ++i) {
467 string url = "http://host/low" + base::IntToString(i);
468 lows.push_back(NewRequest(url.c_str(), net::IDLE));
471 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::IDLE));
472 EXPECT_FALSE(request->started());
474 ChangeRequestPriority(request.get(), net::IDLE, 1);
475 EXPECT_FALSE(request->started());
477 scheduler_.OnWillInsertBody(kChildId, kRouteId);
478 EXPECT_TRUE(request->started());
481 TEST_F(ResourceSchedulerTest, NonHTTPSchedulesImmediately) {
482 // Dummies to enforce scheduling.
483 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
484 scoped_ptr<TestRequest> low(NewRequest("http://host/high", net::LOWEST));
486 scoped_ptr<TestRequest> request(
487 NewRequest("chrome-extension://req", net::LOWEST));
488 EXPECT_TRUE(request->started());
491 TEST_F(ResourceSchedulerTest, SpdyProxySchedulesImmediately) {
492 scoped_ptr<TestRequest> high(NewRequest("http://host/high", net::HIGHEST));
493 scoped_ptr<TestRequest> low(NewRequest("http://host/low", net::LOWEST));
495 scoped_ptr<TestRequest> request(NewRequest("http://host/req", net::IDLE));
496 EXPECT_FALSE(request->started());
498 scheduler_.OnReceivedSpdyProxiedHttpResponse(kChildId, kRouteId);
499 EXPECT_TRUE(request->started());
501 scoped_ptr<TestRequest> after(NewRequest("http://host/after", net::IDLE));
502 EXPECT_TRUE(after->started());
505 TEST_F(ResourceSchedulerTest, NewSpdyHostInDelayableRequests) {
506 scheduler_.OnWillInsertBody(kChildId, kRouteId);
507 const int kMaxNumDelayableRequestsPerClient = 10; // Should match the .cc.
509 scoped_ptr<TestRequest> low1_spdy(
510 NewRequest("http://spdyhost1:8080/low", net::LOWEST));
511 // Cancel a request after we learn the server supports SPDY.
512 ScopedVector<TestRequest> lows;
513 for (int i = 0; i < kMaxNumDelayableRequestsPerClient - 1; ++i) {
514 string url = "http://host" + base::IntToString(i) + "/low";
515 lows.push_back(NewRequest(url.c_str(), net::LOWEST));
517 scoped_ptr<TestRequest> low1(NewRequest("http://host/low", net::LOWEST));
518 EXPECT_FALSE(low1->started());
519 http_server_properties_.SetSupportsSpdy(
520 net::HostPortPair("spdyhost1", 8080), true);
522 EXPECT_TRUE(low1->started());
525 scoped_ptr<TestRequest> low2_spdy(
526 NewRequest("http://spdyhost2:8080/low", net::IDLE));
527 // Reprioritize a request after we learn the server supports SPDY.
528 EXPECT_TRUE(low2_spdy->started());
529 http_server_properties_.SetSupportsSpdy(
530 net::HostPortPair("spdyhost2", 8080), true);
531 ChangeRequestPriority(low2_spdy.get(), net::LOWEST);
532 scoped_ptr<TestRequest> low2(NewRequest("http://host/low", net::LOWEST));
533 EXPECT_TRUE(low2->started());
536 } // unnamed namespace
538 } // namespace content