Plumb invalidations from Tango to the extensions code for the Push Messaging API.

BUG=139661,139663

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

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@152877 0039d316-1c4b-4281-b951-d872f2087c98
diff --git a/chrome/browser/extensions/api/push_messaging/DEPS b/chrome/browser/extensions/api/push_messaging/DEPS
new file mode 100644
index 0000000..577116b
--- /dev/null
+++ b/chrome/browser/extensions/api/push_messaging/DEPS
@@ -0,0 +1,4 @@
+include_rules = [
+  "+google/cacheinvalidation/types.pb.h",
+  "+sync/notifier/sync_notifier_observer.h",
+]
diff --git a/chrome/browser/extensions/api/push_messaging/invalidation_handler_observer.h b/chrome/browser/extensions/api/push_messaging/invalidation_handler_observer.h
deleted file mode 100644
index f1f42f2..0000000
--- a/chrome/browser/extensions/api/push_messaging/invalidation_handler_observer.h
+++ /dev/null
@@ -1,25 +0,0 @@
-// Copyright (c) 2012 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_API_PUSH_MESSAGING_INVALIDATION_HANDLER_OBSERVER_H_
-#define CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_INVALIDATION_HANDLER_OBSERVER_H_
-
-#include <string>
-
-namespace extensions {
-
-// Observer interface for the push messaging invalidation handler. For each
-// object that was invalidated, OnMessage() will be called back once for that
-// object.
-class InvalidationHandlerObserver {
- public:
-  virtual ~InvalidationHandlerObserver() {}
-  virtual void OnMessage(const std::string& extension_id,
-                         int subchannel,
-                         const std::string& payload) = 0;
-};
-
-}  // namespace extensions
-
-#endif  // CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_INVALIDATION_HANDLER_OBSERVER_H_
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_api.cc b/chrome/browser/extensions/api/push_messaging/push_messaging_api.cc
index cc92f87..fffd6a9 100644
--- a/chrome/browser/extensions/api/push_messaging/push_messaging_api.cc
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_api.cc
@@ -4,18 +4,31 @@
 
 #include "chrome/browser/extensions/api/push_messaging/push_messaging_api.h"
 
+#include <set>
+
 #include "base/bind.h"
+#include "base/logging.h"
 #include "base/values.h"
+#include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h"
 #include "chrome/browser/extensions/event_names.h"
 #include "chrome/browser/extensions/event_router.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
 #include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/sync/profile_sync_service.h"
+#include "chrome/browser/sync/profile_sync_service_factory.h"
+#include "chrome/common/chrome_notification_types.h"
 #include "chrome/common/extensions/api/experimental_push_messaging.h"
