Added BackgroundContentsService to manage lifecycle of BackgroundContents.

If --restore-background-contents flag is passed, stores the URLs of running
BackgroundContents in preferences so they can be re-launched when the browser
restarts.

Moved logic to shutdown background contents into BackgroundContentsService so
we can use this to coordinate when to keep the browser process running.

BUG=43382
TEST=new tests

Review URL: http://codereview.chromium.org/2104018

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@50329 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/background_contents_service.cc b/chrome/browser/background_contents_service.cc
new file mode 100644
index 0000000..685b12bb
--- /dev/null
+++ b/chrome/browser/background_contents_service.cc
@@ -0,0 +1,256 @@
+// Copyright (c) 2010 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/background_contents_service.h"
+
+#include "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/string_util.h"
+#include "base/utf_string_conversions.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/browser/profile.h"
+#include "chrome/browser/tab_contents/background_contents.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/notification_service.h"
+#include "chrome/common/notification_type.h"
+#include "chrome/common/pref_names.h"
+
+// Keys for the information we store about individual BackgroundContents in
+// prefs. There is one top-level DictionaryValue (stored at
+// prefs::kRegisteredBackgroundContents). Information about each
+// BackgroundContents is stored under that top-level DictionaryValue, keyed
+// by the parent application ID for easy lookup.
+//
+// kRegisteredBackgroundContents:
+//    DictionaryValue {
+//       <appid_1>: { "url": <url1>, "name": <frame_name> },
+//       <appid_2>: { "url": <url2>, "name": <frame_name> },
+//         ... etc ...
+//    }
+const wchar_t kUrlKey[] = L"url";
+const wchar_t kFrameNameKey[] = L"name";
+
+BackgroundContentsService::BackgroundContentsService(
+    Profile* profile, const CommandLine* command_line)
+    : prefs_(NULL) {
+  // Don't load/store preferences if the proper switch is not enabled, or if
+  // the parent profile is off the record.
+  if (!profile->IsOffTheRecord() &&
+      command_line->HasSwitch(switches::kRestoreBackgroundContents))
+    prefs_ = profile->GetPrefs();
+
+  // Listen for events to tell us when to load/unload persisted background
+  // contents.
+  StartObserving(profile);
+}
+
+BackgroundContentsService::~BackgroundContentsService() {
+  // BackgroundContents should be shutdown before we go away, as otherwise
+  // our browser process refcount will be off.
+  DCHECK(contents_map_.empty());
+}
+
+void BackgroundContentsService::StartObserving(Profile* profile) {
+  // On startup, load our background pages after extension-apps have loaded.
+  registrar_.Add(this, NotificationType::EXTENSIONS_READY,
+                 Source<Profile>(profile));
+
+  // Track the lifecycle of all BackgroundContents in the system to allow us
+  // to store an up-to-date list of the urls. Start tracking contents when they
+  // have been opened, and stop tracking them when they are closed by script.
+  registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_OPENED,
+                 Source<Profile>(profile));
+  registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_CLOSED,
+                 Source<Profile>(profile));
+
+  // Stop tracking BackgroundContents when they have been deleted (happens
+  // during shutdown or if the render process dies).
+  registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_DELETED,
+                 Source<Profile>(profile));
+  // Track when the BackgroundContents navigates to a new URL so we can update
+  // our persisted information as appropriate.
+  registrar_.Add(this, NotificationType::BACKGROUND_CONTENTS_NAVIGATED,
+                 Source<Profile>(profile));
+
+  // Listen for extensions to be unloaded so we can shutdown associated
+  // BackgroundContents.
+  registrar_.Add(this, NotificationType::EXTENSION_UNLOADED,
+                 Source<Profile>(profile));
+}
+
+void BackgroundContentsService::Observe(NotificationType type,
+                                       const NotificationSource& source,
+                                       const NotificationDetails& details) {
+  switch (type.value) {
+    case NotificationType::EXTENSIONS_READY:
+      LoadBackgroundContentsFromPrefs();
+      break;
+    case NotificationType::BACKGROUND_CONTENTS_DELETED:
+      BackgroundContentsShutdown(Details<BackgroundContents>(details).ptr());
+      break;
+    case NotificationType::BACKGROUND_CONTENTS_OPENED:
+      BackgroundContentsOpened(
+          Details<BackgroundContentsOpenedDetails>(details).ptr());
+      break;
+    case NotificationType::BACKGROUND_CONTENTS_CLOSED:
+      DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
+      UnregisterBackgroundContents(Details<BackgroundContents>(details).ptr());
+      break;
+    case NotificationType::BACKGROUND_CONTENTS_NAVIGATED:
+      DCHECK(IsTracked(Details<BackgroundContents>(details).ptr()));
+      RegisterBackgroundContents(Details<BackgroundContents>(details).ptr());
+      break;
+    case NotificationType::EXTENSION_UNLOADED:
+      ShutdownAssociatedBackgroundContents(
+          ASCIIToUTF16(Details<Extension>(details)->id()));
+      break;
+    default:
+      NOTREACHED();
+      break;
+  }
+}
+
+// Loads all background contents whose urls have been stored in prefs.
+void BackgroundContentsService::LoadBackgroundContentsFromPrefs() {
+  if (!prefs_)
+    return;
+  DLOG(INFO) << "Starting to load background contents";
+  const DictionaryValue* contents =
+      prefs_->GetDictionary(prefs::kRegisteredBackgroundContents);
+  if (!contents)
+    return;
+  DLOG(INFO) << "Loading " << contents->size() << " background contents";
+  for (DictionaryValue::key_iterator it = contents->begin_keys();
+       it != contents->end_keys(); ++it) {
+    DictionaryValue* dict;
+    contents->GetDictionaryWithoutPathExpansion(*it, &dict);
+    string16 frame_name;
+    std::string url;
+    dict->GetString(kUrlKey, &url);
+    dict->GetStringAsUTF16(kFrameNameKey, &frame_name);
+    CreateBackgroundContents(GURL(url),
+                             frame_name,
+                             WideToUTF16(*it));
+  }
+}
+
+void BackgroundContentsService::CreateBackgroundContents(
+    const GURL& url,
+    const string16& frame_name,
+    const string16& application_id) {
+  // We are depending on the fact that we will initialize before any user
+  // actions or session restore can take place, so no BackgroundContents should
+  // be running yet for the passed application_id.
+  DCHECK(!GetAppBackgroundContents(application_id));
+  DCHECK(url.is_valid());
+  // TODO(atwilson): Fire up renderer and load BackgroundContents for this url,
+  // and set its initial url in the contents_map.
+  DLOG(INFO) << "Loading background content url: " << url;
+}
+
+void BackgroundContentsService::RegisterBackgroundContents(
+    BackgroundContents* background_contents) {
+  DCHECK(IsTracked(background_contents));
+  if (!prefs_)
+    return;
+
+  // We store the first URL we receive for a given application. If there's
+  // already an entry for this application, no need to do anything.
+  DictionaryValue* pref = prefs_->GetMutableDictionary(
+      prefs::kRegisteredBackgroundContents);
+  const string16& appid = GetParentApplicationId(background_contents);
+  DictionaryValue* current;
+  if (pref->GetDictionaryWithoutPathExpansion(UTF16ToWide(appid), &current))
+    return;
+
+  // No entry for this application yet, so add one.
+  DictionaryValue* dict = new DictionaryValue();
+  dict->SetString(kUrlKey, background_contents->GetURL().spec());
+  dict->SetStringFromUTF16(kFrameNameKey, contents_map_[appid].frame_name);
+  pref->SetWithoutPathExpansion(UTF16ToWide(appid), dict);
+  prefs_->ScheduleSavePersistentPrefs();
+}
+
+void BackgroundContentsService::UnregisterBackgroundContents(
+    BackgroundContents* background_contents) {
+  if (!prefs_)
+    return;
+  DCHECK(IsTracked(background_contents));
+  const string16 appid = GetParentApplicationId(background_contents);
+  DictionaryValue* pref = prefs_->GetMutableDictionary(
+      prefs::kRegisteredBackgroundContents);
+  pref->RemoveWithoutPathExpansion(UTF16ToWide(appid), NULL);
+  prefs_->ScheduleSavePersistentPrefs();
+}
+
+void BackgroundContentsService::ShutdownAssociatedBackgroundContents(
+    const string16& appid) {
+  BackgroundContents* contents = GetAppBackgroundContents(appid);
+  if (contents) {
+    UnregisterBackgroundContents(contents);
+    // Background contents destructor shuts down the renderer.
+    delete contents;
+  }
+}
+
+void BackgroundContentsService::BackgroundContentsOpened(
+    BackgroundContentsOpenedDetails* details) {
+  // If this is the first BackgroundContents loaded, kick ourselves into
+  // persistent mode.
+  // TODO(atwilson): Enable this when we support running with no active windows
+  // on all platforms (http://crbug.com/45275).
+  //  if (contents_map_.empty())
+  //    g_browser_process->AddRefModule();
+
+  // Add the passed object to our list. Should not already be tracked.
+  DCHECK(!IsTracked(details->contents));
+  DCHECK(!details->application_id.empty());
+  contents_map_[details->application_id].contents = details->contents;
+  contents_map_[details->application_id].frame_name = details->frame_name;
+}
+
+// Used by test code and debug checks to verify whether a given
+// BackgroundContents is being tracked by this instance.
+bool BackgroundContentsService::IsTracked(
+    BackgroundContents* background_contents) const {
+  return !GetParentApplicationId(background_contents).empty();
+}
+
+void BackgroundContentsService::BackgroundContentsShutdown(
+    BackgroundContents* background_contents) {
+  // Remove the passed object from our list.
+  DCHECK(IsTracked(background_contents));
+  string16 appid = GetParentApplicationId(background_contents);
+  contents_map_.erase(appid);
+  // If we have no more BackgroundContents active, then stop keeping the browser
+  // process alive.
+  // TODO(atwilson): Enable this when we support running with no active windows
+  // on all platforms (http://crbug.com/45275).
+  //  if (contents_map_.empty())
+  //    g_browser_process->ReleaseModule();
+}
+
+BackgroundContents* BackgroundContentsService::GetAppBackgroundContents(
+    const string16& application_id) {
+  BackgroundContentsMap::const_iterator it = contents_map_.find(application_id);
+  return (it != contents_map_.end()) ? it->second.contents : NULL;
+}
+
+const string16& BackgroundContentsService::GetParentApplicationId(
+    BackgroundContents* contents) const {
+  for (BackgroundContentsMap::const_iterator it = contents_map_.begin();
+       it != contents_map_.end(); ++it) {
+    if (contents == it->second.contents)
+      return it->first;
+  }
+  return EmptyString16();
+}
+
+// static
+void BackgroundContentsService::RegisterUserPrefs(PrefService* prefs) {
+  prefs->RegisterDictionaryPref(prefs::kRegisteredBackgroundContents);
+}
diff --git a/chrome/browser/background_contents_service.h b/chrome/browser/background_contents_service.h
new file mode 100644
index 0000000..d9e9e67
--- /dev/null
+++ b/chrome/browser/background_contents_service.h
@@ -0,0 +1,113 @@
+// Copyright (c) 2010 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_BACKGROUND_CONTENTS_SERVICE_H_
+#define CHROME_BROWSER_BACKGROUND_CONTENTS_SERVICE_H_
+
+#include <map>
+
+#include "chrome/common/notification_observer.h"
+#include "chrome/common/notification_registrar.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest_prod.h"
+
+class BackgroundContents;
+class CommandLine;
+class PrefService;
+class Profile;
+struct BackgroundContentsOpenedDetails;
+
+// BackgroundContentsService is owned by the profile, and is responsible for
+// managing the lifetime of BackgroundContents (tracking the set of background
+// urls, loading them at startup, and keeping the browser process alive as long
+// as there are BackgroundContents loaded).
+//
+// It is also responsible for tracking the association between
+// BackgroundContents and their parent app, and shutting them down when the
+// parent app is unloaded.
+class BackgroundContentsService : private NotificationObserver {
+ public:
+  BackgroundContentsService(Profile* profile, const CommandLine* command_line);
+  virtual ~BackgroundContentsService();
+
+  // Returns the BackgroundContents associated with the passed application id,
+  // or NULL if none.
+  BackgroundContents* GetAppBackgroundContents(const string16& appid);
+
+  static void RegisterUserPrefs(PrefService* prefs);
+
+ private:
+  friend class BackgroundContentsServiceTest;
+  FRIEND_TEST(BackgroundContentsServiceTest, BackgroundContentsCreateDestroy);
+  FRIEND_TEST(BackgroundContentsServiceTest, TestApplicationIDLinkage);
+
+  // Registers for various notifications.
+  void StartObserving(Profile* profile);
+
+  // NotificationObserver implementation.
+  virtual void Observe(NotificationType type,
+                       const NotificationSource& source,
+                       const NotificationDetails& details);
+
+  // Loads all registered BackgroundContents at startup.
+  void LoadBackgroundContentsFromPrefs();
+
+  // Creates a single BackgroundContents associated with the specified |appid|.
+  // The BackgroundContents frame will be given the name specified by
+  // |frame_name| and navigated to the passed URL.
+  void CreateBackgroundContents(const GURL& url,
+                                const string16& frame_name,
+                                const string16& appid);
+
+  // Invoked when a new BackgroundContents is opened.
+  void BackgroundContentsOpened(BackgroundContentsOpenedDetails* details);
+
+  // Invoked when a BackgroundContents object is destroyed.
+  void BackgroundContentsShutdown(BackgroundContents* contents);
+
+  // Registers the |contents->GetURL()| to be run at startup. Only happens for
+  // the first navigation after window.open() (future calls to
+  // RegisterBackgroundContents() for the same BackgroundContents will do
+  // nothing).
+  void RegisterBackgroundContents(BackgroundContents* contents);
+
+  // Stops loading the passed BackgroundContents on startup.
+  void UnregisterBackgroundContents(BackgroundContents* contents);
+
+  // Unregisters and deletes the BackgroundContents associated with the
+  // passed extension.
+  void ShutdownAssociatedBackgroundContents(const string16& appid);
+
+  // Returns true if this BackgroundContents is in the contents_list_.
+  bool IsTracked(BackgroundContents* contents) const;
+
+  // Gets the parent application id for the passed BackgroundContents. Returns
+  // an empty string if no parent application found (e.g. passed
+  // BackgroundContents has already shut down).
+  const string16& GetParentApplicationId(BackgroundContents* contents) const;
+
+  // PrefService used to store list of background pages (or NULL if this is
+  // running under an off-the-record profile).
+  PrefService* prefs_;
+  NotificationRegistrar registrar_;
+
+  // Information we track about each BackgroundContents.
+  struct BackgroundContentsInfo {
+    // The BackgroundContents whose information we are tracking.
+    BackgroundContents* contents;
+    // The name of the top level frame for this BackgroundContents.
+    string16 frame_name;
+  };
+
+  // Map associating currently loaded BackgroundContents with their parent
+  // applications.
+  // Key: application id
+  // Value: BackgroundContentsInfo for the BC associated with that application
+  typedef std::map<string16, BackgroundContentsInfo> BackgroundContentsMap;
+  BackgroundContentsMap contents_map_;
+
+  DISALLOW_COPY_AND_ASSIGN(BackgroundContentsService);
+};
+
+#endif  // CHROME_BROWSER_BACKGROUND_CONTENTS_SERVICE_H_
diff --git a/chrome/browser/background_contents_service_unittest.cc b/chrome/browser/background_contents_service_unittest.cc
new file mode 100644
index 0000000..8e4394e
--- /dev/null
+++ b/chrome/browser/background_contents_service_unittest.cc
@@ -0,0 +1,238 @@
+// Copyright (c) 2010 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 "base/basictypes.h"
+#include "base/command_line.h"
+#include "base/scoped_ptr.h"
+#include "base/utf_string_conversions.h"
+#include "chrome/browser/background_contents_service.h"
+#include "chrome/browser/pref_service.h"
+#include "chrome/browser/tab_contents/background_contents.h"
+#include "chrome/common/chrome_switches.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/testing_browser_process.h"
+#include "chrome/test/testing_profile.h"
+#include "googleurl/src/gurl.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "testing/platform_test.h"
+
+class BackgroundContentsServiceTest : public testing::Test {
+ public:
+  BackgroundContentsServiceTest() {}
+  ~BackgroundContentsServiceTest() {}
+  void SetUp() {
+    command_line_.reset(new CommandLine(CommandLine::ARGUMENTS_ONLY));
+    command_line_->AppendSwitch(switches::kRestoreBackgroundContents);
+  }
+
+  DictionaryValue* GetPrefs(Profile* profile) {
+    return profile->GetPrefs()->GetMutableDictionary(
+        prefs::kRegisteredBackgroundContents);
+  }
+
+  // Returns the stored pref URL for the passed app id.
+  std::string GetPrefURLForApp(Profile* profile, const string16& appid) {
+    DictionaryValue* pref = GetPrefs(profile);
+    EXPECT_TRUE(pref->HasKey(UTF16ToWide(appid)));
+    DictionaryValue* value;
+    pref->GetDictionaryWithoutPathExpansion(UTF16ToWide(appid), &value);
+    std::string url;
+    value->GetString(L"url", &url);
+    return url;
+  }
+
+  scoped_ptr<CommandLine> command_line_;
+};
+
+class MockBackgroundContents : public BackgroundContents {
+ public:
+  explicit MockBackgroundContents(Profile* profile)
+      : appid_(ASCIIToUTF16("app_id")),
+        profile_(profile) {
+  }
+  MockBackgroundContents(Profile* profile, const std::string& id)
+      : appid_(ASCIIToUTF16(id)),
+        profile_(profile) {
+  }
+
+  void SendOpenedNotification() {
+    string16 frame_name = ASCIIToUTF16("background");
+    BackgroundContentsOpenedDetails details = {
+        this, frame_name, appid_ };
+    NotificationService::current()->Notify(
+        NotificationType::BACKGROUND_CONTENTS_OPENED,
+        Source<Profile>(profile_),
+        Details<BackgroundContentsOpenedDetails>(&details));
+  }
+
+  virtual const void Navigate(GURL url) {
+    url_ = url;
+    NotificationService::current()->Notify(
+        NotificationType::BACKGROUND_CONTENTS_NAVIGATED,
+        Source<Profile>(profile_),
+        Details<BackgroundContents>(this));
+  }
+  virtual const GURL& GetURL() const { return url_; }
+
+  void MockClose(Profile* profile) {
+    NotificationService::current()->Notify(
+        NotificationType::BACKGROUND_CONTENTS_CLOSED,
+        Source<Profile>(profile),
+        Details<BackgroundContents>(this));
+    delete this;
+  }
+
+  ~MockBackgroundContents() {
+    NotificationService::current()->Notify(
+        NotificationType::BACKGROUND_CONTENTS_DELETED,
+        Source<Profile>(profile_),
+        Details<BackgroundContents>(this));
+  }
+
+  const string16& appid() { return appid_; }
+
+ private:
+  GURL url_;
+
+  // The ID of our parent application
+  string16 appid_;
+
+  // Parent profile
+  Profile* profile_;
+};
+
+TEST_F(BackgroundContentsServiceTest, Create) {
+  // Check for creation and leaks.
+  TestingProfile profile;
+  BackgroundContentsService service(&profile, command_line_.get());
+}
+
+TEST_F(BackgroundContentsServiceTest, BackgroundContentsCreateDestroy) {
+  TestingProfile profile;
+  BackgroundContentsService service(&profile, command_line_.get());
+  // TODO(atwilson): Enable the refcount part of the test once we fix the
+  // APP_TERMINATING notification to be sent at the proper time.
+  // (http://crbug.com/45275).
+  //
+  //  TestingBrowserProcess* process =
+  //      static_cast<TestingBrowserProcess*>(g_browser_process);
+  // unsigned int starting_ref_count = process->module_ref_count();
+  MockBackgroundContents* contents = new MockBackgroundContents(&profile);
+  EXPECT_FALSE(service.IsTracked(contents));
+  contents->SendOpenedNotification();
+  // EXPECT_EQ(starting_ref_count+1, process->module_ref_count());
+  EXPECT_TRUE(service.IsTracked(contents));
+  delete contents;
+  // EXPECT_EQ(starting_ref_count, process->module_ref_count());
+  EXPECT_FALSE(service.IsTracked(contents));
+}
+
+TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAdded) {
+  TestingProfile profile;
+  GetPrefs(&profile)->Clear();
+  BackgroundContentsService service(&profile, command_line_.get());
+  GURL orig_url;
+  GURL url("http://a/");
+  GURL url2("http://a/");
+  {
+    scoped_ptr<MockBackgroundContents> contents(
+        new MockBackgroundContents(&profile));
+    EXPECT_EQ(0U, GetPrefs(&profile)->size());
+    contents->SendOpenedNotification();
+
+    contents->Navigate(url);
+    EXPECT_EQ(1U, GetPrefs(&profile)->size());
+    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
+
+    // Navigate the contents to a new url, should not change url.
+    contents->Navigate(url2);
+    EXPECT_EQ(1U, GetPrefs(&profile)->size());
+    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
+
+  }
+  // Contents are deleted, url should persist.
+  EXPECT_EQ(1U, GetPrefs(&profile)->size());
+}
+
+TEST_F(BackgroundContentsServiceTest, BackgroundContentsUrlAddedAndClosed) {
+  TestingProfile profile;
+  GetPrefs(&profile)->Clear();
+  BackgroundContentsService service(&profile, command_line_.get());
+  GURL url("http://a/");
+  MockBackgroundContents* contents = new MockBackgroundContents(&profile);
+  EXPECT_EQ(0U, GetPrefs(&profile)->size());
+  contents->SendOpenedNotification();
+  contents->Navigate(url);
+  EXPECT_EQ(1U, GetPrefs(&profile)->size());
+  EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
+
+  // Fake a window closed by script.
+  contents->MockClose(&profile);
+  EXPECT_EQ(0U, GetPrefs(&profile)->size());
+}
+
+// Test what happens if a BackgroundContents shuts down (say, due to a renderer
+// crash) then is restarted. Should not persist URL twice.
+TEST_F(BackgroundContentsServiceTest, RestartBackgroundContents) {
+  TestingProfile profile;
+  GetPrefs(&profile)->Clear();
+  BackgroundContentsService service(&profile, command_line_.get());
+  GURL url("http://a/");
+  {
+    scoped_ptr<MockBackgroundContents> contents(new MockBackgroundContents(
+        &profile, "appid"));
+    contents->SendOpenedNotification();
+    contents->Navigate(url);
+    EXPECT_EQ(1U, GetPrefs(&profile)->size());
+    EXPECT_EQ(url.spec(), GetPrefURLForApp(&profile, contents->appid()));
+  }
+  // Contents deleted, url should be persisted.
+  EXPECT_EQ(1U, GetPrefs(&profile)->size());
+
+  {
+    // Reopen the BackgroundContents to the same URL, we should not register the
+    // URL again.
+    scoped_ptr<MockBackgroundContents> contents(new MockBackgroundContents(
+        &profile, "appid"));
+    contents->SendOpenedNotification();
+    contents->Navigate(url);
+    EXPECT_EQ(1U, GetPrefs(&profile)->size());
+  }
+}
+
+// Ensures that BackgroundContentsService properly tracks the association
+// between a BackgroundContents and its parent extension, including
+// unregistering the BC when the extension is uninstalled.
+TEST_F(BackgroundContentsServiceTest, TestApplicationIDLinkage) {
+  TestingProfile profile;
+  BackgroundContentsService service(&profile, command_line_.get());
+  GetPrefs(&profile)->Clear();
+
+  EXPECT_EQ(NULL, service.GetAppBackgroundContents(ASCIIToUTF16("appid")));
+  MockBackgroundContents* contents = new MockBackgroundContents(&profile,
+                                                                "appid");
+  scoped_ptr<MockBackgroundContents> contents2(
+      new MockBackgroundContents(&profile, "appid2"));
+  contents->SendOpenedNotification();
+  EXPECT_EQ(contents, service.GetAppBackgroundContents(contents->appid()));
+  contents2->SendOpenedNotification();
+  EXPECT_EQ(contents2.get(), service.GetAppBackgroundContents(
+      contents2->appid()));
+  EXPECT_EQ(0U, GetPrefs(&profile)->size());
+
+  // Navigate the contents, then make sure the one associated with the extension
+  // is unregistered.
+  GURL url("http://a/");
+  GURL url2("http://b/");
+  contents->Navigate(url);
+  EXPECT_EQ(1U, GetPrefs(&profile)->size());
+  contents2->Navigate(url2);
+  EXPECT_EQ(2U, GetPrefs(&profile)->size());
+  service.ShutdownAssociatedBackgroundContents(ASCIIToUTF16("appid"));
+  EXPECT_FALSE(service.IsTracked(contents));
+  EXPECT_EQ(NULL, service.GetAppBackgroundContents(ASCIIToUTF16("appid")));
+  EXPECT_EQ(1U, GetPrefs(&profile)->size());
+  EXPECT_EQ(url2.spec(), GetPrefURLForApp(&profile, contents2->appid()));
+}
diff --git a/chrome/browser/browser_init.cc b/chrome/browser/browser_init.cc
index a38224f..63b6d326 100644
--- a/chrome/browser/browser_init.cc
+++ b/chrome/browser/browser_init.cc
@@ -439,7 +439,7 @@
   }
 #endif
 
