| Index: content/browser/loader/resource_scheduler.cc | 
| diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc | 
| index 680ddfee3c5944aa913af4162c872a47736a095c..f21dd4badfadafc10f5b15e2dce73e9e003200d3 100644 | 
| --- a/content/browser/loader/resource_scheduler.cc | 
| +++ b/content/browser/loader/resource_scheduler.cc | 
| @@ -24,7 +24,7 @@ namespace content { | 
|  | 
| static const size_t kMaxNumDelayableRequestsPerClient = 10; | 
| static const size_t kMaxNumDelayableRequestsPerHost = 6; | 
| - | 
| +static const size_t kMaxNumThrottledRequestsPerClient = 1; | 
|  | 
| struct ResourceScheduler::RequestPriorityParams { | 
| RequestPriorityParams() | 
| @@ -229,10 +229,16 @@ void ResourceScheduler::RequestQueue::Insert( | 
| // Each client represents a tab. | 
| class ResourceScheduler::Client { | 
| public: | 
| -  Client() | 
| -      : has_body_(false), | 
| +  explicit Client(ResourceScheduler* scheduler) | 
| +      : is_audible_(false), | 
| +        is_visible_(false), | 
| +        is_loaded_(false), | 
| +        has_body_(false), | 
| using_spdy_proxy_(false), | 
| -        total_delayable_count_(0) {} | 
| +        total_delayable_count_(0), | 
| +        throttle_state_(ResourceScheduler::THROTTLED) { | 
| +    scheduler_ = scheduler; | 
| +  } | 
| ~Client() {} | 
|  | 
| void ScheduleRequest( | 
| @@ -268,8 +274,60 @@ class ResourceScheduler::Client { | 
| return unowned_requests; | 
| } | 
|  | 
| +  bool is_active() const { return is_visible_ || is_audible_; } | 
| + | 
| +  bool is_loaded() const { return is_loaded_; } | 
| + | 
| +  void OnAudibilityChanged(bool is_audible) { | 
| +    if (is_audible == is_audible_) { | 
| +      return; | 
| +    } | 
| +    is_audible_ = is_audible; | 
| +    UpdateThrottleState(); | 
| +  } | 
| + | 
| +  void OnVisibilityChanged(bool is_visible) { | 
| +    if (is_visible == is_visible_) { | 
| +      return; | 
| +    } | 
| +    is_visible_ = is_visible; | 
| +    UpdateThrottleState(); | 
| +  } | 
| + | 
| +  void OnLoadingStateChanged(bool is_loaded) { | 
| +    if (is_loaded == is_loaded_) { | 
| +      return; | 
| +    } | 
| +    is_loaded_ = is_loaded; | 
| +    UpdateThrottleState(); | 
| +  } | 
| + | 
| +  void UpdateThrottleState() { | 
| +    ClientThrottleState old_throttle_state = throttle_state_; | 
| +    if (is_active() && !is_loaded_) { | 
| +      SetThrottleState(ACTIVE_AND_LOADING); | 
| +    } else if (is_active()) { | 
| +      SetThrottleState(UNTHROTTLED); | 
| +    } else if (!scheduler_->active_clients_loaded()) { | 
| +      SetThrottleState(THROTTLED); | 
| +    } else if (is_loaded_ && scheduler_->should_coalesce()) { | 
| +      SetThrottleState(COALESCED); | 
| +    } else if (!is_active()) { | 
| +      SetThrottleState(UNTHROTTLED); | 
| +    } | 
| +    if (throttle_state_ == old_throttle_state) { | 
| +      return; | 
| +    } | 
| +    if (throttle_state_ == ACTIVE_AND_LOADING) { | 
| +      scheduler_->IncrementActiveClientsLoading(); | 
| +    } else if (old_throttle_state == ACTIVE_AND_LOADING) { | 
| +      scheduler_->DecrementActiveClientsLoading(); | 
| +    } | 
| +  } | 
| + | 
| void OnNavigate() { | 
| has_body_ = false; | 
| +    is_loaded_ = false; | 
| } | 
|  | 
| void OnWillInsertBody() { | 
| @@ -307,6 +365,47 @@ class ResourceScheduler::Client { | 
| } | 
| } | 
|  | 
| +  // Called on Client creation, when a Client changes user observability, | 
| +  // possibly when all observable Clients have finished loading, and | 
| +  // possibly when this Client has finished loading. | 
| +  // State changes: | 
| +  // Client became observable. | 
| +  //   any state -> UNTHROTTLED | 
| +  // Client is unobservable, but all observable clients finished loading. | 
| +  //   THROTTLED -> UNTHROTTLED | 
| +  // Non-observable client finished loading. | 
| +  //   THROTTLED || UNTHROTTLED -> COALESCED | 
| +  // Non-observable client, an observable client starts loading. | 
| +  //   COALESCED -> THROTTLED | 
| +  // A COALESCED client will transition into UNTHROTTLED when the network is | 
| +  // woken up by a heartbeat and then transition back into COALESCED. | 
| +  void SetThrottleState(ResourceScheduler::ClientThrottleState throttle_state) { | 
| +    if (throttle_state == throttle_state_) { | 
| +      return; | 
| +    } | 
| +    throttle_state_ = throttle_state; | 
| +    LoadAnyStartablePendingRequests(); | 
| +    // TODO(aiolos): Stop any started but not inflght requests when | 
| +    // switching to stricter throttle state? | 
| +  } | 
| + | 
| +  ResourceScheduler::ClientThrottleState throttle_state() const { | 
| +    return throttle_state_; | 
| +  } | 
| + | 
| +  void LoadCoalescedRequests() { | 
| +    if (throttle_state_ != COALESCED) { | 
| +      return; | 
| +    } | 
| +    if (scheduler_->active_clients_loaded()) { | 
| +      SetThrottleState(UNTHROTTLED); | 
| +    } else { | 
| +      SetThrottleState(THROTTLED); | 
| +    } | 
| +    LoadAnyStartablePendingRequests(); | 
| +    SetThrottleState(COALESCED); | 
| +  } | 
| + | 
| private: | 
| enum ShouldStartReqResult { | 
| DO_NOT_START_REQUEST_AND_STOP_SEARCHING, | 
| @@ -379,46 +478,73 @@ class ResourceScheduler::Client { | 
|  | 
| // ShouldStartRequest is the main scheduling algorithm. | 
| // | 
| -  // Requests are categorized into two categories: | 
| -  // | 
| -  // 1. Immediately issued requests, which are: | 
| +  // Requests are categorized into three categories: | 
| // | 
| -  //   * Higher priority requests (>= net::LOW). | 
| +  // 1. Non-delayable requests: | 
| //   * Synchronous requests. | 
| -  //   * Requests to SPDY-capable origin servers. | 
| //   * Non-HTTP[S] requests. | 
| // | 
| -  // 2. The remainder are delayable requests, which follow these rules: | 
| +  // 2. Requests to SPDY-capable origin servers. | 
| // | 
| +  // 3. High-priority requests: | 
| +  //   * Higher priority requests (>= net::LOW). | 
| +  // | 
| +  // 4. Low priority requests | 
| +  // | 
| +  //  The following rules are followed: | 
| +  // | 
| +  //  ACTIVE_AND_LOADING and UNTHROTTLED Clients follow these rules: | 
| +  //   * Non-delayable, High-priority and SDPY capable requests are issued | 
| +  //     immediately | 
| //   * If no high priority requests are in flight, start loading low priority | 
| -  //      requests. | 
| +  //     requests. | 
| +  //   * Low priority requests are delayable. | 
| //   * Once the renderer has a <body>, start loading delayable requests. | 
| //   * Never exceed 10 delayable requests in flight per client. | 
| //   * Never exceed 6 delayable requests for a given host. | 
| //   * Prior to <body>, allow one delayable request to load at a time. | 
| +  // | 
| +  //  THROTTLED Clients follow these rules: | 
| +  //   * Non-delayable and SPDY-capable requests are issued immediately. | 
| +  //   * At most one non-SPDY request will be issued per THROTTLED Client | 
| +  //   * If no high priority requests are in flight, start loading low priority | 
| +  //     requests. | 
| +  // | 
| +  //  COALESCED Clients never load requests, with the following exceptions: | 
| +  //   * Non-delayable requests are issued imediately. | 
| +  //   * On a (currently 5 second) heart beat, they load all requests as an | 
| +  //     UNTHROTTLED Client, and then return to the COALESCED state. | 
| +  //   * When an active Client makes a request, they are THROTTLED until the | 
| +  //     active Client finishes loading. | 
| ShouldStartReqResult ShouldStartRequest( | 
| ScheduledResourceRequest* request) const { | 
| const net::URLRequest& url_request = *request->url_request(); | 
| +    // Syncronous requests could block the entire render, which could impact | 
| +    // user-observable Clients. | 
| +    if (!ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) { | 
| +      return START_REQUEST; | 
| +    } | 
| + | 
| // TODO(simonjam): This may end up causing disk contention. We should | 
| // experiment with throttling if that happens. | 
| +    // TODO(aiolos): We probably want to Coalesce these as well to avoid | 
| +    // waking the disk. | 
| if (!url_request.url().SchemeIsHTTPOrHTTPS()) { | 
| return START_REQUEST; | 
| } | 
|  | 
| -    if (using_spdy_proxy_ && url_request.url().SchemeIs("http")) { | 
| -      return START_REQUEST; | 
| +    if (throttle_state_ == COALESCED) { | 
| +      return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; | 
| } | 
|  | 
| -    net::HttpServerProperties& http_server_properties = | 
| -        *url_request.context()->http_server_properties(); | 
| - | 
| -    if (url_request.priority() >= net::LOW || | 
| -        !ResourceRequestInfo::ForRequest(&url_request)->IsAsync()) { | 
| +    if (using_spdy_proxy_ && url_request.url().SchemeIs("http")) { | 
| return START_REQUEST; | 
| } | 
|  | 
| net::HostPortPair host_port_pair = | 
| net::HostPortPair::FromURL(url_request.url()); | 
| +    net::HttpServerProperties& http_server_properties = | 
| +        *url_request.context()->http_server_properties(); | 
|  | 
| // TODO(willchan): We should really improve this algorithm as described in | 
| // crbug.com/164101. Also, theoretically we should not count a SPDY request | 
| @@ -427,6 +553,16 @@ class ResourceScheduler::Client { | 
| return START_REQUEST; | 
| } | 
|  | 
| +    if (throttle_state_ == THROTTLED && | 
| +        in_flight_requests_.size() >= kMaxNumThrottledRequestsPerClient) { | 
| +      // There may still be SPDY-capable requests that should be issued. | 
| +      return DO_NOT_START_REQUEST_AND_KEEP_SEARCHING; | 
| +    } | 
| + | 
| +    if (url_request.priority() >= net::LOW) { | 
| +      return START_REQUEST; | 
| +    } | 
| + | 
| size_t num_delayable_requests_in_flight = total_delayable_count_; | 
| if (num_delayable_requests_in_flight >= kMaxNumDelayableRequestsPerClient) { | 
| return DO_NOT_START_REQUEST_AND_STOP_SEARCHING; | 
| @@ -485,15 +621,20 @@ class ResourceScheduler::Client { | 
| } | 
| } | 
|  | 
| +  bool is_audible_; | 
| +  bool is_visible_; | 
| +  bool is_loaded_; | 
| bool has_body_; | 
| bool using_spdy_proxy_; | 
| RequestQueue pending_requests_; | 
| RequestSet in_flight_requests_; | 
| +  ResourceScheduler* scheduler_; | 
| // The number of delayable in-flight requests. | 
| size_t total_delayable_count_; | 
| +  ResourceScheduler::ClientThrottleState throttle_state_; | 
| }; | 
|  | 
| -ResourceScheduler::ResourceScheduler() { | 
| +ResourceScheduler::ResourceScheduler() : active_clients_loading_(0) { | 
| } | 
|  | 
| ResourceScheduler::~ResourceScheduler() { | 
| @@ -501,6 +642,20 @@ ResourceScheduler::~ResourceScheduler() { | 
| DCHECK(client_map_.empty()); | 
| } | 
|  | 
| +void ResourceScheduler::SetThrottleOptionsForTesting(bool should_throttle, | 
| +                                                     bool should_coalesce) { | 
| +  should_coalesce_ = should_coalesce; | 
| +  should_throttle_ = should_throttle; | 
| +  OnLoadingActiveClientsStateChanged(); | 
| +} | 
| + | 
| +ResourceScheduler::ClientThrottleState | 
| +ResourceScheduler::GetClientStateForTesting(int child_id, int route_id) { | 
| +  Client* client = GetClient(child_id, route_id); | 
| +  DCHECK(client); | 
| +  return client->throttle_state(); | 
| +} | 
| + | 
| scoped_ptr<ResourceThrottle> ResourceScheduler::ScheduleRequest( | 
| int child_id, | 
| int route_id, | 
| @@ -548,7 +703,12 @@ void ResourceScheduler::OnClientCreated(int child_id, int route_id) { | 
| ClientId client_id = MakeClientId(child_id, route_id); | 
| DCHECK(!ContainsKey(client_map_, client_id)); | 
|  | 
| -  client_map_[client_id] = new Client; | 
| +  Client* client = new Client(this); | 
| +  client_map_[client_id] = client; | 
| + | 
| +  // TODO(aiolos): set Client visibility/audibility when signals are added | 
| +  // this will UNTHROTTLE Clients as needed | 
| +  client->UpdateThrottleState(); | 
| } | 
|  | 
| void ResourceScheduler::OnClientDeleted(int child_id, int route_id) { | 
| @@ -560,7 +720,7 @@ void ResourceScheduler::OnClientDeleted(int child_id, int route_id) { | 
| return; | 
|  | 
| Client* client = it->second; | 
| - | 
| +  client->OnLoadingStateChanged(true); | 
| // FYI, ResourceDispatcherHost cancels all of the requests after this function | 
| // is called. It should end up canceling all of the requests except for a | 
| // cross-renderer navigation. | 
| @@ -617,6 +777,79 @@ void ResourceScheduler::OnReceivedSpdyProxiedHttpResponse( | 
| client->OnReceivedSpdyProxiedHttpResponse(); | 
| } | 
|  | 
| +void ResourceScheduler::OnAudibilityChanged(int child_id, | 
| +                                            int route_id, | 
| +                                            bool is_audible) { | 
| +  Client* client = GetClient(child_id, route_id); | 
| +  DCHECK(client); | 
| +  client->OnAudibilityChanged(is_audible); | 
| +} | 
| + | 
| +void ResourceScheduler::OnVisibilityChanged(int child_id, | 
| +                                            int route_id, | 
| +                                            bool is_visible) { | 
| +  Client* client = GetClient(child_id, route_id); | 
| +  DCHECK(client); | 
| +  client->OnVisibilityChanged(is_visible); | 
| +} | 
| + | 
| +void ResourceScheduler::OnLoadingStateChanged(int child_id, | 
| +                                              int route_id, | 
| +                                              bool is_loaded) { | 
| +  Client* client = GetClient(child_id, route_id); | 
| +  DCHECK(client); | 
| +  client->OnLoadingStateChanged(is_loaded); | 
| +} | 
| + | 
| +ResourceScheduler::Client* ResourceScheduler::GetClient(int child_id, | 
| +                                                        int route_id) { | 
| +  ClientId client_id = MakeClientId(child_id, route_id); | 
| +  ClientMap::iterator client_it = client_map_.find(client_id); | 
| +  if (client_it == client_map_.end()) { | 
| +    return NULL; | 
| +  } | 
| +  return client_it->second; | 
| +} | 
| + | 
| +void ResourceScheduler::DecrementActiveClientsLoading() { | 
| +  DCHECK_NE(0u, active_clients_loading_); | 
| +  --active_clients_loading_; | 
| +  DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading()); | 
| +  if (active_clients_loading_ == 0) { | 
| +    OnLoadingActiveClientsStateChanged(); | 
| +  } | 
| +} | 
| + | 
| +void ResourceScheduler::IncrementActiveClientsLoading() { | 
| +  ++active_clients_loading_; | 
| +  DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading()); | 
| +  if (active_clients_loading_ == 1) { | 
| +    OnLoadingActiveClientsStateChanged(); | 
| +  } | 
| +} | 
| + | 
| +void ResourceScheduler::OnLoadingActiveClientsStateChanged() { | 
| +  ClientMap::iterator client_it = client_map_.begin(); | 
| +  while (client_it != client_map_.end()) { | 
| +    Client* client = client_it->second; | 
| +    client->UpdateThrottleState(); | 
| +    ++client_it; | 
| +  } | 
| +} | 
| + | 
| +size_t ResourceScheduler::CountActiveClientsLoading() { | 
| +  size_t active_and_loading = 0; | 
| +  ClientMap::iterator client_it = client_map_.begin(); | 
| +  while (client_it != client_map_.end()) { | 
| +    Client* client = client_it->second; | 
| +    if (client->throttle_state() == ACTIVE_AND_LOADING) { | 
| +      ++active_and_loading; | 
| +    } | 
| +    ++client_it; | 
| +  } | 
| +  return active_and_loading; | 
| +} | 
| + | 
| void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request, | 
| net::RequestPriority new_priority, | 
| int new_intra_priority_value) { | 
|  |