Change the way lazy background pages shutdown.

The browser process now keeps a generic count of activity from the lazy background page. This will be used for all types of activity, such as outstanding events, resource requests, and API calls.

BUG=81752
TEST=no

Review URL: https://chromiumcodereview.appspot.com/9447042

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@123821 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/extension_event_router.cc b/chrome/browser/extensions/extension_event_router.cc
index 4c4b8f69..c92521e 100644
--- a/chrome/browser/extensions/extension_event_router.cc
+++ b/chrome/browser/extensions/extension_event_router.cc
@@ -259,11 +259,9 @@
   if (extension->has_background_page() &&
       !extension->background_page_persists()) {
     ExtensionProcessManager* pm = profile_->GetExtensionProcessManager();
-
-    // TODO(mpcomplete): this is incorrect. We need to check whether the page
-    // has finished loading. If not, we can't dispatch the event (because the
-    // listener hasn't been set up yet).
-    if (!pm->GetBackgroundHostForExtension(extension->id()))
+    ExtensionHost* background_host =
+        pm->GetBackgroundHostForExtension(extension->id());
+    if (!background_host || !background_host->did_stop_loading())
       return false;
   }
 
@@ -369,18 +367,20 @@
 }
 
 void ExtensionEventRouter::IncrementInFlightEvents(const Extension* extension) {
-  if (!extension->background_page_persists())
-    in_flight_events_[extension->id()]++;
+  if (!extension->background_page_persists()) {
+    profile_->GetExtensionProcessManager()->IncrementLazyKeepaliveCount(
+        extension);
+  }
 }
 
 void ExtensionEventRouter::OnExtensionEventAck(
     const std::string& extension_id) {
-  CHECK(in_flight_events_[extension_id] > 0);
-  in_flight_events_[extension_id]--;
-}
-
-bool ExtensionEventRouter::HasInFlightEvents(const std::string& extension_id) {
-  return in_flight_events_[extension_id] > 0;
+  const Extension* extension =
+      profile_->GetExtensionService()->extensions()->GetByID(extension_id);
+  if (extension && !extension->background_page_persists()) {
+    profile_->GetExtensionProcessManager()->DecrementLazyKeepaliveCount(
+        extension);
+  }
 }
 
 void ExtensionEventRouter::AppendEvent(
@@ -403,8 +403,18 @@
   CHECK(!extension_id.empty());
   PendingEventsPerExtMap::const_iterator map_it =
       pending_events_.find(extension_id);
-  if (map_it == pending_events_.end())
+  if (map_it == pending_events_.end()) {
+    NOTREACHED();  // lazy page should not load without any pending events
     return;
+  }
+
+  // Temporarily increment the keepalive count while dispatching the events.
+  // This also ensures that if no events were dispatched, the extension returns
+  // to "idle" and is shut down.
+  const Extension* extension =
+      profile_->GetExtensionService()->extensions()->GetByID(extension_id);
+  ExtensionProcessManager* pm = profile_->GetExtensionProcessManager();
+  pm->IncrementLazyKeepaliveCount(extension);
 
   PendingEventsList* events_list = map_it->second.get();
   for (PendingEventsList::const_iterator it = events_list->begin();
@@ -414,9 +424,7 @@
   events_list->clear();
   pending_events_.erase(extension_id);
 
-  // Check if the extension is idle, which may be the case if no events were
-  // successfully dispatched.
-  profile_->GetExtensionProcessManager()->OnExtensionIdle(extension_id);
+  pm->DecrementLazyKeepaliveCount(extension);
 }
 
 void ExtensionEventRouter::Observe(
@@ -452,6 +460,7 @@
       if (eh->extension_host_type() ==
               chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE &&
           !eh->extension()->background_page_persists()) {
+        CHECK(eh->did_stop_loading());
         DispatchPendingEvents(eh->extension_id());
       }
       break;
diff --git a/chrome/browser/extensions/extension_event_router.h b/chrome/browser/extensions/extension_event_router.h
index daef554..80e6df0 100644
--- a/chrome/browser/extensions/extension_event_router.h
+++ b/chrome/browser/extensions/extension_event_router.h
@@ -103,10 +103,6 @@
   // Record the Event Ack from the renderer. (One less event in-flight.)
   void OnExtensionEventAck(const std::string& extension_id);
 
-  // Check if there are any Extension Events that have not yet been acked by
-  // the renderer.
-  bool HasInFlightEvents(const std::string& extension_id);
-
  protected:
   // The details of an event to be dispatched.
   struct ExtensionEvent;
@@ -179,7 +175,6 @@
   // Track of the number of dispatched events that have not yet sent an
   // ACK from the renderer.
   void IncrementInFlightEvents(const Extension* extension);
-  std::map<std::string, int> in_flight_events_;
 
   DISALLOW_COPY_AND_ASSIGN(ExtensionEventRouter);
 };
diff --git a/chrome/browser/extensions/extension_function.cc b/chrome/browser/extensions/extension_function.cc
index 0519aa5..4299d5c5 100644
--- a/chrome/browser/extensions/extension_function.cc
+++ b/chrome/browser/extensions/extension_function.cc
@@ -151,6 +151,8 @@
 }
 
 UIThreadExtensionFunction::~UIThreadExtensionFunction() {
+  if (dispatcher())
+    dispatcher()->OnExtensionFunctionCompleted(GetExtension());
 }
 
 UIThreadExtensionFunction*