-  if (command_line.HasSwitch(switches::kLongLivedExtensions)) {
+  if (command_line.HasSwitch(switches::kRestoreBackgroundContents)) {
     // Create status icons
     StatusTrayManager* tray = g_browser_process->status_tray_manager();
     if (tray)
diff --git a/chrome/browser/browser_prefs.cc b/chrome/browser/browser_prefs.cc
index 816c50b..251a4f0 100644
--- a/chrome/browser/browser_prefs.cc
+++ b/chrome/browser/browser_prefs.cc
@@ -5,6 +5,7 @@
 #include "chrome/browser/browser_prefs.h"
 
 #include "chrome/browser/autofill/autofill_manager.h"
+#include "chrome/browser/background_contents_service.h"
 #include "chrome/browser/bookmarks/bookmark_utils.h"
 #include "chrome/browser/browser.h"
 #include "chrome/browser/browser_shutdown.h"
@@ -126,6 +127,7 @@
 #if defined(OS_CHROMEOS)
   chromeos::Preferences::RegisterUserPrefs(user_prefs);
 #endif
+  BackgroundContentsService::RegisterUserPrefs(user_prefs);
 }
 
 }  // namespace browser
diff --git a/chrome/browser/browser_process_impl.cc b/chrome/browser/browser_process_impl.cc
index a74f5ab..486dd1e 100644
--- a/chrome/browser/browser_process_impl.cc
+++ b/chrome/browser/browser_process_impl.cc
@@ -576,10 +576,8 @@
       }
   }
 
