Add coalescing timer to ResourceScheduler. CL 2 from BUG=128035.

This is a continuation of https://codereview.chromium.org/357583003

BUG=128035

Review URL: https://codereview.chromium.org/393163003

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@286372 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/content/browser/loader/resource_scheduler.cc b/content/browser/loader/resource_scheduler.cc
index fadbc29..85adbdb 100644
--- a/content/browser/loader/resource_scheduler.cc
+++ b/content/browser/loader/resource_scheduler.cc
@@ -22,6 +22,7 @@
 
 namespace content {
 
+static const size_t kCoalescedTimerPeriod = 5000;
 static const size_t kMaxNumDelayableRequestsPerClient = 10;
 static const size_t kMaxNumDelayableRequestsPerHost = 6;
 static const size_t kMaxNumThrottledRequestsPerClient = 1;
@@ -233,13 +234,22 @@
       : is_audible_(false),
         is_visible_(false),
         is_loaded_(false),
+        is_paused_(false),
         has_body_(false),
         using_spdy_proxy_(false),
         total_delayable_count_(0),
         throttle_state_(ResourceScheduler::THROTTLED) {
     scheduler_ = scheduler;
   }
-  ~Client() {}
+
+  ~Client() {
+    // Update to default state and pause to ensure the scheduler has a
+    // correct count of relevant types of clients.
+    is_visible_ = false;
+    is_audible_ = false;
+    is_paused_ = true;
+    UpdateThrottleState();
+  }
 
   void ScheduleRequest(
       net::URLRequest* url_request,
@@ -302,12 +312,19 @@
     UpdateThrottleState();
   }
 
+  void SetPaused() {
+    is_paused_ = true;
+    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 (is_paused_) {
+      SetThrottleState(PAUSED);
     } else if (!scheduler_->active_clients_loaded()) {
       SetThrottleState(THROTTLED);
     } else if (is_loaded_ && scheduler_->should_coalesce()) {
@@ -315,6 +332,7 @@
     } else if (!is_active()) {
       SetThrottleState(UNTHROTTLED);
     }
+
     if (throttle_state_ == old_throttle_state) {
       return;
     }
@@ -323,6 +341,11 @@
     } else if (old_throttle_state == ACTIVE_AND_LOADING) {
       scheduler_->DecrementActiveClientsLoading();
     }
+    if (throttle_state_ == COALESCED) {
+      scheduler_->IncrementCoalescedClients();
+    } else if (old_throttle_state == COALESCED) {
+      scheduler_->DecrementCoalescedClients();
+    }
   }
 
   void OnNavigate() {
@@ -384,6 +407,9 @@
       return;
     }
     throttle_state_ = throttle_state;
+    if (throttle_state_ != PAUSED) {
+      is_paused_ = false;
+    }
     LoadAnyStartablePendingRequests();
     // TODO(aiolos): Stop any started but not inflght requests when
     // switching to stricter throttle state?
@@ -624,6 +650,7 @@
   bool is_audible_;
   bool is_visible_;
   bool is_loaded_;
+  bool is_paused_;
   bool has_body_;
   bool using_spdy_proxy_;
   RequestQueue pending_requests_;
@@ -634,9 +661,13 @@
   ResourceScheduler::ClientThrottleState throttle_state_;
 };
 
-ResourceScheduler::ResourceScheduler(): should_coalesce_(false),
-                                        should_throttle_(false),
-                                        active_clients_loading_(0) {
+ResourceScheduler::ResourceScheduler()
+    : should_coalesce_(false),
+      should_throttle_(false),
+      active_clients_loading_(0),
+      coalesced_clients_(0),
+      coalescing_timer_(new base::Timer(true /* retain_user_task */,
+                                        true /* is_repeating */)) {
 }
 
 ResourceScheduler::~ResourceScheduler() {
@@ -648,7 +679,7 @@
                                                      bool should_coalesce) {
   should_coalesce_ = should_coalesce;
   should_throttle_ = should_throttle;
-  OnLoadingActiveClientsStateChanged();
+  OnLoadingActiveClientsStateChangedForAllClients();
 }
 
 ResourceScheduler::ClientThrottleState
@@ -722,7 +753,6 @@
     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.
@@ -818,7 +848,7 @@
   --active_clients_loading_;
   DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
   if (active_clients_loading_ == 0) {
-    OnLoadingActiveClientsStateChanged();
+    OnLoadingActiveClientsStateChangedForAllClients();
   }
 }
 