diff --git a/chrome/browser/extensions/extension_function.h b/chrome/browser/extensions/extension_function.h
index bbf89c9..b595fb5 100644
--- a/chrome/browser/extensions/extension_function.h
+++ b/chrome/browser/extensions/extension_function.h
@@ -287,7 +287,7 @@
  private:
   // Helper class to track the lifetime of ExtensionFunction's RenderViewHost
   // pointer and NULL it out when it dies. It also allows us to filter IPC
-  // messages comming from the RenderViewHost. We use this separate class
+  // messages coming from the RenderViewHost. We use this separate class
   // (instead of implementing NotificationObserver on ExtensionFunction) because
   // it is/ common for subclasses of ExtensionFunction to be
   // NotificationObservers, and it would be an easy error to forget to call the
diff --git a/chrome/browser/extensions/extension_function_dispatcher.cc b/chrome/browser/extensions/extension_function_dispatcher.cc
index e16c810fe..9191e2d 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.cc
+++ b/chrome/browser/extensions/extension_function_dispatcher.cc
@@ -728,6 +728,19 @@
     function->OnQuotaExceeded();
     LogFailure(extension, params.name, kQuotaExceeded);
   }
+
+  // We only adjust the keepalive count for UIThreadExtensionFunction for
+  // now, largely for simplicity's sake. This is OK because currently, only
+  // the webRequest API uses IOThreadExtensionFunction, and that API is not
+  // compatible with lazy background pages.
+  profile()->GetExtensionProcessManager()->IncrementLazyKeepaliveCount(
+      extension);
+}
+
+void ExtensionFunctionDispatcher::OnExtensionFunctionCompleted(
+    const Extension* extension) {
+  profile()->GetExtensionProcessManager()->DecrementLazyKeepaliveCount(
+      extension);
 }
 
 // static
diff --git a/chrome/browser/extensions/extension_function_dispatcher.h b/chrome/browser/extensions/extension_function_dispatcher.h
index 120c7b1..cb429e0b 100644
--- a/chrome/browser/extensions/extension_function_dispatcher.h
+++ b/chrome/browser/extensions/extension_function_dispatcher.h
@@ -100,6 +100,10 @@
   void Dispatch(const ExtensionHostMsg_Request_Params& params,
                 RenderViewHost* sender);
 
+  // Called when an ExtensionFunction is done executing, after it has sent
+  // a response (if any) to the extension.
+  void OnExtensionFunctionCompleted(const Extension* extension);
+
   // Returns the current browser. Callers should generally prefer
   // ExtensionFunction::GetCurrentBrowser() over this method, as that one
   // provides the correct value for |include_incognito|.
diff --git a/chrome/browser/extensions/extension_host.cc b/chrome/browser/extensions/extension_host.cc
index 3eae9ca..3b91af0 100644
--- a/chrome/browser/extensions/extension_host.cc
+++ b/chrome/browser/extensions/extension_host.cc
@@ -131,7 +131,8 @@
       ALLOW_THIS_IN_INITIALIZER_LIST(
           extension_function_dispatcher_(profile_, this)),
       extension_host_type_(host_type),