-  // TODO(atwilson): Uncomment the following two lines to add the "persistence"
-  // switch when the corresponding CL is committed.
-  // if (!new_cl->HasSwitch(switches::kLongLivedExtensions))
-  //  new_cl->AppendSwitch(switches::kLongLivedExtensions);
+  if (!new_cl->HasSwitch(switches::kRestoreBackgroundContents))
+    new_cl->AppendSwitch(switches::kRestoreBackgroundContents);
 
   DLOG(WARNING) << "Shutting down current instance of the browser.";
   BrowserList::CloseAllBrowsersAndExit();
diff --git a/chrome/browser/extensions/extensions_ui.cc b/chrome/browser/extensions/extensions_ui.cc
index 8c6664b6..5cd3711 100644
--- a/chrome/browser/extensions/extensions_ui.cc
+++ b/chrome/browser/extensions/extensions_ui.cc
@@ -28,6 +28,7 @@
 #include "chrome/browser/renderer_host/render_process_host.h"
 #include "chrome/browser/renderer_host/render_widget_host.h"
 #include "chrome/browser/renderer_host/render_view_host.h"
+#include "chrome/browser/tab_contents/background_contents.h"
 #include "chrome/browser/tab_contents/tab_contents.h"
 #include "chrome/browser/tab_contents/tab_contents_view.h"
 #include "chrome/common/chrome_switches.h"
