Reland: Refactor code that defers extension background page loading

src/extensions depends on chrome::NOTIFICATION_PROFILE_CREATED to support
deferred loading of extension background pages when the profile isn't ready
yet. This is a layering violation.

* Remove Chrome concepts like "browser window ready" and "profile created"
from ProcessManager. Introduce ProcessManagerDelegate with a general concept
of deferring background page loading.
* Consolidate all the tricky Chrome-specific background page loading rules into
ChromeProcessManagerDelegate. This keeps all the rules in one place. Annotate
each block of special case code with the bug that inspired it.
* Extend unit test coverage for ProcessManager.

This will make it easier to eliminate chrome::NOTIFICATION_PROFILE_DESTROYED
in ProcessManager in a later CL.

(Original CL https://codereview.chromium.org/381283002 broke valgrind bots
because it was initializing left-over BrowserContextKeyedServices from tests
running earlier in the same process.)

BUG=392658
TEST=unit_tests ProcessManagerTest, browser_tests ProcessManagerBrowserTest, manual

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@284593 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.cc b/chrome/browser/extensions/chrome_extensions_browser_client.cc
index c66d0236..db2ae3b 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.cc
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.cc
@@ -21,7 +21,6 @@
 #include "chrome/browser/external_protocol/external_protocol_handler.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/profiles/profile_manager.h"
-#include "chrome/browser/ui/browser_finder.h"
 #include "chrome/common/chrome_paths.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/common/chrome_version_info.h"
@@ -42,12 +41,14 @@
 #include "chrome/browser/extensions/activity_log/activity_log.h"
 #include "chrome/browser/extensions/api/chrome_extensions_api_client.h"
 #include "chrome/browser/extensions/api/content_settings/content_settings_service.h"