-      associated_web_contents_(NULL) {
+      associated_web_contents_(NULL),
+      close_sequence_id_(0) {
   host_contents_.reset(WebContents::Create(
       profile_, site_instance, MSG_ROUTING_NONE, NULL, NULL));
   content::WebContentsObserver::Observe(host_contents_.get());
@@ -214,6 +215,25 @@
   }
 }
 
+void ExtensionHost::SendShouldClose() {
+  CHECK(!extension()->background_page_persists());
+  render_view_host()->Send(new ExtensionMsg_ShouldClose(
+      extension()->id(), ++close_sequence_id_));
+  // TODO(mpcomplete): start timeout
+}
+
+void ExtensionHost::CancelShouldClose() {
+  CHECK(!extension()->background_page_persists());
+  ++close_sequence_id_;
+}
+
+void ExtensionHost::OnShouldCloseAck(int sequence_id) {
+  CHECK(!extension()->background_page_persists());
+  if (sequence_id != close_sequence_id_)
+    return;
+  Close();
+}
+
 const Browser* ExtensionHost::GetBrowser() const {
   return view() ? view()->browser() : NULL;
 }
@@ -240,6 +260,13 @@
       std::string());
 }
 
+void ExtensionHost::Close() {
+  content::NotificationService::current()->Notify(
+      chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
+      content::Source<Profile>(profile_),
+      content::Details<ExtensionHost>(this));
+}
+
 void ExtensionHost::Observe(int type,
                             const content::NotificationSource& source,
                             const content::NotificationDetails& details) {
@@ -380,16 +407,14 @@
 }
 
 void ExtensionHost::CloseContents(WebContents* contents) {
+  // TODO(mpcomplete): is this check really necessary?
   if (extension_host_type_ == chrome::VIEW_TYPE_EXTENSION_POPUP ||
       extension_host_type_ == chrome::VIEW_TYPE_EXTENSION_DIALOG ||
       extension_host_type_ == chrome::VIEW_TYPE_EXTENSION_BACKGROUND_PAGE ||
       extension_host_type_ == chrome::VIEW_TYPE_EXTENSION_INFOBAR ||
       extension_host_type_ == chrome::VIEW_TYPE_APP_SHELL ||
       extension_host_type_ == chrome::VIEW_TYPE_PANEL) {
-    content::NotificationService::current()->Notify(
-        chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
-        content::Source<Profile>(profile_),
-        content::Details<ExtensionHost>(this));
+    Close();
   }
 }
 
@@ -433,10 +458,7 @@
   if (extension_host_type_ == chrome::VIEW_TYPE_EXTENSION_POPUP) {
     if (event.type == NativeWebKeyboardEvent::RawKeyDown &&
         event.windowsKeyCode == ui::VKEY_ESCAPE) {
-      content::NotificationService::current()->Notify(
-          chrome::NOTIFICATION_EXTENSION_HOST_VIEW_SHOULD_CLOSE,
-          content::Source<Profile>(profile_),
-          content::Details<ExtensionHost>(this));
+      Close();
       return;
     }
   }
@@ -545,7 +567,6 @@
   browser::Navigate(&params);
 }
 