@@ -698,8 +699,13 @@
     // Doing it this way gets everything but causes the page to be rendered
     // more than we need. It doesn't seem to result in any noticeable flicker.
     case NotificationType::RENDER_VIEW_HOST_DELETED:
-    case NotificationType::BACKGROUND_CONTENTS_DELETED:
       deleting_rvh_ = Details<RenderViewHost>(details).ptr();
+      MaybeUpdateAfterNotification();
+      break;
+    case NotificationType::BACKGROUND_CONTENTS_DELETED:
+      deleting_rvh_ = Details<BackgroundContents>(details)->render_view_host();
+      MaybeUpdateAfterNotification();
+      break;
     case NotificationType::EXTENSION_LOADED:
     case NotificationType::EXTENSION_PROCESS_CREATED:
     case NotificationType::EXTENSION_UNLOADED:
@@ -709,16 +715,19 @@
     case NotificationType::EXTENSION_FUNCTION_DISPATCHER_DESTROYED:
     case NotificationType::NAV_ENTRY_COMMITTED:
     case NotificationType::BACKGROUND_CONTENTS_NAVIGATED:
-      if (!ignore_notifications_ && dom_ui_->tab_contents())
-        HandleRequestExtensionsData(NULL);
-      deleting_rvh_ = NULL;
+      MaybeUpdateAfterNotification();
       break;