-#include "content/public/browser/browser_thread.h"
+#include "chrome/common/extensions/extension.h"
+#include "chrome/common/extensions/extension_set.h"
+#include "chrome/common/extensions/permissions/api_permission.h"
+#include "content/public/browser/notification_details.h"
+#include "content/public/browser/notification_source.h"
 #include "googleurl/src/gurl.h"
 
 namespace extensions {
 
-namespace glue = extensions::api::experimental_push_messaging;
+namespace glue = api::experimental_push_messaging;
 
 PushMessagingEventRouter::PushMessagingEventRouter(Profile* profile)
     : profile_(profile) {
@@ -24,7 +37,52 @@
 PushMessagingEventRouter::~PushMessagingEventRouter() {}
 
 void PushMessagingEventRouter::Init() {
-  // TODO(dcheng): Add hooks into InvalidationHandler when landed.
+  ProfileSyncService* pss = ProfileSyncServiceFactory::GetForProfile(profile_);
+  // This may be NULL; for example, for the ChromeOS guest user. In these cases,
+  // just return without setting up anything, since it won't work anyway.
+  if (!pss)
+    return;
+
+  const ExtensionSet* extensions =
+      ExtensionSystem::Get(profile_)->extension_service()->extensions();
+  std::set<std::string> push_messaging_extensions;
+  for (ExtensionSet::const_iterator it = extensions->begin();
+       it != extensions->end(); ++it) {
+    const Extension* extension = *it;
+    if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
+      push_messaging_extensions.insert(extension->id());
+    }
+  }
+  handler_.reset(new PushMessagingInvalidationHandler(
+      pss, this, push_messaging_extensions));
+
+  // Register for extension load/unload as well, so we can update any
+  // registrations as appropriate.
+  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_LOADED,
+                 content::Source<Profile>(profile_->GetOriginalProfile()));
+  registrar_.Add(this, chrome::NOTIFICATION_EXTENSION_UNLOADED,
+                 content::Source<Profile>(profile_->GetOriginalProfile()));
+}
+
+void PushMessagingEventRouter::Shutdown() {
+  // We need an explicit Shutdown() due to the dependencies among the various
+  // ProfileKeyedServices. ProfileSyncService depends on ExtensionSystem, so
+  // it is destroyed before us in the destruction phase of Profile shutdown.
+  // As a result, we need to drop any references to it in the Shutdown() phase
+  // instead.
+  handler_.reset();
+}
+
+void PushMessagingEventRouter::SetMapperForTest(
+    scoped_ptr<PushMessagingInvalidationMapper> mapper) {
+  handler_ = mapper.Pass();
+}
+
+void PushMessagingEventRouter::TriggerMessageForTest(
+    const std::string& extension_id,
+    int subchannel,
+    const std::string& payload) {
+  OnMessage(extension_id, subchannel, payload);
 }
 
 void PushMessagingEventRouter::OnMessage(const std::string& extension_id,
@@ -35,7 +93,7 @@
   message.payload = payload;
 
   scoped_ptr<base::ListValue> args(glue::OnMessage::Create(message));
-  profile_->GetExtensionEventRouter()->DispatchEventToExtension(
+  ExtensionSystem::Get(profile_)->event_router()->DispatchEventToExtension(
       extension_id,
       event_names::kOnPushMessage,
       args.Pass(),
@@ -43,4 +101,29 @@
       GURL());
 }
 
+void PushMessagingEventRouter::Observe(
+    int type,
+    const content::NotificationSource& source,
+    const content::NotificationDetails& details) {
+  switch (type) {
+    case chrome::NOTIFICATION_EXTENSION_LOADED: {
+      const Extension* extension = content::Details<Extension>(details).ptr();
+      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
+        handler_->RegisterExtension(extension->id());
+      }
+      break;
+    }
+    case chrome::NOTIFICATION_EXTENSION_UNLOADED: {
+      const Extension* extension =
+          content::Details<UnloadedExtensionInfo>(details)->extension;
+      if (extension->HasAPIPermission(APIPermission::kPushMessaging)) {
+        handler_->UnregisterExtension(extension->id());
+      }
+      break;
+    }
+    default:
+      NOTREACHED();
+  }
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_api.h b/chrome/browser/extensions/api/push_messaging/push_messaging_api.h
index 5d8e95c..02c55dff 100644
--- a/chrome/browser/extensions/api/push_messaging/push_messaging_api.h
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_api.h
@@ -6,32 +6,54 @@
 #define CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_PUSH_MESSAGING_API_H__
 
 #include <string>
+
 #include "base/basictypes.h"
 #include "base/compiler_specific.h"
 #include "base/gtest_prod_util.h"
-#include "chrome/browser/extensions/api/push_messaging/invalidation_handler_observer.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
 
 class Profile;
 
 namespace extensions {
 
+class PushMessagingInvalidationMapper;
+
 // Observes a single InvalidationHandler and generates onMessage events.
-class PushMessagingEventRouter : public InvalidationHandlerObserver {
+class PushMessagingEventRouter
+    : public PushMessagingInvalidationHandlerDelegate,
+      public content::NotificationObserver {
  public:
   explicit PushMessagingEventRouter(Profile* profile);
   virtual ~PushMessagingEventRouter();
 
   void Init();
+  void Shutdown();
+
+  PushMessagingInvalidationMapper* GetMapperForTest() const {
+    return handler_.get();
+  }
+  void SetMapperForTest(scoped_ptr<PushMessagingInvalidationMapper> mapper);
+  void TriggerMessageForTest(const std::string& extension_id,
+                             int subchannel,
+                             const std::string& payload);
 
  private:
-  FRIEND_TEST_ALL_PREFIXES(PushMessagingApiTest, EventDispatch);
-
-  // InvalidationHandlerObserver implementation.
+  // InvalidationHandlerDelegate implementation.
   virtual void OnMessage(const std::string& extension_id,
                          int subchannel,
                          const std::string& payload) OVERRIDE;
 
-  Profile* profile_;
+  // content::NotificationDelegate implementation.
+  virtual void Observe(int type,
+                       const content::NotificationSource& source,
+                       const content::NotificationDetails& details) OVERRIDE;
+
+  content::NotificationRegistrar registrar_;
+  Profile* const profile_;
+  scoped_ptr<PushMessagingInvalidationMapper> handler_;
 
   DISALLOW_COPY_AND_ASSIGN(PushMessagingEventRouter);
 };
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_apitest.cc b/chrome/browser/extensions/api/push_messaging/push_messaging_apitest.cc
index c78f77a7..20c523b 100644
--- a/chrome/browser/extensions/api/push_messaging/push_messaging_apitest.cc
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_apitest.cc
@@ -3,22 +3,47 @@
 // found in the LICENSE file.
 
 #include "chrome/browser/extensions/api/push_messaging/push_messaging_api.h"
+#include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h"
+#include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_mapper.h"
 #include "chrome/browser/extensions/extension_apitest.h"
 #include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_system.h"
 #include "chrome/browser/extensions/extension_test_message_listener.h"
 #include "chrome/browser/profiles/profile.h"
 #include "chrome/browser/ui/browser.h"
 #include "chrome/common/chrome_switches.h"
 #include "chrome/test/base/ui_test_utils.h"
+#include "testing/gmock/include/gmock/gmock.h"
+
+using ::testing::_;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
 
 namespace extensions {
 
+class MockInvalidationMapper : public PushMessagingInvalidationMapper {
+ public:
+  MockInvalidationMapper();
+  ~MockInvalidationMapper();
+
+  MOCK_METHOD1(RegisterExtension, void(const std::string&));
+  MOCK_METHOD1(UnregisterExtension, void(const std::string&));
+};
+
+MockInvalidationMapper::MockInvalidationMapper() {}
+MockInvalidationMapper::~MockInvalidationMapper() {}
+
 class PushMessagingApiTest : public ExtensionApiTest {
  public:
   virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
     ExtensionApiTest::SetUpCommandLine(command_line);
     command_line->AppendSwitch(switches::kEnableExperimentalExtensionApis);
   }
+
+  PushMessagingEventRouter* GetEventRouter() {
+    return ExtensionSystem::Get(browser()->profile())->extension_service()->
+        push_messaging_event_router();
+  }
 };
 
 IN_PROC_BROWSER_TEST_F(PushMessagingApiTest, EventDispatch) {
@@ -33,12 +58,48 @@
   ui_test_utils::NavigateToURL(browser(), page_url);
   EXPECT_TRUE(ready.WaitUntilSatisfied());
 
-  // Trigger a callback.
-  browser()->profile()->GetExtensionService()->
-      push_messaging_event_router()->OnMessage(
-          extension->id(), 1, "payload");
+  GetEventRouter()->TriggerMessageForTest(extension->id(), 1, "payload");
 
   EXPECT_TRUE(catcher.GetNextResult()) << catcher.message();
 }
 
+// Checks that an extension with the pushMessaging permission gets automatically
+// registered for invalidations when it is loaded.
+IN_PROC_BROWSER_TEST_F(PushMessagingApiTest, AutoRegistration) {
+  scoped_ptr<StrictMock<MockInvalidationMapper> > mapper(
+      new StrictMock<MockInvalidationMapper>);
+  StrictMock<MockInvalidationMapper>* unsafe_mapper = mapper.get();
+  // PushMessagingEventRouter owns the mapper now.
+  GetEventRouter()->SetMapperForTest(
+          mapper.PassAs<PushMessagingInvalidationMapper>());
+
+  std::string extension_id;
+  EXPECT_CALL(*unsafe_mapper, RegisterExtension(_))
+      .WillOnce(SaveArg<0>(&extension_id));
+  const extensions::Extension* extension =
+      LoadExtension(test_data_dir_.AppendASCII("push_messaging"));
+  ASSERT_TRUE(extension);
+  EXPECT_EQ(extension->id(), extension_id);
+  EXPECT_CALL(*unsafe_mapper, UnregisterExtension(extension->id()));
+  UnloadExtension(extension->id());
+}
+
+// Tests that we re-register for invalidations on restart for extensions that
+// are already installed.
+IN_PROC_BROWSER_TEST_F(PushMessagingApiTest, PRE_Restart) {
+  PushMessagingInvalidationHandler* handler =
+      static_cast<PushMessagingInvalidationHandler*>(
+          GetEventRouter()->GetMapperForTest());
+  EXPECT_TRUE(handler->GetRegisteredExtensionsForTest().empty());
+  ASSERT_TRUE(InstallExtension(test_data_dir_.AppendASCII("push_messaging"),
+                               1 /* new install */));
+}
+
+IN_PROC_BROWSER_TEST_F(PushMessagingApiTest, Restart) {
+  PushMessagingInvalidationHandler* handler =
+      static_cast<PushMessagingInvalidationHandler*>(
+          GetEventRouter()->GetMapperForTest());
+  EXPECT_EQ(1U, handler->GetRegisteredExtensionsForTest().size());
+}
+
 }  // namespace extensions
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.cc b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.cc
new file mode 100644
index 0000000..1b45bd1
--- /dev/null
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.cc
@@ -0,0 +1,155 @@
+// Copyright (c) 2012 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/api/push_messaging/push_messaging_invalidation_handler.h"
+
+#include <algorithm>
+#include <vector>
+
+#include "base/string_number_conversions.h"
+#include "base/string_split.h"
+#include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h"
+#include "chrome/browser/sync/invalidation_frontend.h"
+#include "chrome/common/extensions/extension.h"
+#include "google/cacheinvalidation/types.pb.h"
+
+namespace extensions {
+
+namespace {
+
+const int kNumberOfSubchannels = 4;
+
+// Chrome push messaging object IDs currently have the following format:
+// <format type>/<GAIA ID>/<extension ID>/<subchannel>
+// <format type> must be 'U', and <GAIA ID> is handled server-side so the client
+// never sees it.
+syncer::ObjectIdSet ExtensionIdToObjectIds(const std::string& extension_id) {
+  syncer::ObjectIdSet object_ids;
+  for (int i = 0; i < kNumberOfSubchannels; ++i) {
+    std::string name("U/");
+    name += extension_id;
+    name += "/";
+    name += base::IntToString(i);
+    // TODO(dcheng): CHROME_COMPONENTS is temporary, we need to update this once
+    // we roll cacheinvalidation.
+    object_ids.insert(invalidation::ObjectId(
+        ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+        name));
+  }
+  return object_ids;
+}
+
+// Returns true iff the conversion was successful.
+bool ObjectIdToExtensionAndSubchannel(const invalidation::ObjectId& object_id,
+                                      std::string* extension_id,
+                                      int* subchannel) {
+    // TODO(dcheng): CHROME_COMPONENTS is temporary, we need to update this once
+    // we roll cacheinvalidation.
+  if (object_id.source() !=
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS) {
+    DLOG(WARNING) << "Invalid source: " << object_id.source();
+    return false;
+  }
+
+  const std::string& name = object_id.name();
+  std::vector<std::string> components;
+  base::SplitStringDontTrim(name, '/', &components);
+  if (components.size() < 3) {
+    DLOG(WARNING) << "Invalid format type from object name " << name;
+    return false;
+  }
+  if (components[0] != "U") {
+    DLOG(WARNING) << "Invalid format type from object name " << name;
+    return false;
+  }
+  if (!Extension::IdIsValid(components[1])) {
+    DLOG(WARNING) << "Invalid extension ID from object name " << name;
+    return false;
+  }
+  *extension_id = components[1];
+  if (!base::StringToInt(components[2], subchannel)) {
+    DLOG(WARNING) << "Subchannel not a number from object name " << name;
+    return false;
+  }
+  if (*subchannel < 0 || *subchannel >= kNumberOfSubchannels) {
+    DLOG(WARNING) << "Subchannel out of range from object name " << name;
+    return false;
+  }
+  return true;
+}
+
+}  // namespace
+
+PushMessagingInvalidationHandler::PushMessagingInvalidationHandler(
+    InvalidationFrontend* service,
+    PushMessagingInvalidationHandlerDelegate* delegate,
+    const std::set<std::string>& extension_ids)
+    : service_(service),
+      registered_extensions_(extension_ids),
+      delegate_(delegate) {
+  DCHECK(service_);
+  service_->RegisterInvalidationHandler(this);
+  UpdateRegistrations();
+}
+
+PushMessagingInvalidationHandler::~PushMessagingInvalidationHandler() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  service_->UnregisterInvalidationHandler(this);
+}
+
+void PushMessagingInvalidationHandler::RegisterExtension(
+    const std::string& extension_id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(Extension::IdIsValid(extension_id));
+  registered_extensions_.insert(extension_id);
+  UpdateRegistrations();
+}
+
+void PushMessagingInvalidationHandler::UnregisterExtension(
+    const std::string& extension_id) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  DCHECK(Extension::IdIsValid(extension_id));
+  registered_extensions_.erase(extension_id);
+  UpdateRegistrations();
+}
+
+void PushMessagingInvalidationHandler::OnNotificationsEnabled() {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // Nothing to do.
+}
+
+void PushMessagingInvalidationHandler::OnNotificationsDisabled(
+    syncer::NotificationsDisabledReason reason) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  // Nothing to do.
+}
+
+void PushMessagingInvalidationHandler::OnIncomingNotification(
+    const syncer::ObjectIdPayloadMap& id_payloads,
+    syncer::IncomingNotificationSource source) {
+  DCHECK(thread_checker_.CalledOnValidThread());
+  for (syncer::ObjectIdPayloadMap::const_iterator it = id_payloads.begin();
+       it != id_payloads.end(); ++it) {
+    std::string extension_id;
+    int subchannel;
+    if (ObjectIdToExtensionAndSubchannel(it->first,
+                                         &extension_id,
+                                         &subchannel)) {
+      delegate_->OnMessage(extension_id, subchannel, it->second);
+    }
+  }
+}
+
+void PushMessagingInvalidationHandler::UpdateRegistrations() {
+  syncer::ObjectIdSet ids;
+  for (std::set<std::string>::const_iterator it =
+       registered_extensions_.begin(); it != registered_extensions_.end();
+       ++it) {
+    const syncer::ObjectIdSet& object_ids = ExtensionIdToObjectIds(*it);
+    ids.insert(object_ids.begin(), object_ids.end());
+  }
+  service_->UpdateRegisteredInvalidationIds(this, ids);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h
new file mode 100644
index 0000000..cef2ea42
--- /dev/null
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler.h
@@ -0,0 +1,67 @@
+// Copyright (c) 2012 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_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_HANDLER_H_
+#define CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_HANDLER_H_
+
+#include <set>
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/compiler_specific.h"
+#include "base/threading/thread_checker.h"
+#include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_mapper.h"
+#include "sync/notifier/sync_notifier_observer.h"
+
+class InvalidationFrontend;
+
+namespace extensions {
+
+class PushMessagingInvalidationHandlerDelegate;
+
+// This class maps extension IDs to the corresponding object IDs, manages
+// invalidation registrations, and dispatches callbacks for any invalidations
+// received.
+class PushMessagingInvalidationHandler : public PushMessagingInvalidationMapper,
+                                         public syncer::SyncNotifierObserver {
+ public:
+  // |service| and |delegate| must remain valid for the lifetime of this object.
+  // |extension_ids| is the set of extension IDs for which push messaging is
+  // enabled.
+  PushMessagingInvalidationHandler(
+      InvalidationFrontend* service,
+      PushMessagingInvalidationHandlerDelegate* delegate,
+      const std::set<std::string>& extension_ids);
+  virtual ~PushMessagingInvalidationHandler();
+
+  // PushMessagingInvalidationMapper implementation.
+  virtual void RegisterExtension(const std::string& extension_id) OVERRIDE;
+  virtual void UnregisterExtension(const std::string& extension_id) OVERRIDE;
+
+  // SyncNotifierObserver implementation.
+  virtual void OnNotificationsEnabled() OVERRIDE;
+  virtual void OnNotificationsDisabled(
+      syncer::NotificationsDisabledReason reason) OVERRIDE;
+  virtual void OnIncomingNotification(
+      const syncer::ObjectIdPayloadMap& id_payloads,
+      syncer::IncomingNotificationSource source) OVERRIDE;
+
+  const std::set<std::string>& GetRegisteredExtensionsForTest() const {
+    return registered_extensions_;
+  }
+
+ private:
+  void UpdateRegistrations();
+
+  base::ThreadChecker thread_checker_;
+  InvalidationFrontend* const service_;
+  std::set<std::string> registered_extensions_;
+  PushMessagingInvalidationHandlerDelegate* const delegate_;
+
+  DISALLOW_COPY_AND_ASSIGN(PushMessagingInvalidationHandler);
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_HANDLER_H_
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h
new file mode 100644
index 0000000..07a7f42
--- /dev/null
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h
@@ -0,0 +1,27 @@
+// Copyright (c) 2012 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_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_HANDLER_DELEGATE_H_
+#define CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_HANDLER_DELEGATE_H_
+
+#include <string>
+
+namespace extensions {
+
+// Delegate interface for the push messaging invalidation handler. For each
+// object that was invalidated, OnMessage() will be called back once for that
+// object.
+class PushMessagingInvalidationHandlerDelegate {
+ public:
+  virtual void OnMessage(const std::string& extension_id,
+                         int subchannel,
+                         const std::string& payload) = 0;
+
+ protected:
+  virtual ~PushMessagingInvalidationHandlerDelegate() {}
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_HANDLER_DELEGATE_H_
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_unittest.cc b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_unittest.cc
new file mode 100644
index 0000000..d4683502
--- /dev/null
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_unittest.cc
@@ -0,0 +1,197 @@
+// Copyright (c) 2012 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/api/push_messaging/push_messaging_invalidation_handler.h"
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_handler_delegate.h"
+#include "chrome/browser/sync/invalidation_frontend.h"
+#include "google/cacheinvalidation/types.pb.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using ::testing::_;
+using ::testing::InSequence;
+using ::testing::NotNull;
+using ::testing::SaveArg;
+using ::testing::StrictMock;
+
+namespace extensions {
+
+namespace {
+
+class MockInvalidationFrontend : public InvalidationFrontend {
+ public:
+  MockInvalidationFrontend();
+  ~MockInvalidationFrontend();
+  MOCK_METHOD1(RegisterInvalidationHandler,
+               void(syncer::SyncNotifierObserver*));
+  MOCK_METHOD2(UpdateRegisteredInvalidationIds,
+               void(syncer::SyncNotifierObserver*, const syncer::ObjectIdSet&));
+  MOCK_METHOD1(UnregisterInvalidationHandler,
+               void(syncer::SyncNotifierObserver*));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockInvalidationFrontend);
+};
+
+MockInvalidationFrontend::MockInvalidationFrontend() {}
+MockInvalidationFrontend::~MockInvalidationFrontend() {}
+
+class MockInvalidationHandlerDelegate
+    : public PushMessagingInvalidationHandlerDelegate {
+ public:
+  MockInvalidationHandlerDelegate();
+  ~MockInvalidationHandlerDelegate();
+  MOCK_METHOD3(OnMessage,
+               void(const std::string&, int, const std::string&));
+
+ private:
+  DISALLOW_COPY_AND_ASSIGN(MockInvalidationHandlerDelegate);
+};
+
+MockInvalidationHandlerDelegate::MockInvalidationHandlerDelegate() {}
+MockInvalidationHandlerDelegate::~MockInvalidationHandlerDelegate() {}
+
+}  // namespace
+
+class PushMessagingInvalidationHandlerTest : public ::testing::Test {
+ protected:
+  virtual void SetUp() OVERRIDE {
+    SetUpWithArgs(std::set<std::string>(), syncer::ObjectIdSet());
+  }
+
+  virtual void SetUpWithArgs(const std::set<std::string>& extension_ids,
+                             const syncer::ObjectIdSet& expected_ids) {
+    InSequence seq;
+    syncer::SyncNotifierObserver* handler[2] = {};
+    EXPECT_CALL(service_, RegisterInvalidationHandler(NotNull()))
+        .WillOnce(SaveArg<0>(&handler[0]));
+    EXPECT_CALL(service_,
+                UpdateRegisteredInvalidationIds(NotNull(), expected_ids))
+        .WillOnce(SaveArg<0>(&handler[1]));
+    handler_.reset(new PushMessagingInvalidationHandler(
+        &service_, &delegate_, extension_ids));
+    EXPECT_EQ(handler[0], handler[1]);
+    EXPECT_EQ(handler_.get(), handler[0]);
+
+  }
+  virtual void TearDown() OVERRIDE {
+    EXPECT_CALL(service_, UnregisterInvalidationHandler(handler_.get()));
+    handler_.reset();
+  }
+  StrictMock<MockInvalidationFrontend> service_;
+  StrictMock<MockInvalidationHandlerDelegate> delegate_;
+  scoped_ptr<PushMessagingInvalidationHandler> handler_;
+};
+
+// Tests that we correctly register any extensions passed in when constructed.
+TEST_F(PushMessagingInvalidationHandlerTest, Construction) {
+  TearDown();
+
+  InSequence seq;
+  std::set<std::string> extension_ids;
+  extension_ids.insert("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa");
+  extension_ids.insert("bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb");
+  syncer::ObjectIdSet expected_ids;
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/0"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/1"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/2"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa/3"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/0"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/1"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/2"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb/3"));
+
+  SetUpWithArgs(extension_ids, expected_ids);
+}
+
+TEST_F(PushMessagingInvalidationHandlerTest, RegisterUnregisterExtension) {
+  syncer::ObjectIdSet expected_ids;
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/cccccccccccccccccccccccccccccccc/0"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/cccccccccccccccccccccccccccccccc/1"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/cccccccccccccccccccccccccccccccc/2"));
+  expected_ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/cccccccccccccccccccccccccccccccc/3"));
+  EXPECT_CALL(service_,
+              UpdateRegisteredInvalidationIds(handler_.get(), expected_ids));
+  handler_->RegisterExtension("cccccccccccccccccccccccccccccccc");
+  EXPECT_CALL(service_,
+              UpdateRegisteredInvalidationIds(handler_.get(),
+                                              syncer::ObjectIdSet()));
+  handler_->UnregisterExtension("cccccccccccccccccccccccccccccccc");
+}
+
+TEST_F(PushMessagingInvalidationHandlerTest, Dispatch) {
+  syncer::ObjectIdSet ids;
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/dddddddddddddddddddddddddddddddd/0"));
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/dddddddddddddddddddddddddddddddd/3"));
+  EXPECT_CALL(delegate_,
+              OnMessage("dddddddddddddddddddddddddddddddd", 0, "payload"));
+  EXPECT_CALL(delegate_,
+              OnMessage("dddddddddddddddddddddddddddddddd", 3, "payload"));
+  handler_->OnIncomingNotification(ObjectIdSetToPayloadMap(ids, "payload"),
+                                   syncer::REMOTE_NOTIFICATION);
+}
+
+// Tests that malformed object IDs don't trigger spurious callbacks.
+TEST_F(PushMessagingInvalidationHandlerTest, DispatchInvalidObjectIds) {
+  syncer::ObjectIdSet ids;
+  // Completely incorrect format.
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::TEST,
+      "Invalid"));
+  // Incorrect source.
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::TEST,
+      "U/dddddddddddddddddddddddddddddddd/3"));
+  // Incorrect format type.
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "V/dddddddddddddddddddddddddddddddd/3"));
+  // Invalid extension ID length.
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/ddddddddddddddddddddddddddddddddd/3"));
+  // Non-numeric subchannel.
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/dddddddddddddddddddddddddddddddd/z"));
+  // Subchannel out of range.
+  ids.insert(invalidation::ObjectId(
+      ipc::invalidation::ObjectSource::CHROME_COMPONENTS,
+      "U/dddddddddddddddddddddddddddddddd/4"));
+  handler_->OnIncomingNotification(ObjectIdSetToPayloadMap(ids, "payload"),
+                                   syncer::REMOTE_NOTIFICATION);
+}
+
+}  // namespace extensions
diff --git a/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_mapper.h b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_mapper.h
new file mode 100644
index 0000000..a78bbcb
--- /dev/null
+++ b/chrome/browser/extensions/api/push_messaging/push_messaging_invalidation_mapper.h
@@ -0,0 +1,24 @@
+// Copyright (c) 2012 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_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_MAPPER_H_
+#define CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_MAPPER_H_
+
+#include <string>
+
+namespace extensions {
+
+// Interface for mapping extension IDs to object IDs.
+class PushMessagingInvalidationMapper {
+ public:
+  virtual ~PushMessagingInvalidationMapper() {}
+
+  // Register/unregister the object IDs associated with |extension_id|.
+  virtual void RegisterExtension(const std::string& extension_id) = 0;
+  virtual void UnregisterExtension(const std::string& extension_id) = 0;
+};
+
+}  // namespace extensions
+
+#endif  // CHROME_BROWSER_EXTENSIONS_API_PUSH_MESSAGING_PUSH_MESSAGING_INVALIDATION_MAPPER_H_
diff --git a/chrome/browser/extensions/extension_service.cc b/chrome/browser/extensions/extension_service.cc
index 5e6fd54c..b896ceb 100644
--- a/chrome/browser/extensions/extension_service.cc
+++ b/chrome/browser/extensions/extension_service.cc
@@ -517,6 +517,10 @@
   event_routers_initialized_ = true;
 }
 