-
 void ExtensionHost::RenderViewReady() {
   content::NotificationService::current()->Notify(
       chrome::NOTIFICATION_EXTENSION_HOST_CREATED,
diff --git a/chrome/browser/extensions/extension_host.h b/chrome/browser/extensions/extension_host.h
index 2a5d1d34..6d27b68 100644
--- a/chrome/browser/extensions/extension_host.h
+++ b/chrome/browser/extensions/extension_host.h
@@ -95,6 +95,21 @@
   // Helper variant of the above for cases where no Browser is present.
   void CreateViewWithoutBrowser();
 
+  // Send a message to the renderer to notify it we are about to close.
+  // This is a simple ping that the renderer will respond to. The purpose
+  // is to control sequencing: if the extension remains idle until the renderer
+  // responds with an ACK, then we know that the extension process is ready to
+  // shut down.
+  void SendShouldClose();
+
+  // Cancels the current close sequence. Any future Close ACKs will be ignored
+  // (unless SendShouldClose is called again).
+  void CancelShouldClose();
+
+  // Handles the close ACK. The sequence ID lets us identify whether we have
+  // cancelled this close sequence.
+  void OnShouldCloseAck(int sequence_id);
+
   const Extension* extension() const { return extension_; }
   const std::string& extension_id() const { return extension_id_; }
   content::WebContents* host_contents() const { return host_contents_.get(); }
@@ -179,6 +194,9 @@
   // Navigates to the initial page.
   void LoadInitialURL();
 
+  // Closes this host (results in deletion).
+  void Close();
+
   // Const version of below function.
   const Browser* GetBrowser() const;
 
@@ -233,8 +251,7 @@
 
   ExtensionFunctionDispatcher extension_function_dispatcher_;
 
-  // Only EXTENSION_INFOBAR, EXTENSION_POPUP, and EXTENSION_BACKGROUND_PAGE
-  // are used here, others are not hosted by ExtensionHost.
+  // The type of view being hosted.
   content::ViewType extension_host_type_;
 
   // The relevant WebContents associated with this ExtensionHost, if any.
@@ -243,6 +260,10 @@
   // Used to measure how long it's been since the host was created.
   PerfTimer since_created_;
 
+  // A unique ID associated with each call to ShouldClose. This allows us
+  // to differentiate which ShouldClose message the renderer is responding to.
+  int close_sequence_id_;
+
   DISALLOW_COPY_AND_ASSIGN(ExtensionHost);
 };
 
diff --git a/chrome/browser/extensions/extension_process_manager.cc b/chrome/browser/extensions/extension_process_manager.cc
index 9e824dbc..f249cf4 100644
--- a/chrome/browser/extensions/extension_process_manager.cc
+++ b/chrome/browser/extensions/extension_process_manager.cc
@@ -37,6 +37,22 @@
 
 namespace {
 
+// An accessor for an extension's lazy_keepalive_count.
+// TODO(mpcomplete): Should we store this on ExtensionHost instead?
+static base::LazyInstance<base::PropertyAccessor<int> >
+    g_property_accessor = LAZY_INSTANCE_INITIALIZER;
+
+int& GetLazyKeepaliveCount(Profile* profile, const Extension* extension) {
+  base::PropertyBag* bag =
+      profile->GetExtensionService()->GetPropertyBag(extension);
+  int* count = g_property_accessor.Get().GetProperty(bag);
+  if (!count) {
+    g_property_accessor.Get().SetProperty(bag, 0);
+    count = g_property_accessor.Get().GetProperty(bag);
+  }
+  return *count;
+}
+
 // Incognito profiles use this process manager. It is mostly a shim that decides
 // whether to fall back on the original profile's ExtensionProcessManager based
 // on whether a given extension uses "split" or "spanning" incognito behavior.
@@ -290,13 +306,58 @@
   return all_hosts_.find(host) != all_hosts_.end();
 }
 
-void ExtensionProcessManager::OnExtensionIdle(const std::string& extension_id) {
+int ExtensionProcessManager::GetLazyKeepaliveCount(const Extension* extension) {
+  if (extension->background_page_persists())
+    return 0;
+
+  return ::GetLazyKeepaliveCount(GetProfile(), extension);
+}
+
+int ExtensionProcessManager::IncrementLazyKeepaliveCount(
+     const Extension* extension) {
+  if (extension->background_page_persists())
+    return 0;
+
+  // TODO(mpcomplete): Handle visible views changing.
+  int& count = ::GetLazyKeepaliveCount(GetProfile(), extension);
+  if (++count == 1)
+    OnLazyBackgroundPageActive(extension->id());
+
+  return count;
+}
+
+int ExtensionProcessManager::DecrementLazyKeepaliveCount(
+     const Extension* extension) {
+  if (extension->background_page_persists())
+    return 0;
+
+  int& count = ::GetLazyKeepaliveCount(GetProfile(), extension);
+  DCHECK(count > 0);
+  if (--count == 0)
+    OnLazyBackgroundPageIdle(extension->id());
+
+  return count;
+}
+
+void ExtensionProcessManager::OnLazyBackgroundPageIdle(
+    const std::string& extension_id) {
   ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
-  if (host && !HasVisibleViews(extension_id)) {
-    Profile* profile = GetProfile();
-    if (!profile->GetExtensionEventRouter()->HasInFlightEvents(extension_id))
-      CloseBackgroundHost(host);
-  }
+  if (host && !HasVisibleViews(extension_id))
+    host->SendShouldClose();
+}
+
+void ExtensionProcessManager::OnLazyBackgroundPageActive(
+    const std::string& extension_id) {
+  ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
+  if (host)
+    host->CancelShouldClose();
+}
+
+void ExtensionProcessManager::OnShouldCloseAck(
+     const std::string& extension_id, int sequence_id) {
+  ExtensionHost* host = GetBackgroundHostForExtension(extension_id);
+  if (host)
+    host->OnShouldCloseAck(sequence_id);
 }
 
 bool ExtensionProcessManager::HasVisibleViews(const std::string& extension_id) {
diff --git a/chrome/browser/extensions/extension_process_manager.h b/chrome/browser/extensions/extension_process_manager.h
index 41f7c60..bbf2109f 100644
--- a/chrome/browser/extensions/extension_process_manager.h
+++ b/chrome/browser/extensions/extension_process_manager.h
@@ -89,9 +89,22 @@
   // Returns true if |host| is managed by this process manager.
   bool HasExtensionHost(ExtensionHost* host) const;
 
-  // Called when the render reports that the extension is idle (only if
-  // lazy background pages are enabled).
-  void OnExtensionIdle(const std::string& extension_id);
+  // Getter and setter for the lazy background page's keepalive count. This is
+  // the count of how many outstanding "things" are keeping the page alive.
+  // When this reaches 0, we will begin the process of shutting down the page.
+  // "Things" include pending events, resource loads, and API calls.
+  int GetLazyKeepaliveCount(const Extension* extension);
+  int IncrementLazyKeepaliveCount(const Extension* extension);
+  int DecrementLazyKeepaliveCount(const Extension* extension);
+
+  // These are called when the extension transitions between idle and active.
+  // They control the process of closing the background page when idle.
+  void OnLazyBackgroundPageIdle(const std::string& extension_id);
+  void OnLazyBackgroundPageActive(const std::string& extension_id);
+
+  // Handle a response to the ShouldClose message, used for lazy background
+  // pages.
+  void OnShouldCloseAck(const std::string& extension_id, int sequence_id);
 
   typedef std::set<ExtensionHost*> ExtensionHostSet;
   typedef ExtensionHostSet::const_iterator const_iterator;
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 3bc6a49..1698552 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -2528,7 +2528,8 @@
 }
 
 bool ExtensionService::IsBackgroundPageReady(const Extension* extension) {
-  return (!extension->has_background_page() ||
+  return (!(extension->has_background_page() &&
+            extension->background_page_persists()) ||
           extension_runtime_data_[extension->id()].background_page_ready);
 }
 
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index 819dc19..86bb3b0 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -263,9 +263,9 @@
   bool GetBrowserActionVisibility(const Extension* extension);
   void SetBrowserActionVisibility(const Extension* extension, bool visible);
 
-  // Whether the background page, if any, is ready. We don't load other
-  // components until then. If there is no background page, we consider it to
-  // be ready.
+  // Whether the persistent background page, if any, is ready. We don't load
+  // other components until then. If there is no background page, or if it is
+  // non-persistent (lazy), we consider it to be ready.
   bool IsBackgroundPageReady(const Extension* extension);
   void SetBackgroundPageReady(const Extension* extension);
 
diff --git a/chrome/browser/extensions/lazy_background_page_apitest.cc b/chrome/browser/extensions/lazy_background_page_apitest.cc
index c23d307..593a422 100644
--- a/chrome/browser/extensions/lazy_background_page_apitest.cc
+++ b/chrome/browser/extensions/lazy_background_page_apitest.cc
@@ -45,18 +45,24 @@
     ExtensionApiTest::SetUpCommandLine(command_line);
     command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis);
   }
+
+  // Loads the extension, which temporarily starts the lazy background page
+  // to dispatch the onInstalled event. We wait until it shuts down again.
+  const Extension* LoadExtensionAndWait(const std::string& test_name) {
+    LazyBackgroundObserver page_complete;
+    FilePath extdir = test_data_dir_.AppendASCII("lazy_background_page").
+        AppendASCII(test_name);
+    const Extension* extension = LoadExtension(extdir);
+    if (extension)
+      page_complete.Wait();
+    return extension;
+  }
 };
 
 IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, BrowserActionCreateTab) {
-  FilePath extdir = test_data_dir_.AppendASCII("lazy_background_page").
-      AppendASCII("browser_action_create_tab");
-  ASSERT_TRUE(LoadExtension(extdir));
+  ASSERT_TRUE(LoadExtensionAndWait("browser_action_create_tab"));
 
   // Lazy Background Page doesn't exist yet.
-  // Note: We actually loaded and destroyed the page to dispatch the onInstalled
-  // event. LoadExtension waits for the load to finish, after which onInstalled
-  // is dispatched. Since the extension isn't listening to it, we immediately
-  // tear it down again.
   ExtensionProcessManager* pm =
       browser()->profile()->GetExtensionProcessManager();
   EXPECT_FALSE(pm->GetBackgroundHostForExtension(last_loaded_extension_id_));
@@ -77,9 +83,7 @@
 
 IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest,
                        BrowserActionCreateTabAfterCallback) {
-  FilePath extdir = test_data_dir_.AppendASCII("lazy_background_page").
-      AppendASCII("browser_action_with_callback");
-  ASSERT_TRUE(LoadExtension(extdir));
+  ASSERT_TRUE(LoadExtensionAndWait("browser_action_with_callback"));
 
   // Lazy Background Page doesn't exist yet.
   ExtensionProcessManager* pm =
@@ -101,9 +105,7 @@
 IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, BroadcastEvent) {
   ASSERT_TRUE(StartTestServer());
 
-  FilePath extdir = test_data_dir_.AppendASCII("lazy_background_page").
-      AppendASCII("broadcast_event");
-  const Extension* extension = LoadExtension(extdir);
+  const Extension* extension = LoadExtensionAndWait("broadcast_event");
   ASSERT_TRUE(extension);
 
   // Lazy Background Page doesn't exist yet.
@@ -131,16 +133,12 @@
                 GetLocationBarForTesting()->PageActionVisibleCount());
 }
 