-
     default:
       NOTREACHED();
   }
 }
 
+void ExtensionsDOMHandler::MaybeUpdateAfterNotification() {
+  if (!ignore_notifications_ && dom_ui_->tab_contents())
+    HandleRequestExtensionsData(NULL);
+  deleting_rvh_ = NULL;
+}
+
 static void CreateScriptFileDetailValue(
     const FilePath& extension_path, const UserScript::FileList& scripts,
     const wchar_t* key, DictionaryValue* script_data) {
diff --git a/chrome/browser/extensions/extensions_ui.h b/chrome/browser/extensions/extensions_ui.h
index 449c8da6..e85e586a0 100644
--- a/chrome/browser/extensions/extensions_ui.h
+++ b/chrome/browser/extensions/extensions_ui.h
@@ -170,6 +170,9 @@
   // Callback for "selectFilePath" message.
   void HandleSelectFilePathMessage(const Value* value);
 
+  // Forces a UI update if appropriate after a notification is received.
+  void MaybeUpdateAfterNotification();
+
   // SelectFileDialog::Listener
   virtual void FileSelected(const FilePath& path,
                             int index, void* params);
diff --git a/chrome/browser/profile.cc b/chrome/browser/profile.cc
index bbfd60d..a2fbff3 100644
--- a/chrome/browser/profile.cc
+++ b/chrome/browser/profile.cc
@@ -16,6 +16,7 @@
 #include "chrome/browser/appcache/chrome_appcache_service.h"
 #include "chrome/browser/autocomplete/autocomplete_classifier.h"
 #include "chrome/browser/autofill/personal_data_manager.h"
+#include "chrome/browser/background_contents_service.h"
 #include "chrome/browser/bookmarks/bookmark_model.h"
 #include "chrome/browser/browser_list.h"
 #include "chrome/browser/browser_prefs.h"
@@ -309,6 +310,8 @@
     // (cookies, downloads...).
     registrar_.Add(this, NotificationType::BROWSER_CLOSED,
                    NotificationService::AllSources());
+    background_contents_service_.reset(
+        new BackgroundContentsService(this, CommandLine::ForCurrentProcess()));
   }
 
   virtual ~OffTheRecordProfileImpl() {
@@ -366,6 +369,10 @@
     return GetOriginalProfile()->GetExtensionsService();
   }
 
+  virtual BackgroundContentsService* GetBackgroundContentsService() {
+    return background_contents_service_.get();
+  }
+
   virtual UserScriptMaster* GetUserScriptMaster() {
     return GetOriginalProfile()->GetUserScriptMaster();
   }
@@ -721,6 +728,9 @@
 
   FilePath last_selected_directory_;
 
+  // Tracks all BackgroundContents running under this profile.
+  scoped_ptr<BackgroundContentsService> background_contents_service_;
+
   DISALLOW_COPY_AND_ASSIGN(OffTheRecordProfileImpl);
 };
 
@@ -808,6 +818,9 @@
 
   pinned_tab_service_.reset(new PinnedTabService(this));
 
+  background_contents_service_.reset(
+      new BackgroundContentsService(this, CommandLine::ForCurrentProcess()));
+
   // Log the profile size after a reasonable startup delay.
   ChromeThread::PostDelayedTask(ChromeThread::FILE, FROM_HERE,
                                 new ProfileSizeTask(path_), 112000);
@@ -1049,6 +1062,10 @@
   return extensions_service_.get();
 }
 