+#include "chrome/browser/extensions/chrome_process_manager_delegate.h"
 #endif
 
 namespace extensions {
 
 ChromeExtensionsBrowserClient::ChromeExtensionsBrowserClient() {
 #if defined(ENABLE_EXTENSIONS)
+  process_manager_delegate_.reset(new ChromeProcessManagerDelegate);
   api_client_.reset(new ChromeExtensionsAPIClient);
 #endif
   // Only set if it hasn't already been set (e.g. by a test).
@@ -158,35 +159,15 @@
 #endif
 }
 
-bool ChromeExtensionsBrowserClient::DeferLoadingBackgroundHosts(
-    content::BrowserContext* context) const {
-  Profile* profile = static_cast<Profile*>(context);
-
-  // The profile may not be valid yet if it is still being initialized.
-  // In that case, defer loading, since it depends on an initialized profile.
-  // http://crbug.com/222473
-  if (!g_browser_process->profile_manager()->IsValidProfile(profile))
-    return true;
-
-#if defined(OS_ANDROID)
-  return false;
+ProcessManagerDelegate*
+ChromeExtensionsBrowserClient::GetProcessManagerDelegate() const {
+#if defined(ENABLE_EXTENSIONS)
+  return process_manager_delegate_.get();
 #else
-  // There are no browser windows open and the browser process was
-  // started to show the app launcher.
-  return chrome::GetTotalBrowserCountForProfile(profile) == 0 &&
-         CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowAppList);
+  return NULL;
 #endif
 }
 
-bool ChromeExtensionsBrowserClient::IsBackgroundPageAllowed(
-    content::BrowserContext* context) const {
-  // Returns true if current session is Guest mode session and current
-  // browser context is *not* off-the-record. Such context is artificial and
-  // background page shouldn't be created in it.
-  return !static_cast<Profile*>(context)->IsGuestSession() ||
-         context->IsOffTheRecord();
-}
-
 scoped_ptr<ExtensionHostDelegate>
 ChromeExtensionsBrowserClient::CreateExtensionHostDelegate() {
   return scoped_ptr<ExtensionHostDelegate>(new ChromeExtensionHostDelegate);
diff --git a/chrome/browser/extensions/chrome_extensions_browser_client.h b/chrome/browser/extensions/chrome_extensions_browser_client.h
index 3c4a2c6..5957838 100644
--- a/chrome/browser/extensions/chrome_extensions_browser_client.h
+++ b/chrome/browser/extensions/chrome_extensions_browser_client.h
@@ -26,6 +26,7 @@
 
 class ChromeComponentExtensionResourceManager;
 class ChromeExtensionsAPIClient;
+class ChromeProcessManagerDelegate;
 class ContentSettingsPrefsObserver;
 
 // Implementation of extensions::BrowserClient for Chrome, which includes
@@ -76,10 +77,7 @@
   virtual void GetEarlyExtensionPrefsObservers(
       content::BrowserContext* context,
       std::vector<ExtensionPrefsObserver*>* observers) const OVERRIDE;
-  virtual bool DeferLoadingBackgroundHosts(
-      content::BrowserContext* context) const OVERRIDE;
-  virtual bool IsBackgroundPageAllowed(
-      content::BrowserContext* context) const OVERRIDE;
+  virtual ProcessManagerDelegate* GetProcessManagerDelegate() const OVERRIDE;
   virtual scoped_ptr<ExtensionHostDelegate> CreateExtensionHostDelegate()
       OVERRIDE;
   virtual bool DidVersionUpdate(content::BrowserContext* context) OVERRIDE;
@@ -102,6 +100,9 @@
   ChromeNotificationObserver notification_observer_;
 
 #if defined(ENABLE_EXTENSIONS)
+  // Support for ProcessManager.
+  scoped_ptr<ChromeProcessManagerDelegate> process_manager_delegate_;
+
   // Client for API implementations.
   scoped_ptr<ChromeExtensionsAPIClient> api_client_;
 #endif
diff --git a/chrome/browser/extensions/chrome_notification_observer.cc b/chrome/browser/extensions/chrome_notification_observer.cc
index 5d966c8..c8e60163 100644
--- a/chrome/browser/extensions/chrome_notification_observer.cc
+++ b/chrome/browser/extensions/chrome_notification_observer.cc
@@ -4,59 +4,22 @@
 
 #include "chrome/browser/extensions/chrome_notification_observer.h"
 
-#include "base/logging.h"
-#include "chrome/browser/chrome_notification_types.h"
-#include "chrome/browser/profiles/profile.h"
-#include "chrome/browser/ui/browser.h"
 #include "chrome/common/extensions/features/feature_channel.h"
 #include "content/public/browser/notification_service.h"
 #include "content/public/browser/notification_types.h"
 #include "content/public/browser/render_process_host.h"
-#include "extensions/browser/extension_system.h"
-#include "extensions/browser/process_manager.h"
 #include "extensions/common/extension_messages.h"
 
 namespace extensions {
 
 ChromeNotificationObserver::ChromeNotificationObserver() {
   registrar_.Add(this,
-                 chrome::NOTIFICATION_BROWSER_WINDOW_READY,
-                 content::NotificationService::AllSources());
-  registrar_.Add(this,
                  content::NOTIFICATION_RENDERER_PROCESS_CREATED,
                  content::NotificationService::AllBrowserContextsAndSources());
 }
 
 ChromeNotificationObserver::~ChromeNotificationObserver() {}
 
-void ChromeNotificationObserver::OnBrowserWindowReady(Browser* browser) {
-  Profile* profile = browser->profile();
-  DCHECK(profile);
-
-  // Inform the process manager for this profile that the window is ready.
-  // We continue to observe the notification in case browser windows open for
-  // a related incognito profile or other regular profiles.
-  extensions::ProcessManager* manager =
-      ExtensionSystem::Get(profile)->process_manager();
-  if (!manager)  // Tests may not have a process manager.
-    return;
-  DCHECK_EQ(profile, manager->GetBrowserContext());
-  manager->OnBrowserWindowReady();
-
-  // For incognito profiles also inform the original profile's process manager
-  // that the window is ready. This will usually be a no-op because the
-  // original profile's process manager should have been informed when the
-  // non-incognito window opened.
-  if (profile->IsOffTheRecord()) {
-    Profile* original_profile = profile->GetOriginalProfile();
-    extensions::ProcessManager* original_manager =
-        ExtensionSystem::Get(original_profile)->process_manager();
-    DCHECK(original_manager);
-    DCHECK_EQ(original_profile, original_manager->GetBrowserContext());
-    original_manager->OnBrowserWindowReady();
-  }
-}
-
 void ChromeNotificationObserver::OnRendererProcessCreated(
     content::RenderProcessHost* process) {
   // Extensions need to know the channel for API restrictions. Send the channel
@@ -68,12 +31,6 @@
                        const content::NotificationSource& source,
                        const content::NotificationDetails& details) {
   switch (type) {
-    case chrome::NOTIFICATION_BROWSER_WINDOW_READY: {
-      Browser* browser = content::Source<Browser>(source).ptr();
-      OnBrowserWindowReady(browser);
-      break;
-    }
-
     case content::NOTIFICATION_RENDERER_PROCESS_CREATED: {
       content::RenderProcessHost* process =
           content::Source<content::RenderProcessHost>(source).ptr();
diff --git a/chrome/browser/extensions/chrome_notification_observer.h b/chrome/browser/extensions/chrome_notification_observer.h
index a31dc87..8045b156 100644
--- a/chrome/browser/extensions/chrome_notification_observer.h
+++ b/chrome/browser/extensions/chrome_notification_observer.h
@@ -25,8 +25,7 @@
   ChromeNotificationObserver();
   virtual ~ChromeNotificationObserver();
 
-  // IPC message handlers:
-  void OnBrowserWindowReady(Browser* browser);
+  // Notification handlers:
   void OnRendererProcessCreated(content::RenderProcessHost* process);
 
   // content::NotificationObserver overrides:
diff --git a/chrome/browser/extensions/chrome_process_manager_delegate.cc b/chrome/browser/extensions/chrome_process_manager_delegate.cc
new file mode 100644
index 0000000..12e0c16
--- /dev/null
+++ b/chrome/browser/extensions/chrome_process_manager_delegate.cc
@@ -0,0 +1,143 @@
+// Copyright 2014 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 "chrome/browser/extensions/chrome_process_manager_delegate.h"
+
+#include "base/command_line.h"
+#include "base/logging.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/profiles/profile_manager.h"
+#include "chrome/browser/ui/browser.h"
+#include "content/public/browser/notification_service.h"
+#include "extensions/browser/extension_system.h"
+#include "extensions/browser/process_manager.h"
+#include "extensions/common/one_shot_event.h"
+
+#if !defined(OS_ANDROID)
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/common/chrome_switches.h"
+#endif
+
+namespace extensions {
+
+ChromeProcessManagerDelegate::ChromeProcessManagerDelegate() {
+  registrar_.Add(this,
+                 chrome::NOTIFICATION_BROWSER_WINDOW_READY,
+                 content::NotificationService::AllSources());
+  registrar_.Add(this,
+                 chrome::NOTIFICATION_PROFILE_CREATED,
+                 content::NotificationService::AllSources());
+  // TODO(jamescook): Move observation of NOTIFICATION_PROFILE_DESTROYED here.
+  // http://crbug.com/392658
+}
+
+ChromeProcessManagerDelegate::~ChromeProcessManagerDelegate() {
+}
+
+bool ChromeProcessManagerDelegate::IsBackgroundPageAllowed(
+    content::BrowserContext* context) const {
+  Profile* profile = static_cast<Profile*>(context);
+
+  // Disallow if the current session is a Guest mode session but the current
+  // browser context is *not* off-the-record. Such context is artificial and
+  // background page shouldn't be created in it.
+  // http://crbug.com/329498
+  return !(profile->IsGuestSession() && !profile->IsOffTheRecord());
+}
+
+bool ChromeProcessManagerDelegate::DeferCreatingStartupBackgroundHosts(
+    content::BrowserContext* context) const {
+  Profile* profile = static_cast<Profile*>(context);
+
+  // The profile may not be valid yet if it is still being initialized.
+  // In that case, defer loading, since it depends on an initialized profile.
+  // Background hosts will be loaded later via NOTIFICATION_PROFILE_CREATED.
+  // http://crbug.com/222473
+  if (!g_browser_process->profile_manager()->IsValidProfile(profile))
+    return true;
+
+#if defined(OS_ANDROID)
+  return false;
+#else
+  // There are no browser windows open and the browser process was
+  // started to show the app launcher. Background hosts will be loaded later
+  // via NOTIFICATION_BROWSER_WINDOW_READY. http://crbug.com/178260
+  return chrome::GetTotalBrowserCountForProfile(profile) == 0 &&
+         CommandLine::ForCurrentProcess()->HasSwitch(switches::kShowAppList);
+#endif
+}
+
+void ChromeProcessManagerDelegate::Observe(
+    int type,
+    const content::NotificationSource& source,
+    const content::NotificationDetails& details) {
+  switch (type) {
+    case chrome::NOTIFICATION_BROWSER_WINDOW_READY: {
+      Browser* browser = content::Source<Browser>(source).ptr();
+      OnBrowserWindowReady(browser);
+      break;
+    }
+    case chrome::NOTIFICATION_PROFILE_CREATED: {
+      Profile* profile = content::Source<Profile>(source).ptr();
+      OnProfileCreated(profile);
+      break;
+    }
+
+    default:
+      NOTREACHED();
+  }
+}
+
+void ChromeProcessManagerDelegate::OnBrowserWindowReady(Browser* browser) {
+  Profile* profile = browser->profile();
+  DCHECK(profile);
+
+  // If the extension system isn't ready yet the background hosts will be
+  // created automatically when it is.
+  ExtensionSystem* system = ExtensionSystem::Get(profile);
+  if (!system->ready().is_signaled())
+    return;
+
+  // Inform the process manager for this profile that the window is ready.
+  // We continue to observe the notification in case browser windows open for
+  // a related incognito profile or other regular profiles.
+  ProcessManager* manager = system->process_manager();
+  if (!manager)  // Tests may not have a process manager.
+    return;
+  DCHECK_EQ(profile, manager->GetBrowserContext());
+  manager->MaybeCreateStartupBackgroundHosts();
+
+  // For incognito profiles also inform the original profile's process manager
+  // that the window is ready. This will usually be a no-op because the
+  // original profile's process manager should have been informed when the
+  // non-incognito window opened.
+  if (profile->IsOffTheRecord()) {
+    Profile* original_profile = profile->GetOriginalProfile();
+    ProcessManager* original_manager =
+        ExtensionSystem::Get(original_profile)->process_manager();
+    DCHECK(original_manager);
+    DCHECK_EQ(original_profile, original_manager->GetBrowserContext());
+    original_manager->MaybeCreateStartupBackgroundHosts();
+  }
+}
+
+void ChromeProcessManagerDelegate::OnProfileCreated(Profile* profile) {
+  // Incognito profiles are handled by their original profile.
+  if (profile->IsOffTheRecord())
+    return;
+
+  // The profile can be created before the extension system is ready.
+  ProcessManager* manager = ExtensionSystem::Get(profile)->process_manager();
+  if (!manager)
+    return;
+
+  // The profile might have been initialized asynchronously (in parallel with
+  // extension system startup). Now that initialization is complete the
+  // ProcessManager can load deferred background pages.
+  manager->MaybeCreateStartupBackgroundHosts();
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/chrome_process_manager_delegate.h b/chrome/browser/extensions/chrome_process_manager_delegate.h
new file mode 100644
index 0000000..9bcc58a
--- /dev/null
+++ b/chrome/browser/extensions/chrome_process_manager_delegate.h
@@ -0,0 +1,50 @@
+// Copyright 2014 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_CHROME_PROCESS_MANAGER_DELEGATE_H_
+#define CHROME_BROWSER_EXTENSIONS_CHROME_PROCESS_MANAGER_DELEGATE_H_
+
+#include "base/compiler_specific.h"
+#include "base/macros.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/browser/process_manager_delegate.h"
+
+class Browser;
+class Profile;
+
+namespace extensions {
+
+// Support for ProcessManager. Controls cases where Chrome wishes to disallow
+// extension background pages or defer their creation.
+class ChromeProcessManagerDelegate : public ProcessManagerDelegate,
+                                     public content::NotificationObserver {
+ public:
+  ChromeProcessManagerDelegate();
+  virtual ~ChromeProcessManagerDelegate();
+
+  // ProcessManagerDelegate implementation:
+  virtual bool IsBackgroundPageAllowed(
+      content::BrowserContext* context) const OVERRIDE;
+  virtual bool DeferCreatingStartupBackgroundHosts(
+      content::BrowserContext* context) const OVERRIDE;
+
+  // content::NotificationObserver implementation:
+  virtual void Observe(int type,
+                       const content::NotificationSource& source,
+                       const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+  // Notification handlers.
+  void OnBrowserWindowReady(Browser* browser);
+  void OnProfileCreated(Profile* profile);
+
+  content::NotificationRegistrar registrar_;
+
+  DISALLOW_COPY_AND_ASSIGN(ChromeProcessManagerDelegate);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_CHROME_PROCESS_MANAGER_DELEGATE_H_