+// Tests that the lazy background page receives the onInstalled event and shuts
+// down.
 IN_PROC_BROWSER_TEST_F(LazyBackgroundPageApiTest, OnInstalled) {
-  LazyBackgroundObserver page_complete;
   ResultCatcher catcher;
-  FilePath extdir = test_data_dir_.AppendASCII("lazy_background_page").
-      AppendASCII("on_installed");
-  const Extension* extension = LoadExtension(extdir);
-  ASSERT_TRUE(extension);
-
+  ASSERT_TRUE(LoadExtensionAndWait("on_installed"));
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
-  page_complete.Wait();
 
   // Lazy Background Page has been shut down.
   ExtensionProcessManager* pm =
diff --git a/chrome/browser/renderer_host/chrome_render_message_filter.cc b/chrome/browser/renderer_host/chrome_render_message_filter.cc
index 5dff4e01..1563b5e8 100644
--- a/chrome/browser/renderer_host/chrome_render_message_filter.cc
+++ b/chrome/browser/renderer_host/chrome_render_message_filter.cc
@@ -90,11 +90,12 @@
                         OnExtensionAddLazyListener)
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_RemoveLazyListener,
                         OnExtensionRemoveLazyListener)
-    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExtensionIdle, OnExtensionIdle)
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_ExtensionEventAck, OnExtensionEventAck)
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_CloseChannel, OnExtensionCloseChannel)
     IPC_MESSAGE_HANDLER(ExtensionHostMsg_RequestForIOThread,
                         OnExtensionRequestForIOThread)
