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;