+BackgroundContentsService* ProfileImpl::GetBackgroundContentsService() {
+  return background_contents_service_.get();
+}
+
 UserScriptMaster* ProfileImpl::GetUserScriptMaster() {
   return user_script_master_.get();
 }
diff --git a/chrome/browser/profile.h b/chrome/browser/profile.h
index 5ec66d6c..4fc6223 100644
--- a/chrome/browser/profile.h
+++ b/chrome/browser/profile.h
@@ -40,6 +40,7 @@
 }
 
 class AutocompleteClassifier;
+class BackgroundContentsService;
 class Blacklist;
 class BookmarkModel;
 class BrowserThemeProvider;
@@ -403,6 +404,9 @@
   // Returns the provider of desktop notifications for this profile.
   virtual DesktopNotificationService* GetDesktopNotificationService() = 0;
 
+  // Returns the service that manages BackgroundContents for this profile.
+  virtual BackgroundContentsService* GetBackgroundContentsService() = 0;
+
   // Marks the profile as cleanly shutdown.
   //
   // NOTE: this is invoked internally on a normal shutdown, but is public so
@@ -537,6 +541,7 @@
   virtual void ReinitializeSpellCheckHost(bool force);
   virtual WebKitContext* GetWebKitContext();
   virtual DesktopNotificationService* GetDesktopNotificationService();
+  virtual BackgroundContentsService* GetBackgroundContentsService();
   virtual void MarkAsCleanShutdown();
   virtual void InitExtensions();
   virtual void InitWebResources();
@@ -634,6 +639,7 @@
   scoped_ptr<BrowserThemeProvider> theme_provider_;
   scoped_refptr<WebKitContext> webkit_context_;
   scoped_ptr<DesktopNotificationService> desktop_notification_service_;
+  scoped_ptr<BackgroundContentsService> background_contents_service_;
   scoped_refptr<PersonalDataManager> personal_data_manager_;
   scoped_ptr<PinnedTabService> pinned_tab_service_;
   bool history_service_created_;
diff --git a/chrome/browser/tab_contents/background_contents.cc b/chrome/browser/tab_contents/background_contents.cc
index 5409d65..2e65d9d 100644
--- a/chrome/browser/tab_contents/background_contents.cc
+++ b/chrome/browser/tab_contents/background_contents.cc
@@ -4,6 +4,7 @@
 
 #include "chrome/browser/tab_contents/background_contents.h"
 
+#include "chrome/browser/background_contents_service.h"
 #include "chrome/browser/browser.h"
 #include "chrome/browser/browser_list.h"
 #include "chrome/browser/browsing_instance.h"
@@ -14,6 +15,7 @@
 #include "chrome/browser/renderer_host/site_instance.h"
 #include "chrome/browser/renderer_preferences_util.h"
 #include "chrome/common/notification_service.h"
+#include "chrome/common/url_constants.h"
 #include "chrome/common/view_types.h"
 #include "chrome/common/render_messages.h"
 
@@ -32,13 +34,15 @@
                                          session_storage_namespace_id);
   render_view_host_->AllowScriptToClose(true);
 
-#if defined(OS_WIN) || defined(OS_LINUX)
-  registrar_.Add(this, NotificationType::BROWSER_CLOSED,
-                 NotificationService::AllSources());
-#elif defined(OS_MACOSX)
-  registrar_.Add(this, NotificationType::APP_TERMINATING,
-                 NotificationService::AllSources());
-#endif
+  // Register for our parent profile to shutdown, so we can shut ourselves down
+  // as well.
+  registrar_.Add(this, NotificationType::PROFILE_DESTROYED,
+                 Source<Profile>(profile));
+}
+
+// Exposed to allow creating mocks.
+BackgroundContents::BackgroundContents()
+    : render_view_host_(NULL) {
 }
 
 void BackgroundContents::Observe(NotificationType type,
@@ -47,19 +51,10 @@
   // TODO(rafaelw): Implement pagegroup ref-counting so that non-persistent
   // background pages are closed when the last referencing frame is closed.
   switch (type.value) {
-#if defined(OS_WIN) || defined(OS_LINUX)
-    case NotificationType::BROWSER_CLOSED: {
-      bool app_closing = *Details<bool>(details).ptr();
-      if (app_closing)
-        delete this;
-      break;
-    }
-#elif defined(OS_MACOSX)
-    case NotificationType::APP_TERMINATING: {
+    case NotificationType::PROFILE_DESTROYED: {
       delete this;
       break;
     }
-#endif
     default:
       NOTREACHED() << "Unexpected notification sent.";
       break;
@@ -67,10 +62,13 @@
 }
 
 BackgroundContents::~BackgroundContents() {
+  if (!render_view_host_)   // Will be null for unit tests.
+    return;
+  Profile* profile = render_view_host_->process()->profile();
   NotificationService::current()->Notify(
       NotificationType::BACKGROUND_CONTENTS_DELETED,
-      Source<BackgroundContents>(this),
-      Details<RenderViewHost>(render_view_host_));
+      Source<Profile>(profile),
+      Details<BackgroundContents>(this));
   render_view_host_->Shutdown();  // deletes render_view_host
 }
 
@@ -90,10 +88,11 @@
   // extent a background page will be opened but will remain at about:blank.
   url_ = params.url;
 
+  Profile* profile = render_view_host->process()->profile();
   NotificationService::current()->Notify(
       NotificationType::BACKGROUND_CONTENTS_NAVIGATED,
-      Source<BackgroundContents>(this),
-      Details<RenderViewHost>(render_view_host_));
+      Source<Profile>(profile),
+      Details<BackgroundContents>(this));
 }
 
 void BackgroundContents::RunJavaScriptMessage(
@@ -126,6 +125,11 @@
 }
 
 void BackgroundContents::Close(RenderViewHost* render_view_host) {
+  Profile* profile = render_view_host->process()->profile();
+  NotificationService::current()->Notify(
+      NotificationType::BACKGROUND_CONTENTS_CLOSED,
+      Source<Profile>(profile),
+      Details<BackgroundContents>(this));
   delete this;
 }
 
diff --git a/chrome/browser/tab_contents/background_contents.h b/chrome/browser/tab_contents/background_contents.h
index af07a19a..6b94c82d 100644
--- a/chrome/browser/tab_contents/background_contents.h
+++ b/chrome/browser/tab_contents/background_contents.h
@@ -32,7 +32,7 @@
  public:
   BackgroundContents(SiteInstance* site_instance,
                      int routing_id);
-  ~BackgroundContents();
+  virtual ~BackgroundContents();
 
   // Provide access to the RenderViewHost for the
   // RenderViewHostDelegateViewHelper
@@ -109,6 +109,10 @@
   virtual TabContents* AsTabContents() { return NULL; }
   virtual ExtensionHost* AsExtensionHost() { return NULL; }
 