+void ExtensionService::Shutdown() {
+  push_messaging_event_router_->Shutdown();
+}
+
 const Extension* ExtensionService::GetExtensionById(
     const std::string& id, bool include_disabled) const {
   int include_mask = INCLUDE_ENABLED;
diff --git a/chrome/browser/extensions/extension_service.h b/chrome/browser/extensions/extension_service.h
index 8c67cef3..2f5be17 100644
--- a/chrome/browser/extensions/extension_service.h
+++ b/chrome/browser/extensions/extension_service.h
@@ -286,6 +286,9 @@
   // Start up the extension event routers.
   void InitEventRouters();
 
+  // Called when the associated Profile is going to be destroyed.
+  void Shutdown();
+
   // Look up an extension by ID.  Does not include terminated
   // extensions.
   virtual const extensions::Extension* GetExtensionById(
diff --git a/chrome/browser/extensions/extension_system.cc b/chrome/browser/extensions/extension_system.cc
index 086d2ef..8c7c39d0 100644
--- a/chrome/browser/extensions/extension_system.cc
+++ b/chrome/browser/extensions/extension_system.cc
@@ -191,6 +191,11 @@
   }
 }
 
+void ExtensionSystemImpl::Shared::Shutdown() {
+  if (extension_service_.get())
+    extension_service_->Shutdown();
+}
+
 StateStore* ExtensionSystemImpl::Shared::state_store() {
   return state_store_.get();
 }