@@ -826,11 +856,11 @@
   ++active_clients_loading_;
   DCHECK_EQ(active_clients_loading_, CountActiveClientsLoading());
   if (active_clients_loading_ == 1) {
-    OnLoadingActiveClientsStateChanged();
+    OnLoadingActiveClientsStateChangedForAllClients();
   }
 }
 
-void ResourceScheduler::OnLoadingActiveClientsStateChanged() {
+void ResourceScheduler::OnLoadingActiveClientsStateChangedForAllClients() {
   ClientMap::iterator client_it = client_map_.begin();
   while (client_it != client_map_.end()) {
     Client* client = client_it->second;
@@ -839,9 +869,9 @@
   }
 }
 
-size_t ResourceScheduler::CountActiveClientsLoading() {
+size_t ResourceScheduler::CountActiveClientsLoading() const {
   size_t active_and_loading = 0;
-  ClientMap::iterator client_it = client_map_.begin();
+  ClientMap::const_iterator client_it = client_map_.begin();
   while (client_it != client_map_.end()) {
     Client* client = client_it->second;
     if (client->throttle_state() == ACTIVE_AND_LOADING) {
@@ -852,6 +882,53 @@
   return active_and_loading;
 }
 
+void ResourceScheduler::IncrementCoalescedClients() {
+  ++coalesced_clients_;
+  DCHECK(should_coalesce_);
+  DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
+  if (coalesced_clients_ == 1) {
+    coalescing_timer_->Start(
+        FROM_HERE,
+        base::TimeDelta::FromMilliseconds(kCoalescedTimerPeriod),
+        base::Bind(&ResourceScheduler::LoadCoalescedRequests,
+                   base::Unretained(this)));
+  }
+}
+
+void ResourceScheduler::DecrementCoalescedClients() {
+  DCHECK(should_coalesce_);
+  DCHECK_NE(0U, coalesced_clients_);
+  --coalesced_clients_;
+  DCHECK_EQ(coalesced_clients_, CountCoalescedClients());
+  if (coalesced_clients_ == 0) {
+    coalescing_timer_->Stop();
+  }
+}
+
+size_t ResourceScheduler::CountCoalescedClients() const {
+  DCHECK(should_coalesce_);
+  size_t coalesced_clients = 0;
+  ClientMap::const_iterator client_it = client_map_.begin();
+  while (client_it != client_map_.end()) {
+    Client* client = client_it->second;
+    if (client->throttle_state() == COALESCED) {
+      ++coalesced_clients;
+    }
+    ++client_it;
+  }
+  return coalesced_clients_;
+}
+
+void ResourceScheduler::LoadCoalescedRequests() {
+  DCHECK(should_coalesce_);
+  ClientMap::iterator client_it = client_map_.begin();
+  while (client_it != client_map_.end()) {
+    Client* client = client_it->second;
+    client->LoadCoalescedRequests();
+    ++client_it;
+  }
+}
+
 void ResourceScheduler::ReprioritizeRequest(ScheduledResourceRequest* request,
                                             net::RequestPriority new_priority,
                                             int new_intra_priority_value) {
diff --git a/content/browser/loader/resource_scheduler.h b/content/browser/loader/resource_scheduler.h
index b445a68..74ac16d 100644
--- a/content/browser/loader/resource_scheduler.h
+++ b/content/browser/loader/resource_scheduler.h
@@ -12,6 +12,7 @@
 #include "base/compiler_specific.h"
 #include "base/memory/scoped_ptr.h"
 #include "base/threading/non_thread_safe.h"
+#include "base/timer/timer.h"
 #include "content/common/content_export.h"
 #include "net/base/priority_queue.h"
 #include "net/base/request_priority.h"
@@ -53,6 +54,17 @@
 class CONTENT_EXPORT ResourceScheduler : public base::NonThreadSafe {
  public:
   enum ClientThrottleState {
+    // TODO(aiolos): Add logic to ShouldStartRequest for PAUSED Clients to only
+    // issue synchronous requests.
+    // TODO(aiolos): Add max number of THROTTLED Clients, and logic to set
+    // subsquent Clients to PAUSED instead. Also add logic to unpause a Client
+    // when a background Client becomes COALESCED (ie, finishes loading.)
+    // TODO(aiolos): Add tests for the above mentioned logic.
+
+    // Currently being deleted client.
+    // This state currently follows the same logic for loading requests as
+    // UNTHROTTLED/ACTIVE_AND_LOADING Clients. See above TODO's.
+    PAUSED,
     // Loaded background client, all observable clients loaded.
     COALESCED,
     // Background client, an observable client is loading.
@@ -69,6 +81,11 @@
   ResourceScheduler();
   ~ResourceScheduler();
 
+  // Use a mock timer when testing.
+  void set_timer_for_testing(scoped_ptr<base::Timer> timer) {
+    coalescing_timer_.reset(timer.release());
+  }
+
   // TODO(aiolos): Remove when throttling and coalescing have landed
   void SetThrottleOptionsForTesting(bool should_throttle, bool should_coalesce);
 
@@ -136,16 +153,26 @@
   // Called when a ScheduledResourceRequest is destroyed.
   void RemoveRequest(ScheduledResourceRequest* request);
 
-  // These Calls may update the ThrottleState of all clients, and have the
-  // potential to be re-entarant.
+  // These calls may update the ThrottleState of all clients, and have the
+  // potential to be re-entrant.
   // Called when a Client newly becomes active loading.
-  void DecrementActiveClientsLoading();
-  // Caled when a Client stops being active loading.
   void IncrementActiveClientsLoading();
+  // Called when an active and loading Client either completes loading or
+  // becomes inactive.
+  void DecrementActiveClientsLoading();
 
-  void OnLoadingActiveClientsStateChanged();
+  void OnLoadingActiveClientsStateChangedForAllClients();
 
-  size_t CountActiveClientsLoading();
+  size_t CountActiveClientsLoading() const;
+
+  // Called when a Client becomes coalesced.
+  void IncrementCoalescedClients();
+  // Called when a client stops being coalesced.
+  void DecrementCoalescedClients();
+
+  void LoadCoalescedRequests();
+
+  size_t CountCoalescedClients() const;
 
   // Update the queue position for |request|, possibly causing it to start
   // loading.
@@ -167,6 +194,9 @@
   bool should_throttle_;
   ClientMap client_map_;
   size_t active_clients_loading_;
+  size_t coalesced_clients_;
+  // This is a repeating timer to initiate requests on COALESCED Clients.
+  scoped_ptr<base::Timer> coalescing_timer_;
   RequestSet unowned_requests_;
 };
 
diff --git a/content/browser/loader/resource_scheduler_unittest.cc b/content/browser/loader/resource_scheduler_unittest.cc
index ea32a95d..e424c278 100644
--- a/content/browser/loader/resource_scheduler_unittest.cc
+++ b/content/browser/loader/resource_scheduler_unittest.cc
@@ -7,6 +7,8 @@
 #include "base/memory/scoped_vector.h"
 #include "base/message_loop/message_loop.h"
 #include "base/strings/string_number_conversions.h"
+#include "base/timer/mock_timer.h"
+#include "base/timer/timer.h"
 #include "content/browser/browser_thread_impl.h"
 #include "content/browser/loader/resource_dispatcher_host_impl.h"
 #include "content/browser/loader/resource_message_filter.h"
@@ -136,7 +138,13 @@
   ResourceSchedulerTest()
       : next_request_id_(0),
         ui_thread_(BrowserThread::UI, &message_loop_),
-        io_thread_(BrowserThread::IO, &message_loop_) {
+        io_thread_(BrowserThread::IO, &message_loop_),
+        mock_timer_(new base::MockTimer(true, true)) {
+    scheduler_.set_timer_for_testing(scoped_ptr<base::Timer>(mock_timer_));
+
+    // TODO(aiolos): Remove when throttling and coalescing have both landed.
+    scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                            false /* should_coalesce */);
 
     scheduler_.OnClientCreated(kChildId, kRouteId);
     scheduler_.OnVisibilityChanged(kChildId, kRouteId, true);
@@ -257,12 +265,18 @@
     rdh_.OnMessageReceived(msg, filter.get());
   }
 
+  void FireCoalescingTimer() {
+    EXPECT_TRUE(mock_timer_->IsRunning());
+    mock_timer_->Fire();
+  }
+
   int next_request_id_;
   base::MessageLoopForIO message_loop_;
   BrowserThreadImpl ui_thread_;
   BrowserThreadImpl io_thread_;
   ResourceDispatcherHostImpl rdh_;
   ResourceScheduler scheduler_;
+  base::MockTimer* mock_timer_;
   net::HttpServerPropertiesImpl http_server_properties_;
   net::TestURLRequestContext context_;
 };
@@ -1751,6 +1765,338 @@
   scheduler_.OnClientDeleted(kBackgroundChildId2, kBackgroundRouteId2);
 }
 
+TEST_F(ResourceSchedulerTest, CoalescedClientCreationStartsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+}
+
+TEST_F(ResourceSchedulerTest, ActiveLoadingClientLoadedAndHiddenStartsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  EXPECT_EQ(ResourceScheduler::ACTIVE_AND_LOADING,
+            scheduler_.GetClientStateForTesting(kChildId, kRouteId));
+  EXPECT_EQ(ResourceScheduler::THROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kChildId, kRouteId));
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  scheduler_.OnVisibilityChanged(kChildId, kRouteId, false);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kChildId, kRouteId));
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+}
+
+TEST_F(ResourceSchedulerTest, ActiveLoadingClientHiddenAndLoadedStartsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  EXPECT_EQ(ResourceScheduler::ACTIVE_AND_LOADING,
+            scheduler_.GetClientStateForTesting(kChildId, kRouteId));
+  EXPECT_EQ(ResourceScheduler::THROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  scheduler_.OnVisibilityChanged(kChildId, kRouteId, false);
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kChildId, kRouteId));
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kChildId, kRouteId));
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+}
+
+TEST_F(ResourceSchedulerTest, CoalescedClientBecomesAudibleStopsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnAudibilityChanged(kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_FALSE(mock_timer_->IsRunning());
+}
+
+TEST_F(ResourceSchedulerTest, LastCoalescedClientDeletionStopsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnClientCreated(kBackgroundChildId2, kBackgroundRouteId2);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId2, kBackgroundRouteId2, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId2,
+                                                kBackgroundRouteId2));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnClientDeleted(kBackgroundChildId, kBackgroundRouteId);
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnClientDeleted(kBackgroundChildId2, kBackgroundRouteId2);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  // To avoid errors on test tear down.
+  scheduler_.OnClientCreated(kBackgroundChildId, kBackgroundRouteId);
+}
+
+TEST_F(ResourceSchedulerTest, LastCoalescedClientStartsLoadingStopsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnClientCreated(kBackgroundChildId2, kBackgroundRouteId2);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId2, kBackgroundRouteId2, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId2,
+                                                kBackgroundRouteId2));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, false);
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId2, kBackgroundRouteId2, false);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  // This is needed to avoid errors on test tear down.
+  scheduler_.OnClientDeleted(kBackgroundChildId2, kBackgroundRouteId2);
+}
+
+TEST_F(ResourceSchedulerTest, LastCoalescedClientBecomesVisibleStopsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnClientCreated(kBackgroundChildId2, kBackgroundRouteId2);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId2, kBackgroundRouteId2, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId2,
+                                                kBackgroundRouteId2));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnVisibilityChanged(kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnVisibilityChanged(
+      kBackgroundChildId2, kBackgroundRouteId2, true);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  // To avoid errors on test tear down.
+  scheduler_.OnClientDeleted(kBackgroundChildId2, kBackgroundRouteId2);
+}
+
+TEST_F(ResourceSchedulerTest,
+       CoalescedClientBecomesLoadingAndVisibleStopsTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  EXPECT_FALSE(mock_timer_->IsRunning());
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(mock_timer_->IsRunning());
+
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, false);
+  EXPECT_EQ(ResourceScheduler::UNTHROTTLED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_FALSE(mock_timer_->IsRunning());
+
+  scheduler_.OnVisibilityChanged(kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::ACTIVE_AND_LOADING,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_FALSE(mock_timer_->IsRunning());
+}
+
+TEST_F(ResourceSchedulerTest, CoalescedRequestsIssueOnTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(scheduler_.active_clients_loaded());
+
+  scoped_ptr<TestRequest> high(
+      NewBackgroundRequest("http://host/high", net::HIGHEST));
+  scoped_ptr<TestRequest> low(
+      NewBackgroundRequest("http://host/low", net::LOWEST));
+  EXPECT_FALSE(high->started());
+  EXPECT_FALSE(low->started());
+
+  FireCoalescingTimer();
+
+  EXPECT_TRUE(high->started());
+  EXPECT_TRUE(low->started());
+}
+
+TEST_F(ResourceSchedulerTest, CoalescedRequestsUnthrottleCorrectlyOnTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(scheduler_.active_clients_loaded());
+
+  scoped_ptr<TestRequest> high(
+      NewBackgroundRequest("http://host/high", net::HIGHEST));
+  scoped_ptr<TestRequest> high2(
+      NewBackgroundRequest("http://host/high", net::HIGHEST));
+  scoped_ptr<TestRequest> high3(
+      NewBackgroundRequest("http://host/high", net::HIGHEST));
+  scoped_ptr<TestRequest> high4(
+      NewBackgroundRequest("http://host/high", net::HIGHEST));
+  scoped_ptr<TestRequest> low(
+      NewBackgroundRequest("http://host/low", net::LOWEST));
+  scoped_ptr<TestRequest> low2(
+      NewBackgroundRequest("http://host/low", net::LOWEST));
+  scoped_ptr<TestRequest> low3(
+      NewBackgroundRequest("http://host/low", net::LOWEST));
+  scoped_ptr<TestRequest> low4(
+      NewBackgroundRequest("http://host/low", net::LOWEST));
+
+  http_server_properties_.SetSupportsSpdy(net::HostPortPair("spdyhost", 443),
+                                          true);
+  scoped_ptr<TestRequest> low_spdy(
+      NewBackgroundRequest("https://spdyhost/low", net::LOW));
+  scoped_ptr<TestRequest> sync_request(
+      NewBackgroundSyncRequest("http://host/req", net::LOW));
+  scoped_ptr<TestRequest> non_http_request(
+      NewBackgroundRequest("chrome-extension://req", net::LOW));
+
+  // Sync requests should issue immediately.
+  EXPECT_TRUE(sync_request->started());
+  // Non-http(s) requests should issue immediately.
+  EXPECT_TRUE(non_http_request->started());
+  // Nothing else should issue without a timer fire.
+  EXPECT_FALSE(high->started());
+  EXPECT_FALSE(high2->started());
+  EXPECT_FALSE(high3->started());
+  EXPECT_FALSE(high4->started());
+  EXPECT_FALSE(low->started());
+  EXPECT_FALSE(low2->started());
+  EXPECT_FALSE(low3->started());
+  EXPECT_FALSE(low4->started());
+  EXPECT_FALSE(low_spdy->started());
+
+  FireCoalescingTimer();
+
+  // All high priority requests should issue.
+  EXPECT_TRUE(high->started());
+  EXPECT_TRUE(high2->started());
+  EXPECT_TRUE(high3->started());
+  EXPECT_TRUE(high4->started());
+  // There should only be one net::LOWEST priority request issued with
+  // non-delayable requests in flight.
+  EXPECT_TRUE(low->started());
+  EXPECT_FALSE(low2->started());
+  EXPECT_FALSE(low3->started());
+  EXPECT_FALSE(low4->started());
+  // Spdy-Enable requests should issue regardless of priority.
+  EXPECT_TRUE(low_spdy->started());
+}
+
+TEST_F(ResourceSchedulerTest, CoalescedRequestsWaitForNextTimer) {
+  scheduler_.SetThrottleOptionsForTesting(true /* should_throttle */,
+                                          true /* should_coalesce */);
+  scheduler_.OnLoadingStateChanged(kChildId, kRouteId, true);
+  scheduler_.OnLoadingStateChanged(
+      kBackgroundChildId, kBackgroundRouteId, true);
+
+  EXPECT_EQ(ResourceScheduler::COALESCED,
+            scheduler_.GetClientStateForTesting(kBackgroundChildId,
+                                                kBackgroundRouteId));
+  EXPECT_TRUE(scheduler_.active_clients_loaded());
+
+  scoped_ptr<TestRequest> high(
+      NewBackgroundRequest("http://host/high", net::HIGHEST));
+  EXPECT_FALSE(high->started());
+
+  FireCoalescingTimer();
+
+  scoped_ptr<TestRequest> high2(
+      NewBackgroundRequest("http://host/high2", net::HIGHEST));
+  scoped_ptr<TestRequest> low(
+      NewBackgroundRequest("http://host/low", net::LOWEST));
+
+  EXPECT_TRUE(high->started());
+  EXPECT_FALSE(high2->started());
+  EXPECT_FALSE(low->started());
+
+  FireCoalescingTimer();
+
+  EXPECT_TRUE(high->started());
+  EXPECT_TRUE(high2->started());
+  EXPECT_TRUE(low->started());
+}
+
 }  // unnamed namespace
 
 }  // namespace content