+ protected:
+  // Exposed for testing.
+  BackgroundContents();
+
  private:
   // The host for our HTML content.
   RenderViewHost* render_view_host_;
@@ -124,4 +128,16 @@
   DISALLOW_COPY_AND_ASSIGN(BackgroundContents);
 };
 
+// This is the data sent out as the details with BACKGROUND_CONTENTS_OPENED.
+struct BackgroundContentsOpenedDetails {
+  // The BackgroundContents object that has just been opened.
+  BackgroundContents* contents;
+
+  // The name of the parent frame for these contents.
+  const string16& frame_name;
+
+  // The ID of the parent application (if any).
+  const string16& application_id;
+};
+
 #endif  // CHROME_BROWSER_TAB_CONTENTS_BACKGROUND_CONTENTS_H_
diff --git a/chrome/browser/tab_contents/render_view_host_delegate_helper.cc b/chrome/browser/tab_contents/render_view_host_delegate_helper.cc
index be662e29..6edd8739 100644
--- a/chrome/browser/tab_contents/render_view_host_delegate_helper.cc
+++ b/chrome/browser/tab_contents/render_view_host_delegate_helper.cc
@@ -6,6 +6,7 @@
 
 #include "base/command_line.h"
 #include "base/string_util.h"
+#include "chrome/browser/background_contents_service.h"
 #include "chrome/browser/browser.h"
 #include "chrome/browser/character_encoding.h"
 #include "chrome/browser/extensions/extensions_service.h"
@@ -21,35 +22,56 @@
 #include "chrome/browser/tab_contents/tab_contents_view.h"
 #include "chrome/browser/user_style_sheet_watcher.h"
 #include "chrome/common/chrome_switches.h"
+#include "chrome/common/notification_service.h"
 #include "chrome/common/pref_names.h"
 