diff --git a/chrome/browser/extensions/extension_system.h b/chrome/browser/extensions/extension_system.h
index 884f036..280c955 100644
--- a/chrome/browser/extensions/extension_system.h
+++ b/chrome/browser/extensions/extension_system.h
@@ -187,6 +187,9 @@
     void RegisterManagementPolicyProviders();
     void Init(bool extensions_enabled);
 
+    // ProfileKeyedService implementation.
+    virtual void Shutdown() OVERRIDE;
+
     StateStore* state_store();
     ExtensionService* extension_service();
     ManagementPolicy* management_policy();
diff --git a/chrome/browser/sync/invalidation_frontend.h b/chrome/browser/sync/invalidation_frontend.h
new file mode 100644
index 0000000..9914271
--- /dev/null
+++ b/chrome/browser/sync/invalidation_frontend.h
@@ -0,0 +1,46 @@
+// Copyright (c) 2012 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_SYNC_INVALIDATION_FRONTEND_H_
+#define CHROME_BROWSER_SYNC_INVALIDATION_FRONTEND_H_
+
+#include "sync/notifier/invalidation_util.h"
+
+namespace syncer {
+class SyncNotifierObserver;
+}  // namespace syncer
+
+// Interface for classes that handle invalidation registrations and send out
+// invalidations to register handlers.
+class InvalidationFrontend {
+ public:
+  // Starts sending notifications to |handler|.  |handler| must not be NULL,
+  // and it must not already be registered.
+  //
+  // Handler registrations are persisted across restarts of sync.
+  virtual void RegisterInvalidationHandler(
+      syncer::SyncNotifierObserver* handler) = 0;
+
+  // Updates the set of ObjectIds associated with |handler|.  |handler| must
+  // not be NULL, and must already be registered.  An ID must be registered for
+  // at most one handler.
+  //
+  // Registered IDs are persisted across restarts of sync.
+  virtual void UpdateRegisteredInvalidationIds(
+      syncer::SyncNotifierObserver* handler,
+      const syncer::ObjectIdSet& ids) = 0;
+
+  // Stops sending notifications to |handler|.  |handler| must not be NULL, and
+  // it must already be registered.  Note that this doesn't unregister the IDs
+  // associated with |handler|.
+  //
+  // Handler registrations are persisted across restarts of sync.
+  virtual void UnregisterInvalidationHandler(
+      syncer::SyncNotifierObserver* handler) = 0;
+
+ protected:
+  virtual ~InvalidationFrontend() { }
+};
+
+#endif  // CHROME_BROWSER_SYNC_INVALIDATION_FRONTEND_H_
diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h
index b0fd1ba..6c3bc91bb 100644
--- a/chrome/browser/sync/profile_sync_service.h
+++ b/chrome/browser/sync/profile_sync_service.h
@@ -25,6 +25,7 @@
 #include "chrome/browser/sync/glue/data_type_manager.h"
 #include "chrome/browser/sync/glue/data_type_manager_observer.h"
 #include "chrome/browser/sync/glue/sync_backend_host.h"
