Chromium Code Reviews| Index: content/browser/loader/resource_scheduler.cc |
| diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..68abb1b53c79c72332e0dd49679c363059a6481b |
| --- /dev/null |
| +++ b/content/browser/loader/resource_scheduler.cc |
| @@ -0,0 +1,216 @@ |
| +// Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| +// Use of this source code is governed by a BSD-style license that can be |
| +// found in the LICENSE file. |
| + |
| +#include "content/browser/loader/resource_scheduler.h" |
| + |
| +#include "base/stl_util.h" |
| +#include "content/public/browser/resource_controller.h" |
| +#include "content/public/browser/resource_throttle.h" |
| +#include "net/base/load_flags.h" |
| +#include "net/base/request_priority.h" |
| +#include "net/url_request/url_request.h" |
| + |
| +namespace content { |
| + |
| +namespace { |
| + |
| +ResourceScheduler::ClientId MakeClientId(int child_id, int route_id) { |
| + return (static_cast<ResourceScheduler::ClientId>(child_id) << 32) | route_id; |
| +} |
| + |
| +} // unnamed namespace |
| + |
| +class ResourceScheduler::ScheduledResourceRequest : public ResourceThrottle { |
| + public: |
| + ScheduledResourceRequest(const ClientId& client_id, |
| + const net::URLRequest& request, |
| + ResourceScheduler* scheduler) |
| + : client_id_(client_id), |
| + request_(request), |
| + ready_(false), |
| + deferred_(false), |
| + scheduler_(scheduler) { |
| + } |
| + |
| + virtual ~ScheduledResourceRequest() { |
| + scheduler_->RemoveRequest(this); |
| + } |
| + |
| + void Start() { |
| + ready_ = true; |
| + if (deferred_) { |
| + controller()->Resume(); |
| + } |
| + } |
| + |
| + const ClientId& client_id() const { return client_id_; } |
| + const net::URLRequest& url_request() const { return request_; } |
| + |
| + private: |
| + virtual void WillStartRequest(bool* defer) OVERRIDE { |
| + deferred_ = *defer = !ready_; |
| + } |
| + |
| + ClientId client_id_; |
| + const net::URLRequest& request_; |
| + bool ready_; |
| + bool deferred_; |
| + ResourceScheduler* scheduler_; |
| + |
| + DISALLOW_COPY_AND_ASSIGN(ScheduledResourceRequest); |
| +}; |
| + |
| +ResourceScheduler::ResourceScheduler() { |
| + DetachFromThread(); // Construction happens on the main thread. |
| +} |
| + |
| +ResourceScheduler::~ResourceScheduler() { |
| + DetachFromThread(); // Destruction happens on the main thread. |
| +} |
| + |
| +scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest( |
| + int child_id, |
| + int route_id, |
| + const net::URLRequest& url_request) { |
| + DCHECK(CalledOnValidThread()); |
| + ClientId client_id = MakeClientId(child_id, route_id); |
| + scoped_ptr<ScheduledResourceRequest> request( |
| + new ScheduledResourceRequest(client_id, url_request, this)); |
| + |
| + if (route_id == 0 || !ContainsKey(client_map_, client_id)) { |
| + // <a ping> requests don't belong to a specific page. Most tests don't call |
| + // OnCreate et. al. as needed to register Clients either. |
| + unowned_requests_.insert(request.get()); |
| + request->Start(); |
| + return request.PassAs<ResourceThrottle>(); |
| + } |
| + |
| + Client* client = client_map_[client_id]; |
| + DCHECK(!client->has_closed); |
| + |
| + // Treat synchronous requests as high priority. |
| + bool is_low_priority = url_request.priority() < net::MEDIUM && |
| + !(url_request.load_flags() & net::LOAD_IGNORE_LIMITS); |
| + |
| + if (is_low_priority && !client->in_flight_requests.empty() && |
| + !client->has_painted) { |
| + client->pending_requests.push_back(request.get()); |
| + } else { |
| + StartRequest(request.get(), client); |
| + } |
| + return request.PassAs<ResourceThrottle>(); |
| +} |
| + |
| +void ResourceScheduler::RemoveRequest(ScheduledResourceRequest* request) { |
| + DCHECK(CalledOnValidThread()); |
| + if (ContainsKey(unowned_requests_, request)) { |
| + unowned_requests_.erase(request); |
| + return; |
| + } |
| + |
| + DCHECK(ContainsKey(client_map_, request->client_id())); |
| + Client* client = client_map_[request->client_id()]; |
| + RequestSet::iterator it = client->in_flight_requests.find(request); |
| + if (it == client->in_flight_requests.end()) { |
| + bool removed = false; |
| + RequestQueue::iterator queue_it; |
| + for (queue_it = client->pending_requests.begin(); |
| + queue_it != client->pending_requests.end(); ++queue_it) { |
| + if (*queue_it == request) { |
| + client->pending_requests.erase(queue_it); |
| + removed = true; |
| + break; |
| + } |
| + } |
| + DCHECK(removed); |
| + DCHECK(!ContainsKey(client->in_flight_requests, request)); |
| + } else { |
| + size_t erased = client->in_flight_requests.erase(request); |
| + DCHECK(erased); |
| + } |
| + |
| + if (client->has_closed) { |
| + if (client->in_flight_requests.empty() |
| + && client->pending_requests.empty()) { |
| + delete client; |
| + client_map_.erase(request->client_id()); |
| + } |
| + } else if (client->in_flight_requests.empty()) { |
| + LoadPendingRequests(client); |
| + } |
| +} |
| + |
| +void ResourceScheduler::OnCreate(int child_id, int route_id) { |
| + DCHECK(CalledOnValidThread()); |
| + ClientId client_id = MakeClientId(child_id, route_id); |
| + if (!ContainsKey(client_map_, client_id)) { |
| + client_map_[client_id] = new Client; |
| + } |
| +} |
| + |
| +void ResourceScheduler::OnNavigate(int child_id, int route_id) { |
| + DCHECK(CalledOnValidThread()); |
| + ClientId client_id = MakeClientId(child_id, route_id); |
| + if (!ContainsKey(client_map_, client_id)) { |
| + return; |
|
darin (slow to review)
2012/12/18 21:09:56
nit: What happens if a navigation occurs before On
|
| + } |
| + |
| + Client* client = client_map_[client_id]; |
| + client->has_painted = false; |
| +} |
| + |
| +void ResourceScheduler::OnFirstPaint(int child_id, int route_id) { |
| + DCHECK(CalledOnValidThread()); |
| + ClientId client_id = MakeClientId(child_id, route_id); |
| + if (!ContainsKey(client_map_, client_id)) { |
| + return; |
|
darin (slow to review)
2012/12/18 21:09:56
ditto
|
| + } |
| + |
| + Client* client = client_map_[client_id]; |
| + if (!client->has_painted) { |
| + client->has_painted = true; |
| + LoadPendingRequests(client); |
| + } |
| +} |
| + |
| +void ResourceScheduler::OnDestroy(int child_id, int route_id) { |
| + DCHECK(CalledOnValidThread()); |
| + ClientId client_id = MakeClientId(child_id, route_id); |
| + if (!ContainsKey(client_map_, client_id)) { |
| + return; |
|
darin (slow to review)
2012/12/18 21:09:56
ditto
|
| + } |
| + |
| + Client* client = client_map_[client_id]; |
| + if (client->pending_requests.empty() && client->in_flight_requests.empty()) { |
| + delete client; |
| + client_map_.erase(client_id); |
| + } else { |
| + client->has_closed = true; |
| + } |
| +} |
| + |
| +void ResourceScheduler::StartRequest(ScheduledResourceRequest* request, |
| + Client* client) { |
| + client->in_flight_requests.insert(request); |
| + request->Start(); |
| +} |
| + |
| +void ResourceScheduler::LoadPendingRequests(Client* client) { |
| + RequestQueue::iterator it; |
| + for (it = client->pending_requests.begin(); |
| + it != client->pending_requests.end(); ++it) { |
| + StartRequest(*it, client); |
| + } |
| + client->pending_requests.clear(); |
| +} |
| + |
| +ResourceScheduler::Client::Client() |
| + : has_painted(false), |
| + has_closed(false) { |
| +} |
| + |
| +ResourceScheduler::Client::~Client() { |
| +} |
| + |
| +} // namespace content |