+    IPC_MESSAGE_HANDLER(ExtensionHostMsg_ShouldCloseAck,
+                        OnExtensionShouldCloseAck)
 #if defined(USE_TCMALLOC)
     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_RendererTcmalloc, OnRendererTcmalloc)
     IPC_MESSAGE_HANDLER(ChromeViewHostMsg_WriteTcmallocHeapProfile_ACK,
@@ -139,9 +140,9 @@
     case ExtensionHostMsg_RemoveListener::ID:
     case ExtensionHostMsg_AddLazyListener::ID:
     case ExtensionHostMsg_RemoveLazyListener::ID:
-    case ExtensionHostMsg_ExtensionIdle::ID:
     case ExtensionHostMsg_ExtensionEventAck::ID:
     case ExtensionHostMsg_CloseChannel::ID:
+    case ExtensionHostMsg_ShouldCloseAck::ID:
     case ChromeViewHostMsg_UpdatedCacheStats::ID:
       *thread = BrowserThread::UI;
       break;
@@ -370,12 +371,6 @@
         event_name, extension_id);
 }
 
-void ChromeRenderMessageFilter::OnExtensionIdle(
-    const std::string& extension_id) {
-  if (profile_->GetExtensionProcessManager())
-    profile_->GetExtensionProcessManager()->OnExtensionIdle(extension_id);
-}
-
 void ChromeRenderMessageFilter::OnExtensionEventAck(
     const std::string& extension_id) {
   if (profile_->GetExtensionEventRouter())
@@ -400,6 +395,13 @@
       weak_ptr_factory_.GetWeakPtr(), routing_id, params);
 }
 