+#include "chrome/browser/sync/invalidation_frontend.h"
 #include "chrome/browser/sync/invalidations/invalidator_storage.h"
 #include "chrome/browser/sync/profile_sync_service_observer.h"
 #include "chrome/browser/sync/sync_prefs.h"
@@ -153,7 +154,8 @@
                            public browser_sync::DataTypeManagerObserver,
                            public syncer::UnrecoverableErrorHandler,
                            public content::NotificationObserver,
-                           public ProfileKeyedService {
+                           public ProfileKeyedService,
+                           public InvalidationFrontend {
  public:
   typedef ProfileSyncServiceObserver Observer;
   typedef browser_sync::SyncBackendHost::Status Status;
@@ -594,22 +596,25 @@
   // and it must already be registered.
   //
   // Handler registrations are persisted across restarts of sync.
-  void RegisterInvalidationHandler(syncer::SyncNotifierObserver* handler);
+  virtual void RegisterInvalidationHandler(
+      syncer::SyncNotifierObserver* handler) OVERRIDE;
 
   // Updates the set of ObjectIds associated with |handler|.  |handler| must
   // not be NULL, and must already be registered.  An ID must be registered for
   // at most one handler.
   //
   // Registered IDs are persisted across restarts of sync.
-  void UpdateRegisteredInvalidationIds(syncer::SyncNotifierObserver* handler,
-                                       const syncer::ObjectIdSet& ids);
+  virtual void UpdateRegisteredInvalidationIds(
+      syncer::SyncNotifierObserver* handler,
+      const syncer::ObjectIdSet& ids) OVERRIDE;
 
   // Stops sending notifications to |handler|.  |handler| must not be NULL, and
   // it must already be registered.  Note that this doesn't unregister the IDs
   // associated with |handler|.
   //
   // Handler registrations are persisted across restarts of sync.
-  void UnregisterInvalidationHandler(syncer::SyncNotifierObserver* handler);
+  virtual void UnregisterInvalidationHandler(
+      syncer::SyncNotifierObserver* handler) OVERRIDE;
 
   // ProfileKeyedService implementation.
   virtual void Shutdown() OVERRIDE;