diff --git a/chrome/browser/signin/account_reconcilor_unittest.cc b/chrome/browser/signin/account_reconcilor_unittest.cc index 6a90a0e965f9..c4e69e80112b 100644 --- a/chrome/browser/signin/account_reconcilor_unittest.cc +++ b/chrome/browser/signin/account_reconcilor_unittest.cc @@ -763,6 +763,41 @@ TEST_P(AccountReconcilorTest, StartReconcileWithSessionInfoExpiredDefault) { ASSERT_FALSE(reconcilor->is_reconcile_started_); } +TEST_F(AccountReconcilorTest, MergeSessionCompletedWithBogusAccount) { + signin_manager()->SetAuthenticatedUsername("user@gmail.com"); + token_service()->UpdateCredentials("user@gmail.com", "refresh_token"); + + EXPECT_CALL(*GetMockReconcilor(), PerformMergeAction("user@gmail.com")); + + SetFakeResponse(GaiaUrls::GetInstance()->list_accounts_url().spec(), + "[\"f\", [[\"b\", 0, \"n\", \"user@gmail.com\", \"p\", 0, 0, 0, 0, 0]]]", + net::HTTP_OK, net::URLRequestStatus::SUCCESS); + SetFakeResponse("https://www.googleapis.com/oauth2/v1/userinfo", + "{\"id\":\"foo\"}", net::HTTP_OK, net::URLRequestStatus::SUCCESS); + + AccountReconcilor* reconcilor = + AccountReconcilorFactory::GetForProfile(profile()); + ASSERT_TRUE(reconcilor); + + ASSERT_FALSE(reconcilor->is_reconcile_started_); + reconcilor->StartReconcile(); + ASSERT_TRUE(reconcilor->is_reconcile_started_); + + token_service()->IssueAllTokensForAccount("user@gmail.com", "access_token", + base::Time::Now() + base::TimeDelta::FromHours(1)); + base::RunLoop().RunUntilIdle(); + + // If an unknown account id is sent, it should not upset the state. + SimulateMergeSessionCompleted(reconcilor, "bogus@gmail.com", + GoogleServiceAuthError::AuthErrorNone()); + ASSERT_TRUE(reconcilor->is_reconcile_started_); + + SimulateMergeSessionCompleted(reconcilor, "user@gmail.com", + GoogleServiceAuthError::AuthErrorNone()); + ASSERT_FALSE(reconcilor->is_reconcile_started_); +} + INSTANTIATE_TEST_CASE_P(AccountReconcilorMaybeEnabled, AccountReconcilorTest, testing::Bool()); + diff --git a/chrome/browser/signin/android_profile_oauth2_token_service.cc b/chrome/browser/signin/android_profile_oauth2_token_service.cc index cb2dea71834a..2d0da70df77f 100644 --- a/chrome/browser/signin/android_profile_oauth2_token_service.cc +++ b/chrome/browser/signin/android_profile_oauth2_token_service.cc @@ -212,6 +212,8 @@ void AndroidProfileOAuth2TokenService::ValidateAccounts( curr_ids.clear(); } + ScopedBacthChange batch(this); + JNIEnv* env = AttachCurrentThread(); ScopedJavaLocalRef java_accounts( base::android::ToJavaArrayOfStrings(env, curr_ids)); @@ -363,6 +365,7 @@ void AndroidProfileOAuth2TokenService::FireRefreshTokensLoaded() { void AndroidProfileOAuth2TokenService::RevokeAllCredentials() { VLOG(1) << "AndroidProfileOAuth2TokenService::RevokeAllCredentials"; + ScopedBacthChange batch(this); std::vector accounts = GetAccounts(); for (std::vector::iterator it = accounts.begin(); it != accounts.end(); it++) { diff --git a/chrome/browser/signin/fake_profile_oauth2_token_service.cc b/chrome/browser/signin/fake_profile_oauth2_token_service.cc index da34533194dc..f05a6a2b2897 100644 --- a/chrome/browser/signin/fake_profile_oauth2_token_service.cc +++ b/chrome/browser/signin/fake_profile_oauth2_token_service.cc @@ -57,6 +57,7 @@ void FakeProfileOAuth2TokenService::IssueRefreshToken( void FakeProfileOAuth2TokenService::IssueRefreshTokenForUser( const std::string& account_id, const std::string& token) { + ScopedBacthChange batch(this); if (token.empty()) { refresh_tokens_.erase(account_id); FireRefreshTokenRevoked(account_id); diff --git a/components/signin/core/browser/account_reconcilor.cc b/components/signin/core/browser/account_reconcilor.cc index 7964d36d4bd4..a47a9ee57648 100644 --- a/components/signin/core/browser/account_reconcilor.cc +++ b/components/signin/core/browser/account_reconcilor.cc @@ -332,17 +332,15 @@ void AccountReconcilor::OnCookieChanged(const net::CanonicalCookie* cookie) { } } -void AccountReconcilor::OnRefreshTokenAvailable(const std::string& account_id) { - VLOG(1) << "AccountReconcilor::OnRefreshTokenAvailable: " << account_id; - StartReconcile(); -} - void AccountReconcilor::OnRefreshTokenRevoked(const std::string& account_id) { VLOG(1) << "AccountReconcilor::OnRefreshTokenRevoked: " << account_id; PerformStartRemoveAction(account_id); } -void AccountReconcilor::OnRefreshTokensLoaded() {} +void AccountReconcilor::OnEndBatchChanges() { + VLOG(1) << "AccountReconcilor::OnEndBatchChanges"; + StartReconcile(); +} void AccountReconcilor::GoogleSigninSucceeded(const std::string& username, const std::string& password) { @@ -426,7 +424,9 @@ void AccountReconcilor::PerformLogoutAllAccountsAction() { } void AccountReconcilor::StartReconcile() { - if (!IsProfileConnected() || is_reconcile_started_) + if (!IsProfileConnected() || is_reconcile_started_ || + get_gaia_accounts_callbacks_.size() > 0 || + merge_session_helper_.is_running()) return; is_reconcile_started_ = true; @@ -529,7 +529,6 @@ void AccountReconcilor::ValidateAccountsFromTokenService() { DCHECK(!primary_account_.empty()); chrome_accounts_ = token_service_->GetAccounts(); - DCHECK_GT(chrome_accounts_.size(), 0u); VLOG(1) << "AccountReconcilor::ValidateAccountsFromTokenService: " << "Chrome " << chrome_accounts_.size() << " accounts, " @@ -722,16 +721,17 @@ void AccountReconcilor::ScheduleStartReconcileIfChromeAccountsChanged() { } // Remove the account from the list that is being merged. -void AccountReconcilor::MarkAccountAsAddedToCookie( +bool AccountReconcilor::MarkAccountAsAddedToCookie( const std::string& account_id) { for (std::vector::iterator i = add_to_cookie_.begin(); i != add_to_cookie_.end(); ++i) { if (account_id == *i) { add_to_cookie_.erase(i); - break; + return true; } } + return false; } void AccountReconcilor::MergeSessionCompleted( @@ -740,9 +740,10 @@ void AccountReconcilor::MergeSessionCompleted( VLOG(1) << "AccountReconcilor::MergeSessionCompleted: account_id=" << account_id; - MarkAccountAsAddedToCookie(account_id); - CalculateIfReconcileIsDone(); - ScheduleStartReconcileIfChromeAccountsChanged(); + if (MarkAccountAsAddedToCookie(account_id)) { + CalculateIfReconcileIsDone(); + ScheduleStartReconcileIfChromeAccountsChanged(); + } } void AccountReconcilor::HandleSuccessfulAccountIdCheck( diff --git a/components/signin/core/browser/account_reconcilor.h b/components/signin/core/browser/account_reconcilor.h index 019f75f2d347..a4521069e43c 100644 --- a/components/signin/core/browser/account_reconcilor.h +++ b/components/signin/core/browser/account_reconcilor.h @@ -128,6 +128,8 @@ class AccountReconcilor : public KeyedService, FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileOnlyOnce); FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, StartReconcileWithSessionInfoExpiredDefault); + FRIEND_TEST_ALL_PREFIXES(AccountReconcilorTest, + MergeSessionCompletedWithBogusAccount); // Register and unregister with dependent services. void RegisterForCookieChanges(); @@ -177,7 +179,7 @@ class AccountReconcilor : public KeyedService, const std::vector >& accounts); void ValidateAccountsFromTokenService(); // Note internally that this |account_id| is added to the cookie jar. - void MarkAccountAsAddedToCookie(const std::string& account_id); + bool MarkAccountAsAddedToCookie(const std::string& account_id); // Note internally that this |account_id| is added to the token service. void MarkAccountAsAddedToChrome(const std::string& account_id); @@ -201,9 +203,8 @@ class AccountReconcilor : public KeyedService, const GoogleServiceAuthError& error) OVERRIDE; // Overriden from OAuth2TokenService::Observer. - virtual void OnRefreshTokenAvailable(const std::string& account_id) OVERRIDE; virtual void OnRefreshTokenRevoked(const std::string& account_id) OVERRIDE; - virtual void OnRefreshTokensLoaded() OVERRIDE; + virtual void OnEndBatchChanges() OVERRIDE; // Overriden from SigninManagerBase::Observer. virtual void GoogleSigninSucceeded(const std::string& username, diff --git a/components/signin/core/browser/mutable_profile_oauth2_token_service.cc b/components/signin/core/browser/mutable_profile_oauth2_token_service.cc index ab6a65e07e6d..d62a30d9041f 100644 --- a/components/signin/core/browser/mutable_profile_oauth2_token_service.cc +++ b/components/signin/core/browser/mutable_profile_oauth2_token_service.cc @@ -223,34 +223,38 @@ void MutableProfileOAuth2TokenService::LoadAllCredentialsIntoMemory( const std::map& db_tokens) { std::string old_login_token; - for (std::map::const_iterator iter = - db_tokens.begin(); - iter != db_tokens.end(); - ++iter) { - std::string prefixed_account_id = iter->first; - std::string refresh_token = iter->second; - - if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty()) - old_login_token = refresh_token; - - if (IsLegacyServiceId(prefixed_account_id)) { - scoped_refptr token_web_data = client()->GetDatabase(); - if (token_web_data.get()) - token_web_data->RemoveTokenForService(prefixed_account_id); - } else { - DCHECK(!refresh_token.empty()); - std::string account_id = RemoveAccountIdPrefix(prefixed_account_id); - refresh_tokens()[account_id].reset( - new AccountInfo(this, account_id, refresh_token)); - FireRefreshTokenAvailable(account_id); - // TODO(fgorski): Notify diagnostic observers. + { + ScopedBacthChange batch(this); + + for (std::map::const_iterator iter = + db_tokens.begin(); + iter != db_tokens.end(); + ++iter) { + std::string prefixed_account_id = iter->first; + std::string refresh_token = iter->second; + + if (IsLegacyRefreshTokenId(prefixed_account_id) && !refresh_token.empty()) + old_login_token = refresh_token; + + if (IsLegacyServiceId(prefixed_account_id)) { + scoped_refptr token_web_data = client()->GetDatabase(); + if (token_web_data.get()) + token_web_data->RemoveTokenForService(prefixed_account_id); + } else { + DCHECK(!refresh_token.empty()); + std::string account_id = RemoveAccountIdPrefix(prefixed_account_id); + refresh_tokens()[account_id].reset( + new AccountInfo(this, account_id, refresh_token)); + FireRefreshTokenAvailable(account_id); + // TODO(fgorski): Notify diagnostic observers. + } } - } - if (!old_login_token.empty()) { - DCHECK(!loading_primary_account_id_.empty()); - if (refresh_tokens().count(loading_primary_account_id_) == 0) - UpdateCredentials(loading_primary_account_id_, old_login_token); + if (!old_login_token.empty()) { + DCHECK(!loading_primary_account_id_.empty()); + if (refresh_tokens().count(loading_primary_account_id_) == 0) + UpdateCredentials(loading_primary_account_id_, old_login_token); + } } FireRefreshTokensLoaded(); @@ -307,6 +311,8 @@ void MutableProfileOAuth2TokenService::UpdateCredentials( bool refresh_token_present = refresh_tokens_.count(account_id) > 0; if (!refresh_token_present || refresh_tokens_[account_id]->refresh_token() != refresh_token) { + ScopedBacthChange batch(this); + // If token present, and different from the new one, cancel its requests, // and clear the entries in cache related to that account. if (refresh_token_present) { @@ -336,6 +342,7 @@ void MutableProfileOAuth2TokenService::RevokeCredentials( DCHECK(thread_checker_.CalledOnValidThread()); if (refresh_tokens_.count(account_id) > 0) { + ScopedBacthChange batch(this); RevokeCredentialsOnServer(refresh_tokens_[account_id]->refresh_token()); CancelRequestsForAccount(account_id); ClearCacheForAccount(account_id); @@ -366,6 +373,9 @@ void MutableProfileOAuth2TokenService::RevokeAllCredentials() { if (!client()->CanRevokeCredentials()) return; DCHECK(thread_checker_.CalledOnValidThread()); + + ScopedBacthChange batch(this); + CancelWebTokenFetch(); CancelAllRequests(); ClearCache(); diff --git a/components/signin/core/browser/mutable_profile_oauth2_token_service_unittest.cc b/components/signin/core/browser/mutable_profile_oauth2_token_service_unittest.cc index 153e0b42ad82..b76d7494e35c 100644 --- a/components/signin/core/browser/mutable_profile_oauth2_token_service_unittest.cc +++ b/components/signin/core/browser/mutable_profile_oauth2_token_service_unittest.cc @@ -33,7 +33,9 @@ class MutableProfileOAuth2TokenServiceTest : factory_(NULL), token_available_count_(0), token_revoked_count_(0), - tokens_loaded_count_(0) {} + tokens_loaded_count_(0), + start_batch_changes_(0), + end_batch_changes_(0) {} virtual void SetUp() OVERRIDE { #if defined(OS_MACOSX) @@ -71,10 +73,20 @@ class MutableProfileOAuth2TokenServiceTest } virtual void OnRefreshTokensLoaded() OVERRIDE { ++tokens_loaded_count_; } + virtual void OnStartBatchChanges() OVERRIDE { + ++start_batch_changes_; + } + + virtual void OnEndBatchChanges() OVERRIDE { + ++end_batch_changes_; + } + void ResetObserverCounts() { token_available_count_ = 0; token_revoked_count_ = 0; tokens_loaded_count_ = 0; + start_batch_changes_ = 0; + end_batch_changes_ = 0; } void ExpectNoNotifications() { @@ -114,6 +126,8 @@ class MutableProfileOAuth2TokenServiceTest int token_available_count_; int token_revoked_count_; int tokens_loaded_count_; + int start_batch_changes_; + int end_batch_changes_; }; TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceDBUpgrade) { @@ -133,6 +147,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceDBUpgrade) { // Legacy tokens get discarded, but the old refresh token is kept. EXPECT_EQ(1, tokens_loaded_count_); EXPECT_EQ(1, token_available_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); EXPECT_TRUE(oauth2_service_.RefreshTokenIsAvailable(main_account_id)); EXPECT_EQ(1U, oauth2_service_.refresh_tokens().size()); EXPECT_EQ(main_refresh_token, @@ -159,6 +175,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceDBUpgrade) { // token is present it is not overwritten. EXPECT_EQ(2, token_available_count_); EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); EXPECT_TRUE(oauth2_service_.RefreshTokenIsAvailable(main_account_id)); // TODO(fgorski): cover both using RefreshTokenIsAvailable() and then get the // tokens using GetRefreshToken() @@ -170,6 +188,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceDBUpgrade) { oauth2_service_.refresh_tokens()[other_account_id]->refresh_token()); oauth2_service_.RevokeAllCredentials(); + EXPECT_EQ(2, start_batch_changes_); + EXPECT_EQ(2, end_batch_changes_); } TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceRevokeCredentials) { @@ -184,6 +204,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceRevokeCredentials) { oauth2_service_.UpdateCredentials(account_id_1, refresh_token_1); oauth2_service_.UpdateCredentials(account_id_2, refresh_token_2); + EXPECT_EQ(2, start_batch_changes_); + EXPECT_EQ(2, end_batch_changes_); // TODO(fgorski): Enable below when implemented: // EXPECT_TRUE(oauth2_servive_->RefreshTokenIsAvailable(account_id_1)); @@ -191,6 +213,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceRevokeCredentials) { ResetObserverCounts(); oauth2_service_.RevokeCredentials(account_id_1); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); ExpectOneTokenRevokedNotification(); // TODO(fgorski): Enable below when implemented: @@ -201,6 +225,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceRevokeCredentials) { EXPECT_EQ(0, token_available_count_); EXPECT_EQ(1, token_revoked_count_); EXPECT_EQ(0, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); ResetObserverCounts(); } @@ -211,6 +237,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceLoadCredentials) { // Perform a load from an empty DB. oauth2_service_.LoadCredentials("account_id"); base::RunLoop().RunUntilIdle(); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); ExpectOneTokensLoadedNotification(); // LoadCredentials() guarantees that the account given to it as argument // is in the refresh_token map. @@ -221,6 +249,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceLoadCredentials) { oauth2_service_.UpdateCredentials("account_id", "refresh_token"); oauth2_service_.UpdateCredentials("account_id2", "refresh_token2"); oauth2_service_.refresh_tokens().clear(); + EXPECT_EQ(2, start_batch_changes_); + EXPECT_EQ(2, end_batch_changes_); ResetObserverCounts(); oauth2_service_.LoadCredentials("account_id"); @@ -228,6 +258,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceLoadCredentials) { EXPECT_EQ(2, token_available_count_); EXPECT_EQ(0, token_revoked_count_); EXPECT_EQ(1, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); ResetObserverCounts(); // TODO(fgorski): Enable below when implemented: @@ -238,6 +270,8 @@ TEST_F(MutableProfileOAuth2TokenServiceTest, PersistenceLoadCredentials) { EXPECT_EQ(0, token_available_count_); EXPECT_EQ(2, token_revoked_count_); EXPECT_EQ(0, tokens_loaded_count_); + EXPECT_EQ(1, start_batch_changes_); + EXPECT_EQ(1, end_batch_changes_); ResetObserverCounts(); } diff --git a/components/signin/ios/browser/profile_oauth2_token_service_ios.mm b/components/signin/ios/browser/profile_oauth2_token_service_ios.mm index 775b17893fab..735bee38eb43 100644 --- a/components/signin/ios/browser/profile_oauth2_token_service_ios.mm +++ b/components/signin/ios/browser/profile_oauth2_token_service_ios.mm @@ -275,6 +275,8 @@ GoogleServiceAuthError auth_error( return; } + ScopedBacthChange batch(this); + // Remove all old accounts that do not appear in |new_accounts| and then // load |new_accounts|. std::vector new_accounts(GetProvider()->GetAllAccountIds()); @@ -312,6 +314,7 @@ GoogleServiceAuthError auth_error( return; } + ScopedBacthChange batch(this); CancelAllRequests(); ClearCache(); AccountInfoMap toRemove = accounts_; @@ -359,6 +362,7 @@ GoogleServiceAuthError auth_error( MutableProfileOAuth2TokenService::RevokeAllCredentials(); } + ScopedBacthChange batch(this); for (auto i = accounts.begin(); i != accounts.end(); ++i) { std::string account_id = *i; MutableProfileOAuth2TokenService::UpdateCredentials( diff --git a/google_apis/gaia/merge_session_helper.cc b/google_apis/gaia/merge_session_helper.cc index c3c8a2ead109..cf2fa95730dd 100644 --- a/google_apis/gaia/merge_session_helper.cc +++ b/google_apis/gaia/merge_session_helper.cc @@ -27,6 +27,7 @@ MergeSessionHelper::~MergeSessionHelper() { void MergeSessionHelper::LogIn(const std::string& account_id) { DCHECK(!account_id.empty()); + VLOG(1) << "MergeSessionHelper::LogIn: " << account_id; accounts_.push_back(account_id); if (accounts_.size() == 1) StartFetching(); @@ -41,6 +42,7 @@ void MergeSessionHelper::RemoveObserver(Observer* observer) { } void MergeSessionHelper::CancelAll() { + VLOG(1) << "MergeSessionHelper::CancelAll"; gaia_auth_fetcher_.reset(); uber_token_fetcher_.reset(); accounts_.clear(); @@ -50,6 +52,8 @@ void MergeSessionHelper::LogOut( const std::string& account_id, const std::vector& accounts) { DCHECK(!account_id.empty()); + VLOG(1) << "MergeSessionHelper::LogOut: " << account_id + << " accounts=" << accounts.size(); LogOutInternal(account_id, accounts); } @@ -93,6 +97,7 @@ void MergeSessionHelper::LogOutInternal( } void MergeSessionHelper::LogOutAllAccounts() { + VLOG(1) << "MergeSessionHelper::LogOutAllAccounts"; LogOutInternal("", std::vector()); } @@ -108,6 +113,7 @@ void MergeSessionHelper::SignalComplete( void MergeSessionHelper::StartLogOutUrlFetch() { DCHECK(accounts_.front().empty()); + VLOG(1) << "MergeSessionHelper::StartLogOutUrlFetch"; GURL logout_url(GaiaUrls::GetInstance()->service_logout_url()); net::URLFetcher* fetcher = net::URLFetcher::Create(logout_url, net::URLFetcher::GET, this); @@ -135,7 +141,7 @@ void MergeSessionHelper::OnUbertokenFailure( } void MergeSessionHelper::OnMergeSessionSuccess(const std::string& data) { - DVLOG(1) << "MergeSession successful account=" << accounts_.front(); + VLOG(1) << "MergeSession successful account=" << accounts_.front(); const std::string account_id = accounts_.front(); HandleNextAccount(); SignalComplete(account_id, GoogleServiceAuthError::AuthErrorNone()); @@ -152,6 +158,8 @@ void MergeSessionHelper::OnMergeSessionFailure( } void MergeSessionHelper::StartFetching() { + VLOG(1) << "MergeSessionHelper::StartFetching account_id=" + << accounts_.front(); uber_token_fetcher_.reset(new UbertokenFetcher(token_service_, this, request_context_)); @@ -160,13 +168,16 @@ void MergeSessionHelper::StartFetching() { void MergeSessionHelper::OnURLFetchComplete(const net::URLFetcher* source) { DCHECK(accounts_.front().empty()); + VLOG(1) << "MergeSessionHelper::OnURLFetchComplete"; HandleNextAccount(); } void MergeSessionHelper::HandleNextAccount() { + VLOG(1) << "MergeSessionHelper::HandleNextAccount"; accounts_.pop_front(); gaia_auth_fetcher_.reset(); if (accounts_.empty()) { + VLOG(1) << "MergeSessionHelper::HandleNextAccount: no more"; uber_token_fetcher_.reset(); } else { if (accounts_.front().empty()) { diff --git a/google_apis/gaia/merge_session_helper.h b/google_apis/gaia/merge_session_helper.h index f4af8f041323..962eaa5ea6d0 100644 --- a/google_apis/gaia/merge_session_helper.h +++ b/google_apis/gaia/merge_session_helper.h @@ -71,6 +71,9 @@ class MergeSessionHelper : public GaiaAuthConsumer, void SignalComplete(const std::string& account_id, const GoogleServiceAuthError& error); + // Returns true of there are pending log ins or outs. + bool is_running() const { return accounts_.size() > 0; } + private: // Overridden from UbertokenConsumer. virtual void OnUbertokenSuccess(const std::string& token) OVERRIDE; diff --git a/google_apis/gaia/oauth2_token_service.cc b/google_apis/gaia/oauth2_token_service.cc index 7a183b6e6c58..44ca1b6bca43 100644 --- a/google_apis/gaia/oauth2_token_service.cc +++ b/google_apis/gaia/oauth2_token_service.cc @@ -77,6 +77,16 @@ void OAuth2TokenService::RequestImpl::InformConsumer( consumer_->OnGetTokenFailure(this, error); } +OAuth2TokenService::ScopedBacthChange::ScopedBacthChange( + OAuth2TokenService* token_service) : token_service_(token_service) { + DCHECK(token_service_); + token_service_->StartBatchChanges(); +} + +OAuth2TokenService::ScopedBacthChange::~ScopedBacthChange() { + token_service_->EndBatchChanges(); +} + // Class that fetches an OAuth2 access token for a given account id and set of // scopes. // @@ -366,7 +376,7 @@ OAuth2TokenService::Consumer::Consumer(const std::string& id) OAuth2TokenService::Consumer::~Consumer() { } -OAuth2TokenService::OAuth2TokenService() { +OAuth2TokenService::OAuth2TokenService() : batch_change_depth_(0) { } OAuth2TokenService::~OAuth2TokenService() { @@ -760,6 +770,19 @@ void OAuth2TokenService::FireRefreshTokensLoaded() { FOR_EACH_OBSERVER(Observer, observer_list_, OnRefreshTokensLoaded()); } +void OAuth2TokenService::StartBatchChanges() { + ++batch_change_depth_; + if (batch_change_depth_ == 1) + FOR_EACH_OBSERVER(Observer, observer_list_, OnStartBatchChanges()); +} + +void OAuth2TokenService::EndBatchChanges() { + --batch_change_depth_; + DCHECK_LE(0, batch_change_depth_); + if (batch_change_depth_ == 0) + FOR_EACH_OBSERVER(Observer, observer_list_, OnEndBatchChanges()); +} + int OAuth2TokenService::cache_size_for_testing() const { return token_cache_.size(); } diff --git a/google_apis/gaia/oauth2_token_service.h b/google_apis/gaia/oauth2_token_service.h index 685b9dbb3400..ecef9e4530b2 100644 --- a/google_apis/gaia/oauth2_token_service.h +++ b/google_apis/gaia/oauth2_token_service.h @@ -100,6 +100,10 @@ class OAuth2TokenService : public base::NonThreadSafe { // Called after all refresh tokens are loaded during OAuth2TokenService // startup. virtual void OnRefreshTokensLoaded() {} + // Sent before starting a batch of refresh token changes. + virtual void OnStartBatchChanges() {} + // Sent after a batch of refresh token changes is done. + virtual void OnEndBatchChanges() {} protected: virtual ~Observer() {} @@ -229,6 +233,16 @@ class OAuth2TokenService : public base::NonThreadSafe { Consumer* const consumer_; }; + // Helper class to scope batch changes. + class ScopedBacthChange { + public: + ScopedBacthChange(OAuth2TokenService* token_service); + ~ScopedBacthChange(); + private: + OAuth2TokenService* token_service_; // Weak. + DISALLOW_COPY_AND_ASSIGN(ScopedBacthChange); + }; + // Subclasses can override if they want to report errors to the user. virtual void UpdateAuthError( const std::string& account_id, @@ -262,6 +276,9 @@ class OAuth2TokenService : public base::NonThreadSafe { virtual void FireRefreshTokenRevoked(const std::string& account_id); virtual void FireRefreshTokensLoaded(); + virtual void StartBatchChanges(); + virtual void EndBatchChanges(); + // Fetches an OAuth token for the specified client/scopes. Virtual so it can // be overridden for tests and for platform-specific behavior on Android. virtual void FetchOAuth2Token(RequestImpl* request, @@ -374,6 +391,9 @@ class OAuth2TokenService : public base::NonThreadSafe { // List of observers to notify when access token status changes. ObserverList diagnostics_observer_list_; + // The depth of batch changes. + int batch_change_depth_; + // Maximum number of retries in fetching an OAuth2 access token. static int max_fetch_retry_num_;