+void ChromeRenderMessageFilter::OnExtensionShouldCloseAck(
+     const std::string& extension_id, int sequence_id) {
+  if (profile_->GetExtensionProcessManager())
+    profile_->GetExtensionProcessManager()->OnShouldCloseAck(
+        extension_id, sequence_id);
+}
+
 #if defined(USE_TCMALLOC)
 void ChromeRenderMessageFilter::OnRendererTcmalloc(const std::string& output) {
   base::ProcessId pid = base::GetProcId(peer_handle());
diff --git a/chrome/browser/renderer_host/chrome_render_message_filter.h b/chrome/browser/renderer_host/chrome_render_message_filter.h
index f3271c9..2e6fa44 100644
--- a/chrome/browser/renderer_host/chrome_render_message_filter.h
+++ b/chrome/browser/renderer_host/chrome_render_message_filter.h
@@ -117,12 +117,13 @@
                                   const std::string& event_name);
   void OnExtensionRemoveLazyListener(const std::string& extension_id,
                                      const std::string& event_name);
-  void OnExtensionIdle(const std::string& extension_id);
   void OnExtensionEventAck(const std::string& extension_id);
   void OnExtensionCloseChannel(int port_id);
   void OnExtensionRequestForIOThread(
       int routing_id,
       const ExtensionHostMsg_Request_Params& params);
+  void OnExtensionShouldCloseAck(const std::string& extension_id,
+                                 int sequence_id);
 #if defined(USE_TCMALLOC)
   void OnRendererTcmalloc(const std::string& output);
   void OnWriteTcmallocHeapProfile(const FilePath::StringType& filename,
diff --git a/chrome/common/extensions/extension_messages.h b/chrome/common/extensions/extension_messages.h
index c295985..5207734 100644
--- a/chrome/common/extensions/extension_messages.h
+++ b/chrome/common/extensions/extension_messages.h
@@ -231,7 +231,7 @@
 // Tell the renderer to update an extension's permission set.
 IPC_MESSAGE_CONTROL5(ExtensionMsg_UpdatePermissions,
                      int /* UpdateExtensionPermissionsInfo::REASON */,
-                     std::string /* extension_id*/,
+                     std::string /* extension_id */,
                      ExtensionAPIPermissionSet /* permissions */,
                      URLPatternSet /* explicit_hosts */,
                      URLPatternSet /* scriptable_hosts */)
@@ -246,6 +246,13 @@
                      bool /* adblock_plus */,
                      bool /* other_webrequest */)
 
+// Ask the renderer if it is ready to shutdown. Used for lazy background pages
+// when they are considered idle. The renderer will reply with the same
+// sequence_id so that we can tell which message it is responding to.
+IPC_MESSAGE_CONTROL2(ExtensionMsg_ShouldClose,
+                     std::string /* extension_id */,
+                     int /* sequence_id */)
+
 // Messages sent from the renderer to the browser.
 
 // A renderer sends this message when an extension process starts an API
@@ -282,11 +289,6 @@
                      std::string /* extension_id */,
                      std::string /* name */)
 
-// Notify the browser that the extension is idle so it's lazy background page
-// can be closed.
-IPC_MESSAGE_CONTROL1(ExtensionHostMsg_ExtensionIdle,
-                     std::string /* extension_id */)
-
 // Notify the browser that an event has finished being dispatched.
 IPC_MESSAGE_CONTROL1(ExtensionHostMsg_ExtensionEventAck,
                      std::string /* extension_id */)
