diff options
author | rlarocque@chromium.org <rlarocque@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-12 22:50:54 +0000 |
---|---|---|
committer | rlarocque@chromium.org <rlarocque@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98> | 2014-06-12 22:50:54 +0000 |
commit | f7deb90adb4a02ff839ab1c25ed0c4e612a70cdb (patch) | |
tree | 086d2744bd6fa12254b5a35cb3820a4c675884b4 /components/invalidation | |
parent | 908c7f37c550020d84bca287c941753bd255c2cf (diff) | |
download | chromium_src-f7deb90adb4a02ff839ab1c25ed0c4e612a70cdb.zip chromium_src-f7deb90adb4a02ff839ab1c25ed0c4e612a70cdb.tar.gz chromium_src-f7deb90adb4a02ff839ab1c25ed0c4e612a70cdb.tar.bz2 |
Componentize TiclInvalidationService
Moves gcm_invalidation_bridge and ticl_invalidation_service files from
chrome/browser/sync to components/invalidation.
Refactors TiclInvalidationService so it no longer depends on
chrome/common/chrome_content_client.h. Its only dependency on this file
was a call to GetUserAgent(). It was removed by changing the class so
that the user agent is specified at construction time.
Moves some related tests and test framework classes into
components/invalidation.
Updates the gyp files to prevent much of the TICL invalidation service
code from being built on Android. This is the same setup as before the
move, though it does fix a bug where one of the previously componentized
files was accidentally enabled for building on Android.
BUG=336571
Review URL: https://codereview.chromium.org/319223002
git-svn-id: svn://svn.chromium.org/chrome/trunk/src@276838 0039d316-1c4b-4281-b951-d872f2087c98
Diffstat (limited to 'components/invalidation')
-rw-r--r-- | components/invalidation/DEPS | 1 | ||||
-rw-r--r-- | components/invalidation/gcm_invalidation_bridge.cc | 319 | ||||
-rw-r--r-- | components/invalidation/gcm_invalidation_bridge.h | 102 | ||||
-rw-r--r-- | components/invalidation/invalidation_service_test_template.cc | 27 | ||||
-rw-r--r-- | components/invalidation/invalidation_service_test_template.h | 388 | ||||
-rw-r--r-- | components/invalidation/ticl_invalidation_service.cc | 436 | ||||
-rw-r--r-- | components/invalidation/ticl_invalidation_service.h | 172 | ||||
-rw-r--r-- | components/invalidation/ticl_invalidation_service_unittest.cc | 150 |
8 files changed, 1595 insertions, 0 deletions
diff --git a/components/invalidation/DEPS b/components/invalidation/DEPS index 20d1ff3..293bdee 100644 --- a/components/invalidation/DEPS +++ b/components/invalidation/DEPS @@ -4,6 +4,7 @@ include_rules = [ "+components/gcm_driver", "+components/keyed_service", "+components/pref_registry", + "+components/signin", "+google/cacheinvalidation", "+google_apis/gaia", diff --git a/components/invalidation/gcm_invalidation_bridge.cc b/components/invalidation/gcm_invalidation_bridge.cc new file mode 100644 index 0000000..1a7f293 --- /dev/null +++ b/components/invalidation/gcm_invalidation_bridge.cc @@ -0,0 +1,319 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "base/bind.h" +#include "base/location.h" +#include "base/single_thread_task_runner.h" +#include "base/thread_task_runner_handle.h" +#include "components/gcm_driver/gcm_driver.h" +#include "components/invalidation/gcm_invalidation_bridge.h" +#include "components/signin/core/browser/profile_oauth2_token_service.h" +#include "components/signin/core/browser/signin_manager.h" +#include "google_apis/gaia/gaia_constants.h" +#include "google_apis/gaia/identity_provider.h" + +namespace invalidation { +namespace { +// For 3rd party developers SenderId should come from application dashboard when +// server side application is registered with Google. Android invalidations use +// legacy format where gmail account can be specificed. Below value is copied +// from Android. +const char kInvalidationsSenderId[] = "ipc.invalidation@gmail.com"; +// In Android world AppId is provided by operating system and should +// match package name and hash of application. In desktop world these values +// are arbitrary and not verified/enforced by registration service (yet). +const char kInvalidationsAppId[] = "com.google.chrome.invalidations"; + +// Cacheinvalidation specific gcm message keys. +const char kContentKey[] = "content"; +const char kEchoTokenKey[] = "echo-token"; +} // namespace + +// Core should be very simple class that implements GCMNetwrokChannelDelegate +// and passes all calls to GCMInvalidationBridge. All calls should be serialized +// through GCMInvalidationBridge to avoid race conditions. +class GCMInvalidationBridge::Core : public syncer::GCMNetworkChannelDelegate, + public base::NonThreadSafe { + public: + Core(base::WeakPtr<GCMInvalidationBridge> bridge, + scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner); + virtual ~Core(); + + // syncer::GCMNetworkChannelDelegate implementation. + virtual void Initialize() OVERRIDE; + virtual void RequestToken(RequestTokenCallback callback) OVERRIDE; + virtual void InvalidateToken(const std::string& token) OVERRIDE; + virtual void Register(RegisterCallback callback) OVERRIDE; + virtual void SetMessageReceiver(MessageCallback callback) OVERRIDE; + + void RequestTokenFinished(RequestTokenCallback callback, + const GoogleServiceAuthError& error, + const std::string& token); + + void RegisterFinished(RegisterCallback callback, + const std::string& registration_id, + gcm::GCMClient::Result result); + + void OnIncomingMessage(const std::string& message, + const std::string& echo_token); + + private: + base::WeakPtr<GCMInvalidationBridge> bridge_; + scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner_; + + MessageCallback message_callback_; + + base::WeakPtrFactory<Core> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +GCMInvalidationBridge::Core::Core( + base::WeakPtr<GCMInvalidationBridge> bridge, + scoped_refptr<base::SingleThreadTaskRunner> ui_thread_task_runner) + : bridge_(bridge), + ui_thread_task_runner_(ui_thread_task_runner), + weak_factory_(this) { + // Core is created on UI thread but all calls happen on IO thread. + DetachFromThread(); +} + +GCMInvalidationBridge::Core::~Core() {} + +void GCMInvalidationBridge::Core::Initialize() { + DCHECK(CalledOnValidThread()); + // Pass core WeapPtr and TaskRunner to GCMInvalidationBridge for it to be able + // to post back. + ui_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::CoreInitializationDone, + bridge_, + weak_factory_.GetWeakPtr(), + base::ThreadTaskRunnerHandle::Get())); +} + +void GCMInvalidationBridge::Core::RequestToken(RequestTokenCallback callback) { + DCHECK(CalledOnValidThread()); + ui_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::RequestToken, bridge_, callback)); +} + +void GCMInvalidationBridge::Core::InvalidateToken(const std::string& token) { + DCHECK(CalledOnValidThread()); + ui_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::InvalidateToken, bridge_, token)); +} + +void GCMInvalidationBridge::Core::Register(RegisterCallback callback) { + DCHECK(CalledOnValidThread()); + ui_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::Register, bridge_, callback)); +} + +void GCMInvalidationBridge::Core::SetMessageReceiver(MessageCallback callback) { + message_callback_ = callback; + ui_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::SubscribeForIncomingMessages, + bridge_)); +} + +void GCMInvalidationBridge::Core::RequestTokenFinished( + RequestTokenCallback callback, + const GoogleServiceAuthError& error, + const std::string& token) { + DCHECK(CalledOnValidThread()); + callback.Run(error, token); +} + +void GCMInvalidationBridge::Core::RegisterFinished( + RegisterCallback callback, + const std::string& registration_id, + gcm::GCMClient::Result result) { + DCHECK(CalledOnValidThread()); + callback.Run(registration_id, result); +} + +void GCMInvalidationBridge::Core::OnIncomingMessage( + const std::string& message, + const std::string& echo_token) { + DCHECK(!message_callback_.is_null()); + message_callback_.Run(message, echo_token); +} + +GCMInvalidationBridge::GCMInvalidationBridge( + gcm::GCMDriver* gcm_driver, + IdentityProvider* identity_provider) + : OAuth2TokenService::Consumer("gcm_network_channel"), + gcm_driver_(gcm_driver), + identity_provider_(identity_provider), + subscribed_for_incoming_messages_(false), + weak_factory_(this) {} + +GCMInvalidationBridge::~GCMInvalidationBridge() { + if (subscribed_for_incoming_messages_) + gcm_driver_->RemoveAppHandler(kInvalidationsAppId); +} + +scoped_ptr<syncer::GCMNetworkChannelDelegate> +GCMInvalidationBridge::CreateDelegate() { + DCHECK(CalledOnValidThread()); + scoped_ptr<syncer::GCMNetworkChannelDelegate> core(new Core( + weak_factory_.GetWeakPtr(), base::ThreadTaskRunnerHandle::Get())); + return core.Pass(); +} + +void GCMInvalidationBridge::CoreInitializationDone( + base::WeakPtr<Core> core, + scoped_refptr<base::SingleThreadTaskRunner> core_thread_task_runner) { + DCHECK(CalledOnValidThread()); + core_ = core; + core_thread_task_runner_ = core_thread_task_runner; +} + +void GCMInvalidationBridge::RequestToken( + syncer::GCMNetworkChannelDelegate::RequestTokenCallback callback) { + DCHECK(CalledOnValidThread()); + if (access_token_request_ != NULL) { + // Report previous request as cancelled. + GoogleServiceAuthError error(GoogleServiceAuthError::REQUEST_CANCELED); + std::string access_token; + core_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::Core::RequestTokenFinished, + core_, + request_token_callback_, + error, + access_token)); + } + request_token_callback_ = callback; + OAuth2TokenService::ScopeSet scopes; + scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); + access_token_request_ = identity_provider_->GetTokenService()->StartRequest( + identity_provider_->GetActiveAccountId(), scopes, this); +} + +void GCMInvalidationBridge::OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(access_token_request_, request); + core_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::Core::RequestTokenFinished, + core_, + request_token_callback_, + GoogleServiceAuthError::AuthErrorNone(), + access_token)); + request_token_callback_.Reset(); + access_token_request_.reset(); +} + +void GCMInvalidationBridge::OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) { + DCHECK(CalledOnValidThread()); + DCHECK_EQ(access_token_request_, request); + core_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::Core::RequestTokenFinished, + core_, + request_token_callback_, + error, + std::string())); + request_token_callback_.Reset(); + access_token_request_.reset(); +} + +void GCMInvalidationBridge::InvalidateToken(const std::string& token) { + DCHECK(CalledOnValidThread()); + OAuth2TokenService::ScopeSet scopes; + scopes.insert(GaiaConstants::kChromeSyncOAuth2Scope); + identity_provider_->GetTokenService()->InvalidateToken( + identity_provider_->GetActiveAccountId(), scopes, token); +} + +void GCMInvalidationBridge::Register( + syncer::GCMNetworkChannelDelegate::RegisterCallback callback) { + DCHECK(CalledOnValidThread()); + // No-op if GCMClient is disabled. + if (gcm_driver_ == NULL) + return; + + std::vector<std::string> sender_ids; + sender_ids.push_back(kInvalidationsSenderId); + gcm_driver_->Register(kInvalidationsAppId, + sender_ids, + base::Bind(&GCMInvalidationBridge::RegisterFinished, + weak_factory_.GetWeakPtr(), + callback)); +} + +void GCMInvalidationBridge::RegisterFinished( + syncer::GCMNetworkChannelDelegate::RegisterCallback callback, + const std::string& registration_id, + gcm::GCMClient::Result result) { + DCHECK(CalledOnValidThread()); + core_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::Core::RegisterFinished, + core_, + callback, + registration_id, + result)); +} + +void GCMInvalidationBridge::SubscribeForIncomingMessages() { + // No-op if GCMClient is disabled. + if (gcm_driver_ == NULL) + return; + + DCHECK(!subscribed_for_incoming_messages_); + gcm_driver_->AddAppHandler(kInvalidationsAppId, this); + subscribed_for_incoming_messages_ = true; +} + +void GCMInvalidationBridge::ShutdownHandler() { + // Nothing to do. +} + +void GCMInvalidationBridge::OnMessage( + const std::string& app_id, + const gcm::GCMClient::IncomingMessage& message) { + gcm::GCMClient::MessageData::const_iterator it; + std::string content; + std::string echo_token; + it = message.data.find(kContentKey); + if (it != message.data.end()) + content = it->second; + it = message.data.find(kEchoTokenKey); + if (it != message.data.end()) + echo_token = it->second; + + core_thread_task_runner_->PostTask( + FROM_HERE, + base::Bind(&GCMInvalidationBridge::Core::OnIncomingMessage, + core_, + content, + echo_token)); +} + +void GCMInvalidationBridge::OnMessagesDeleted(const std::string& app_id) { + // Cacheinvalidation doesn't use long lived non-collapsable messages with GCM. + // Android implementation of cacheinvalidation doesn't handle MessagesDeleted + // callback so this should be no-op in desktop version as well. +} + +void GCMInvalidationBridge::OnSendError( + const std::string& app_id, + const gcm::GCMClient::SendErrorDetails& send_error_details) { + // cacheinvalidation doesn't send messages over GCM. + NOTREACHED(); +} + +} // namespace invalidation diff --git a/components/invalidation/gcm_invalidation_bridge.h b/components/invalidation/gcm_invalidation_bridge.h new file mode 100644 index 0000000..4c5efa3 --- /dev/null +++ b/components/invalidation/gcm_invalidation_bridge.h @@ -0,0 +1,102 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_INVALIDATION_GCM_INVALIDATION_BRIDGE_H_ +#define COMPONENTS_INVALIDATION_GCM_INVALIDATION_BRIDGE_H_ + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "components/gcm_driver/gcm_app_handler.h" +#include "components/gcm_driver/gcm_client.h" +#include "components/invalidation/gcm_network_channel_delegate.h" +#include "google_apis/gaia/oauth2_token_service.h" + +class IdentityProvider; + +namespace base { +class SingleThreadTaskRunner; +} // namespace base + +namespace gcm { +class GCMDriver; +} // namespace gcm + +namespace invalidation { + +// GCMInvalidationBridge and GCMInvalidationBridge::Core implement functions +// needed for GCMNetworkChannel. GCMInvalidationBridge lives on UI thread while +// Core lives on IO thread. Core implements GCMNetworkChannelDelegate and posts +// all function calls to GCMInvalidationBridge which does actual work to perform +// them. +class GCMInvalidationBridge : public gcm::GCMAppHandler, + public OAuth2TokenService::Consumer, + public base::NonThreadSafe { + public: + class Core; + + GCMInvalidationBridge(gcm::GCMDriver* gcm_driver, + IdentityProvider* identity_provider); + virtual ~GCMInvalidationBridge(); + + // OAuth2TokenService::Consumer implementation. + virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) OVERRIDE; + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE; + + // gcm::GCMEventRouter implementation. + virtual void ShutdownHandler() OVERRIDE; + virtual void OnMessage(const std::string& app_id, + const gcm::GCMClient::IncomingMessage& message) + OVERRIDE; + virtual void OnMessagesDeleted(const std::string& app_id) OVERRIDE; + virtual void OnSendError( + const std::string& app_id, + const gcm::GCMClient::SendErrorDetails& send_error_details) OVERRIDE; + + scoped_ptr<syncer::GCMNetworkChannelDelegate> CreateDelegate(); + + void CoreInitializationDone( + base::WeakPtr<Core> core, + scoped_refptr<base::SingleThreadTaskRunner> core_thread_task_runner); + + // Functions reflecting GCMNetworkChannelDelegate interface. These are called + // on UI thread to perform actual work. + void RequestToken( + syncer::GCMNetworkChannelDelegate::RequestTokenCallback callback); + void InvalidateToken(const std::string& token); + + void Register(syncer::GCMNetworkChannelDelegate::RegisterCallback callback); + + void SubscribeForIncomingMessages(); + + void RegisterFinished( + syncer::GCMNetworkChannelDelegate::RegisterCallback callback, + const std::string& registration_id, + gcm::GCMClient::Result result); + + private: + gcm::GCMDriver* const gcm_driver_; + IdentityProvider* const identity_provider_; + + base::WeakPtr<Core> core_; + scoped_refptr<base::SingleThreadTaskRunner> core_thread_task_runner_; + + // Fields related to RequestToken function. + scoped_ptr<OAuth2TokenService::Request> access_token_request_; + syncer::GCMNetworkChannelDelegate::RequestTokenCallback + request_token_callback_; + bool subscribed_for_incoming_messages_; + + base::WeakPtrFactory<GCMInvalidationBridge> weak_factory_; + + DISALLOW_COPY_AND_ASSIGN(GCMInvalidationBridge); +}; + +} // namespace invalidation + +#endif // COMPONENTS_INVALIDATION_GCM_INVALIDATION_BRIDGE_H_ diff --git a/components/invalidation/invalidation_service_test_template.cc b/components/invalidation/invalidation_service_test_template.cc new file mode 100644 index 0000000..3205c6f --- /dev/null +++ b/components/invalidation/invalidation_service_test_template.cc @@ -0,0 +1,27 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/invalidation/invalidation_service_test_template.h" + +namespace internal { + +BoundFakeInvalidationHandler::BoundFakeInvalidationHandler( + const invalidation::InvalidationService& invalidator) + : invalidator_(invalidator), + last_retrieved_state_(syncer::DEFAULT_INVALIDATION_ERROR) {} + +BoundFakeInvalidationHandler::~BoundFakeInvalidationHandler() {} + +syncer::InvalidatorState +BoundFakeInvalidationHandler::GetLastRetrievedState() const { + return last_retrieved_state_; +} + +void BoundFakeInvalidationHandler::OnInvalidatorStateChange( + syncer::InvalidatorState state) { + FakeInvalidationHandler::OnInvalidatorStateChange(state); + last_retrieved_state_ = invalidator_.GetInvalidatorState(); +} + +} // namespace internal diff --git a/components/invalidation/invalidation_service_test_template.h b/components/invalidation/invalidation_service_test_template.h new file mode 100644 index 0000000..fd7e24f --- /dev/null +++ b/components/invalidation/invalidation_service_test_template.h @@ -0,0 +1,388 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +// This class defines tests that implementations of InvalidationService should +// pass in order to be conformant. Here's how you use it to test your +// implementation. +// +// Say your class is called MyInvalidationService. Then you need to define a +// class called MyInvalidationServiceTestDelegate in +// my_invalidation_frontend_unittest.cc like this: +// +// class MyInvalidationServiceTestDelegate { +// public: +// MyInvalidationServiceTestDelegate() ... +// +// ~MyInvalidationServiceTestDelegate() { +// // DestroyInvalidator() may not be explicitly called by tests. +// DestroyInvalidator(); +// } +// +// // Create the InvalidationService implementation with the given params. +// void CreateInvalidationService() { +// ... +// } +// +// // Should return the InvalidationService implementation. Only called +// // after CreateInvalidator and before DestroyInvalidator. +// MyInvalidationService* GetInvalidationService() { +// ... +// } +// +// // Destroy the InvalidationService implementation. +// void DestroyInvalidationService() { +// ... +// } +// +// // The Trigger* functions below should block until the effects of +// // the call are visible on the current thread. +// +// // Should cause OnInvalidatorStateChange() to be called on all +// // observers of the InvalidationService implementation with the given +// // parameters. +// void TriggerOnInvalidatorStateChange(InvalidatorState state) { +// ... +// } +// +// // Should cause OnIncomingInvalidation() to be called on all +// // observers of the InvalidationService implementation with the given +// // parameters. +// void TriggerOnIncomingInvalidation( +// const ObjectIdInvalidationMap& invalidation_map) { +// ... +// } +// }; +// +// The InvalidationServiceTest test harness will have a member variable of +// this delegate type and will call its functions in the various +// tests. +// +// Then you simply #include this file as well as gtest.h and add the +// following statement to my_sync_notifier_unittest.cc: +// +// INSTANTIATE_TYPED_TEST_CASE_P( +// MyInvalidationService, +// InvalidationServiceTest, +// MyInvalidatorTestDelegate); +// +// Easy! + +#ifndef COMPONENTS_INVALIDATION_INVALIDATION_SERVICE_TEST_TEMPLATE_H_ +#define COMPONENTS_INVALIDATION_INVALIDATION_SERVICE_TEST_TEMPLATE_H_ + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "components/invalidation/fake_invalidation_handler.h" +#include "components/invalidation/invalidation_service.h" +#include "google/cacheinvalidation/include/types.h" +#include "google/cacheinvalidation/types.pb.h" +#include "sync/internal_api/public/base/ack_handle.h" +#include "sync/internal_api/public/base/invalidation.h" +#include "sync/internal_api/public/base/object_id_invalidation_map_test_util.h" +#include "sync/notifier/object_id_invalidation_map.h" +#include "testing/gtest/include/gtest/gtest.h" + +template <typename InvalidatorTestDelegate> +class InvalidationServiceTest : public testing::Test { + protected: + InvalidationServiceTest() + : id1(ipc::invalidation::ObjectSource::CHROME_SYNC, "BOOKMARK"), + id2(ipc::invalidation::ObjectSource::CHROME_SYNC, "PREFERENCE"), + id3(ipc::invalidation::ObjectSource::CHROME_SYNC, "AUTOFILL"), + id4(ipc::invalidation::ObjectSource::CHROME_PUSH_MESSAGING, + "PUSH_MESSAGE") { + } + + invalidation::InvalidationService* + CreateAndInitializeInvalidationService() { + this->delegate_.CreateInvalidationService(); + return this->delegate_.GetInvalidationService(); + } + + InvalidatorTestDelegate delegate_; + + const invalidation::ObjectId id1; + const invalidation::ObjectId id2; + const invalidation::ObjectId id3; + const invalidation::ObjectId id4; +}; + +TYPED_TEST_CASE_P(InvalidationServiceTest); + +// Initialize the invalidator, register a handler, register some IDs for that +// handler, and then unregister the handler, dispatching invalidations in +// between. The handler should only see invalidations when its registered and +// its IDs are registered. +TYPED_TEST_P(InvalidationServiceTest, Basic) { + invalidation::InvalidationService* const invalidator = + this->CreateAndInitializeInvalidationService(); + + syncer::FakeInvalidationHandler handler; + + invalidator->RegisterInvalidationHandler(&handler); + + syncer::ObjectIdInvalidationMap invalidation_map; + invalidation_map.Insert(syncer::Invalidation::Init(this->id1, 1, "1")); + invalidation_map.Insert(syncer::Invalidation::Init(this->id2, 2, "2")); + invalidation_map.Insert(syncer::Invalidation::Init(this->id3, 3, "3")); + + // Should be ignored since no IDs are registered to |handler|. + this->delegate_.TriggerOnIncomingInvalidation(invalidation_map); + EXPECT_EQ(0, handler.GetInvalidationCount()); + + syncer::ObjectIdSet ids; + ids.insert(this->id1); + ids.insert(this->id2); + invalidator->UpdateRegisteredInvalidationIds(&handler, ids); + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::INVALIDATIONS_ENABLED); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler.GetInvalidatorState()); + + syncer::ObjectIdInvalidationMap expected_invalidations; + expected_invalidations.Insert(syncer::Invalidation::Init(this->id1, 1, "1")); + expected_invalidations.Insert(syncer::Invalidation::Init(this->id2, 2, "2")); + + this->delegate_.TriggerOnIncomingInvalidation(invalidation_map); + EXPECT_EQ(1, handler.GetInvalidationCount()); + EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap())); + + ids.erase(this->id1); + ids.insert(this->id3); + invalidator->UpdateRegisteredInvalidationIds(&handler, ids); + + expected_invalidations = syncer::ObjectIdInvalidationMap(); + expected_invalidations.Insert(syncer::Invalidation::Init(this->id2, 2, "2")); + expected_invalidations.Insert(syncer::Invalidation::Init(this->id3, 3, "3")); + + // Removed object IDs should not be notified, newly-added ones should. + this->delegate_.TriggerOnIncomingInvalidation(invalidation_map); + EXPECT_EQ(2, handler.GetInvalidationCount()); + EXPECT_THAT(expected_invalidations, Eq(handler.GetLastInvalidationMap())); + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::TRANSIENT_INVALIDATION_ERROR); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler.GetInvalidatorState()); + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::INVALIDATIONS_ENABLED); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, + handler.GetInvalidatorState()); + + invalidator->UnregisterInvalidationHandler(&handler); + + // Should be ignored since |handler| isn't registered anymore. + this->delegate_.TriggerOnIncomingInvalidation(invalidation_map); + EXPECT_EQ(2, handler.GetInvalidationCount()); +} + +// Register handlers and some IDs for those handlers, register a handler with +// no IDs, and register a handler with some IDs but unregister it. Then, +// dispatch some invalidations and invalidations. Handlers that are registered +// should get invalidations, and the ones that have registered IDs should +// receive invalidations for those IDs. +TYPED_TEST_P(InvalidationServiceTest, MultipleHandlers) { + invalidation::InvalidationService* const invalidator = + this->CreateAndInitializeInvalidationService(); + + syncer::FakeInvalidationHandler handler1; + syncer::FakeInvalidationHandler handler2; + syncer::FakeInvalidationHandler handler3; + syncer::FakeInvalidationHandler handler4; + + invalidator->RegisterInvalidationHandler(&handler1); + invalidator->RegisterInvalidationHandler(&handler2); + invalidator->RegisterInvalidationHandler(&handler3); + invalidator->RegisterInvalidationHandler(&handler4); + + { + syncer::ObjectIdSet ids; + ids.insert(this->id1); + ids.insert(this->id2); + invalidator->UpdateRegisteredInvalidationIds(&handler1, ids); + } + + { + syncer::ObjectIdSet ids; + ids.insert(this->id3); + invalidator->UpdateRegisteredInvalidationIds(&handler2, ids); + } + + // Don't register any IDs for handler3. + + { + syncer::ObjectIdSet ids; + ids.insert(this->id4); + invalidator->UpdateRegisteredInvalidationIds(&handler4, ids); + } + + invalidator->UnregisterInvalidationHandler(&handler4); + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::INVALIDATIONS_ENABLED); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler1.GetInvalidatorState()); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler2.GetInvalidatorState()); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler3.GetInvalidatorState()); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler4.GetInvalidatorState()); + + { + syncer::ObjectIdInvalidationMap invalidation_map; + invalidation_map.Insert(syncer::Invalidation::Init(this->id1, 1, "1")); + invalidation_map.Insert(syncer::Invalidation::Init(this->id2, 2, "2")); + invalidation_map.Insert(syncer::Invalidation::Init(this->id3, 3, "3")); + invalidation_map.Insert(syncer::Invalidation::Init(this->id4, 4, "4")); + this->delegate_.TriggerOnIncomingInvalidation(invalidation_map); + + syncer::ObjectIdInvalidationMap expected_invalidations; + expected_invalidations.Insert( + syncer::Invalidation::Init(this->id1, 1, "1")); + expected_invalidations.Insert( + syncer::Invalidation::Init(this->id2, 2, "2")); + + EXPECT_EQ(1, handler1.GetInvalidationCount()); + EXPECT_THAT(expected_invalidations, Eq(handler1.GetLastInvalidationMap())); + + expected_invalidations = syncer::ObjectIdInvalidationMap(); + expected_invalidations.Insert( + syncer::Invalidation::Init(this->id3, 3, "3")); + + EXPECT_EQ(1, handler2.GetInvalidationCount()); + EXPECT_THAT(expected_invalidations, Eq(handler2.GetLastInvalidationMap())); + + EXPECT_EQ(0, handler3.GetInvalidationCount()); + EXPECT_EQ(0, handler4.GetInvalidationCount()); + } + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::TRANSIENT_INVALIDATION_ERROR); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler1.GetInvalidatorState()); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler2.GetInvalidatorState()); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler3.GetInvalidatorState()); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler4.GetInvalidatorState()); + + invalidator->UnregisterInvalidationHandler(&handler3); + invalidator->UnregisterInvalidationHandler(&handler2); + invalidator->UnregisterInvalidationHandler(&handler1); +} + +// Make sure that passing an empty set to UpdateRegisteredInvalidationIds clears +// the corresponding entries for the handler. +TYPED_TEST_P(InvalidationServiceTest, EmptySetUnregisters) { + invalidation::InvalidationService* const invalidator = + this->CreateAndInitializeInvalidationService(); + + syncer::FakeInvalidationHandler handler1; + + // Control observer. + syncer::FakeInvalidationHandler handler2; + + invalidator->RegisterInvalidationHandler(&handler1); + invalidator->RegisterInvalidationHandler(&handler2); + + { + syncer::ObjectIdSet ids; + ids.insert(this->id1); + ids.insert(this->id2); + invalidator->UpdateRegisteredInvalidationIds(&handler1, ids); + } + + { + syncer::ObjectIdSet ids; + ids.insert(this->id3); + invalidator->UpdateRegisteredInvalidationIds(&handler2, ids); + } + + // Unregister the IDs for the first observer. It should not receive any + // further invalidations. + invalidator->UpdateRegisteredInvalidationIds(&handler1, + syncer::ObjectIdSet()); + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::INVALIDATIONS_ENABLED); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler1.GetInvalidatorState()); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler2.GetInvalidatorState()); + + { + syncer::ObjectIdInvalidationMap invalidation_map; + invalidation_map.Insert(syncer::Invalidation::Init(this->id1, 1, "1")); + invalidation_map.Insert(syncer::Invalidation::Init(this->id2, 2, "2")); + invalidation_map.Insert(syncer::Invalidation::Init(this->id3, 3, "3")); + this->delegate_.TriggerOnIncomingInvalidation(invalidation_map); + EXPECT_EQ(0, handler1.GetInvalidationCount()); + EXPECT_EQ(1, handler2.GetInvalidationCount()); + } + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::TRANSIENT_INVALIDATION_ERROR); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler1.GetInvalidatorState()); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler2.GetInvalidatorState()); + + invalidator->UnregisterInvalidationHandler(&handler2); + invalidator->UnregisterInvalidationHandler(&handler1); +} + +namespace internal { + +// A FakeInvalidationHandler that is "bound" to a specific +// InvalidationService. This is for cross-referencing state information with +// the bound InvalidationService. +class BoundFakeInvalidationHandler : public syncer::FakeInvalidationHandler { + public: + explicit BoundFakeInvalidationHandler( + const invalidation::InvalidationService& invalidator); + virtual ~BoundFakeInvalidationHandler(); + + // Returns the last return value of GetInvalidatorState() on the + // bound invalidator from the last time the invalidator state + // changed. + syncer::InvalidatorState GetLastRetrievedState() const; + + // InvalidationHandler implementation. + virtual void OnInvalidatorStateChange( + syncer::InvalidatorState state) OVERRIDE; + + private: + const invalidation::InvalidationService& invalidator_; + syncer::InvalidatorState last_retrieved_state_; + + DISALLOW_COPY_AND_ASSIGN(BoundFakeInvalidationHandler); +}; + +} // namespace internal + +TYPED_TEST_P(InvalidationServiceTest, GetInvalidatorStateAlwaysCurrent) { + invalidation::InvalidationService* const invalidator = + this->CreateAndInitializeInvalidationService(); + + internal::BoundFakeInvalidationHandler handler(*invalidator); + invalidator->RegisterInvalidationHandler(&handler); + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::INVALIDATIONS_ENABLED); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler.GetInvalidatorState()); + EXPECT_EQ(syncer::INVALIDATIONS_ENABLED, handler.GetLastRetrievedState()); + + this->delegate_.TriggerOnInvalidatorStateChange( + syncer::TRANSIENT_INVALIDATION_ERROR); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler.GetInvalidatorState()); + EXPECT_EQ(syncer::TRANSIENT_INVALIDATION_ERROR, + handler.GetLastRetrievedState()); + + invalidator->UnregisterInvalidationHandler(&handler); +} + +REGISTER_TYPED_TEST_CASE_P(InvalidationServiceTest, + Basic, MultipleHandlers, EmptySetUnregisters, + GetInvalidatorStateAlwaysCurrent); + +#endif // COMPONENTS_INVALIDATION_INVALIDATION_SERVICE_TEST_TEMPLATE_H_ diff --git a/components/invalidation/ticl_invalidation_service.cc b/components/invalidation/ticl_invalidation_service.cc new file mode 100644 index 0000000..6654fc8 --- /dev/null +++ b/components/invalidation/ticl_invalidation_service.cc @@ -0,0 +1,436 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/invalidation/ticl_invalidation_service.h" + +#include "base/command_line.h" +#include "base/metrics/histogram.h" +#include "components/gcm_driver/gcm_driver.h" +#include "components/invalidation/gcm_invalidation_bridge.h" +#include "components/invalidation/invalidation_service_util.h" +#include "components/invalidation/non_blocking_invalidator.h" +#include "google_apis/gaia/gaia_constants.h" +#include "net/url_request/url_request_context_getter.h" +#include "sync/internal_api/public/base/invalidator_state.h" +#include "sync/notifier/invalidation_util.h" +#include "sync/notifier/invalidator.h" +#include "sync/notifier/object_id_invalidation_map.h" + +static const char* kOAuth2Scopes[] = { + GaiaConstants::kGoogleTalkOAuth2Scope +}; + +static const net::BackoffEntry::Policy kRequestAccessTokenBackoffPolicy = { + // Number of initial errors (in sequence) to ignore before applying + // exponential back-off rules. + 0, + + // Initial delay for exponential back-off in ms. + 2000, + + // Factor by which the waiting time will be multiplied. + 2, + + // Fuzzing percentage. ex: 10% will spread requests randomly + // between 90%-100% of the calculated time. + 0.2, // 20% + + // Maximum amount of time we are willing to delay our request in ms. + // TODO(pavely): crbug.com/246686 ProfileSyncService should retry + // RequestAccessToken on connection state change after backoff + 1000 * 3600 * 4, // 4 hours. + + // Time to keep an entry from being discarded even when it + // has no significant state, -1 to never discard. + -1, + + // Don't use initial delay unless the last request was an error. + false, +}; + +namespace invalidation { + +TiclInvalidationService::TiclInvalidationService( + const std::string& user_agent, + scoped_ptr<IdentityProvider> identity_provider, + scoped_ptr<TiclSettingsProvider> settings_provider, + gcm::GCMDriver* gcm_driver, + const scoped_refptr<net::URLRequestContextGetter>& request_context) + : OAuth2TokenService::Consumer("ticl_invalidation"), + user_agent_(user_agent), + identity_provider_(identity_provider.Pass()), + settings_provider_(settings_provider.Pass()), + invalidator_registrar_(new syncer::InvalidatorRegistrar()), + request_access_token_backoff_(&kRequestAccessTokenBackoffPolicy), + network_channel_type_(PUSH_CLIENT_CHANNEL), + gcm_driver_(gcm_driver), + request_context_(request_context), + logger_() {} + +TiclInvalidationService::~TiclInvalidationService() { + DCHECK(CalledOnValidThread()); + settings_provider_->RemoveObserver(this); + identity_provider_->RemoveActiveAccountRefreshTokenObserver(this); + identity_provider_->RemoveObserver(this); + if (IsStarted()) { + StopInvalidator(); + } +} + +void TiclInvalidationService::Init( + scoped_ptr<syncer::InvalidationStateTracker> invalidation_state_tracker) { + DCHECK(CalledOnValidThread()); + invalidation_state_tracker_ = invalidation_state_tracker.Pass(); + + if (invalidation_state_tracker_->GetInvalidatorClientId().empty()) { + invalidation_state_tracker_->ClearAndSetNewClientId( + GenerateInvalidatorClientId()); + } + + UpdateInvalidationNetworkChannel(); + if (IsReadyToStart()) { + StartInvalidator(network_channel_type_); + } + + identity_provider_->AddObserver(this); + identity_provider_->AddActiveAccountRefreshTokenObserver(this); + settings_provider_->AddObserver(this); +} + +void TiclInvalidationService::InitForTest( + scoped_ptr<syncer::InvalidationStateTracker> invalidation_state_tracker, + syncer::Invalidator* invalidator) { + // Here we perform the equivalent of Init() and StartInvalidator(), but with + // some minor changes to account for the fact that we're injecting the + // invalidator. + invalidation_state_tracker_ = invalidation_state_tracker.Pass(); + invalidator_.reset(invalidator); + + invalidator_->RegisterHandler(this); + invalidator_->UpdateRegisteredIds( + this, + invalidator_registrar_->GetAllRegisteredIds()); +} + +void TiclInvalidationService::RegisterInvalidationHandler( + syncer::InvalidationHandler* handler) { + DCHECK(CalledOnValidThread()); + DVLOG(2) << "Registering an invalidation handler"; + invalidator_registrar_->RegisterHandler(handler); + logger_.OnRegistration(handler->GetOwnerName()); +} + +void TiclInvalidationService::UpdateRegisteredInvalidationIds( + syncer::InvalidationHandler* handler, + const syncer::ObjectIdSet& ids) { + DCHECK(CalledOnValidThread()); + DVLOG(2) << "Registering ids: " << ids.size(); + invalidator_registrar_->UpdateRegisteredIds(handler, ids); + if (invalidator_) { + invalidator_->UpdateRegisteredIds( + this, + invalidator_registrar_->GetAllRegisteredIds()); + } + logger_.OnUpdateIds(invalidator_registrar_->GetSanitizedHandlersIdsMap()); +} + +void TiclInvalidationService::UnregisterInvalidationHandler( + syncer::InvalidationHandler* handler) { + DCHECK(CalledOnValidThread()); + DVLOG(2) << "Unregistering"; + invalidator_registrar_->UnregisterHandler(handler); + if (invalidator_) { + invalidator_->UpdateRegisteredIds( + this, + invalidator_registrar_->GetAllRegisteredIds()); + } + logger_.OnUnregistration(handler->GetOwnerName()); +} + +syncer::InvalidatorState TiclInvalidationService::GetInvalidatorState() const { + DCHECK(CalledOnValidThread()); + if (invalidator_) { + DVLOG(2) << "GetInvalidatorState returning " + << invalidator_->GetInvalidatorState(); + return invalidator_->GetInvalidatorState(); + } else { + DVLOG(2) << "Invalidator currently stopped"; + return syncer::TRANSIENT_INVALIDATION_ERROR; + } +} + +std::string TiclInvalidationService::GetInvalidatorClientId() const { + DCHECK(CalledOnValidThread()); + return invalidation_state_tracker_->GetInvalidatorClientId(); +} + +InvalidationLogger* TiclInvalidationService::GetInvalidationLogger() { + return &logger_; +} + +IdentityProvider* TiclInvalidationService::GetIdentityProvider() { + return identity_provider_.get(); +} + +void TiclInvalidationService::RequestDetailedStatus( + base::Callback<void(const base::DictionaryValue&)> return_callback) const { + if (IsStarted()) { + return_callback.Run(network_channel_options_); + invalidator_->RequestDetailedStatus(return_callback); + } +} + +void TiclInvalidationService::RequestAccessToken() { + // Only one active request at a time. + if (access_token_request_ != NULL) + return; + request_access_token_retry_timer_.Stop(); + OAuth2TokenService::ScopeSet oauth2_scopes; + for (size_t i = 0; i < arraysize(kOAuth2Scopes); i++) + oauth2_scopes.insert(kOAuth2Scopes[i]); + // Invalidate previous token, otherwise token service will return the same + // token again. + const std::string& account_id = identity_provider_->GetActiveAccountId(); + OAuth2TokenService* token_service = identity_provider_->GetTokenService(); + token_service->InvalidateToken(account_id, oauth2_scopes, access_token_); + access_token_.clear(); + access_token_request_ = + token_service->StartRequest(account_id, oauth2_scopes, this); +} + +void TiclInvalidationService::OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) { + DCHECK_EQ(access_token_request_, request); + access_token_request_.reset(); + // Reset backoff time after successful response. + request_access_token_backoff_.Reset(); + access_token_ = access_token; + if (!IsStarted() && IsReadyToStart()) { + StartInvalidator(network_channel_type_); + } else { + UpdateInvalidatorCredentials(); + } +} + +void TiclInvalidationService::OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) { + DCHECK_EQ(access_token_request_, request); + DCHECK_NE(error.state(), GoogleServiceAuthError::NONE); + access_token_request_.reset(); + switch (error.state()) { + case GoogleServiceAuthError::CONNECTION_FAILED: + case GoogleServiceAuthError::SERVICE_UNAVAILABLE: { + // Transient error. Retry after some time. + request_access_token_backoff_.InformOfRequest(false); + request_access_token_retry_timer_.Start( + FROM_HERE, + request_access_token_backoff_.GetTimeUntilRelease(), + base::Bind(&TiclInvalidationService::RequestAccessToken, + base::Unretained(this))); + break; + } + case GoogleServiceAuthError::SERVICE_ERROR: + case GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS: { + invalidator_registrar_->UpdateInvalidatorState( + syncer::INVALIDATION_CREDENTIALS_REJECTED); + break; + } + default: { + // We have no way to notify the user of this. Do nothing. + } + } +} + +void TiclInvalidationService::OnRefreshTokenAvailable( + const std::string& account_id) { + if (!IsStarted() && IsReadyToStart()) + StartInvalidator(network_channel_type_); +} + +void TiclInvalidationService::OnRefreshTokenRevoked( + const std::string& account_id) { + access_token_.clear(); + if (IsStarted()) + UpdateInvalidatorCredentials(); +} + +void TiclInvalidationService::OnActiveAccountLogout() { + access_token_request_.reset(); + request_access_token_retry_timer_.Stop(); + + if (IsStarted()) { + StopInvalidator(); + } + + // This service always expects to have a valid invalidation state. Thus, we + // must generate a new client ID to replace the existing one. Setting a new + // client ID also clears all other state. + invalidation_state_tracker_-> + ClearAndSetNewClientId(GenerateInvalidatorClientId()); +} + +void TiclInvalidationService::OnUseGCMChannelChanged() { + UpdateInvalidationNetworkChannel(); +} + +void TiclInvalidationService::OnInvalidatorStateChange( + syncer::InvalidatorState state) { + if (state == syncer::INVALIDATION_CREDENTIALS_REJECTED) { + // This may be due to normal OAuth access token expiration. If so, we must + // fetch a new one using our refresh token. Resetting the invalidator's + // access token will not reset the invalidator's exponential backoff, so + // it's safe to try to update the token every time we receive this signal. + // + // We won't be receiving any invalidations while the refresh is in progress, + // we set our state to TRANSIENT_INVALIDATION_ERROR. If the credentials + // really are invalid, the refresh request should fail and + // OnGetTokenFailure() will put us into a INVALIDATION_CREDENTIALS_REJECTED + // state. + invalidator_registrar_->UpdateInvalidatorState( + syncer::TRANSIENT_INVALIDATION_ERROR); + RequestAccessToken(); + } else { + invalidator_registrar_->UpdateInvalidatorState(state); + } + logger_.OnStateChange(state); +} + +void TiclInvalidationService::OnIncomingInvalidation( + const syncer::ObjectIdInvalidationMap& invalidation_map) { + invalidator_registrar_->DispatchInvalidationsToHandlers(invalidation_map); + + logger_.OnInvalidation(invalidation_map); +} + +std::string TiclInvalidationService::GetOwnerName() const { return "TICL"; } + +bool TiclInvalidationService::IsReadyToStart() { + if (identity_provider_->GetActiveAccountId().empty()) { + DVLOG(2) << "Not starting TiclInvalidationService: User is not signed in."; + return false; + } + + OAuth2TokenService* token_service = identity_provider_->GetTokenService(); + if (!token_service) { + DVLOG(2) + << "Not starting TiclInvalidationService: " + << "OAuth2TokenService unavailable."; + return false; + } + + if (!token_service->RefreshTokenIsAvailable( + identity_provider_->GetActiveAccountId())) { + DVLOG(2) + << "Not starting TiclInvalidationServce: Waiting for refresh token."; + return false; + } + + return true; +} + +bool TiclInvalidationService::IsStarted() const { + return invalidator_.get() != NULL; +} + +void TiclInvalidationService::StartInvalidator( + InvalidationNetworkChannel network_channel) { + DCHECK(CalledOnValidThread()); + DCHECK(!invalidator_); + DCHECK(invalidation_state_tracker_); + DCHECK(!invalidation_state_tracker_->GetInvalidatorClientId().empty()); + + // Request access token for PushClientChannel. GCMNetworkChannel will request + // access token before sending message to server. + if (network_channel == PUSH_CLIENT_CHANNEL && access_token_.empty()) { + DVLOG(1) + << "TiclInvalidationService: " + << "Deferring start until we have an access token."; + RequestAccessToken(); + return; + } + + syncer::NetworkChannelCreator network_channel_creator; + + switch (network_channel) { + case PUSH_CLIENT_CHANNEL: { + notifier::NotifierOptions options = + ParseNotifierOptions(*CommandLine::ForCurrentProcess()); + options.request_context_getter = request_context_; + options.auth_mechanism = "X-OAUTH2"; + network_channel_options_.SetString("Options.HostPort", + options.xmpp_host_port.ToString()); + network_channel_options_.SetString("Options.AuthMechanism", + options.auth_mechanism); + DCHECK_EQ(notifier::NOTIFICATION_SERVER, options.notification_method); + network_channel_creator = + syncer::NonBlockingInvalidator::MakePushClientChannelCreator(options); + break; + } + case GCM_NETWORK_CHANNEL: { + gcm_invalidation_bridge_.reset(new GCMInvalidationBridge( + gcm_driver_, identity_provider_.get())); + network_channel_creator = + syncer::NonBlockingInvalidator::MakeGCMNetworkChannelCreator( + request_context_, + gcm_invalidation_bridge_->CreateDelegate().Pass()); + break; + } + default: { + NOTREACHED(); + return; + } + } + + UMA_HISTOGRAM_ENUMERATION( + "Invalidations.NetworkChannel", network_channel, NETWORK_CHANNELS_COUNT); + invalidator_.reset(new syncer::NonBlockingInvalidator( + network_channel_creator, + invalidation_state_tracker_->GetInvalidatorClientId(), + invalidation_state_tracker_->GetSavedInvalidations(), + invalidation_state_tracker_->GetBootstrapData(), + invalidation_state_tracker_.get(), + user_agent_, + request_context_)); + + UpdateInvalidatorCredentials(); + + invalidator_->RegisterHandler(this); + invalidator_->UpdateRegisteredIds( + this, + invalidator_registrar_->GetAllRegisteredIds()); +} + +void TiclInvalidationService::UpdateInvalidationNetworkChannel() { + const InvalidationNetworkChannel network_channel_type = + settings_provider_->UseGCMChannel() ? GCM_NETWORK_CHANNEL + : PUSH_CLIENT_CHANNEL; + if (network_channel_type_ == network_channel_type) + return; + network_channel_type_ = network_channel_type; + if (IsStarted()) { + StopInvalidator(); + StartInvalidator(network_channel_type_); + } +} + +void TiclInvalidationService::UpdateInvalidatorCredentials() { + std::string email = identity_provider_->GetActiveAccountId(); + + DCHECK(!email.empty()) << "Expected user to be signed in."; + + DVLOG(2) << "UpdateCredentials: " << email; + invalidator_->UpdateCredentials(email, access_token_); +} + +void TiclInvalidationService::StopInvalidator() { + DCHECK(invalidator_); + gcm_invalidation_bridge_.reset(); + invalidator_->UnregisterHandler(this); + invalidator_.reset(); +} + +} // namespace invalidation diff --git a/components/invalidation/ticl_invalidation_service.h b/components/invalidation/ticl_invalidation_service.h new file mode 100644 index 0000000..274e6fe --- /dev/null +++ b/components/invalidation/ticl_invalidation_service.h @@ -0,0 +1,172 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef COMPONENTS_INVALIDATION_TICL_INVALIDATION_SERVICE_H_ +#define COMPONENTS_INVALIDATION_TICL_INVALIDATION_SERVICE_H_ + +#include <string> + +#include "base/compiler_specific.h" +#include "base/macros.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/threading/non_thread_safe.h" +#include "base/timer/timer.h" +#include "base/values.h" +#include "components/invalidation/invalidation_logger.h" +#include "components/invalidation/invalidation_service.h" +#include "components/invalidation/invalidator_registrar.h" +#include "components/invalidation/ticl_settings_provider.h" +#include "components/keyed_service/core/keyed_service.h" +#include "google_apis/gaia/identity_provider.h" +#include "google_apis/gaia/oauth2_token_service.h" +#include "net/base/backoff_entry.h" +#include "sync/notifier/invalidation_handler.h" + +namespace gcm { +class GCMDriver; +} + +namespace net { +class URLRequestContextGetter; +} + +namespace syncer { +class InvalidationStateTracker; +class Invalidator; +} + +namespace invalidation { +class GCMInvalidationBridge; + +// This InvalidationService wraps the C++ Invalidation Client (TICL) library. +// It provides invalidations for desktop platforms (Win, Mac, Linux). +class TiclInvalidationService : public base::NonThreadSafe, + public InvalidationService, + public OAuth2TokenService::Consumer, + public OAuth2TokenService::Observer, + public IdentityProvider::Observer, + public TiclSettingsProvider::Observer, + public syncer::InvalidationHandler { + public: + enum InvalidationNetworkChannel { + PUSH_CLIENT_CHANNEL = 0, + GCM_NETWORK_CHANNEL = 1, + + // This enum is used in UMA_HISTOGRAM_ENUMERATION. Insert new values above + // this line. + NETWORK_CHANNELS_COUNT = 2 + }; + + TiclInvalidationService( + const std::string& user_agent, + scoped_ptr<IdentityProvider> identity_provider, + scoped_ptr<TiclSettingsProvider> settings_provider, + gcm::GCMDriver* gcm_driver, + const scoped_refptr<net::URLRequestContextGetter>& request_context); + virtual ~TiclInvalidationService(); + + void Init( + scoped_ptr<syncer::InvalidationStateTracker> invalidation_state_tracker); + + // InvalidationService implementation. + // It is an error to have registered handlers when the service is destroyed. + virtual void RegisterInvalidationHandler( + syncer::InvalidationHandler* handler) OVERRIDE; + virtual void UpdateRegisteredInvalidationIds( + syncer::InvalidationHandler* handler, + const syncer::ObjectIdSet& ids) OVERRIDE; + virtual void UnregisterInvalidationHandler( + syncer::InvalidationHandler* handler) OVERRIDE; + virtual syncer::InvalidatorState GetInvalidatorState() const OVERRIDE; + virtual std::string GetInvalidatorClientId() const OVERRIDE; + virtual InvalidationLogger* GetInvalidationLogger() OVERRIDE; + virtual void RequestDetailedStatus( + base::Callback<void(const base::DictionaryValue&)> caller) const OVERRIDE; + virtual IdentityProvider* GetIdentityProvider() OVERRIDE; + + void RequestAccessToken(); + + // OAuth2TokenService::Consumer implementation + virtual void OnGetTokenSuccess( + const OAuth2TokenService::Request* request, + const std::string& access_token, + const base::Time& expiration_time) OVERRIDE; + virtual void OnGetTokenFailure( + const OAuth2TokenService::Request* request, + const GoogleServiceAuthError& error) OVERRIDE; + + // OAuth2TokenService::Observer implementation + virtual void OnRefreshTokenAvailable(const std::string& account_id) OVERRIDE; + virtual void OnRefreshTokenRevoked(const std::string& account_id) OVERRIDE; + + // IdentityProvider::Observer implementation. + virtual void OnActiveAccountLogout() OVERRIDE; + + // TiclSettingsProvider::Observer implementation. + virtual void OnUseGCMChannelChanged() OVERRIDE; + + // syncer::InvalidationHandler implementation. + virtual void OnInvalidatorStateChange( + syncer::InvalidatorState state) OVERRIDE; + virtual void OnIncomingInvalidation( + const syncer::ObjectIdInvalidationMap& invalidation_map) OVERRIDE; + virtual std::string GetOwnerName() const OVERRIDE; + + protected: + // Initializes with an injected invalidator. + void InitForTest( + scoped_ptr<syncer::InvalidationStateTracker> invalidation_state_tracker, + syncer::Invalidator* invalidator); + + private: + friend class TiclInvalidationServiceTestDelegate; + friend class TiclProfileSettingsProviderTest; + + bool IsReadyToStart(); + bool IsStarted() const; + + void StartInvalidator(InvalidationNetworkChannel network_channel); + void UpdateInvalidationNetworkChannel(); + void UpdateInvalidatorCredentials(); + void StopInvalidator(); + + const std::string user_agent_; + + scoped_ptr<IdentityProvider> identity_provider_; + scoped_ptr<TiclSettingsProvider> settings_provider_; + + scoped_ptr<syncer::InvalidatorRegistrar> invalidator_registrar_; + scoped_ptr<syncer::InvalidationStateTracker> invalidation_state_tracker_; + scoped_ptr<syncer::Invalidator> invalidator_; + + // TiclInvalidationService needs to remember access token in order to + // invalidate it with OAuth2TokenService. + std::string access_token_; + + // TiclInvalidationService needs to hold reference to access_token_request_ + // for the duration of request in order to receive callbacks. + scoped_ptr<OAuth2TokenService::Request> access_token_request_; + base::OneShotTimer<TiclInvalidationService> request_access_token_retry_timer_; + net::BackoffEntry request_access_token_backoff_; + + InvalidationNetworkChannel network_channel_type_; + gcm::GCMDriver* gcm_driver_; + scoped_ptr<GCMInvalidationBridge> gcm_invalidation_bridge_; + scoped_refptr<net::URLRequestContextGetter> request_context_; + + // The invalidation logger object we use to record state changes + // and invalidations. + InvalidationLogger logger_; + + // Keep a copy of the important parameters used in network channel creation + // for debugging. + base::DictionaryValue network_channel_options_; + + DISALLOW_COPY_AND_ASSIGN(TiclInvalidationService); +}; + +} // namespace invalidation + +#endif // COMPONENTS_INVALIDATION_TICL_INVALIDATION_SERVICE_H_ diff --git a/components/invalidation/ticl_invalidation_service_unittest.cc b/components/invalidation/ticl_invalidation_service_unittest.cc new file mode 100644 index 0000000..b9fd878 --- /dev/null +++ b/components/invalidation/ticl_invalidation_service_unittest.cc @@ -0,0 +1,150 @@ +// Copyright 2014 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#include "components/invalidation/ticl_invalidation_service.h" + +#include "base/bind.h" +#include "base/files/file_path.h" +#include "base/memory/weak_ptr.h" +#include "components/gcm_driver/fake_gcm_driver.h" +#include "components/gcm_driver/gcm_driver.h" +#include "components/invalidation/fake_invalidation_state_tracker.h" +#include "components/invalidation/fake_invalidator.h" +#include "components/invalidation/gcm_invalidation_bridge.h" +#include "components/invalidation/invalidation_service_test_template.h" +#include "google_apis/gaia/fake_identity_provider.h" +#include "google_apis/gaia/fake_oauth2_token_service.h" +#include "net/url_request/url_request_context_getter.h" +#include "sync/notifier/invalidation_state_tracker.h" +#include "sync/notifier/invalidator.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace invalidation { + +namespace { + +class FakeTiclSettingsProvider : public TiclSettingsProvider { + public: + FakeTiclSettingsProvider(); + virtual ~FakeTiclSettingsProvider(); + + // TiclSettingsProvider: + virtual bool UseGCMChannel() const OVERRIDE; + + private: + DISALLOW_COPY_AND_ASSIGN(FakeTiclSettingsProvider); +}; + +FakeTiclSettingsProvider::FakeTiclSettingsProvider() { +} + +FakeTiclSettingsProvider::~FakeTiclSettingsProvider() { +} + +bool FakeTiclSettingsProvider::UseGCMChannel() const { + return false; +} + +} // namespace + +class TiclInvalidationServiceTestDelegate { + public: + TiclInvalidationServiceTestDelegate() {} + + ~TiclInvalidationServiceTestDelegate() { + } + + void CreateInvalidationService() { + CreateUninitializedInvalidationService(); + InitializeInvalidationService(); + } + + void CreateUninitializedInvalidationService() { + gcm_driver_.reset(new gcm::FakeGCMDriver()); + invalidation_service_.reset(new TiclInvalidationService( + "TestUserAgent", + scoped_ptr<IdentityProvider>(new FakeIdentityProvider(&token_service_)), + scoped_ptr<TiclSettingsProvider>(new FakeTiclSettingsProvider), + gcm_driver_.get(), + NULL)); + } + + void InitializeInvalidationService() { + fake_invalidator_ = new syncer::FakeInvalidator(); + invalidation_service_->InitForTest( + scoped_ptr<syncer::InvalidationStateTracker>( + new syncer::FakeInvalidationStateTracker), + fake_invalidator_); + } + + InvalidationService* GetInvalidationService() { + return invalidation_service_.get(); + } + + void DestroyInvalidationService() { + invalidation_service_.reset(); + } + + void TriggerOnInvalidatorStateChange(syncer::InvalidatorState state) { + fake_invalidator_->EmitOnInvalidatorStateChange(state); + } + + void TriggerOnIncomingInvalidation( + const syncer::ObjectIdInvalidationMap& invalidation_map) { + fake_invalidator_->EmitOnIncomingInvalidation(invalidation_map); + } + + FakeOAuth2TokenService token_service_; + scoped_ptr<gcm::GCMDriver> gcm_driver_; + syncer::FakeInvalidator* fake_invalidator_; // Owned by the service. + + scoped_ptr<TiclInvalidationService> invalidation_service_; +}; + +INSTANTIATE_TYPED_TEST_CASE_P( + TiclInvalidationServiceTest, InvalidationServiceTest, + TiclInvalidationServiceTestDelegate); + +namespace internal { + +class FakeCallbackContainer { + public: + FakeCallbackContainer() : called_(false), + weak_ptr_factory_(this) {} + + void FakeCallback(const base::DictionaryValue& value) { + called_ = true; + } + + bool called_; + base::WeakPtrFactory<FakeCallbackContainer> weak_ptr_factory_; +}; + +} // namespace internal + +// Test that requesting for detailed status doesn't crash even if the +// underlying invalidator is not initialized. +TEST(TiclInvalidationServiceLoggingTest, DetailedStatusCallbacksWork) { + scoped_ptr<TiclInvalidationServiceTestDelegate> delegate ( + new TiclInvalidationServiceTestDelegate()); + + delegate->CreateUninitializedInvalidationService(); + invalidation::InvalidationService* const invalidator = + delegate->GetInvalidationService(); + + internal::FakeCallbackContainer fake_container; + invalidator->RequestDetailedStatus( + base::Bind(&internal::FakeCallbackContainer::FakeCallback, + fake_container.weak_ptr_factory_.GetWeakPtr())); + EXPECT_FALSE(fake_container.called_); + + delegate->InitializeInvalidationService(); + + invalidator->RequestDetailedStatus( + base::Bind(&internal::FakeCallbackContainer::FakeCallback, + fake_container.weak_ptr_factory_.GetWeakPtr())); + EXPECT_TRUE(fake_container.called_); +} + +} // namespace invalidation |