-bool RenderViewHostDelegateViewHelper::ShouldOpenBackgroundContents(
-    WindowContainerType window_container_type,
+BackgroundContents*
+RenderViewHostDelegateViewHelper::MaybeCreateBackgroundContents(
+    int route_id,
+    Profile* profile,
+    SiteInstance* site,
     GURL opener_url,
-    RenderProcessHost* opener_process,
-    Profile* profile) {
+    const string16& frame_name) {
   ExtensionsService* extensions_service = profile->GetExtensionsService();
-  if (window_container_type != WINDOW_CONTAINER_TYPE_BACKGROUND ||
-      !opener_url.is_valid() ||
+
+  if (!opener_url.is_valid() ||
+      frame_name.empty() ||
       !extensions_service ||
       !extensions_service->is_ready())
-    return false;
+    return NULL;
 
   Extension* extension = extensions_service->GetExtensionByURL(opener_url);
   if (!extension)
     extension = extensions_service->GetExtensionByWebExtent(opener_url);
   if (!extension ||
       !extension->HasApiPermission(Extension::kBackgroundPermission))
-    return false;
+    return NULL;
 
+  // Only allow a single background contents per app.
+  if (!profile->GetBackgroundContentsService() ||
+      profile->GetBackgroundContentsService()->GetAppBackgroundContents(
+          ASCIIToUTF16(extension->id())))
+    return NULL;
+
+  // Ensure that we're trying to open this from the extension's process.
   ExtensionProcessManager* process_manager =
       profile->GetExtensionProcessManager();
-  if (!opener_process || !process_manager ||
-      opener_process != process_manager->GetExtensionProcess(opener_url))
-    return false;
+  if (!site->GetProcess() || !process_manager ||
+      site->GetProcess() != process_manager->GetExtensionProcess(opener_url))
+    return NULL;
 
-  return true;
+  // Passed all the checks, so this should be created as a BackgroundContents.
+  BackgroundContents* contents = new BackgroundContents(site, route_id);
+  string16 appid = ASCIIToUTF16(extension->id());
+  BackgroundContentsOpenedDetails details = { contents, frame_name, appid };
+  NotificationService::current()->Notify(
+      NotificationType::BACKGROUND_CONTENTS_OPENED,
+      Source<Profile>(profile),
+      Details<BackgroundContentsOpenedDetails>(&details));
+
+  return contents;
 }
+
 TabContents* RenderViewHostDelegateViewHelper::CreateNewWindow(
     int route_id,
     Profile* profile,
@@ -58,13 +80,17 @@
     RenderViewHostDelegate* opener,
     WindowContainerType window_container_type,
     const string16& frame_name) {
-  if (ShouldOpenBackgroundContents(window_container_type,
-                                   opener->GetURL(),
-                                   site->GetProcess(),
-                                   profile)) {
-    BackgroundContents* contents = new BackgroundContents(site, route_id);
-    pending_contents_[route_id] = contents->render_view_host();
-    return NULL;
+  if (window_container_type == WINDOW_CONTAINER_TYPE_BACKGROUND) {
+    BackgroundContents* contents = MaybeCreateBackgroundContents(
+        route_id,
+        profile,
+        site,
+        opener->GetURL(),
+        frame_name);
+    if (contents) {
+      pending_contents_[route_id] = contents->render_view_host();
+      return NULL;
+    }
   }
 
   // Create the new web contents. This will automatically create the new
diff --git a/chrome/browser/tab_contents/render_view_host_delegate_helper.h b/chrome/browser/tab_contents/render_view_host_delegate_helper.h
index 08ef2db..5b1d99b0 100644
--- a/chrome/browser/tab_contents/render_view_host_delegate_helper.h
+++ b/chrome/browser/tab_contents/render_view_host_delegate_helper.h
@@ -16,6 +16,7 @@
 #include "webkit/glue/webpreferences.h"
 #include "webkit/glue/window_open_disposition.h"
 
+class BackgroundContents;
 class Browser;
 class ExtensionsService;
 class PrefService;
@@ -68,10 +69,12 @@
   void RenderWidgetHostDestroyed(RenderWidgetHost* host);
 
  private:
-  bool ShouldOpenBackgroundContents(WindowContainerType window_container_type,
-                                    GURL opener_url,
-                                    RenderProcessHost* opener_process,
-                                    Profile* profile);
+  BackgroundContents* MaybeCreateBackgroundContents(
+      int route_id,
+      Profile* profile,
+      SiteInstance* site,
+      GURL opener_url,
+      const string16& frame_name);
 
   // Tracks created RenderViewHost objects that have not been shown yet.
   // They are identified by the route ID passed to CreateNewWindow.
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 291f5dc..a5f6a245 100755
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -221,6 +221,8 @@
         'browser/automation/ui_controls.h',
         'browser/back_forward_menu_model.cc',
         'browser/back_forward_menu_model.h',
+        'browser/background_contents_service.h',
+        'browser/background_contents_service.cc',
         'browser/blocked_popup_container.cc',
         'browser/blocked_popup_container.h',
         'browser/bookmarks/bookmark_codec.cc',
diff --git a/chrome/chrome_tests.gypi b/chrome/chrome_tests.gypi
index 72d24db..46a855d 100755
--- a/chrome/chrome_tests.gypi
+++ b/chrome/chrome_tests.gypi
@@ -582,6 +582,7 @@
         'browser/autofill/phone_number_unittest.cc',
         'browser/automation/automation_provider_unittest.cc',
         'browser/back_forward_menu_model_unittest.cc',
+        'browser/background_contents_service_unittest.cc',
         'browser/bookmarks/bookmark_codec_unittest.cc',
         'browser/bookmarks/bookmark_context_menu_controller_unittest.cc',
         'browser/bookmarks/bookmark_drag_data_unittest.cc',
diff --git a/chrome/common/chrome_switches.cc b/chrome/common/chrome_switches.cc
index f5b6dac5..da839ae6 100644
--- a/chrome/common/chrome_switches.cc
+++ b/chrome/common/chrome_switches.cc
@@ -514,9 +514,6 @@
 // Load an NPAPI plugin from the specified path.
 const char kLoadPlugin[]                    = "load-plugin";
 
-// Long lived extensions.
-const char kLongLivedExtensions[]           = "long-lived-extensions";
-
 // Will filter log messages to show only the messages that are prefixed
 // with the specified value. See also kEnableLogging and kLoggingLevel.
 const char kLogFilterPrefix[]               = "log-filter-prefix";
@@ -711,6 +708,10 @@
 // Causes the renderer process to display a dialog on launch.
 const char kRendererStartupDialog[]         = "renderer-startup-dialog";
 
+// Causes the URLs of BackgroundContents to be remembered and re-launched when
+// the browser restarts.
+const char kRestoreBackgroundContents[]     = "restore-background-contents";
+
 // Indicates the last session should be restored on startup. This overrides
 // the preferences value and is primarily intended for testing. The value of
 // this switch is the number of tabs to wait until loaded before
diff --git a/chrome/common/chrome_switches.h b/chrome/common/chrome_switches.h
index a6ea1d4..31446733 100644
--- a/chrome/common/chrome_switches.h
+++ b/chrome/common/chrome_switches.h
@@ -154,7 +154,6 @@
 extern const char kJavaScriptFlags[];
 extern const char kLoadExtension[];
 extern const char kLoadPlugin[];
-extern const char kLongLivedExtensions[];
 extern const char kLogFilterPrefix[];
 extern const char kLogPluginMessages[];
 extern const char kLoggingLevel[];
@@ -203,6 +202,7 @@
 extern const char kRendererCrashTest[];
 extern const char kRendererProcess[];
 extern const char kRendererStartupDialog[];
+extern const char kRestoreBackgroundContents[];
 extern const char kRestoreLastSession[];
 extern const char kSafePlugins[];
 extern const char kSdchFilter[];
diff --git a/chrome/common/notification_type.h b/chrome/common/notification_type.h
index f47088c..93145d1 100644
--- a/chrome/common/notification_type.h
+++ b/chrome/common/notification_type.h
@@ -482,12 +482,23 @@
 
     // BackgroundContents ------------------------------------------------------
 
+    // A new background contents was opened by script. The source is the parent
+    // profile and the details are BackgroundContentsOpenedDetails.
+    BACKGROUND_CONTENTS_OPENED,
+
     // The background contents navigated to a new location. The source is the
-    // BackgroundContents, and the details are contained RenderViewHost.
+    // parent Profile, and the details are the BackgroundContents that was
+    // navigated.
     BACKGROUND_CONTENTS_NAVIGATED,
 
+    // The background contents were closed by someone invoking window.close()
+    // or the parent application was uninstalled.
+    // The source is the parent profile, and the details are the
+    // BackgroundContents.
+    BACKGROUND_CONTENTS_CLOSED,
+
     // The background contents is being deleted. The source is the
-    // BackgroundContents, and the details are the contained RendeViewHost.
+    // parent Profile, and the details are the BackgroundContents being deleted.
     BACKGROUND_CONTENTS_DELETED,
 
     // Child Processes ---------------------------------------------------------
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 9e13585..f3a8e2b 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -868,6 +868,11 @@
 // The root URL of the cloud print service.
 const wchar_t kCloudPrintServiceURL[] = L"cloud_print.service_url";
 
+// The list of BackgroundContents that should be loaded when the browser
+// launches.
+const wchar_t kRegisteredBackgroundContents[] =
+    L"background_contents.registered";
+
 // *************** SERVICE PREFS ***************
 // These are attached to the service process.
 
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 192b598..6441e9d1 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -336,6 +336,8 @@
 extern const wchar_t kProxyPacUrl[];
 extern const wchar_t kProxyBypassList[];
 
+extern const wchar_t kRegisteredBackgroundContents[];
+
 }  // namespace prefs
 
 #endif  // CHROME_COMMON_PREF_NAMES_H_
diff --git a/chrome/test/testing_browser_process.h b/chrome/test/testing_browser_process.h
index fdfe776f..bf1744f 100644
--- a/chrome/test/testing_browser_process.h
+++ b/chrome/test/testing_browser_process.h
@@ -26,6 +26,7 @@
  public:
   TestingBrowserProcess()
       : shutdown_event_(new base::WaitableEvent(true, false)),
+        module_ref_count_(0),
         app_locale_("en") {
   }
 
@@ -125,10 +126,15 @@
   }
 
   virtual unsigned int AddRefModule() {
-    return 1;
+    return ++module_ref_count_;
   }
   virtual unsigned int ReleaseModule() {
-    return 1;
+    DCHECK(module_ref_count_ > 0);
+    return --module_ref_count_;
+  }
+
+  unsigned int module_ref_count() {
+    return module_ref_count_;
   }
 
   virtual bool IsShuttingDown() {
@@ -165,6 +171,7 @@
  private:
   NotificationService notification_service_;
   scoped_ptr<base::WaitableEvent> shutdown_event_;
+  unsigned int module_ref_count_;
   scoped_ptr<Clipboard> clipboard_;
   std::string app_locale_;
 
diff --git a/chrome/test/testing_profile.h b/chrome/test/testing_profile.h
index 50bd1fe..adc8d95 100644
--- a/chrome/test/testing_profile.h
+++ b/chrome/test/testing_profile.h
@@ -265,6 +265,9 @@
   virtual DesktopNotificationService* GetDesktopNotificationService() {
     return NULL;
   }
+  virtual BackgroundContentsService* GetBackgroundContentsService() {
+    return NULL;
+  }
   virtual FilePath last_selected_directory() {
     return last_selected_directory_;
   }