@@ -368,6 +370,11 @@
 IPC_MESSAGE_ROUTED1(ExtensionHostMsg_ResponseAck,
                     int /* request_id */)
 
+// Response to ExtensionMsg_ShouldClose.
+IPC_MESSAGE_CONTROL2(ExtensionHostMsg_ShouldCloseAck,
+                     std::string /* extension_id */,
+                     int /* sequence_id */)
+
 // Response to the renderer for the above message.
 IPC_MESSAGE_ROUTED3(ExtensionMsg_GetAppNotifyChannelResponse,
                     std::string /* channel_id */,
diff --git a/chrome/renderer/extensions/extension_dispatcher.cc b/chrome/renderer/extensions/extension_dispatcher.cc
index f357830..e59922c3 100644
--- a/chrome/renderer/extensions/extension_dispatcher.cc
+++ b/chrome/renderer/extensions/extension_dispatcher.cc
@@ -95,6 +95,7 @@
     IPC_MESSAGE_HANDLER(ExtensionMsg_UpdatePermissions, OnUpdatePermissions)
     IPC_MESSAGE_HANDLER(ExtensionMsg_UpdateUserScripts, OnUpdateUserScripts)
     IPC_MESSAGE_HANDLER(ExtensionMsg_UsingWebRequestAPI, OnUsingWebRequestAPI)
+    IPC_MESSAGE_HANDLER(ExtensionMsg_ShouldClose, OnShouldClose)
     IPC_MESSAGE_UNHANDLED(handled = false)
   IPC_END_MESSAGE_MAP()
 
@@ -183,17 +184,9 @@
       function_name == "Event.dispatchJSON") { // may always be true
     RenderThread::Get()->Send(
         new ExtensionHostMsg_ExtensionEventAck(extension_id));
-    CheckIdleStatus(extension_id);
   }
 }
 
-void ExtensionDispatcher::CheckIdleStatus(const std::string& extension_id) {
-  const Extension* extension = extensions_.GetByID(extension_id);
-  if (extension && !extension->background_page_persists() &&
-      !SchemaGeneratedBindings::HasPendingRequests(extension_id))
-    RenderThread::Get()->Send(new ExtensionHostMsg_ExtensionIdle(extension_id));
-}
-
 void ExtensionDispatcher::OnDeliverMessage(int target_port_id,
                                            const std::string& message) {
   MiscellaneousBindings::DeliverMessage(
@@ -542,3 +535,9 @@
   webrequest_adblock_plus_ = adblock_plus;
   webrequest_other_ = other;
 }
+
+void ExtensionDispatcher::OnShouldClose(const std::string& extension_id,
+                                        int sequence_id) {
+  RenderThread::Get()->Send(
+      new ExtensionHostMsg_ShouldCloseAck(extension_id, sequence_id));
+}
diff --git a/chrome/renderer/extensions/extension_dispatcher.h b/chrome/renderer/extensions/extension_dispatcher.h
index 5f42c873f..7a2de8c 100644
--- a/chrome/renderer/extensions/extension_dispatcher.h
+++ b/chrome/renderer/extensions/extension_dispatcher.h
@@ -88,10 +88,6 @@
     return webrequest_other_;
   }
 
-  // If the extension is in fact idle, tell the browser process to close
-  // the background page.
-  void CheckIdleStatus(const std::string& extension_id);
-
  private:
   friend class RenderViewTest;
 
@@ -125,6 +121,7 @@
       bool adblock,
       bool adblock_plus,
       bool other_webrequest);
+  void OnShouldClose(const std::string& extension_id, int sequence_id);
 
   // Update the list of active extensions that will be reported when we crash.
   void UpdateActiveExtensions();
diff --git a/chrome/renderer/extensions/extension_helper.cc b/chrome/renderer/extensions/extension_helper.cc
index d144f2c..0e33ec4 100644
--- a/chrome/renderer/extensions/extension_helper.cc
+++ b/chrome/renderer/extensions/extension_helper.cc
@@ -189,8 +189,6 @@
   SchemaGeneratedBindings::HandleResponse(
       extension_dispatcher_->v8_context_set(), request_id, success,
       response, error, &extension_id);
-
-  extension_dispatcher_->CheckIdleStatus(extension_id);
 }
 
 void ExtensionHelper::OnExtensionMessageInvoke(const std::string& extension_id,