diff options
author | Kristian Monsen <kristianm@google.com> | 2011-06-28 21:49:31 +0100 |
---|---|---|
committer | Kristian Monsen <kristianm@google.com> | 2011-07-08 17:55:00 +0100 |
commit | ddb351dbec246cf1fab5ec20d2d5520909041de1 (patch) | |
tree | 158e3fb57bdcac07c7f1e767fde3c70687c9fbb1 /chrome/browser/sync | |
parent | 6b92e04f5f151c896e3088e86f70db7081009308 (diff) | |
download | external_chromium-ddb351dbec246cf1fab5ec20d2d5520909041de1.zip external_chromium-ddb351dbec246cf1fab5ec20d2d5520909041de1.tar.gz external_chromium-ddb351dbec246cf1fab5ec20d2d5520909041de1.tar.bz2 |
Merge Chromium at r12.0.742.93: Initial merge by git
Change-Id: Ic5ee2fec31358bbee305f7e915442377bfa6cda6
Diffstat (limited to 'chrome/browser/sync')
280 files changed, 11980 insertions, 13005 deletions
diff --git a/chrome/browser/sync/abstract_profile_sync_service_test.h b/chrome/browser/sync/abstract_profile_sync_service_test.h index 8f8cbbe..2e681f1 100644 --- a/chrome/browser/sync/abstract_profile_sync_service_test.h +++ b/chrome/browser/sync/abstract_profile_sync_service_test.h @@ -8,8 +8,8 @@ #include <string> +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/task.h" #include "chrome/browser/net/gaia/token_service.h" #include "chrome/browser/sync/profile_sync_factory_mock.h" diff --git a/chrome/browser/sync/backend_migrator.cc b/chrome/browser/sync/backend_migrator.cc new file mode 100644 index 0000000..481d455 --- /dev/null +++ b/chrome/browser/sync/backend_migrator.cc @@ -0,0 +1,176 @@ +// Copyright (c) 2011 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/sync/backend_migrator.h" + +#include <algorithm> + +#include "base/string_number_conversions.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/glue/data_type_manager.h" +#include "chrome/browser/sync/sessions/session_state.h" +#include "content/browser/browser_thread.h" +#include "content/common/notification_details.h" +#include "content/common/notification_source.h" + +using syncable::ModelTypeSet; + +namespace browser_sync { + +using sessions::SyncSessionSnapshot; + +BackendMigrator::BackendMigrator(ProfileSyncService* service, + DataTypeManager* manager) + : state_(IDLE), service_(service), manager_(manager), + restart_migration_(false), + method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { + registrar_.Add(this, NotificationType::SYNC_CONFIGURE_DONE, + Source<DataTypeManager>(manager_)); + service_->AddObserver(this); +} + +BackendMigrator::~BackendMigrator() { + service_->RemoveObserver(this); +} + +bool BackendMigrator::HasStartedMigrating() const { + return state_ >= DISABLING_TYPES; +} + +void BackendMigrator::MigrateTypes(const syncable::ModelTypeSet& types) { + { + ModelTypeSet temp; + std::set_union(to_migrate_.begin(), to_migrate_.end(), + types.begin(), types.end(), + std::inserter(temp, temp.end())); + to_migrate_ = temp; + } + + if (HasStartedMigrating()) { + VLOG(1) << "BackendMigrator::MigrateTypes: STARTED_MIGRATING early-out."; + restart_migration_ = true; + return; + } + + if (manager_->state() != DataTypeManager::CONFIGURED) { + VLOG(1) << "BackendMigrator::MigrateTypes: manager CONFIGURED early-out."; + state_ = WAITING_TO_START; + return; + } + + // We'll now disable any running types that need to be migrated. + state_ = DISABLING_TYPES; + ModelTypeSet full_set; + service_->GetPreferredDataTypes(&full_set); + ModelTypeSet difference; + std::set_difference(full_set.begin(), full_set.end(), + to_migrate_.begin(), to_migrate_.end(), + std::inserter(difference, difference.end())); + VLOG(1) << "BackendMigrator disabling types; calling Configure."; + manager_->Configure(difference); +} + +void BackendMigrator::OnStateChanged() { + if (restart_migration_ == true) { + VLOG(1) << "BackendMigrator restarting migration in OnStateChanged."; + state_ = WAITING_TO_START; + restart_migration_ = false; + MigrateTypes(to_migrate_); + return; + } + + if (state_ != WAITING_FOR_PURGE) + return; + + size_t num_empty_migrated_markers = 0; + const SyncSessionSnapshot* snap = service_->GetLastSessionSnapshot(); + for (ModelTypeSet::const_iterator it = to_migrate_.begin(); + it != to_migrate_.end(); ++it) { + if (snap->download_progress_markers[*it].empty()) + num_empty_migrated_markers++; + } + + if (num_empty_migrated_markers < to_migrate_.size()) + return; + + state_ = REENABLING_TYPES; + ModelTypeSet full_set; + service_->GetPreferredDataTypes(&full_set); + VLOG(1) << "BackendMigrator re-enabling types."; + // Don't use |to_migrate_| for the re-enabling because the user may have + // chosen to disable types during the migration. + manager_->Configure(full_set); +} + +void BackendMigrator::Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details) { + DCHECK_EQ(NotificationType::SYNC_CONFIGURE_DONE, type.value); + if (state_ == IDLE) + return; + + DataTypeManager::ConfigureResultWithErrorLocation* result = + Details<DataTypeManager::ConfigureResultWithErrorLocation>( + details).ptr(); + + ModelTypeSet intersection; + std::set_intersection(result->requested_types.begin(), + result->requested_types.end(), to_migrate_.begin(), to_migrate_.end(), + std::inserter(intersection, intersection.end())); + + // The intersection check is to determine if our disable request was + // interrupted by a user changing preferred types. May still need to purge. + // It's pretty wild if we're in WAITING_FOR_PURGE here, because it would mean + // that after our disable-config finished but before the purge, another config + // was posted externally _and completed_, which means somehow the nudge to + // purge was dropped, yet nudges are reliable. + if (state_ == WAITING_TO_START || state_ == WAITING_FOR_PURGE || + (state_ == DISABLING_TYPES && !intersection.empty())) { + state_ = WAITING_TO_START; + restart_migration_ = false; + VLOG(1) << "BackendMigrator::Observe posting MigrateTypes."; + if (!BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + method_factory_.NewRunnableMethod(&BackendMigrator::MigrateTypes, + to_migrate_))) { + // Unittests need this. + // TODO(tim): Clean this up. + MigrateTypes(to_migrate_); + } + return; + } + + if (result->result != DataTypeManager::OK) { + // If this fails, and we're disabling types, a type may or may not be + // disabled until the user restarts the browser. If this wasn't an abort, + // any failure will be reported as an unrecoverable error to the UI. If it + // was an abort, then typically things are shutting down anyway. There isn't + // much we can do in any case besides wait until a restart to try again. + // The server will send down MIGRATION_DONE again for types needing + // migration as the type will still be enabled on restart. + LOG(WARNING) << "Unable to migrate, configuration failed!"; + state_ = IDLE; + to_migrate_.clear(); + return; + } + + if (state_ == DISABLING_TYPES) { + state_ = WAITING_FOR_PURGE; + VLOG(1) << "BackendMigrator waiting for purge."; + } else if (state_ == REENABLING_TYPES) { + // We're done! + state_ = IDLE; + + std::stringstream ss; + std::copy(to_migrate_.begin(), to_migrate_.end(), + std::ostream_iterator<syncable::ModelType>(ss, ",")); + VLOG(1) << "BackendMigrator: Migration complete for: " << ss.str(); + to_migrate_.clear(); + } +} + +BackendMigrator::State BackendMigrator::state() const { + return state_; +} + +}; // namespace browser_sync diff --git a/chrome/browser/sync/backend_migrator.h b/chrome/browser/sync/backend_migrator.h new file mode 100644 index 0000000..2f2a175 --- /dev/null +++ b/chrome/browser/sync/backend_migrator.h @@ -0,0 +1,71 @@ +// Copyright (c) 2011 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_BACKEND_MIGRATOR_H_ +#define CHROME_BROWSER_SYNC_BACKEND_MIGRATOR_H_ + +#include "base/task.h" +#include "chrome/browser/sync/profile_sync_service_observer.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" + +class ProfileSyncService; + +namespace browser_sync { + +class DataTypeManager; + +// A class to perform migration of a datatype pursuant to the 'MIGRATION_DONE' +// code in the sync protocol definition (protocol/sync.proto). +class BackendMigrator : public NotificationObserver, + public ProfileSyncServiceObserver { + public: + enum State { + IDLE, + WAITING_TO_START, // Waiting for previous configuration to finish. + DISABLING_TYPES, // Exit criteria: SYNC_CONFIGURE_DONE for enabled + // types _excluding_ |to_migrate_|. + WAITING_FOR_PURGE, // Exit criteria: SyncCycleEnded for enabled types + // excluding |to_migrate| + REENABLING_TYPES, // Exit criteria: SYNC_CONFIGURE_DONE for enabled + // types. + }; + + BackendMigrator(ProfileSyncService* service, DataTypeManager* manager); + virtual ~BackendMigrator(); + + // Starts a sequence of events that will disable and reenable |types|. + void MigrateTypes(const syncable::ModelTypeSet& types); + + // ProfileSyncServiceObserver implementation. + virtual void OnStateChanged(); + + // NotificationObserver implementation. + virtual void Observe(NotificationType type, + const NotificationSource& source, + const NotificationDetails& details); + + State state() const; + + private: + bool HasStartedMigrating() const; + + State state_; + ProfileSyncService* service_; + DataTypeManager* manager_; + NotificationRegistrar registrar_; + + syncable::ModelTypeSet to_migrate_; + bool restart_migration_; + + // We use this to gracefully re-start migrations. + ScopedRunnableMethodFactory<BackendMigrator> method_factory_; + + DISALLOW_COPY_AND_ASSIGN(BackendMigrator); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_BACKEND_MIGRATOR_H_ diff --git a/chrome/browser/sync/backend_migrator_unittest.cc b/chrome/browser/sync/backend_migrator_unittest.cc new file mode 100644 index 0000000..39b2ef0 --- /dev/null +++ b/chrome/browser/sync/backend_migrator_unittest.cc @@ -0,0 +1,267 @@ +// Copyright (c) 2011 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/sync/backend_migrator.h" + +#include "chrome/browser/sync/glue/data_type_manager_mock.h" +#include "chrome/browser/sync/profile_sync_service_mock.h" +#include "chrome/browser/sync/sessions/session_state.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +using ::testing::_; +using ::testing::Eq; +using ::testing::Mock; +using ::testing::NiceMock; +using ::testing::Return; +using ::testing::SetArgumentPointee; + +namespace browser_sync { + +using sessions::ErrorCounters; +using sessions::SyncerStatus; +using sessions::SyncSessionSnapshot; + +class BackendMigratorTest : public testing::Test { + public: + BackendMigratorTest() { } + virtual ~BackendMigratorTest() { } + + virtual void SetUp() { + Mock::VerifyAndClear(manager()); + Mock::VerifyAndClear(&service_); + preferred_types_.insert(syncable::BOOKMARKS); + preferred_types_.insert(syncable::PREFERENCES); + preferred_types_.insert(syncable::AUTOFILL); + + ON_CALL(service_, GetPreferredDataTypes(_)). + WillByDefault(SetArgumentPointee<0>(preferred_types_)); + } + + void ReturnEmptyProgressMarkersInSnapshot() { + ReturnNonEmptyProgressMarkersInSnapshot(syncable::ModelTypeSet()); + } + + void ReturnNonEmptyProgressMarkersInSnapshot( + const syncable::ModelTypeSet& for_types) { + std::string download_progress_markers[syncable::MODEL_TYPE_COUNT]; + for (syncable::ModelTypeSet::const_iterator it = for_types.begin(); + it != for_types.end(); ++it) { + download_progress_markers[*it] = "foobar"; + } + + snap_.reset(new SyncSessionSnapshot(SyncerStatus(), ErrorCounters(), + 0, false, syncable::ModelTypeBitSet(), download_progress_markers, + false, false, 0, 0, false, sessions::SyncSourceInfo())); + EXPECT_CALL(service_, GetLastSessionSnapshot()) + .WillOnce(Return(snap_.get())); + } + + void SendConfigureDone(DataTypeManager::ConfigureResult result, + const syncable::ModelTypeSet& types) { + DataTypeManager::ConfigureResultWithErrorLocation result_with_location( + result, FROM_HERE, types); + NotificationService::current()->Notify( + NotificationType::SYNC_CONFIGURE_DONE, + Source<DataTypeManager>(&manager_), + Details<DataTypeManager::ConfigureResultWithErrorLocation>( + &result_with_location)); + } + + ProfileSyncService* service() { return &service_; } + DataTypeManagerMock* manager() { return &manager_; } + const syncable::ModelTypeSet& preferred_types() { return preferred_types_; } + void RemovePreferredType(syncable::ModelType type) { + preferred_types_.erase(type); + Mock::VerifyAndClear(&service_); + ON_CALL(service_, GetPreferredDataTypes(_)). + WillByDefault(SetArgumentPointee<0>(preferred_types_)); + } + private: + scoped_ptr<SyncSessionSnapshot> snap_; + syncable::ModelTypeSet preferred_types_; + NiceMock<ProfileSyncServiceMock> service_; + NiceMock<DataTypeManagerMock> manager_; +}; + +// Test that in the normal case a migration does transition through each state +// and wind up back in IDLE. +TEST_F(BackendMigratorTest, Sanity) { + BackendMigrator migrator(service(), manager()); + syncable::ModelTypeSet to_migrate, difference; + to_migrate.insert(syncable::PREFERENCES); + difference.insert(syncable::AUTOFILL); + difference.insert(syncable::BOOKMARKS); + + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(_)); + + migrator.MigrateTypes(to_migrate); + EXPECT_EQ(BackendMigrator::DISABLING_TYPES, migrator.state()); + + SendConfigureDone(DataTypeManager::OK, difference); + EXPECT_EQ(BackendMigrator::WAITING_FOR_PURGE, migrator.state()); + + ReturnEmptyProgressMarkersInSnapshot(); + EXPECT_CALL(*manager(), Configure(preferred_types())); + migrator.OnStateChanged(); + EXPECT_EQ(BackendMigrator::REENABLING_TYPES, migrator.state()); + + SendConfigureDone(DataTypeManager::OK, preferred_types()); + EXPECT_EQ(BackendMigrator::IDLE, migrator.state()); +} + +// Test that the migrator waits for the data type manager to be idle before +// starting a migration. +TEST_F(BackendMigratorTest, WaitToStart) { + BackendMigrator migrator(service(), manager()); + syncable::ModelTypeSet to_migrate; + to_migrate.insert(syncable::PREFERENCES); + + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURING)); + EXPECT_CALL(*manager(), Configure(_)).Times(0); + migrator.MigrateTypes(to_migrate); + EXPECT_EQ(BackendMigrator::WAITING_TO_START, migrator.state()); + + Mock::VerifyAndClearExpectations(manager()); + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(_)); + SendConfigureDone(DataTypeManager::OK, syncable::ModelTypeSet()); + + EXPECT_EQ(BackendMigrator::DISABLING_TYPES, migrator.state()); +} + +// Test that the migrator can cope with a migration request while a migration +// is in progress. +TEST_F(BackendMigratorTest, RestartMigration) { + BackendMigrator migrator(service(), manager()); + syncable::ModelTypeSet to_migrate1, to_migrate2, bookmarks; + to_migrate1.insert(syncable::PREFERENCES); + to_migrate2.insert(syncable::AUTOFILL); + bookmarks.insert(syncable::BOOKMARKS); + + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(_)).Times(1); + migrator.MigrateTypes(to_migrate1); + + EXPECT_EQ(BackendMigrator::DISABLING_TYPES, migrator.state()); + migrator.MigrateTypes(to_migrate2); + + syncable::ModelTypeSet difference1; + std::set_difference(preferred_types().begin(), preferred_types().end(), + to_migrate1.begin(), to_migrate1.end(), + std::inserter(difference1, difference1.end())); + + Mock::VerifyAndClearExpectations(manager()); + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(bookmarks)); + SendConfigureDone(DataTypeManager::OK, difference1); + EXPECT_EQ(BackendMigrator::DISABLING_TYPES, migrator.state()); + + SendConfigureDone(DataTypeManager::OK, bookmarks); + EXPECT_EQ(BackendMigrator::WAITING_FOR_PURGE, migrator.state()); +} + +// Test that an external invocation of Configure(...) during a migration results +// in a migration reattempt. +TEST_F(BackendMigratorTest, InterruptedWhileDisablingTypes) { + BackendMigrator migrator(service(), manager()); + syncable::ModelTypeSet to_migrate; + syncable::ModelTypeSet difference; + to_migrate.insert(syncable::PREFERENCES); + difference.insert(syncable::AUTOFILL); + difference.insert(syncable::BOOKMARKS); + + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(difference)); + migrator.MigrateTypes(to_migrate); + EXPECT_EQ(BackendMigrator::DISABLING_TYPES, migrator.state()); + + Mock::VerifyAndClearExpectations(manager()); + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(difference)); + SendConfigureDone(DataTypeManager::OK, preferred_types()); + + EXPECT_EQ(BackendMigrator::DISABLING_TYPES, migrator.state()); +} + +// Test that spurious OnStateChanged events don't confuse the migrator while +// it's waiting for disabled types to have been purged from the sync db. +TEST_F(BackendMigratorTest, WaitingForPurge) { + BackendMigrator migrator(service(), manager()); + syncable::ModelTypeSet to_migrate, difference; + to_migrate.insert(syncable::PREFERENCES); + to_migrate.insert(syncable::AUTOFILL); + difference.insert(syncable::BOOKMARKS); + + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(_)); + migrator.MigrateTypes(to_migrate); + SendConfigureDone(DataTypeManager::OK, difference); + EXPECT_EQ(BackendMigrator::WAITING_FOR_PURGE, migrator.state()); + + ReturnNonEmptyProgressMarkersInSnapshot(to_migrate); + migrator.OnStateChanged(); + EXPECT_EQ(BackendMigrator::WAITING_FOR_PURGE, migrator.state()); + + syncable::ModelTypeSet prefs; + prefs.insert(syncable::PREFERENCES); + ReturnNonEmptyProgressMarkersInSnapshot(prefs); + migrator.OnStateChanged(); + EXPECT_EQ(BackendMigrator::WAITING_FOR_PURGE, migrator.state()); + + syncable::ModelTypeSet bookmarks; + bookmarks.insert(syncable::BOOKMARKS); + ReturnNonEmptyProgressMarkersInSnapshot(bookmarks); + EXPECT_CALL(*manager(), Configure(preferred_types())); + migrator.OnStateChanged(); + EXPECT_EQ(BackendMigrator::REENABLING_TYPES, migrator.state()); +} + +TEST_F(BackendMigratorTest, MigratedTypeDisabledByUserDuringMigration) { + BackendMigrator migrator(service(), manager()); + syncable::ModelTypeSet to_migrate; + to_migrate.insert(syncable::PREFERENCES); + + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(_)); + migrator.MigrateTypes(to_migrate); + + RemovePreferredType(syncable::PREFERENCES); + SendConfigureDone(DataTypeManager::OK, preferred_types()); + EXPECT_EQ(BackendMigrator::WAITING_FOR_PURGE, migrator.state()); + + Mock::VerifyAndClearExpectations(manager()); + ReturnEmptyProgressMarkersInSnapshot(); + EXPECT_CALL(*manager(), Configure(preferred_types())); + migrator.OnStateChanged(); + + EXPECT_EQ(BackendMigrator::REENABLING_TYPES, migrator.state()); + SendConfigureDone(DataTypeManager::OK, preferred_types()); + EXPECT_EQ(BackendMigrator::IDLE, migrator.state()); +} + +TEST_F(BackendMigratorTest, ConfigureFailure) { + BackendMigrator migrator(service(), manager()); + syncable::ModelTypeSet to_migrate; + to_migrate.insert(syncable::PREFERENCES); + + EXPECT_CALL(*manager(), state()) + .WillOnce(Return(DataTypeManager::CONFIGURED)); + EXPECT_CALL(*manager(), Configure(_)).Times(1); + migrator.MigrateTypes(to_migrate); + SendConfigureDone(DataTypeManager::ABORTED, syncable::ModelTypeSet()); + EXPECT_EQ(BackendMigrator::IDLE, migrator.state()); +} + +}; // namespace browser_sync diff --git a/chrome/browser/sync/engine/all_status.cc b/chrome/browser/sync/engine/all_status.cc index 1580dc4..dea0085 100644 --- a/chrome/browser/sync/engine/all_status.cc +++ b/chrome/browser/sync/engine/all_status.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -106,12 +106,10 @@ void AllStatus::OnSyncEngineEvent(const SyncEngineEvent& event) { case SyncEngineEvent::STATUS_CHANGED: status_ = CalcSyncing(event); break; - case SyncEngineEvent::SYNCER_THREAD_PAUSED: - case SyncEngineEvent::SYNCER_THREAD_RESUMED: - case SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION: - case SyncEngineEvent::SYNCER_THREAD_CONNECTED: case SyncEngineEvent::STOP_SYNCING_PERMANENTLY: - case SyncEngineEvent::SYNCER_THREAD_EXITING: + case SyncEngineEvent::UPDATED_TOKEN: + case SyncEngineEvent::CLEAR_SERVER_DATA_FAILED: + case SyncEngineEvent::CLEAR_SERVER_DATA_SUCCEEDED: break; default: LOG(ERROR) << "Unrecognized Syncer Event: " << event.what_happened; @@ -127,9 +125,6 @@ void AllStatus::HandleServerConnectionEvent( status_.server_reachable = event.server_reachable; if (event.connection_code == HttpResponse::SERVER_CONNECTION_OK) { - if (!status_.authenticated) { - status_ = CreateBlankStatus(); - } status_.authenticated = true; } else { status_.authenticated = false; diff --git a/chrome/browser/sync/engine/all_status.h b/chrome/browser/sync/engine/all_status.h index 015bfb7..9bfc1a2 100644 --- a/chrome/browser/sync/engine/all_status.h +++ b/chrome/browser/sync/engine/all_status.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -11,7 +11,7 @@ #include <map> -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/engine/syncer_types.h" diff --git a/chrome/browser/sync/engine/apply_updates_command.cc b/chrome/browser/sync/engine/apply_updates_command.cc index 835c71a..5f98a67 100644 --- a/chrome/browser/sync/engine/apply_updates_command.cc +++ b/chrome/browser/sync/engine/apply_updates_command.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -29,7 +29,7 @@ void ApplyUpdatesCommand::ModelChangingExecuteImpl(SyncSession* session) { UpdateApplicator applicator( session->context()->resolver(), - session->context()->directory_manager()->cryptographer(), + session->context()->directory_manager()->GetCryptographer(&trans), handles.begin(), handles.end(), session->routing_info(), session->status_controller()->group_restriction()); while (applicator.AttemptOneApplication(&trans)) {} diff --git a/chrome/browser/sync/engine/apply_updates_command_unittest.cc b/chrome/browser/sync/engine/apply_updates_command_unittest.cc index df1a1ae..f9c06bd 100644 --- a/chrome/browser/sync/engine/apply_updates_command_unittest.cc +++ b/chrome/browser/sync/engine/apply_updates_command_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -215,8 +215,16 @@ TEST_F(ApplyUpdatesCommandTest, ItemsBothKnownAndUnknown) { TEST_F(ApplyUpdatesCommandTest, DecryptablePassword) { // Decryptable password updates should be applied. - Cryptographer* cryptographer = - session()->context()->directory_manager()->cryptographer(); + Cryptographer* cryptographer; + { + // Storing the cryptographer separately is bad, but for this test we + // know it's safe. + ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); + ASSERT_TRUE(dir.good()); + ReadTransaction trans(dir, __FILE__, __LINE__); + cryptographer = + session()->context()->directory_manager()->GetCryptographer(&trans); + } browser_sync::KeyParams params = {"localhost", "dummy", "foobar"}; cryptographer->AddKey(params); @@ -262,18 +270,22 @@ TEST_F(ApplyUpdatesCommandTest, UndecryptablePassword) { TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) { // Only decryptable password updates should be applied. { - Cryptographer* cryptographer = - session()->context()->directory_manager()->cryptographer(); - - KeyParams params = {"localhost", "dummy", "foobar"}; - cryptographer->AddKey(params); - sync_pb::EntitySpecifics specifics; sync_pb::PasswordSpecificsData data; data.set_origin("http://example.com/1"); - - cryptographer->Encrypt(data, - specifics.MutableExtension(sync_pb::password)->mutable_encrypted()); + { + ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); + ASSERT_TRUE(dir.good()); + ReadTransaction trans(dir, __FILE__, __LINE__); + Cryptographer* cryptographer = + session()->context()->directory_manager()->GetCryptographer(&trans); + + KeyParams params = {"localhost", "dummy", "foobar"}; + cryptographer->AddKey(params); + + cryptographer->Encrypt(data, + specifics.MutableExtension(sync_pb::password)->mutable_encrypted()); + } CreateUnappliedNewItem("item1", specifics, false); } { @@ -304,12 +316,17 @@ TEST_F(ApplyUpdatesCommandTest, SomeUndecryptablePassword) { } TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { + // Storing the cryptographer separately is bad, but for this test we + // know it's safe. + Cryptographer* cryptographer; syncable::ModelTypeSet encrypted_types; { ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); ASSERT_TRUE(dir.good()); ReadTransaction trans(dir, __FILE__, __LINE__); EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); + cryptographer = + session()->context()->directory_manager()->GetCryptographer(&trans); } // Nigori node updates should update the Cryptographer. @@ -325,9 +342,6 @@ TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { encrypted_types.insert(syncable::BOOKMARKS); CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), specifics, true); - - Cryptographer* cryptographer = - session()->context()->directory_manager()->cryptographer(); EXPECT_FALSE(cryptographer->has_pending_keys()); apply_updates_command_.ExecuteImpl(session()); @@ -346,12 +360,17 @@ TEST_F(ApplyUpdatesCommandTest, NigoriUpdate) { } TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { + // Storing the cryptographer separately is bad, but for this test we + // know it's safe. + Cryptographer* cryptographer; syncable::ModelTypeSet encrypted_types; { ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); ASSERT_TRUE(dir.good()); ReadTransaction trans(dir, __FILE__, __LINE__); EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); + cryptographer = + session()->context()->directory_manager()->GetCryptographer(&trans); // With empty encrypted_types, this should be true. EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); @@ -381,8 +400,6 @@ TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { syncable::BOOKMARKS, NULL); } - Cryptographer* cryptographer = - session()->context()->directory_manager()->cryptographer(); KeyParams params = {"localhost", "dummy", "foobar"}; cryptographer->AddKey(params); sync_pb::EntitySpecifics specifics; @@ -437,12 +454,17 @@ TEST_F(ApplyUpdatesCommandTest, EncryptUnsyncedChanges) { } TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) { + // Storing the cryptographer separately is bad, but for this test we + // know it's safe. + Cryptographer* cryptographer; syncable::ModelTypeSet encrypted_types; { ScopedDirLookup dir(syncdb()->manager(), syncdb()->name()); ASSERT_TRUE(dir.good()); ReadTransaction trans(dir, __FILE__, __LINE__); EXPECT_EQ(encrypted_types, GetEncryptedDataTypes(&trans)); + cryptographer = + session()->context()->directory_manager()->GetCryptographer(&trans); // With empty encrypted_types, this should be true. EXPECT_TRUE(VerifyUnsyncedChangesAreEncrypted(&trans, encrypted_types)); @@ -485,8 +507,6 @@ TEST_F(ApplyUpdatesCommandTest, CannotEncryptUnsyncedChanges) { encrypted_types.insert(syncable::BOOKMARKS); CreateUnappliedNewItem(syncable::ModelTypeToRootTag(syncable::NIGORI), specifics, true); - Cryptographer* cryptographer = - session()->context()->directory_manager()->cryptographer(); EXPECT_FALSE(cryptographer->has_pending_keys()); { diff --git a/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc b/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc index 0d14069..ee5873f 100644 --- a/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc +++ b/chrome/browser/sync/engine/build_and_process_conflict_sets_command.cc @@ -1,9 +1,10 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/engine/build_and_process_conflict_sets_command.h" +#include <set> #include <string> #include <sstream> #include <vector> @@ -47,7 +48,7 @@ bool BuildAndProcessConflictSetsCommand::BuildAndProcessConflictSets( session->status_controller()->mutable_conflict_progress()); had_single_direction_sets = ProcessSingleDirectionConflictSets(&trans, session->context()->resolver(), - session->context()->directory_manager()->cryptographer(), + session->context()->directory_manager()->GetCryptographer(&trans), session->status_controller(), session->routing_info()); // We applied some updates transactionally, lets try syncing again. if (had_single_direction_sets) @@ -65,7 +66,7 @@ bool BuildAndProcessConflictSetsCommand::ProcessSingleDirectionConflictSets( for (all_sets_iterator = status->conflict_progress().ConflictSetsBegin(); all_sets_iterator != status->conflict_progress().ConflictSetsEnd();) { const ConflictSet* conflict_set = *all_sets_iterator; - CHECK(conflict_set->size() >= 2); + CHECK_GE(conflict_set->size(), 2U); // We scan the set to see if it consists of changes of only one type. ConflictSet::const_iterator i; size_t unsynced_count = 0, unapplied_count = 0; @@ -312,8 +313,8 @@ class ServerDeletedPathChecker { // returns 0 if we should stop investigating the path. static syncable::Id GetAndExamineParent(syncable::BaseTransaction* trans, - syncable::Id id, - syncable::Id check_id, + const syncable::Id& id, + const syncable::Id& check_id, const syncable::Entry& log_entry) { syncable::Entry parent(trans, syncable::GET_BY_ID, id); CHECK(parent.good()) << "Tree inconsitency, missing id" << id << " " @@ -334,8 +335,8 @@ class LocallyDeletedPathChecker { // returns 0 if we should stop investigating the path. static syncable::Id GetAndExamineParent(syncable::BaseTransaction* trans, - syncable::Id id, - syncable::Id check_id, + const syncable::Id& id, + const syncable::Id& check_id, const syncable::Entry& log_entry) { syncable::Entry parent(trans, syncable::GET_BY_ID, id); if (!parent.good()) diff --git a/chrome/browser/sync/engine/change_reorder_buffer.h b/chrome/browser/sync/engine/change_reorder_buffer.h index 4e0fd82..f4062ef 100644 --- a/chrome/browser/sync/engine/change_reorder_buffer.h +++ b/chrome/browser/sync/engine/change_reorder_buffer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -13,7 +13,7 @@ #include <map> #include <vector> -#include "base/linked_ptr.h" +#include "base/memory/linked_ptr.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/protocol/sync.pb.h" diff --git a/chrome/browser/sync/engine/cleanup_disabled_types_command_unittest.cc b/chrome/browser/sync/engine/cleanup_disabled_types_command_unittest.cc index 9a3e6ab..02ec54d 100644 --- a/chrome/browser/sync/engine/cleanup_disabled_types_command_unittest.cc +++ b/chrome/browser/sync/engine/cleanup_disabled_types_command_unittest.cc @@ -82,17 +82,5 @@ TEST_F(CleanupDisabledTypesCommandTest, TypeDisabled) { command.ExecuteImpl(session()); } -TEST_F(CleanupDisabledTypesCommandTest, - SyncerEndCommandSetsPreviousRoutingInfo) { - SyncerEndCommand command; - - ModelSafeRoutingInfo info; - EXPECT_TRUE(info == session()->context()->previous_session_routing_info()); - command.ExecuteImpl(session()); - ASSERT_FALSE(routing_info().empty()); - EXPECT_TRUE(routing_info() == - session()->context()->previous_session_routing_info()); -} - } // namespace browser_sync diff --git a/chrome/browser/sync/engine/download_updates_command.cc b/chrome/browser/sync/engine/download_updates_command.cc index ba6cb75..e05da4e 100644 --- a/chrome/browser/sync/engine/download_updates_command.cc +++ b/chrome/browser/sync/engine/download_updates_command.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,8 +9,8 @@ #include "chrome/browser/sync/engine/syncer.h" #include "chrome/browser/sync/engine/syncer_proto_util.h" #include "chrome/browser/sync/engine/syncproto.h" -#include "chrome/browser/sync/sessions/sync_session.h" #include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" using syncable::ScopedDirLookup; @@ -43,7 +43,8 @@ void DownloadUpdatesCommand::ExecuteImpl(SyncSession* session) { // Request updates for all enabled types. syncable::ModelTypeBitSet enabled_types; - const sessions::TypePayloadMap& type_payload_map = session->source().types; + const syncable::ModelTypePayloadMap& type_payload_map = + session->source().types; for (ModelSafeRoutingInfo::const_iterator i = session->routing_info().begin(); i != session->routing_info().end(); ++i) { syncable::ModelType model_type = syncable::ModelTypeFromInt(i->first); @@ -53,7 +54,7 @@ void DownloadUpdatesCommand::ExecuteImpl(SyncSession* session) { dir->GetDownloadProgress(model_type, progress_marker); // Set notification hint if present. - sessions::TypePayloadMap::const_iterator type_payload = + syncable::ModelTypePayloadMap::const_iterator type_payload = type_payload_map.find(i->first); if (type_payload != type_payload_map.end()) { progress_marker->set_notification_hint(type_payload->second); diff --git a/chrome/browser/sync/engine/http_post_provider_factory.h b/chrome/browser/sync/engine/http_post_provider_factory.h new file mode 100644 index 0000000..57a54c7 --- /dev/null +++ b/chrome/browser/sync/engine/http_post_provider_factory.h @@ -0,0 +1,34 @@ +// Copyright (c) 2011 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_ENGINE_HTTP_POST_PROVIDER_FACTORY_H_ +#define CHROME_BROWSER_SYNC_ENGINE_HTTP_POST_PROVIDER_FACTORY_H_ +#pragma once + +namespace sync_api { + +class HttpPostProviderInterface; + +// A factory to create HttpPostProviders to hide details about the +// implementations and dependencies. +// A factory instance itself should be owned by whomever uses it to create +// HttpPostProviders. +class HttpPostProviderFactory { + public: + virtual ~HttpPostProviderFactory() {} + + // Obtain a new HttpPostProviderInterface instance, owned by caller. + virtual HttpPostProviderInterface* Create() = 0; + + // When the interface is no longer needed (ready to be cleaned up), clients + // must call Destroy(). + // This allows actual HttpPostProvider subclass implementations to be + // reference counted, which is useful if a particular implementation uses + // multiple threads to serve network requests. + virtual void Destroy(HttpPostProviderInterface* http) = 0; +}; + +} // namespace sync_api + +#endif // CHROME_BROWSER_SYNC_ENGINE_HTTP_POST_PROVIDER_FACTORY_H_ diff --git a/chrome/browser/sync/engine/http_post_provider_interface.h b/chrome/browser/sync/engine/http_post_provider_interface.h new file mode 100644 index 0000000..4bb5a34 --- /dev/null +++ b/chrome/browser/sync/engine/http_post_provider_interface.h @@ -0,0 +1,66 @@ +// Copyright (c) 2011 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_ENGINE_HTTP_POST_PROVIDER_INTERFACE_H_ +#define CHROME_BROWSER_SYNC_ENGINE_HTTP_POST_PROVIDER_INTERFACE_H_ +#pragma once + +#include <string> + +namespace sync_api { + +// An interface the embedding application (e.g. Chromium) implements to provide +// required HTTP POST functionality to the syncer backend. This interface is +// designed for one-time use. You create one, use it, and create another if you +// want to make a subsequent POST. +class HttpPostProviderInterface { + public: + virtual ~HttpPostProviderInterface() {} + + // Use specified user agent string when POSTing. If not called a default UA + // may be used. + virtual void SetUserAgent(const char* user_agent) = 0; + + // Add additional headers to the request. + virtual void SetExtraRequestHeaders(const char* headers) = 0; + + // Set the URL to POST to. + virtual void SetURL(const char* url, int port) = 0; + + // Set the type, length and content of the POST payload. + // |content_type| is a null-terminated MIME type specifier. + // |content| is a data buffer; Do not interpret as a null-terminated string. + // |content_length| is the total number of chars in |content|. It is used to + // assign/copy |content| data. + virtual void SetPostPayload(const char* content_type, + int content_length, + const char* content) = 0; + + // Returns true if the URL request succeeded. If the request failed, + // os_error() may be non-zero and hence contain more information. + virtual bool MakeSynchronousPost(int* os_error_code, int* response_code) = 0; + + // Get the length of the content returned in the HTTP response. + // This does not count the trailing null-terminating character returned + // by GetResponseContent, so it is analogous to calling string.length. + virtual int GetResponseContentLength() const = 0; + + // Get the content returned in the HTTP response. + // This is a null terminated string of characters. + // Value should be copied. + virtual const char* GetResponseContent() const = 0; + + // Get the value of a header returned in the HTTP response. + // If the header is not present, returns the empty string. + virtual const std::string GetResponseHeaderValue( + const std::string& name) const = 0; + + // Abandon any pending POST and unblock caller in MakeSynchronousPost. + // This must be safe to call from any thread. + virtual void Abort() = 0; +}; + +} // namespace sync_api + +#endif // CHROME_BROWSER_SYNC_ENGINE_HTTP_POST_PROVIDER_INTERFACE_H_ diff --git a/chrome/browser/sync/engine/idle_query_linux.cc b/chrome/browser/sync/engine/idle_query_linux.cc index 443db06..55674a1 100644 --- a/chrome/browser/sync/engine/idle_query_linux.cc +++ b/chrome/browser/sync/engine/idle_query_linux.cc @@ -1,10 +1,11 @@ // Copyright (c) 2009 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/sync/engine/idle_query_linux.h" -#include <X11/Xlib.h> #include <X11/extensions/scrnsaver.h> +#include "ui/base/x/x11_util.h" namespace browser_sync { @@ -13,8 +14,8 @@ class IdleData { IdleData() { int event_base; int error_base; - display = XOpenDisplay(NULL); - if (XScreenSaverQueryExtension(display, &event_base, &error_base)) { + if (XScreenSaverQueryExtension(ui::GetXDisplay(), &event_base, + &error_base)) { mit_info = XScreenSaverAllocInfo(); } else { mit_info = NULL; @@ -22,36 +23,28 @@ class IdleData { } ~IdleData() { - if (display) { - XCloseDisplay(display); - display = NULL; - } - if (mit_info) { + if (mit_info) XFree(mit_info); - } } XScreenSaverInfo *mit_info; - Display *display; }; -IdleQueryLinux::IdleQueryLinux() : idle_data_(new IdleData()) { -} +IdleQueryLinux::IdleQueryLinux() : idle_data_(new IdleData()) {} -IdleQueryLinux::~IdleQueryLinux() { -} +IdleQueryLinux::~IdleQueryLinux() {} int IdleQueryLinux::IdleTime() { - if (!idle_data_->mit_info || !idle_data_->display) { + if (!idle_data_->mit_info) return 0; - } - if (XScreenSaverQueryInfo(idle_data_->display, - RootWindow(idle_data_->display, 0), + if (XScreenSaverQueryInfo(ui::GetXDisplay(), + RootWindow(ui::GetXDisplay(), 0), idle_data_->mit_info)) { return (idle_data_->mit_info->idle) / 1000; } else { return 0; } } + } // namespace browser_sync diff --git a/chrome/browser/sync/engine/idle_query_linux.h b/chrome/browser/sync/engine/idle_query_linux.h index ef91bdd..cd61e74 100644 --- a/chrome/browser/sync/engine/idle_query_linux.h +++ b/chrome/browser/sync/engine/idle_query_linux.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,7 +6,7 @@ #define CHROME_BROWSER_SYNC_ENGINE_IDLE_QUERY_LINUX_H_ #pragma once -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" namespace browser_sync { diff --git a/chrome/browser/sync/engine/mock_model_safe_workers.cc b/chrome/browser/sync/engine/mock_model_safe_workers.cc index bd6a26b..77ed4e9 100644 --- a/chrome/browser/sync/engine/mock_model_safe_workers.cc +++ b/chrome/browser/sync/engine/mock_model_safe_workers.cc @@ -24,6 +24,21 @@ MockModelSafeWorkerRegistrar* return m; } +MockModelSafeWorkerRegistrar* MockModelSafeWorkerRegistrar::PassiveForTypes( + const syncable::ModelTypeBitSet& set) { + ModelSafeRoutingInfo routes; + for (int i = syncable::UNSPECIFIED ; i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType type = syncable::ModelTypeFromInt(i); + if (set[type]) { + routes[type] = GROUP_PASSIVE; + } + } + MockModelSafeWorkerRegistrar* m = new MockModelSafeWorkerRegistrar(routes); + m->passive_worker_ = new ModelSafeWorker(); + return m; +} + + void MockModelSafeWorkerRegistrar::GetWorkers( std::vector<ModelSafeWorker*>* out) { if (passive_worker_.get()) diff --git a/chrome/browser/sync/engine/mock_model_safe_workers.h b/chrome/browser/sync/engine/mock_model_safe_workers.h index a76a51c..04f4adf 100644 --- a/chrome/browser/sync/engine/mock_model_safe_workers.h +++ b/chrome/browser/sync/engine/mock_model_safe_workers.h @@ -8,7 +8,7 @@ #include <vector> -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "chrome/browser/sync/engine/model_safe_worker.h" #include "chrome/browser/sync/syncable/model_type.h" @@ -30,6 +30,8 @@ class MockModelSafeWorkerRegistrar : public ModelSafeWorkerRegistrar { public: virtual ~MockModelSafeWorkerRegistrar(); static MockModelSafeWorkerRegistrar* PassiveBookmarks(); + static MockModelSafeWorkerRegistrar* PassiveForTypes( + const syncable::ModelTypeBitSet& set); virtual void GetWorkers(std::vector<ModelSafeWorker*>* out); virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out); diff --git a/chrome/browser/sync/engine/model_safe_worker.cc b/chrome/browser/sync/engine/model_safe_worker.cc index c488e3b..d9f44bf 100644 --- a/chrome/browser/sync/engine/model_safe_worker.cc +++ b/chrome/browser/sync/engine/model_safe_worker.cc @@ -14,7 +14,7 @@ ModelSafeGroup GetGroupForModelType(const syncable::ModelType type, // with the server's PermanentItemPopulator is causing TLF updates in // some cases. See bug 36735. if (type != syncable::UNSPECIFIED && type != syncable::TOP_LEVEL_FOLDER) - NOTREACHED() << "Entry does not belong to active ModelSafeGroup!"; + LOG(WARNING) << "Entry does not belong to active ModelSafeGroup!"; return GROUP_PASSIVE; } return it->second; diff --git a/chrome/browser/sync/engine/model_safe_worker.h b/chrome/browser/sync/engine/model_safe_worker.h index 9f31c67..29e11eb 100644 --- a/chrome/browser/sync/engine/model_safe_worker.h +++ b/chrome/browser/sync/engine/model_safe_worker.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -11,7 +11,7 @@ #include <vector> #include "base/callback.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "chrome/browser/sync/syncable/model_type.h" namespace browser_sync { diff --git a/chrome/browser/sync/engine/net/server_connection_manager.cc b/chrome/browser/sync/engine/net/server_connection_manager.cc index 7f53a29..f84b8b7 100644 --- a/chrome/browser/sync/engine/net/server_connection_manager.cc +++ b/chrome/browser/sync/engine/net/server_connection_manager.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,6 +10,7 @@ #include <string> #include <vector> +#include "base/command_line.h" #include "build/build_config.h" #include "chrome/browser/sync/engine/net/url_translator.h" #include "chrome/browser/sync/engine/syncapi.h" @@ -17,6 +18,7 @@ #include "chrome/browser/sync/engine/syncproto.h" #include "chrome/browser/sync/protocol/sync.pb.h" #include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/common/chrome_switches.h" #include "chrome/common/net/http_return.h" #include "googleurl/src/gurl.h" @@ -144,6 +146,7 @@ ServerConnectionManager::ServerConnectionManager( get_time_path_(kSyncServerGetTimePath), error_count_(0), channel_(new Channel(shutdown_event)), + listeners_(new ObserverListThreadSafe<ServerConnectionEventListener>()), server_status_(HttpResponse::NONE), server_reachable_(false), reset_count_(0), @@ -155,10 +158,8 @@ ServerConnectionManager::~ServerConnectionManager() { } void ServerConnectionManager::NotifyStatusChanged() { - ServerConnectionEvent event = { ServerConnectionEvent::STATUS_CHANGED, - server_status_, - server_reachable_ }; - channel_->NotifyListeners(event); + listeners_->Notify(&ServerConnectionEventListener::OnServerConnectionEvent, + ServerConnectionEvent2(server_status_, server_reachable_)); } bool ServerConnectionManager::PostBufferWithCachedAuth( @@ -329,6 +330,16 @@ std::string ServerConnectionManager::GetServerHost() const { return gurl.host(); } +void ServerConnectionManager::AddListener( + ServerConnectionEventListener* listener) { + listeners_->AddObserver(listener); +} + +void ServerConnectionManager::RemoveListener( + ServerConnectionEventListener* listener) { + listeners_->RemoveObserver(listener); +} + ServerConnectionManager::Post* ServerConnectionManager::MakePost() { return NULL; // For testing. } diff --git a/chrome/browser/sync/engine/net/server_connection_manager.h b/chrome/browser/sync/engine/net/server_connection_manager.h index 214c622..9f49d4a 100644 --- a/chrome/browser/sync/engine/net/server_connection_manager.h +++ b/chrome/browser/sync/engine/net/server_connection_manager.h @@ -10,6 +10,7 @@ #include <string> #include "base/atomicops.h" +#include "base/observer_list_threadsafe.h" #include "base/string_util.h" #include "base/synchronization/lock.h" #include "chrome/browser/sync/syncable/syncable_id.h" @@ -107,6 +108,7 @@ inline bool IsGoodReplyFromServer(HttpResponse::ServerConnectionCode code) { return code >= HttpResponse::SERVER_CONNECTION_OK; } +// TODO(tim): Deprecated. struct ServerConnectionEvent { // Traits. typedef ServerConnectionEvent EventType; @@ -124,6 +126,21 @@ struct ServerConnectionEvent { bool server_reachable; }; +struct ServerConnectionEvent2 { + HttpResponse::ServerConnectionCode connection_code; + bool server_reachable; + ServerConnectionEvent2(HttpResponse::ServerConnectionCode code, + bool server_reachable) : + connection_code(code), server_reachable(server_reachable) {} +}; + +class ServerConnectionEventListener { + public: + virtual void OnServerConnectionEvent(const ServerConnectionEvent2& event) = 0; + protected: + virtual ~ServerConnectionEventListener() {} +}; + class ServerConnectionManager; // A helper class that automatically notifies when the status changes. // TODO(tim): This class shouldn't be exposed outside of the implementation, @@ -240,6 +257,9 @@ class ServerConnectionManager { inline Channel* channel() const { return channel_; } + void AddListener(ServerConnectionEventListener* listener); + void RemoveListener(ServerConnectionEventListener* listener); + inline std::string user_agent() const { return user_agent_; } inline HttpResponse::ServerConnectionCode server_status() const { @@ -344,8 +364,12 @@ class ServerConnectionManager { base::Lock error_count_mutex_; // Protects error_count_ int error_count_; // Tracks the number of connection errors. + // TODO(tim): Deprecated. Channel* const channel_; + scoped_refptr<ObserverListThreadSafe<ServerConnectionEventListener> > + listeners_; + // Volatile so various threads can call server_status() without // synchronization. volatile HttpResponse::ServerConnectionCode server_status_; diff --git a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc index 0d8f2a7..3ba02b4 100644 --- a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc +++ b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.cc @@ -1,14 +1,15 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/engine/net/syncapi_server_connection_manager.h" +#include "chrome/browser/sync/engine/http_post_provider_factory.h" +#include "chrome/browser/sync/engine/http_post_provider_interface.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/common/net/http_return.h" using browser_sync::HttpResponse; -using std::string; namespace sync_api { @@ -20,10 +21,11 @@ SyncAPIBridgedPost::SyncAPIBridgedPost( SyncAPIBridgedPost::~SyncAPIBridgedPost() {} - -bool SyncAPIBridgedPost::Init(const char* path, const string& auth_token, - const string& payload, HttpResponse* response) { - string sync_server; +bool SyncAPIBridgedPost::Init(const char* path, + const std::string& auth_token, + const std::string& payload, + HttpResponse* response) { + std::string sync_server; int sync_server_port = 0; bool use_ssl = false; GetServerParams(&sync_server, &sync_server_port, &use_ssl); @@ -34,7 +36,7 @@ bool SyncAPIBridgedPost::Init(const char* path, const string& auth_token, http->SetURL(connection_url.c_str(), sync_server_port); if (!auth_token.empty()) { - string headers = "Authorization: GoogleLogin auth=" + auth_token; + std::string headers = "Authorization: GoogleLogin auth=" + auth_token; http->SetExtraRequestHeaders(headers.c_str()); } @@ -94,5 +96,4 @@ SyncAPIServerConnectionManager::MakePost() { return new SyncAPIBridgedPost(this, post_provider_factory_.get()); } - } // namespace sync_api diff --git a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h index cdf0711..2c46df8 100644 --- a/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h +++ b/chrome/browser/sync/engine/net/syncapi_server_connection_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,7 +8,7 @@ #include <string> -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "chrome/browser/sync/engine/net/server_connection_manager.h" namespace sync_api { diff --git a/chrome/browser/sync/engine/nudge_source.h b/chrome/browser/sync/engine/nudge_source.h index 3e3b3ae..fc9b02a 100644 --- a/chrome/browser/sync/engine/nudge_source.h +++ b/chrome/browser/sync/engine/nudge_source.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,8 +8,6 @@ namespace browser_sync { -namespace s3 { - enum NudgeSource { NUDGE_SOURCE_UNKNOWN = 0, // We received an invalidation message and are nudging to check for updates. @@ -20,7 +18,6 @@ enum NudgeSource { NUDGE_SOURCE_CONTINUATION, }; -} // namespace s3 } // namespace browser_sync #endif // CHROME_BROWSER_SYNC_ENGINE_NUDGE_SOURCE_H_ diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc index 416df24..9ad111d 100644 --- a/chrome/browser/sync/engine/syncapi.cc +++ b/chrome/browser/sync/engine/syncapi.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -15,31 +15,31 @@ #include "base/base64.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" #include "base/observer_list.h" -#include "base/scoped_ptr.h" #include "base/sha1.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/synchronization/lock.h" #include "base/task.h" +#include "base/time.h" #include "base/utf_string_conversions.h" #include "base/values.h" -#include "chrome/browser/sync/sync_constants.h" #include "chrome/browser/sync/engine/all_status.h" #include "chrome/browser/sync/engine/change_reorder_buffer.h" #include "chrome/browser/sync/engine/model_safe_worker.h" +#include "chrome/browser/sync/engine/nudge_source.h" #include "chrome/browser/sync/engine/net/server_connection_manager.h" #include "chrome/browser/sync/engine/net/syncapi_server_connection_manager.h" #include "chrome/browser/sync/engine/syncer.h" #include "chrome/browser/sync/engine/syncer_thread.h" -#include "chrome/browser/sync/engine/syncer_thread2.h" -#include "chrome/browser/sync/engine/syncer_thread_adapter.h" +#include "chrome/browser/sync/engine/http_post_provider_factory.h" #include "chrome/browser/sync/js_arg_list.h" #include "chrome/browser/sync/js_backend.h" #include "chrome/browser/sync/js_event_router.h" -#include "chrome/browser/sync/notifier/server_notifier_thread.h" -#include "chrome/browser/sync/notifier/state_writer.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "chrome/browser/sync/notifier/sync_notifier_observer.h" #include "chrome/browser/sync/protocol/app_specifics.pb.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" @@ -55,7 +55,10 @@ #include "chrome/browser/sync/sessions/sync_session.h" #include "chrome/browser/sync/sessions/sync_session_context.h" #include "chrome/browser/sync/syncable/autofill_migration.h" +#include "chrome/browser/sync/syncable/directory_change_listener.h" #include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" +#include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/syncable/nigori_util.h" #include "chrome/browser/sync/syncable/syncable.h" #include "chrome/browser/sync/util/crypto_helpers.h" @@ -63,12 +66,9 @@ #include "chrome/common/deprecated/event_sys.h" #include "chrome/common/net/gaia/gaia_authenticator.h" #include "content/browser/browser_thread.h" -#include "jingle/notifier/listener/mediator_thread_impl.h" -#include "jingle/notifier/listener/notification_constants.h" -#include "jingle/notifier/listener/talk_mediator.h" -#include "jingle/notifier/listener/talk_mediator_impl.h" #include "net/base/network_change_notifier.h" +using base::TimeDelta; using browser_sync::AllStatus; using browser_sync::Cryptographer; using browser_sync::KeyParams; @@ -76,15 +76,14 @@ using browser_sync::ModelSafeRoutingInfo; using browser_sync::ModelSafeWorker; using browser_sync::ModelSafeWorkerRegistrar; using browser_sync::ServerConnectionEvent; +using browser_sync::ServerConnectionEvent2; +using browser_sync::ServerConnectionEventListener; using browser_sync::SyncEngineEvent; using browser_sync::SyncEngineEventListener; using browser_sync::Syncer; using browser_sync::SyncerThread; -using browser_sync::SyncerThreadAdapter; using browser_sync::kNigoriTag; using browser_sync::sessions::SyncSessionContext; -using notifier::TalkMediator; -using notifier::TalkMediatorImpl; using std::list; using std::hex; using std::string; @@ -92,6 +91,9 @@ using std::vector; using syncable::Directory; using syncable::DirectoryManager; using syncable::Entry; +using syncable::ModelTypeBitSet; +using syncable::OriginalEntries; +using syncable::WriterTag; using syncable::SPECIFICS; using sync_pb::AutofillProfileSpecifics; @@ -319,21 +321,9 @@ DictionaryValue* BaseNode::ToValue() const { node_info->SetBoolean("isFolder", GetIsFolder()); // TODO(akalin): Add a std::string accessor for the title. node_info->SetString("title", WideToUTF8(GetTitle())); - { - syncable::ModelType model_type = GetModelType(); - if (model_type >= syncable::FIRST_REAL_MODEL_TYPE) { - node_info->SetString("type", ModelTypeToString(model_type)); - } else if (model_type == syncable::TOP_LEVEL_FOLDER) { - node_info->SetString("type", "Top-level folder"); - } else if (model_type == syncable::UNSPECIFIED) { - node_info->SetString("type", "Unspecified"); - } else { - node_info->SetString("type", base::IntToString(model_type)); - } - } - node_info->Set( - "specifics", - browser_sync::EntitySpecificsToValue(GetEntry()->Get(SPECIFICS))); + node_info->Set("type", ModelTypeToValue(GetModelType())); + // Specifics are already in the Entry value, so no need to duplicate + // it here. node_info->SetString("externalId", base::Int64ToString(GetExternalId())); node_info->SetString("predecessorId", @@ -342,6 +332,7 @@ DictionaryValue* BaseNode::ToValue() const { base::Int64ToString(GetSuccessorId())); node_info->SetString("firstChildId", base::Int64ToString(GetFirstChildId())); + node_info->Set("entry", GetEntry()->ToValue()); return node_info; } @@ -566,10 +557,25 @@ void WriteNode::PutNigoriSpecificsAndMarkForSyncing( void WriteNode::SetPasswordSpecifics( const sync_pb::PasswordSpecificsData& data) { DCHECK_EQ(syncable::PASSWORDS, GetModelType()); + + Cryptographer* cryptographer = GetTransaction()->GetCryptographer(); + + // Idempotency check to prevent unnecessary syncing: if the plaintexts match + // and the old ciphertext is encrypted with the most current key, there's + // nothing to do here. Because each encryption is seeded with a different + // random value, checking for equivalence post-encryption doesn't suffice. + const sync_pb::EncryptedData& old_ciphertext = + GetEntry()->Get(SPECIFICS).GetExtension(sync_pb::password).encrypted(); + scoped_ptr<sync_pb::PasswordSpecificsData> old_plaintext( + DecryptPasswordSpecifics(GetEntry()->Get(SPECIFICS), cryptographer)); + if (old_plaintext.get() && + old_plaintext->SerializeAsString() == data.SerializeAsString() && + cryptographer->CanDecryptUsingDefaultKey(old_ciphertext)) { + return; + } + sync_pb::PasswordSpecifics new_value; - if (!GetTransaction()->GetCryptographer()->Encrypt( - data, - new_value.mutable_encrypted())) { + if (!cryptographer->Encrypt(data, new_value.mutable_encrypted())) { NOTREACHED(); } PutPasswordSpecificsAndMarkForSyncing(new_value); @@ -1091,6 +1097,21 @@ DictionaryValue* SyncManager::ChangeRecord::ToValue( return value; } +bool BaseNode::ContainsString(const std::string& lowercase_query) const { + DCHECK(GetEntry()); + // TODO(lipalani) - figure out what to do if the node is encrypted. + const sync_pb::EntitySpecifics& specifics = GetEntry()->Get(SPECIFICS); + std::string temp; + // The protobuf serialized string contains the original strings. So + // we will just serialize it and search it. + specifics.SerializeToString(&temp); + + // Now convert to lower case. + StringToLowerASCII(&temp); + + return temp.find(lowercase_query) != std::string::npos; +} + SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData() {} SyncManager::ExtraPasswordChangeRecordData::ExtraPasswordChangeRecordData( @@ -1109,15 +1130,53 @@ const sync_pb::PasswordSpecificsData& return unencrypted_; } +namespace { + +struct NotificationInfo { + int total_count; + std::string payload; + + NotificationInfo() : total_count(0) {} + + ~NotificationInfo() {} + + // Returned pointer owned by the caller. + DictionaryValue* ToValue() const { + DictionaryValue* value = new DictionaryValue(); + value->SetInteger("totalCount", total_count); + value->SetString("payload", payload); + return value; + } +}; + +typedef std::map<syncable::ModelType, NotificationInfo> NotificationInfoMap; + +// returned pointer is owned by the caller. +DictionaryValue* NotificationInfoToValue( + const NotificationInfoMap& notification_info) { + DictionaryValue* value = new DictionaryValue(); + + for (NotificationInfoMap::const_iterator it = notification_info.begin(); + it != notification_info.end(); ++it) { + const std::string& model_type_str = + syncable::ModelTypeToString(it->first); + value->Set(model_type_str, it->second.ToValue()); + } + + return value; +} + +} // namespace + ////////////////////////////////////////////////////////////////////////// // SyncManager's implementation: SyncManager::SyncInternal class SyncManager::SyncInternal : public net::NetworkChangeNotifier::IPAddressObserver, - public TalkMediator::Delegate, - public sync_notifier::StateWriter, - public browser_sync::ChannelEventHandler<syncable::DirectoryChangeEvent>, + public sync_notifier::SyncNotifierObserver, public browser_sync::JsBackend, - public SyncEngineEventListener { + public SyncEngineEventListener, + public ServerConnectionEventListener, + public syncable::DirectoryChangeListener { static const int kDefaultNudgeDelayMilliseconds; static const int kPreferencesNudgeDelayMilliseconds; public: @@ -1126,15 +1185,13 @@ class SyncManager::SyncInternal parent_router_(NULL), sync_manager_(sync_manager), registrar_(NULL), - notification_pending_(false), initialized_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)), - server_notifier_thread_(NULL) { + ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } virtual ~SyncInternal() { - DCHECK(!core_message_loop_); + CHECK(!core_message_loop_); DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } @@ -1146,7 +1203,7 @@ class SyncManager::SyncInternal ModelSafeWorkerRegistrar* model_safe_worker_registrar, const char* user_agent, const SyncCredentials& credentials, - const notifier::NotifierOptions& notifier_options, + sync_notifier::SyncNotifier* sync_notifier, const std::string& restored_key_for_bootstrapping, bool setup_for_test_mode); @@ -1159,9 +1216,8 @@ class SyncManager::SyncInternal // Update tokens that we're using in Sync. Email must stay the same. void UpdateCredentials(const SyncCredentials& credentials); - // Update the set of enabled sync types. Usually called when the user disables - // or enables a sync type. - void UpdateEnabledTypes(const syncable::ModelTypeSet& types); + // Called when the user disables or enables a sync type. + void UpdateEnabledTypes(); // Tell the sync engine to start the syncing process. void StartSyncing(); @@ -1181,18 +1237,22 @@ class SyncManager::SyncInternal // to the syncapi model. void SaveChanges(); + // DirectoryChangeListener implementation. // This listener is called upon completion of a syncable transaction, and // builds the list of sync-engine initiated changes that will be forwarded to // the SyncManager's Observers. - virtual void HandleChannelEvent(const syncable::DirectoryChangeEvent& event); - void HandleTransactionCompleteChangeEvent( - const syncable::DirectoryChangeEvent& event); - void HandleTransactionEndingChangeEvent( - const syncable::DirectoryChangeEvent& event); - void HandleCalculateChangesChangeEventFromSyncApi( - const syncable::DirectoryChangeEvent& event); - void HandleCalculateChangesChangeEventFromSyncer( - const syncable::DirectoryChangeEvent& event); + virtual void HandleTransactionCompleteChangeEvent( + const ModelTypeBitSet& models_with_changes); + virtual ModelTypeBitSet HandleTransactionEndingChangeEvent( + syncable::BaseTransaction* trans); + virtual void HandleCalculateChangesChangeEventFromSyncApi( + const OriginalEntries& originals, + const WriterTag& writer, + syncable::BaseTransaction* trans); + virtual void HandleCalculateChangesChangeEventFromSyncer( + const OriginalEntries& originals, + const WriterTag& writer, + syncable::BaseTransaction* trans); // Listens for notifications from the ServerConnectionManager void HandleServerConnectionEvent(const ServerConnectionEvent& event); @@ -1200,21 +1260,14 @@ class SyncManager::SyncInternal // Open the directory named with username_for_share bool OpenDirectory(); - // Login to the talk mediator with the given credentials. - void TalkMediatorLogin( - const std::string& email, const std::string& token); - - // TalkMediator::Delegate implementation. + // SyncNotifierObserver implementation. virtual void OnNotificationStateChange( bool notifications_enabled); virtual void OnIncomingNotification( - const IncomingNotificationData& notification_data); - - virtual void OnOutgoingNotification(); + const syncable::ModelTypePayloadMap& type_payloads); - // sync_notifier::StateWriter implementation. - virtual void WriteState(const std::string& state); + virtual void StoreState(const std::string& cookie); void AddObserver(SyncManager::Observer* observer); @@ -1225,8 +1278,7 @@ class SyncManager::SyncInternal SyncAPIServerConnectionManager* connection_manager() { return connection_manager_.get(); } - SyncerThreadAdapter* syncer_thread() { return syncer_thread_.get(); } - TalkMediator* talk_mediator() { return talk_mediator_.get(); } + SyncerThread* syncer_thread() { return syncer_thread_.get(); } UserShare* GetUserShare() { return &share_; } // Return the currently active (validated) username for use with syncable @@ -1237,6 +1289,12 @@ class SyncManager::SyncInternal Status GetStatus(); + void RequestNudge(const tracked_objects::Location& nudge_location); + + void RequestNudgeWithDataTypes(const TimeDelta& delay, + browser_sync::NudgeSource source, const ModelTypeBitSet& types, + const tracked_objects::Location& nudge_location); + // See SyncManager::Shutdown for information. void Shutdown(); @@ -1325,6 +1383,9 @@ class SyncManager::SyncInternal // SyncEngineEventListener implementation. virtual void OnSyncEngineEvent(const SyncEngineEvent& event); + // ServerConnectionEventListener implementation. + virtual void OnServerConnectionEvent(const ServerConnectionEvent2& event); + // browser_sync::JsBackend implementation. virtual void SetParentJsEventRouter(browser_sync::JsEventRouter* router); virtual void RemoveParentJsEventRouter(); @@ -1333,11 +1394,9 @@ class SyncManager::SyncInternal const browser_sync::JsArgList& args, const browser_sync::JsEventHandler* sender); - private: - // Helper to handle the details of initializing the TalkMediator. - // Must be called only after OpenDirectory() is called. - void InitializeTalkMediator(); + ListValue* FindNodesContainingString(const std::string& query); + private: // Helper to call OnAuthError when no authentication credentials are // available. void RaiseAuthNeededEvent(); @@ -1347,10 +1406,8 @@ class SyncManager::SyncInternal // already initialized, this is a no-op. void MarkAndNotifyInitializationComplete(); - // If there's a pending notification to be sent, either from the - // new_pending_notification flag or a previous unsuccessfully sent - // notification, tries to send a notification. - void SendPendingXMPPNotification(bool new_pending_notification); + // Sends notifications to peers. + void SendNotification(); // Determine if the parents or predecessors differ between the old and new // versions of an entry stored in |a| and |b|. Note that a node's index may @@ -1436,11 +1493,17 @@ class SyncManager::SyncInternal // decryption. Otherwise, the cryptographer is made ready (is_ready()). void BootstrapEncryption(const std::string& restored_key_for_bootstrapping); + // Called for every notification. This updates the notification statistics + // to be displayed in about:sync. + void UpdateNotificationInfo( + const syncable::ModelTypePayloadMap& type_payloads); + // Helper for migration to new nigori proto to set // 'using_explicit_passphrase' in the NigoriSpecifics. // TODO(tim): Bug 62103. Remove this after it has been pushed out to dev // channel users. - void SetUsingExplicitPassphrasePrefForMigration(); + void SetUsingExplicitPassphrasePrefForMigration( + WriteTransaction* const trans); // Checks for server reachabilty and requests a nudge. void OnIPAddressChangedImpl(); @@ -1449,6 +1512,9 @@ class SyncManager::SyncInternal browser_sync::JsArgList ProcessGetNodeByIdMessage( const browser_sync::JsArgList& args); + browser_sync::JsArgList ProcessFindNodesContainingString( + const browser_sync::JsArgList& args); + // We couple the DirectoryManager and username together in a UserShare member // so we can return a handle to share_ to clients of the API for use when // constructing any transaction type. @@ -1465,10 +1531,10 @@ class SyncManager::SyncInternal scoped_ptr<SyncAPIServerConnectionManager> connection_manager_; // The thread that runs the Syncer. Needs to be explicitly Start()ed. - scoped_ptr<SyncerThreadAdapter> syncer_thread_; + scoped_ptr<SyncerThread> syncer_thread_; - // Notification (xmpp) handler. - scoped_ptr<TalkMediator> talk_mediator_; + // The SyncNotifier which notifies us when updates need to be downloaded. + sync_notifier::SyncNotifier* sync_notifier_; // A multi-purpose status watch object that aggregates stats from various // sync components. @@ -1482,17 +1548,6 @@ class SyncManager::SyncInternal // TRANSACTION_COMPLETE step by HandleTransactionCompleteChangeEvent. ChangeReorderBuffer change_buffers_[syncable::MODEL_TYPE_COUNT]; - // Bit vector keeping track of which models need to have their - // OnChangesComplete observer set. - // - // Set by HandleTransactionEndingChangeEvent, cleared in - // HandleTransactionCompleteChangeEvent. - std::bitset<syncable::MODEL_TYPE_COUNT> model_has_change_; - - // The event listener hookup that is registered for HandleChangeEvent. - scoped_ptr<browser_sync::ChannelHookup<syncable::DirectoryChangeEvent> > - dir_change_hookup_; - // Event listener hookup for the ServerConnectionManager. scoped_ptr<EventListenerHookup> connection_manager_hookup_; @@ -1503,9 +1558,6 @@ class SyncManager::SyncInternal // The instance is shared between the SyncManager and the Syncer. ModelSafeWorkerRegistrar* registrar_; - // True if the next SyncCycle should notify peers of an update. - bool notification_pending_; - // Set to true once Init has been called, and we know of an authenticated // valid) username either from a fresh authentication attempt (as in // first-use case) or from a previous attempt stored in our UserSettings @@ -1515,17 +1567,15 @@ class SyncManager::SyncInternal bool initialized_; mutable base::Lock initialized_mutex_; - notifier::NotifierOptions notifier_options_; - // True if the SyncManager should be running in test mode (no syncer thread // actually communicating with the server). bool setup_for_test_mode_; - syncable::ModelTypeSet enabled_types_; - ScopedRunnableMethodFactory<SyncManager::SyncInternal> method_factory_; - sync_notifier::ServerNotifierThread* server_notifier_thread_; + // Map used to store the notification info to be displayed in about:sync page. + // TODO(lipalani) - prefill the map with enabled data types. + NotificationInfoMap notification_info_map_; }; const int SyncManager::SyncInternal::kDefaultNudgeDelayMilliseconds = 200; const int SyncManager::SyncInternal::kPreferencesNudgeDelayMilliseconds = 2000; @@ -1544,7 +1594,7 @@ bool SyncManager::Init(const FilePath& database_location, ModelSafeWorkerRegistrar* registrar, const char* user_agent, const SyncCredentials& credentials, - const notifier::NotifierOptions& notifier_options, + sync_notifier::SyncNotifier* sync_notifier, const std::string& restored_key_for_bootstrapping, bool setup_for_test_mode) { DCHECK(post_factory); @@ -1558,7 +1608,7 @@ bool SyncManager::Init(const FilePath& database_location, registrar, user_agent, credentials, - notifier_options, + sync_notifier, restored_key_for_bootstrapping, setup_for_test_mode); } @@ -1567,8 +1617,8 @@ void SyncManager::UpdateCredentials(const SyncCredentials& credentials) { data_->UpdateCredentials(credentials); } -void SyncManager::UpdateEnabledTypes(const syncable::ModelTypeSet& types) { - data_->UpdateEnabledTypes(types); +void SyncManager::UpdateEnabledTypes() { + data_->UpdateEnabledTypes(); } @@ -1615,35 +1665,27 @@ bool SyncManager::IsUsingExplicitPassphrase() { return data_ && data_->IsUsingExplicitPassphrase(); } -bool SyncManager::RequestPause() { - if (data_->syncer_thread()) - return data_->syncer_thread()->RequestPause(); - return false; -} - -bool SyncManager::RequestResume() { - if (data_->syncer_thread()) - return data_->syncer_thread()->RequestResume(); - return false; -} - -void SyncManager::RequestNudge() { - if (data_->syncer_thread()) - data_->syncer_thread()->NudgeSyncer(0, SyncerThread::kLocal); +void SyncManager::RequestNudge(const tracked_objects::Location& location) { + data_->RequestNudge(location); } void SyncManager::RequestClearServerData() { if (data_->syncer_thread()) - data_->syncer_thread()->NudgeSyncer(0, SyncerThread::kClearPrivateData); + data_->syncer_thread()->ScheduleClearUserData(); } void SyncManager::RequestConfig(const syncable::ModelTypeBitSet& types) { if (!data_->syncer_thread()) return; - // It is an error for this to be called if new_impl is null. - data_->syncer_thread()->new_impl()->Start( - browser_sync::s3::SyncerThread::CONFIGURATION_MODE); - data_->syncer_thread()->new_impl()->ScheduleConfig(types); + StartConfigurationMode(NULL); + data_->syncer_thread()->ScheduleConfig(types); +} + +void SyncManager::StartConfigurationMode(ModeChangeCallback* callback) { + if (!data_->syncer_thread()) + return; + data_->syncer_thread()->Start( + browser_sync::SyncerThread::CONFIGURATION_MODE, callback); } const std::string& SyncManager::GetAuthenticatedUsername() { @@ -1660,7 +1702,7 @@ bool SyncManager::SyncInternal::Init( ModelSafeWorkerRegistrar* model_safe_worker_registrar, const char* user_agent, const SyncCredentials& credentials, - const notifier::NotifierOptions& notifier_options, + sync_notifier::SyncNotifier* sync_notifier, const std::string& restored_key_for_bootstrapping, bool setup_for_test_mode) { @@ -1668,20 +1710,21 @@ bool SyncManager::SyncInternal::Init( core_message_loop_ = MessageLoop::current(); DCHECK(core_message_loop_); - notifier_options_ = notifier_options; registrar_ = model_safe_worker_registrar; setup_for_test_mode_ = setup_for_test_mode; + sync_notifier_ = sync_notifier; + sync_notifier_->AddObserver(this); + share_.dir_manager.reset(new DirectoryManager(database_location)); connection_manager_.reset(new SyncAPIServerConnectionManager( sync_server_and_path, port, use_ssl, user_agent, post_factory)); - connection_manager_hookup_.reset( - NewEventListenerHookup(connection_manager()->channel(), this, - &SyncManager::SyncInternal::HandleServerConnectionEvent)); - net::NetworkChangeNotifier::AddIPAddressObserver(this); + + connection_manager()->AddListener(this); + // TODO(akalin): CheckServerReachable() can block, which may cause jank if we // try to shut down sync. Fix this. core_message_loop_->PostTask(FROM_HERE, @@ -1701,15 +1744,19 @@ bool SyncManager::SyncInternal::Init( listeners); context->set_account_name(credentials.email); // The SyncerThread takes ownership of |context|. - syncer_thread_.reset(new SyncerThreadAdapter(context, - CommandLine::ForCurrentProcess()->HasSwitch( - switches::kNewSyncerThread))); + syncer_thread_.reset(new SyncerThread(context, new Syncer())); } bool signed_in = SignIn(credentials); + if (signed_in && syncer_thread()) { + syncer_thread()->Start( + browser_sync::SyncerThread::CONFIGURATION_MODE, NULL); + } + // Do this once the directory is opened. BootstrapEncryption(restored_key_for_bootstrapping); + MarkAndNotifyInitializationComplete(); return signed_in; } @@ -1724,12 +1771,13 @@ void SyncManager::SyncInternal::BootstrapEncryption( if (!lookup->initial_sync_ended_for_type(syncable::NIGORI)) return; - Cryptographer* cryptographer = share_.dir_manager->cryptographer(); - cryptographer->Bootstrap(restored_key_for_bootstrapping); - sync_pb::NigoriSpecifics nigori; { + // Cryptographer should only be accessed while holding a transaction. ReadTransaction trans(GetUserShare()); + Cryptographer* cryptographer = trans.GetCryptographer(); + cryptographer->Bootstrap(restored_key_for_bootstrapping); + ReadNode node(&trans); if (!node.InitByTagLookup(kNigoriTag)) { NOTREACHED(); @@ -1757,11 +1805,12 @@ void SyncManager::SyncInternal::BootstrapEncryption( } void SyncManager::SyncInternal::StartSyncing() { + // Start the syncer thread. This won't actually + // result in any syncing until at least the + // DirectoryManager broadcasts the OPENED event, + // and a valid server connection is detected. if (syncer_thread()) // NULL during certain unittests. - syncer_thread()->Start(); // Start the syncer thread. This won't actually - // result in any syncing until at least the - // DirectoryManager broadcasts the OPENED event, - // and a valid server connection is detected. + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); } void SyncManager::SyncInternal::MarkAndNotifyInitializationComplete() { @@ -1782,38 +1831,14 @@ void SyncManager::SyncInternal::MarkAndNotifyInitializationComplete() { OnInitializationComplete()); } -void SyncManager::SyncInternal::SendPendingXMPPNotification( - bool new_pending_notification) { +void SyncManager::SyncInternal::SendNotification() { DCHECK_EQ(MessageLoop::current(), core_message_loop_); - DCHECK_NE(notifier_options_.notification_method, - notifier::NOTIFICATION_SERVER); - notification_pending_ = notification_pending_ || new_pending_notification; - if (!notification_pending_) { - VLOG(1) << "Not sending notification: no pending notification"; - return; - } - if (!talk_mediator()) { - VLOG(1) << "Not sending notification: shutting down (talk_mediator_ is " - "NULL)"; + if (!sync_notifier_) { + VLOG(1) << "Not sending notification: sync_notifier_ is NULL"; return; } - VLOG(1) << "Sending XMPP notification..."; - OutgoingNotificationData notification_data; - notification_data.service_id = browser_sync::kSyncServiceId; - notification_data.service_url = browser_sync::kSyncServiceUrl; - notification_data.send_content = true; - notification_data.priority = browser_sync::kSyncPriority; - notification_data.write_to_cache_only = true; - notification_data.service_specific_data = - browser_sync::kSyncServiceSpecificData; - notification_data.require_subscription = true; - bool success = talk_mediator()->SendNotification(notification_data); - if (success) { - notification_pending_ = false; - VLOG(1) << "Sent XMPP notification"; - } else { - VLOG(1) << "Could not send XMPP notification"; - } + allstatus_.IncrementNotificationsSent(); + sync_notifier_->SendNotification(); } bool SyncManager::SyncInternal::OpenDirectory() { @@ -1838,11 +1863,7 @@ bool SyncManager::SyncInternal::OpenDirectory() { connection_manager()->set_client_id(lookup->cache_guid()); - if (syncer_thread()) - syncer_thread()->CreateSyncer(username_for_share()); - - MarkAndNotifyInitializationComplete(); - dir_change_hookup_.reset(lookup->AddChangeObserver(this)); + lookup->SetChangeListener(this); return true; } @@ -1855,9 +1876,24 @@ bool SyncManager::SyncInternal::SignIn(const SyncCredentials& credentials) { if (!OpenDirectory()) return false; - if (!setup_for_test_mode_) { - UpdateCredentials(credentials); + // Retrieve and set the sync notifier state. This should be done + // only after OpenDirectory is called. + syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); + std::string state; + if (lookup.good()) { + state = lookup->GetAndClearNotificationState(); + } else { + LOG(ERROR) << "Could not read notification state"; + } + if (VLOG_IS_ON(1)) { + std::string encoded_state; + base::Base64Encode(state, &encoded_state); + VLOG(1) << "Read notification state: " << encoded_state; } + sync_notifier_->SetState(state); + + UpdateCredentials(credentials); + UpdateEnabledTypes(); return true; } @@ -1865,64 +1901,26 @@ void SyncManager::SyncInternal::UpdateCredentials( const SyncCredentials& credentials) { DCHECK_EQ(MessageLoop::current(), core_message_loop_); DCHECK_EQ(credentials.email, share_.name); + DCHECK(!credentials.email.empty()); + DCHECK(!credentials.sync_token.empty()); connection_manager()->set_auth_token(credentials.sync_token); - TalkMediatorLogin(credentials.email, credentials.sync_token); - CheckServerReachable(); - // TODO(tim): Why is this nudge necessary? Possibly just to realize that - // our credentials are invalid (may have been cached, etc), rather than - // wait until a sync needs to happen. Not sure that justifies it... - sync_manager_->RequestNudge(); -} - -void SyncManager::SyncInternal::UpdateEnabledTypes( - const syncable::ModelTypeSet& types) { - DCHECK_EQ(MessageLoop::current(), core_message_loop_); - - enabled_types_ = types; - if (server_notifier_thread_ != NULL) { - server_notifier_thread_->UpdateEnabledTypes(types); + sync_notifier_->UpdateCredentials( + credentials.email, credentials.sync_token); + if (!setup_for_test_mode_) { + CheckServerReachable(); } } -void SyncManager::SyncInternal::InitializeTalkMediator() { - if (notifier_options_.notification_method == - notifier::NOTIFICATION_SERVER) { - syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); - std::string state; - if (lookup.good()) - state = lookup->GetAndClearNotificationState(); - else - LOG(ERROR) << "Could not read notification state"; - if (VLOG_IS_ON(1)) { - std::string encoded_state; - base::Base64Encode(state, &encoded_state); - VLOG(1) << "Read notification state: " << encoded_state; - } - - // |talk_mediator_| takes ownership of |sync_notifier_thread_| - // but it is. guaranteed that |sync_notifier_thread_| is destroyed only - // when |talk_mediator_| is (see the comments in talk_mediator.h). - server_notifier_thread_ = new sync_notifier::ServerNotifierThread( - notifier_options_, state, this); - talk_mediator_.reset( - new TalkMediatorImpl(server_notifier_thread_, - notifier_options_.invalidate_xmpp_login, - notifier_options_.allow_insecure_connection)); - - // Since we may be initialized more than once, make sure that any - // newly created server notifier thread has the latest enabled types. - server_notifier_thread_->UpdateEnabledTypes(enabled_types_); - } else { - notifier::MediatorThread* mediator_thread = - new notifier::MediatorThreadImpl(notifier_options_); - talk_mediator_.reset( - new TalkMediatorImpl(mediator_thread, - notifier_options_.invalidate_xmpp_login, - notifier_options_.allow_insecure_connection)); - talk_mediator_->AddSubscribedServiceUrl(browser_sync::kSyncServiceUrl); - server_notifier_thread_ = NULL; +void SyncManager::SyncInternal::UpdateEnabledTypes() { + DCHECK_EQ(MessageLoop::current(), core_message_loop_); + ModelSafeRoutingInfo routes; + registrar_->GetModelSafeRoutingInfo(&routes); + syncable::ModelTypeSet enabled_types; + for (ModelSafeRoutingInfo::const_iterator it = routes.begin(); + it != routes.end(); ++it) { + enabled_types.insert(it->first); } - talk_mediator()->SetDelegate(this); + sync_notifier_->UpdateEnabledTypes(enabled_types); } void SyncManager::SyncInternal::RaiseAuthNeededEvent() { @@ -1931,9 +1929,9 @@ void SyncManager::SyncInternal::RaiseAuthNeededEvent() { OnAuthError(AuthError(AuthError::INVALID_GAIA_CREDENTIALS))); } -void SyncManager::SyncInternal::SetUsingExplicitPassphrasePrefForMigration() { - WriteTransaction trans(&share_); - WriteNode node(&trans); +void SyncManager::SyncInternal::SetUsingExplicitPassphrasePrefForMigration( + WriteTransaction* const trans) { + WriteNode node(trans); if (!node.InitByTagLookup(kNigoriTag)) { // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. NOTREACHED(); @@ -1946,8 +1944,11 @@ void SyncManager::SyncInternal::SetUsingExplicitPassphrasePrefForMigration() { void SyncManager::SyncInternal::SetPassphrase( const std::string& passphrase, bool is_explicit) { - Cryptographer* cryptographer = dir_manager()->cryptographer(); + // All accesses to the cryptographer are protected by a transaction. + WriteTransaction trans(GetUserShare()); + Cryptographer* cryptographer = trans.GetCryptographer(); KeyParams params = {"localhost", "dummy", passphrase}; + if (cryptographer->has_pending_keys()) { if (!cryptographer->DecryptPendingKeys(params)) { VLOG(1) << "Passphrase failed to decrypt pending keys."; @@ -1960,14 +1961,13 @@ void SyncManager::SyncInternal::SetPassphrase( // since the protocol changed to store passphrase preferences in the cloud, // make sure we update this preference. See bug 62103. if (is_explicit) - SetUsingExplicitPassphrasePrefForMigration(); + SetUsingExplicitPassphrasePrefForMigration(&trans); // Nudge the syncer so that encrypted datatype updates that were waiting for // this passphrase get applied as soon as possible. - sync_manager_->RequestNudge(); + RequestNudge(FROM_HERE); } else { VLOG(1) << "No pending keys, adding provided passphrase."; - WriteTransaction trans(GetUserShare()); WriteNode node(&trans); if (!node.InitByTagLookup(kNigoriTag)) { // TODO(albertb): Plumb an UnrecoverableError all the way back to the PSS. @@ -2048,6 +2048,52 @@ void SyncManager::SyncInternal::EncryptDataTypes( return; } +namespace { + +void FindChildNodesContainingString(const std::string& lowercase_query, + const ReadNode& parent_node, + sync_api::ReadTransaction* trans, + ListValue* result) { + int64 child_id = parent_node.GetFirstChildId(); + while (child_id != kInvalidId) { + ReadNode node(trans); + if (node.InitByIdLookup(child_id)) { + if (node.ContainsString(lowercase_query)) { + result->Append(new StringValue(base::Int64ToString(child_id))); + } + FindChildNodesContainingString(lowercase_query, node, trans, result); + child_id = node.GetSuccessorId(); + } else { + LOG(WARNING) << "Lookup of node failed. Id: " << child_id; + return; + } + } +} +} // namespace + +// Returned pointer owned by the caller. +ListValue* SyncManager::SyncInternal::FindNodesContainingString( + const std::string& query) { + // Convert the query string to lower case to perform case insensitive + // searches. + std::string lowercase_query = query; + StringToLowerASCII(&lowercase_query); + ReadTransaction trans(GetUserShare()); + ReadNode root(&trans); + root.InitByRootLookup(); + + ListValue* result = new ListValue(); + + base::Time start_time = base::Time::Now(); + FindChildNodesContainingString(lowercase_query, root, &trans, result); + base::Time end_time = base::Time::Now(); + + base::TimeDelta delta = end_time - start_time; + VLOG(1) << "Time taken in milliseconds to search " << delta.InMilliseconds(); + + return result; +} + void SyncManager::SyncInternal::ReEncryptEverything(WriteTransaction* trans) { syncable::ModelTypeSet encrypted_types = GetEncryptedDataTypes(trans->GetWrappedTrans()); @@ -2140,36 +2186,24 @@ void SyncManager::Shutdown() { void SyncManager::SyncInternal::Shutdown() { method_factory_.RevokeAll(); - // We NULL out talk_mediator_ so that any tasks pumped below do not - // trigger further XMPP actions. - // - // TODO(akalin): NULL the other member variables defensively, too. - scoped_ptr<TalkMediator> talk_mediator(talk_mediator_.release()); - if (syncer_thread()) { - if (!syncer_thread()->Stop(kThreadExitTimeoutMsec)) { - LOG(FATAL) << "Unable to stop the syncer, it won't be happy..."; - } + syncer_thread()->Stop(); syncer_thread_.reset(); } - // Shutdown the xmpp buzz connection. - if (talk_mediator.get()) { - VLOG(1) << "P2P: Mediator logout started."; - talk_mediator->Logout(); - VLOG(1) << "P2P: Mediator logout completed."; - talk_mediator.reset(); - - // |server_notifier_thread_| is owned by |talk_mediator|. We NULL - // it out here so as to not have a dangling pointer. - server_notifier_thread_= NULL; - VLOG(1) << "P2P: Mediator destroyed."; + // We NULL out sync_notifer_ so that any pending tasks do not + // trigger further notifications. + // TODO(akalin): NULL the other member variables defensively, too. + if (sync_notifier_) { + sync_notifier_->RemoveObserver(this); } - // Pump any messages the auth watcher, syncer thread, or talk - // mediator posted before they shut down. (See OnSyncEngineEvent(), - // and HandleTalkMediatorEvent() for the - // events that may be posted.) + // |this| is about to be destroyed, so we have to ensure any messages + // that were posted to core_thread_ before or during syncer thread shutdown + // are flushed out, else they refer to garbage memory. SendNotification + // is an example. + // TODO(tim): Remove this monstrosity, perhaps with ObserverListTS once core + // thread is removed. Bug 78190. { CHECK(core_message_loop_); bool old_state = core_message_loop_->NestableTasksAllowed(); @@ -2191,9 +2225,6 @@ void SyncManager::SyncInternal::Shutdown() { // handles to backing files. share_.dir_manager.reset(); - // We don't want to process any more events. - dir_change_hookup_.reset(); - core_message_loop_ = NULL; } @@ -2215,50 +2246,16 @@ void SyncManager::SyncInternal::OnIPAddressChangedImpl() { // TODO(akalin): CheckServerReachable() can block, which may cause // jank if we try to shut down sync. Fix this. connection_manager()->CheckServerReachable(); - sync_manager_->RequestNudge(); + RequestNudge(FROM_HERE); } -// Listen to model changes, filter out ones initiated by the sync API, and -// saves the rest (hopefully just backend Syncer changes resulting from -// ApplyUpdates) to data_->changelist. -void SyncManager::SyncInternal::HandleChannelEvent( - const syncable::DirectoryChangeEvent& event) { - if (event.todo == syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE) { - // Safe to perform slow I/O operations now, go ahead and commit. - HandleTransactionCompleteChangeEvent(event); - return; - } else if (event.todo == syncable::DirectoryChangeEvent::TRANSACTION_ENDING) { - HandleTransactionEndingChangeEvent(event); - return; - } else if (event.todo == syncable::DirectoryChangeEvent::CALCULATE_CHANGES) { - if (event.writer == syncable::SYNCAPI) { - HandleCalculateChangesChangeEventFromSyncApi(event); - return; - } - HandleCalculateChangesChangeEventFromSyncer(event); - return; - } else if (event.todo == syncable::DirectoryChangeEvent::SHUTDOWN) { - dir_change_hookup_.reset(); - } -} - -void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent( - const syncable::DirectoryChangeEvent& event) { - // This notification happens immediately after the channel mutex is released - // This allows work to be performed without holding the WriteTransaction lock - // but before the transaction is finished. - DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::TRANSACTION_COMPLETE); - if (observers_.size() <= 0) - return; - - // Call commit - for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { - if (model_has_change_.test(i)) { - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnChangesComplete(syncable::ModelTypeFromInt(i))); - model_has_change_.reset(i); - } - } +void SyncManager::SyncInternal::OnServerConnectionEvent( + const ServerConnectionEvent2& event) { + ServerConnectionEvent legacy; + legacy.what_happened = ServerConnectionEvent::STATUS_CHANGED; + legacy.connection_code = event.connection_code; + legacy.server_reachable = event.server_reachable; + HandleServerConnectionEvent(legacy); } void SyncManager::SyncInternal::HandleServerConnectionEvent( @@ -2279,55 +2276,74 @@ void SyncManager::SyncInternal::HandleServerConnectionEvent( } } -void SyncManager::SyncInternal::HandleTransactionEndingChangeEvent( - const syncable::DirectoryChangeEvent& event) { +void SyncManager::SyncInternal::HandleTransactionCompleteChangeEvent( + const syncable::ModelTypeBitSet& models_with_changes) { + // This notification happens immediately after the transaction mutex is + // released. This allows work to be performed without blocking other threads + // from acquiring a transaction. + if (observers_.size() <= 0) + return; + + // Call commit. + for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { + if (models_with_changes.test(i)) { + FOR_EACH_OBSERVER(SyncManager::Observer, observers_, + OnChangesComplete(syncable::ModelTypeFromInt(i))); + } + } +} + +ModelTypeBitSet SyncManager::SyncInternal::HandleTransactionEndingChangeEvent( + syncable::BaseTransaction* trans) { // This notification happens immediately before a syncable WriteTransaction // falls out of scope. It happens while the channel mutex is still held, // and while the transaction mutex is held, so it cannot be re-entrant. - DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::TRANSACTION_ENDING); if (observers_.size() <= 0 || ChangeBuffersAreEmpty()) - return; + return ModelTypeBitSet(); // This will continue the WriteTransaction using a read only wrapper. // This is the last chance for read to occur in the WriteTransaction // that's closing. This special ReadTransaction will not close the // underlying transaction. - ReadTransaction trans(GetUserShare(), event.trans); + ReadTransaction read_trans(GetUserShare(), trans); + syncable::ModelTypeBitSet models_with_changes; for (int i = 0; i < syncable::MODEL_TYPE_COUNT; ++i) { if (change_buffers_[i].IsEmpty()) continue; vector<ChangeRecord> ordered_changes; - change_buffers_[i].GetAllChangesInTreeOrder(&trans, &ordered_changes); + change_buffers_[i].GetAllChangesInTreeOrder(&read_trans, &ordered_changes); if (!ordered_changes.empty()) { FOR_EACH_OBSERVER( SyncManager::Observer, observers_, - OnChangesApplied(syncable::ModelTypeFromInt(i), &trans, + OnChangesApplied(syncable::ModelTypeFromInt(i), &read_trans, &ordered_changes[0], ordered_changes.size())); - model_has_change_.set(i, true); + models_with_changes.set(i, true); } change_buffers_[i].Clear(); } + return models_with_changes; } void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi( - const syncable::DirectoryChangeEvent& event) { - // We have been notified about a user action changing the bookmark model. - DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES); - DCHECK(event.writer == syncable::SYNCAPI || - event.writer == syncable::UNITTEST); + const OriginalEntries& originals, + const WriterTag& writer, + syncable::BaseTransaction* trans) { + // We have been notified about a user action changing a sync model. + DCHECK(writer == syncable::SYNCAPI || + writer == syncable::UNITTEST); LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << "CALCULATE_CHANGES called with unapplied old changes."; bool exists_unsynced_items = false; bool only_preference_changes = true; syncable::ModelTypeBitSet model_types; - for (syncable::OriginalEntries::const_iterator i = event.originals->begin(); - i != event.originals->end() && !exists_unsynced_items; + for (syncable::OriginalEntries::const_iterator i = originals.begin(); + i != originals.end() && !exists_unsynced_items; ++i) { int64 id = i->ref(syncable::META_HANDLE); - syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id); + syncable::Entry e(trans, syncable::GET_BY_HANDLE, id); DCHECK(e.good()); syncable::ModelType model_type = e.GetModelType(); @@ -2349,10 +2365,12 @@ void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncApi( if (exists_unsynced_items && syncer_thread()) { int nudge_delay = only_preference_changes ? kPreferencesNudgeDelayMilliseconds : kDefaultNudgeDelayMilliseconds; - syncer_thread()->NudgeSyncerWithDataTypes( - nudge_delay, - SyncerThread::kLocal, - model_types); + core_message_loop_->PostTask(FROM_HERE, + NewRunnableMethod(this, &SyncInternal::RequestNudgeWithDataTypes, + TimeDelta::FromMilliseconds(nudge_delay), + browser_sync::NUDGE_SOURCE_LOCAL, + model_types, + FROM_HERE)); } } @@ -2387,19 +2405,21 @@ void SyncManager::SyncInternal::SetExtraChangeRecordData(int64 id, } void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( - const syncable::DirectoryChangeEvent& event) { + const OriginalEntries& originals, + const WriterTag& writer, + syncable::BaseTransaction* trans) { // We only expect one notification per sync step, so change_buffers_ should // contain no pending entries. - DCHECK_EQ(event.todo, syncable::DirectoryChangeEvent::CALCULATE_CHANGES); - DCHECK(event.writer == syncable::SYNCER || - event.writer == syncable::UNITTEST); + DCHECK(writer == syncable::SYNCER || + writer == syncable::UNITTEST); LOG_IF(WARNING, !ChangeBuffersAreEmpty()) << "CALCULATE_CHANGES called with unapplied old changes."; - for (syncable::OriginalEntries::const_iterator i = event.originals->begin(); - i != event.originals->end(); ++i) { + Cryptographer* crypto = dir_manager()->GetCryptographer(trans); + for (syncable::OriginalEntries::const_iterator i = originals.begin(); + i != originals.end(); ++i) { int64 id = i->ref(syncable::META_HANDLE); - syncable::Entry e(event.trans, syncable::GET_BY_HANDLE, id); + syncable::Entry e(trans, syncable::GET_BY_HANDLE, id); bool existed_before = !i->ref(syncable::IS_DEL); bool exists_now = e.good() && !e.Get(syncable::IS_DEL); DCHECK(e.good()); @@ -2414,12 +2434,11 @@ void SyncManager::SyncInternal::HandleCalculateChangesChangeEventFromSyncer( else if (!exists_now && existed_before) change_buffers_[type].PushDeletedItem(id); else if (exists_now && existed_before && - VisiblePropertiesDiffer(*i, e, dir_manager()->cryptographer())) { + VisiblePropertiesDiffer(*i, e, crypto)) { change_buffers_[type].PushUpdatedItem(id, VisiblePositionsDiffer(*i, e)); } - SetExtraChangeRecordData(id, type, &change_buffers_[type], - dir_manager()->cryptographer(), *i, + SetExtraChangeRecordData(id, type, &change_buffers_[type], crypto, *i, existed_before, exists_now); } } @@ -2428,6 +2447,22 @@ SyncManager::Status SyncManager::SyncInternal::GetStatus() { return allstatus_.status(); } +void SyncManager::SyncInternal::RequestNudge( + const tracked_objects::Location& location) { + if (syncer_thread()) + syncer_thread()->ScheduleNudge( + TimeDelta::FromMilliseconds(0), browser_sync::NUDGE_SOURCE_LOCAL, + ModelTypeBitSet(), location); +} + +void SyncManager::SyncInternal::RequestNudgeWithDataTypes( + const TimeDelta& delay, + browser_sync::NudgeSource source, const ModelTypeBitSet& types, + const tracked_objects::Location& nudge_location) { + if (syncer_thread()) + syncer_thread()->ScheduleNudge(delay, source, types, nudge_location); +} + void SyncManager::SyncInternal::OnSyncEngineEvent( const SyncEngineEvent& event) { if (observers_.size() <= 0) @@ -2459,8 +2494,7 @@ void SyncManager::SyncInternal::OnSyncEngineEvent( if (enabled_types.count(syncable::PASSWORDS) > 0) encrypted_types.insert(syncable::PASSWORDS); if (!encrypted_types.empty()) { - Cryptographer* cryptographer = - GetUserShare()->dir_manager->cryptographer(); + Cryptographer* cryptographer = trans.GetCryptographer(); if (!cryptographer->is_ready() && !cryptographer->has_pending_keys()) { if (!nigori.encrypted().blob().empty()) { DCHECK(!cryptographer->CanDecrypt(nigori.encrypted())); @@ -2491,33 +2525,22 @@ void SyncManager::SyncInternal::OnSyncEngineEvent( OnSyncCycleCompleted(event.snapshot)); } - if (notifier_options_.notification_method != - notifier::NOTIFICATION_SERVER) { - // TODO(chron): Consider changing this back to track has_more_to_sync - // only notify peers if a successful commit has occurred. - bool new_pending_notification = - (event.snapshot->syncer_status.num_successful_commits > 0); + // This is here for tests, which are still using p2p notifications. + // SendNotification does not do anything if we are using server based + // notifications. + // TODO(chron): Consider changing this back to track has_more_to_sync + // only notify peers if a successful commit has occurred. + bool new_notification = + (event.snapshot->syncer_status.num_successful_commits > 0); + if (new_notification) { core_message_loop_->PostTask( FROM_HERE, NewRunnableMethod( this, - &SyncManager::SyncInternal::SendPendingXMPPNotification, - new_pending_notification)); + &SyncManager::SyncInternal::SendNotification)); } } - if (event.what_happened == SyncEngineEvent::SYNCER_THREAD_PAUSED) { - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnPaused()); - return; - } - - if (event.what_happened == SyncEngineEvent::SYNCER_THREAD_RESUMED) { - FOR_EACH_OBSERVER(SyncManager::Observer, observers_, - OnResumed()); - return; - } - if (event.what_happened == SyncEngineEvent::STOP_SYNCING_PERMANENTLY) { FOR_EACH_OBSERVER(SyncManager::Observer, observers_, OnStopSyncingPermanently()); @@ -2583,6 +2606,16 @@ void SyncManager::SyncInternal::ProcessMessage( parent_router_->RouteJsEvent( "onGetNotificationStateFinished", browser_sync::JsArgList(return_args), sender); + } else if (name == "getNotificationInfo") { + if (!parent_router_) { + LogNoRouter(name, args); + return; + } + + ListValue return_args; + return_args.Append(NotificationInfoToValue(notification_info_map_)); + parent_router_->RouteJsEvent("onGetNotificationInfoFinished", + browser_sync::JsArgList(return_args), sender); } else if (name == "getRootNode") { if (!parent_router_) { LogNoRouter(name, args); @@ -2603,6 +2636,14 @@ void SyncManager::SyncInternal::ProcessMessage( } parent_router_->RouteJsEvent( "onGetNodeByIdFinished", ProcessGetNodeByIdMessage(args), sender); + } else if (name == "findNodesContainingString") { + if (!parent_router_) { + LogNoRouter(name, args); + return; + } + parent_router_->RouteJsEvent( + "onFindNodesContainingStringFinished", + ProcessFindNodesContainingString(args), sender); } else { VLOG(1) << "Dropping unknown message " << name << " with args " << args.ToString(); @@ -2635,13 +2676,28 @@ browser_sync::JsArgList SyncManager::SyncInternal::ProcessGetNodeByIdMessage( return browser_sync::JsArgList(return_args); } +browser_sync::JsArgList SyncManager::SyncInternal:: + ProcessFindNodesContainingString( + const browser_sync::JsArgList& args) { + std::string query; + ListValue return_args; + if (!args.Get().GetString(0, &query)) { + return_args.Append(new ListValue()); + return browser_sync::JsArgList(return_args); + } + + ListValue* result = FindNodesContainingString(query); + return_args.Append(result); + return browser_sync::JsArgList(return_args); +} + void SyncManager::SyncInternal::OnNotificationStateChange( bool notifications_enabled) { VLOG(1) << "P2P: Notifications enabled = " << (notifications_enabled ? "true" : "false"); allstatus_.SetNotificationsEnabled(notifications_enabled); if (syncer_thread()) { - syncer_thread()->SetNotificationsEnabled(notifications_enabled); + syncer_thread()->set_notifications_enabled(notifications_enabled); } if (parent_router_) { ListValue args; @@ -2650,89 +2706,29 @@ void SyncManager::SyncInternal::OnNotificationStateChange( parent_router_->RouteJsEvent("onSyncNotificationStateChange", browser_sync::JsArgList(args), NULL); } - if ((notifier_options_.notification_method != - notifier::NOTIFICATION_SERVER) && notifications_enabled) { - // Nudge the syncer thread when notifications are enabled, in case there is - // any data that has not yet been synced. If we are listening to - // server-issued notifications, we are already guaranteed to receive a - // notification on a successful connection. - if (syncer_thread()) { - syncer_thread()->NudgeSyncer(0, SyncerThread::kLocal); - } - - // Send a notification as soon as subscriptions are on - // (see http://code.google.com/p/chromium/issues/detail?id=38563 ). - core_message_loop_->PostTask( - FROM_HERE, - NewRunnableMethod( - this, - &SyncManager::SyncInternal::SendPendingXMPPNotification, - true)); - } } -void SyncManager::SyncInternal::TalkMediatorLogin( - const std::string& email, const std::string& token) { - DCHECK_EQ(MessageLoop::current(), core_message_loop_); - DCHECK(!email.empty()); - DCHECK(!token.empty()); - InitializeTalkMediator(); - talk_mediator()->SetAuthToken(email, token, SYNC_SERVICE_NAME); - talk_mediator()->Login(); +void SyncManager::SyncInternal::UpdateNotificationInfo( + const syncable::ModelTypePayloadMap& type_payloads) { + for (syncable::ModelTypePayloadMap::const_iterator it = type_payloads.begin(); + it != type_payloads.end(); ++it) { + NotificationInfo* info = ¬ification_info_map_[it->first]; + info->total_count++; + info->payload = it->second; + } } void SyncManager::SyncInternal::OnIncomingNotification( - const IncomingNotificationData& notification_data) { - browser_sync::sessions::TypePayloadMap model_types_with_payloads; - - // Check if the service url is a sync URL. An empty service URL is - // treated as a legacy sync notification. If we're listening to - // server-issued notifications, no need to check the service_url. - if (notifier_options_.notification_method == - notifier::NOTIFICATION_SERVER) { - VLOG(1) << "Sync received server notification from " << - notification_data.service_url << ": " << - notification_data.service_specific_data; - syncable::ModelTypeBitSet model_types; - const std::string& model_type_list = notification_data.service_url; - const std::string& notification_payload = - notification_data.service_specific_data; - - if (!syncable::ModelTypeBitSetFromString(model_type_list, &model_types)) { - LOG(DFATAL) << "Could not extract model types from server data."; - model_types.set(); - } - - model_types_with_payloads = - browser_sync::sessions::MakeTypePayloadMapFromBitSet(model_types, - notification_payload); - } else if (notification_data.service_url.empty() || - (notification_data.service_url == - browser_sync::kSyncLegacyServiceUrl) || - (notification_data.service_url == - browser_sync::kSyncServiceUrl)) { - VLOG(1) << "Sync received P2P notification."; - - // Catch for sync integration tests (uses p2p). Just set all enabled - // datatypes. - ModelSafeRoutingInfo routes; - registrar_->GetModelSafeRoutingInfo(&routes); - model_types_with_payloads = - browser_sync::sessions::MakeTypePayloadMapFromRoutingInfo(routes, - std::string()); - } else { - LOG(WARNING) << "Notification fron unexpected source: " - << notification_data.service_url; - } - - if (!model_types_with_payloads.empty()) { + const syncable::ModelTypePayloadMap& type_payloads) { + if (!type_payloads.empty()) { if (syncer_thread()) { - syncer_thread()->NudgeSyncerWithPayloads( - kSyncerThreadDelayMsec, - SyncerThread::kNotification, - model_types_with_payloads); + syncer_thread()->ScheduleNudgeWithPayloads( + TimeDelta::FromMilliseconds(kSyncerThreadDelayMsec), + browser_sync::NUDGE_SOURCE_NOTIFICATION, + type_payloads, FROM_HERE); } allstatus_.IncrementNotificationsReceived(); + UpdateNotificationInfo(type_payloads); } else { LOG(WARNING) << "Sync received notification without any type information."; } @@ -2741,9 +2737,9 @@ void SyncManager::SyncInternal::OnIncomingNotification( ListValue args; ListValue* changed_types = new ListValue(); args.Append(changed_types); - for (browser_sync::sessions::TypePayloadMap::const_iterator - it = model_types_with_payloads.begin(); - it != model_types_with_payloads.end(); ++it) { + for (syncable::ModelTypePayloadMap::const_iterator + it = type_payloads.begin(); + it != type_payloads.end(); ++it) { const std::string& model_type_str = syncable::ModelTypeToString(it->first); changed_types->Append(Value::CreateStringValue(model_type_str)); @@ -2753,13 +2749,8 @@ void SyncManager::SyncInternal::OnIncomingNotification( } } -void SyncManager::SyncInternal::OnOutgoingNotification() { - DCHECK_NE(notifier_options_.notification_method, - notifier::NOTIFICATION_SERVER); - allstatus_.IncrementNotificationsSent(); -} - -void SyncManager::SyncInternal::WriteState(const std::string& state) { +void SyncManager::SyncInternal::StoreState( + const std::string& state) { syncable::ScopedDirLookup lookup(dir_manager(), username_for_share()); if (!lookup.good()) { LOG(ERROR) << "Could not write notification state"; @@ -2816,7 +2807,7 @@ BaseTransaction::BaseTransaction(UserShare* share) DCHECK(share && share->dir_manager.get()); lookup_ = new syncable::ScopedDirLookup(share->dir_manager.get(), share->name); - cryptographer_ = share->dir_manager->cryptographer(); + cryptographer_ = share->dir_manager->GetCryptographer(this); if (!(lookup_->good())) DCHECK(false) << "ScopedDirLookup failed on valid DirManager."; } @@ -2841,10 +2832,11 @@ void SyncManager::TriggerOnNotificationStateChangeForTest( void SyncManager::TriggerOnIncomingNotificationForTest( const syncable::ModelTypeBitSet& model_types) { - IncomingNotificationData notification_data; - notification_data.service_url = model_types.to_string(); - // Here we rely on the default notification method being SERVER. - data_->OnIncomingNotification(notification_data); + syncable::ModelTypePayloadMap model_types_with_payloads = + syncable::ModelTypePayloadMapFromBitSet(model_types, + std::string()); + + data_->OnIncomingNotification(model_types_with_payloads); } } // namespace sync_api diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index 3629793..1011a29 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -43,8 +43,10 @@ #include <vector> #include "base/basictypes.h" +#include "base/callback.h" #include "base/gtest_prod_util.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "base/tracked.h" #include "build/build_config.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" #include "chrome/browser/sync/syncable/autofill_migration.h" @@ -65,9 +67,9 @@ struct SyncSessionSnapshot; } } -namespace notifier { -struct NotifierOptions; -} +namespace sync_notifier { +class SyncNotifier; +} // namespace sync_notifier // Forward declarations of internal class types so that sync API objects // may have opaque pointers to these types. @@ -100,7 +102,6 @@ class TypedUrlSpecifics; namespace sync_api { -// Forward declarations of classes to be defined later in this file. class BaseTransaction; class HttpPostProviderFactory; class SyncManager; @@ -256,6 +257,10 @@ class BaseNode { // Transfers ownership of the DictionaryValue to the caller. DictionaryValue* ToValue() const; + // Does a case in-sensitive search for a given string, which must be + // lower case. + bool ContainsString(const std::string& lowercase_query) const; + protected: BaseNode(); virtual ~BaseNode(); @@ -803,12 +808,6 @@ class SyncManager { // message, unless otherwise specified, produces undefined behavior. virtual void OnInitializationComplete() = 0; - // The syncer thread has been paused. - virtual void OnPaused() = 0; - - // The syncer thread has been resumed. - virtual void OnResumed() = 0; - // We are no longer permitted to communicate with the server. Sync should // be disabled and state cleaned up at once. This can happen for a number // of reasons, e.g. swapping from a test instance to production, or a @@ -828,6 +827,8 @@ class SyncManager { virtual ~Observer(); }; + typedef Callback0::Type ModeChangeCallback; + // Create an uninitialized SyncManager. Callers must Init() before using. SyncManager(); virtual ~SyncManager(); @@ -844,7 +845,7 @@ class SyncManager { // |model_safe_worker| ownership is given to the SyncManager. // |user_agent| is a 7-bit ASCII string suitable for use as the User-Agent // HTTP header. Used internally when collecting stats to classify clients. - // |notifier_options| contains options specific to sync notifications. + // |sync_notifier| used to listen for notifications, not owned. bool Init(const FilePath& database_location, const char* sync_server_and_path, int sync_server_port, @@ -853,7 +854,7 @@ class SyncManager { browser_sync::ModelSafeWorkerRegistrar* registrar, const char* user_agent, const SyncCredentials& credentials, - const notifier::NotifierOptions& notifier_options, + sync_notifier::SyncNotifier* sync_notifier, const std::string& restored_key_for_bootstrapping, bool setup_for_test_mode); @@ -884,9 +885,8 @@ class SyncManager { // Update tokens that we're using in Sync. Email must stay the same. void UpdateCredentials(const SyncCredentials& credentials); - // Update the set of enabled sync types. Usually called when the user disables - // or enables a sync type. - void UpdateEnabledTypes(const syncable::ModelTypeSet& types); + // Called when the user disables or enables a sync type. + void UpdateEnabledTypes(); // Start the SyncerThread. // TODO(tim): With the new impl, this would mean starting "NORMAL" operation. @@ -911,19 +911,11 @@ class SyncManager { // types, as we do not currently support decrypting datatypes. void EncryptDataTypes(const syncable::ModelTypeSet& encrypted_types); - // Requests the syncer thread to pause. The observer's OnPause - // method will be called when the syncer thread is paused. Returns - // false if the syncer thread can not be paused (e.g. if it is not - // started). - // TODO(tim): Deprecated. - bool RequestPause(); - - // Requests the syncer thread to resume. The observer's OnResume - // method will be called when the syncer thread is resumed. Returns - // false if the syncer thread can not be resumed (e.g. if it is not - // paused). - // TODO(tim): Deprecated. - bool RequestResume(); + // Puts the SyncerThread into a mode where no normal nudge or poll traffic + // will occur, but calls to RequestConfig will be supported. If |callback| + // is provided, it will be invoked (from the internal SyncerThread) when + // the thread has changed to configuration mode. + void StartConfigurationMode(ModeChangeCallback* callback); // For the new SyncerThread impl, this switches the mode of operation to // CONFIGURATION_MODE and schedules a config task to fetch updates for @@ -932,7 +924,7 @@ class SyncManager { // Request a nudge of the syncer, which will cause the syncer thread // to run at the next available opportunity. - void RequestNudge(); + void RequestNudge(const tracked_objects::Location& nudge_location); // Request a clearing of all data on the server void RequestClearServerData(); @@ -1022,77 +1014,6 @@ class SyncManager { DISALLOW_COPY_AND_ASSIGN(SyncManager); }; -// An interface the embedding application (e.g. Chromium) implements to -// provide required HTTP POST functionality to the syncer backend. -// This interface is designed for one-time use. You create one, use it, and -// create another if you want to make a subsequent POST. -// TODO(timsteele): Bug 1482576. Consider splitting syncapi.h into two files: -// one for the API defining the exports, which doesn't need to be included from -// anywhere internally, and another file for the interfaces like this one. -class HttpPostProviderInterface { - public: - HttpPostProviderInterface() { } - virtual ~HttpPostProviderInterface() { } - - // Use specified user agent string when POSTing. If not called a default UA - // may be used. - virtual void SetUserAgent(const char* user_agent) = 0; - - // Add additional headers to the request. - virtual void SetExtraRequestHeaders(const char * headers) = 0; - - // Set the URL to POST to. - virtual void SetURL(const char* url, int port) = 0; - - // Set the type, length and content of the POST payload. - // |content_type| is a null-terminated MIME type specifier. - // |content| is a data buffer; Do not interpret as a null-terminated string. - // |content_length| is the total number of chars in |content|. It is used to - // assign/copy |content| data. - virtual void SetPostPayload(const char* content_type, int content_length, - const char* content) = 0; - - // Returns true if the URL request succeeded. If the request failed, - // os_error() may be non-zero and hence contain more information. - virtual bool MakeSynchronousPost(int* os_error_code, int* response_code) = 0; - - // Get the length of the content returned in the HTTP response. - // This does not count the trailing null-terminating character returned - // by GetResponseContent, so it is analogous to calling string.length. - virtual int GetResponseContentLength() const = 0; - - // Get the content returned in the HTTP response. - // This is a null terminated string of characters. - // Value should be copied. - virtual const char* GetResponseContent() const = 0; - - // Get the value of a header returned in the HTTP response. - // If the header is not present, returns the empty string. - virtual const std::string GetResponseHeaderValue( - const std::string& name) const = 0; - - private: - DISALLOW_COPY_AND_ASSIGN(HttpPostProviderInterface); -}; - -// A factory to create HttpPostProviders to hide details about the -// implementations and dependencies. -// A factory instance itself should be owned by whomever uses it to create -// HttpPostProviders. -class HttpPostProviderFactory { - public: - // Obtain a new HttpPostProviderInterface instance, owned by caller. - virtual HttpPostProviderInterface* Create() = 0; - - // When the interface is no longer needed (ready to be cleaned up), clients - // must call Destroy(). - // This allows actual HttpPostProvider subclass implementations to be - // reference counted, which is useful if a particular implementation uses - // multiple threads to serve network requests. - virtual void Destroy(HttpPostProviderInterface* http) = 0; - virtual ~HttpPostProviderFactory() { } -}; - } // namespace sync_api #endif // CHROME_BROWSER_SYNC_ENGINE_SYNCAPI_H_ diff --git a/chrome/browser/sync/engine/syncapi_unittest.cc b/chrome/browser/sync/engine/syncapi_unittest.cc index 38b72ab..54c09bd 100644 --- a/chrome/browser/sync/engine/syncapi_unittest.cc +++ b/chrome/browser/sync/engine/syncapi_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,13 +10,15 @@ #include "base/basictypes.h" #include "base/format_macros.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_temp_dir.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" -#include "base/scoped_temp_dir.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/utf_string_conversions.h" #include "base/values.h" +#include "chrome/browser/sync/engine/http_post_provider_factory.h" +#include "chrome/browser/sync/engine/http_post_provider_interface.h" #include "chrome/browser/sync/engine/model_safe_worker.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/js_arg_list.h" @@ -24,6 +26,8 @@ #include "chrome/browser/sync/js_event_handler.h" #include "chrome/browser/sync/js_event_router.h" #include "chrome/browser/sync/js_test_util.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "chrome/browser/sync/notifier/sync_notifier_observer.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" #include "chrome/browser/sync/protocol/proto_value_conversions.h" #include "chrome/browser/sync/sessions/sync_session.h" @@ -32,10 +36,9 @@ #include "chrome/browser/sync/syncable/syncable.h" #include "chrome/browser/sync/syncable/syncable_id.h" #include "chrome/browser/sync/util/cryptographer.h" -#include "chrome/test/sync/engine/test_directory_setter_upper.h" +#include "chrome/test/sync/engine/test_user_share.h" #include "chrome/test/values_test_util.h" #include "content/browser/browser_thread.h" -#include "jingle/notifier/base/notifier_options.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -54,6 +57,7 @@ using syncable::ModelTypeSet; using test::ExpectDictionaryValue; using test::ExpectStringValue; using testing::_; +using testing::AtLeast; using testing::Invoke; using testing::SaveArg; using testing::StrictMock; @@ -164,34 +168,29 @@ int64 MakeServerNodeForType(UserShare* share, class SyncApiTest : public testing::Test { public: virtual void SetUp() { - setter_upper_.SetUp(); - share_.dir_manager.reset(setter_upper_.manager()); - share_.name = setter_upper_.name(); + test_user_share_.SetUp(); } virtual void TearDown() { - // |share_.dir_manager| does not actually own its value. - ignore_result(share_.dir_manager.release()); - setter_upper_.TearDown(); + test_user_share_.TearDown(); } protected: - UserShare share_; - browser_sync::TestDirectorySetterUpper setter_upper_; + browser_sync::TestUserShare test_user_share_; }; TEST_F(SyncApiTest, SanityCheckTest) { { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); EXPECT_TRUE(trans.GetWrappedTrans() != NULL); } { - WriteTransaction trans(&share_); + WriteTransaction trans(test_user_share_.user_share()); EXPECT_TRUE(trans.GetWrappedTrans() != NULL); } { // No entries but root should exist - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode node(&trans); // Metahandle 1 can be root, sanity check 2 EXPECT_FALSE(node.InitByIdLookup(2)); @@ -200,16 +199,17 @@ TEST_F(SyncApiTest, SanityCheckTest) { TEST_F(SyncApiTest, BasicTagWrite) { { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode root_node(&trans); root_node.InitByRootLookup(); EXPECT_EQ(root_node.GetFirstChildId(), 0); } - ignore_result(MakeNode(&share_, syncable::BOOKMARKS, "testtag")); + ignore_result(MakeNode(test_user_share_.user_share(), + syncable::BOOKMARKS, "testtag")); { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode node(&trans); EXPECT_TRUE(node.InitByClientTagLookup(syncable::BOOKMARKS, "testtag")); @@ -239,18 +239,21 @@ TEST_F(SyncApiTest, GenerateSyncableHash) { TEST_F(SyncApiTest, ModelTypesSiloed) { { - WriteTransaction trans(&share_); + WriteTransaction trans(test_user_share_.user_share()); ReadNode root_node(&trans); root_node.InitByRootLookup(); EXPECT_EQ(root_node.GetFirstChildId(), 0); } - ignore_result(MakeNode(&share_, syncable::BOOKMARKS, "collideme")); - ignore_result(MakeNode(&share_, syncable::PREFERENCES, "collideme")); - ignore_result(MakeNode(&share_, syncable::AUTOFILL, "collideme")); + ignore_result(MakeNode(test_user_share_.user_share(), + syncable::BOOKMARKS, "collideme")); + ignore_result(MakeNode(test_user_share_.user_share(), + syncable::PREFERENCES, "collideme")); + ignore_result(MakeNode(test_user_share_.user_share(), + syncable::AUTOFILL, "collideme")); { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode bookmarknode(&trans); EXPECT_TRUE(bookmarknode.InitByClientTagLookup(syncable::BOOKMARKS, @@ -272,13 +275,13 @@ TEST_F(SyncApiTest, ModelTypesSiloed) { TEST_F(SyncApiTest, ReadMissingTagsFails) { { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode node(&trans); EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS, "testtag")); } { - WriteTransaction trans(&share_); + WriteTransaction trans(test_user_share_.user_share()); WriteNode node(&trans); EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS, "testtag")); @@ -293,7 +296,7 @@ TEST_F(SyncApiTest, TestDeleteBehavior) { std::wstring test_title(L"test1"); { - WriteTransaction trans(&share_); + WriteTransaction trans(test_user_share_.user_share()); ReadNode root_node(&trans); root_node.InitByRootLookup(); @@ -314,7 +317,7 @@ TEST_F(SyncApiTest, TestDeleteBehavior) { // Ensure we can delete something with a tag. { - WriteTransaction trans(&share_); + WriteTransaction trans(test_user_share_.user_share()); WriteNode wnode(&trans); EXPECT_TRUE(wnode.InitByClientTagLookup(syncable::BOOKMARKS, "testtag")); @@ -327,7 +330,7 @@ TEST_F(SyncApiTest, TestDeleteBehavior) { // Lookup of a node which was deleted should return failure, // but have found some data about the node. { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode node(&trans); EXPECT_FALSE(node.InitByClientTagLookup(syncable::BOOKMARKS, "testtag")); @@ -337,7 +340,7 @@ TEST_F(SyncApiTest, TestDeleteBehavior) { } { - WriteTransaction trans(&share_); + WriteTransaction trans(test_user_share_.user_share()); ReadNode folder_node(&trans); EXPECT_TRUE(folder_node.InitByIdLookup(folder_id)); @@ -354,7 +357,7 @@ TEST_F(SyncApiTest, TestDeleteBehavior) { // Now look up should work. { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode node(&trans); EXPECT_TRUE(node.InitByClientTagLookup(syncable::BOOKMARKS, "testtag")); @@ -365,9 +368,12 @@ TEST_F(SyncApiTest, TestDeleteBehavior) { TEST_F(SyncApiTest, WriteAndReadPassword) { KeyParams params = {"localhost", "username", "passphrase"}; - share_.dir_manager->cryptographer()->AddKey(params); { - WriteTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); + trans.GetCryptographer()->AddKey(params); + } + { + WriteTransaction trans(test_user_share_.user_share()); ReadNode root_node(&trans); root_node.InitByRootLookup(); @@ -379,7 +385,7 @@ TEST_F(SyncApiTest, WriteAndReadPassword) { password_node.SetPasswordSpecifics(data); } { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode root_node(&trans); root_node.InitByRootLookup(); @@ -420,25 +426,23 @@ void CheckNodeValue(const BaseNode& node, const DictionaryValue& value) { ADD_FAILURE(); } } - { - scoped_ptr<DictionaryValue> expected_specifics( - browser_sync::EntitySpecificsToValue( - node.GetEntry()->Get(syncable::SPECIFICS))); - Value* specifics = NULL; - EXPECT_TRUE(value.Get("specifics", &specifics)); - EXPECT_TRUE(Value::Equals(specifics, expected_specifics.get())); - } ExpectInt64Value(node.GetExternalId(), value, "externalId"); ExpectInt64Value(node.GetPredecessorId(), value, "predecessorId"); ExpectInt64Value(node.GetSuccessorId(), value, "successorId"); ExpectInt64Value(node.GetFirstChildId(), value, "firstChildId"); + { + scoped_ptr<DictionaryValue> expected_entry(node.GetEntry()->ToValue()); + Value* entry = NULL; + EXPECT_TRUE(value.Get("entry", &entry)); + EXPECT_TRUE(Value::Equals(entry, expected_entry.get())); + } EXPECT_EQ(11u, value.size()); } } // namespace TEST_F(SyncApiTest, BaseNodeToValue) { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode node(&trans); node.InitByRootLookup(); scoped_ptr<DictionaryValue> value(node.ToValue()); @@ -518,10 +522,11 @@ class MockExtraChangeRecordData } // namespace TEST_F(SyncApiTest, ChangeRecordToValue) { - int64 child_id = MakeNode(&share_, syncable::BOOKMARKS, "testtag"); + int64 child_id = MakeNode(test_user_share_.user_share(), + syncable::BOOKMARKS, "testtag"); sync_pb::EntitySpecifics child_specifics; { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); ReadNode node(&trans); EXPECT_TRUE(node.InitByIdLookup(child_id)); child_specifics = node.GetEntry()->Get(syncable::SPECIFICS); @@ -529,7 +534,7 @@ TEST_F(SyncApiTest, ChangeRecordToValue) { // Add { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); SyncManager::ChangeRecord record; record.action = SyncManager::ChangeRecord::ACTION_ADD; record.id = 1; @@ -541,7 +546,7 @@ TEST_F(SyncApiTest, ChangeRecordToValue) { // Update { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); SyncManager::ChangeRecord record; record.action = SyncManager::ChangeRecord::ACTION_UPDATE; record.id = child_id; @@ -553,7 +558,7 @@ TEST_F(SyncApiTest, ChangeRecordToValue) { // Delete (no extra) { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); SyncManager::ChangeRecord record; record.action = SyncManager::ChangeRecord::ACTION_DELETE; record.id = child_id + 1; @@ -564,7 +569,7 @@ TEST_F(SyncApiTest, ChangeRecordToValue) { // Delete (with extra) { - ReadTransaction trans(&share_); + ReadTransaction trans(test_user_share_.user_share()); SyncManager::ChangeRecord record; record.action = SyncManager::ChangeRecord::ACTION_DELETE; record.id = child_id + 1; @@ -612,28 +617,67 @@ class SyncManagerObserverMock : public SyncManager::Observer { MOCK_METHOD1(OnPassphraseRequired, void(bool)); // NOLINT MOCK_METHOD0(OnPassphraseFailed, void()); // NOLINT MOCK_METHOD1(OnPassphraseAccepted, void(const std::string&)); // NOLINT - MOCK_METHOD0(OnPaused, void()); // NOLINT - MOCK_METHOD0(OnResumed, void()); // NOLINT MOCK_METHOD0(OnStopSyncingPermanently, void()); // NOLINT MOCK_METHOD1(OnUpdatedToken, void(const std::string&)); // NOLINT + MOCK_METHOD1(OnMigrationNeededForTypes, void(const ModelTypeSet&)); MOCK_METHOD0(OnClearServerDataFailed, void()); // NOLINT MOCK_METHOD0(OnClearServerDataSucceeded, void()); // NOLINT MOCK_METHOD1(OnEncryptionComplete, void(const ModelTypeSet&)); // NOLINT }; +class SyncNotifierMock : public sync_notifier::SyncNotifier { + public: + MOCK_METHOD1(AddObserver, void(sync_notifier::SyncNotifierObserver*)); + MOCK_METHOD1(RemoveObserver, void(sync_notifier::SyncNotifierObserver*)); + MOCK_METHOD1(SetState, void(const std::string&)); + MOCK_METHOD2(UpdateCredentials, + void(const std::string&, const std::string&)); + MOCK_METHOD1(UpdateEnabledTypes, + void(const syncable::ModelTypeSet&)); + MOCK_METHOD0(SendNotification, void()); +}; + class SyncManagerTest : public testing::Test, public ModelSafeWorkerRegistrar { protected: - SyncManagerTest() : ui_thread_(BrowserThread::UI, &ui_loop_) {} + SyncManagerTest() + : ui_thread_(BrowserThread::UI, &ui_loop_), + sync_notifier_observer_(NULL), + update_enabled_types_call_count_(0) {} // Test implementation. void SetUp() { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); + + SyncCredentials credentials; + credentials.email = "foo@bar.com"; + credentials.sync_token = "sometoken"; + + sync_notifier_mock_.reset(new StrictMock<SyncNotifierMock>()); + EXPECT_CALL(*sync_notifier_mock_, AddObserver(_)). + WillOnce(Invoke(this, &SyncManagerTest::SyncNotifierAddObserver)); + EXPECT_CALL(*sync_notifier_mock_, SetState("")); + EXPECT_CALL(*sync_notifier_mock_, + UpdateCredentials(credentials.email, credentials.sync_token)); + EXPECT_CALL(*sync_notifier_mock_, UpdateEnabledTypes(_)). + Times(AtLeast(1)). + WillRepeatedly( + Invoke(this, &SyncManagerTest::SyncNotifierUpdateEnabledTypes)); + EXPECT_CALL(*sync_notifier_mock_, RemoveObserver(_)). + WillOnce(Invoke(this, &SyncManagerTest::SyncNotifierRemoveObserver)); + + EXPECT_FALSE(sync_notifier_observer_); + sync_manager_.Init(temp_dir_.path(), "bogus", 0, false, new TestHttpPostProviderFactory(), this, "bogus", - SyncCredentials(), notifier::NotifierOptions(), - "", true /* setup_for_test_mode */); + credentials, sync_notifier_mock_.get(), "", + true /* setup_for_test_mode */); + + EXPECT_TRUE(sync_notifier_observer_); sync_manager_.AddObserver(&observer_); + + EXPECT_EQ(1, update_enabled_types_call_count_); + ModelSafeRoutingInfo routes; GetModelSafeRoutingInfo(&routes); for (ModelSafeRoutingInfo::iterator i = routes.begin(); i != routes.end(); @@ -650,6 +694,7 @@ class SyncManagerTest : public testing::Test, void TearDown() { sync_manager_.RemoveObserver(&observer_); sync_manager_.Shutdown(); + EXPECT_FALSE(sync_notifier_observer_); } // ModelSafeWorkerRegistrar implementation. @@ -674,14 +719,14 @@ class SyncManagerTest : public testing::Test, return false; // Set the nigori cryptographer information. - Cryptographer* cryptographer = share->dir_manager->cryptographer(); + WriteTransaction trans(share); + Cryptographer* cryptographer = trans.GetCryptographer(); if (!cryptographer) return false; KeyParams params = {"localhost", "dummy", "foobar"}; cryptographer->AddKey(params); sync_pb::NigoriSpecifics nigori; cryptographer->GetKeys(nigori.mutable_encrypted()); - WriteTransaction trans(share); WriteNode node(&trans); node.InitByIdLookup(nigori_id); node.SetNigoriSpecifics(nigori); @@ -694,6 +739,31 @@ class SyncManagerTest : public testing::Test, return type_roots_[type]; } + void SyncNotifierAddObserver( + sync_notifier::SyncNotifierObserver* sync_notifier_observer) { + EXPECT_EQ(NULL, sync_notifier_observer_); + sync_notifier_observer_ = sync_notifier_observer; + } + + void SyncNotifierRemoveObserver( + sync_notifier::SyncNotifierObserver* sync_notifier_observer) { + EXPECT_EQ(sync_notifier_observer_, sync_notifier_observer); + sync_notifier_observer_ = NULL; + } + + void SyncNotifierUpdateEnabledTypes( + const syncable::ModelTypeSet& types) { + ModelSafeRoutingInfo routes; + GetModelSafeRoutingInfo(&routes); + syncable::ModelTypeSet expected_types; + for (ModelSafeRoutingInfo::const_iterator it = routes.begin(); + it != routes.end(); ++it) { + expected_types.insert(it->first); + } + EXPECT_EQ(expected_types, types); + ++update_enabled_types_call_count_; + } + private: // Needed by |ui_thread_|. MessageLoopForUI ui_loop_; @@ -703,12 +773,22 @@ class SyncManagerTest : public testing::Test, ScopedTempDir temp_dir_; // Sync Id's for the roots of the enabled datatypes. std::map<ModelType, int64> type_roots_; + scoped_ptr<StrictMock<SyncNotifierMock> > sync_notifier_mock_; protected: SyncManager sync_manager_; StrictMock<SyncManagerObserverMock> observer_; + sync_notifier::SyncNotifierObserver* sync_notifier_observer_; + int update_enabled_types_call_count_; }; +TEST_F(SyncManagerTest, UpdateEnabledTypes) { + EXPECT_EQ(1, update_enabled_types_call_count_); + // Triggers SyncNotifierUpdateEnabledTypes. + sync_manager_.UpdateEnabledTypes(); + EXPECT_EQ(2, update_enabled_types_call_count_); +} + TEST_F(SyncManagerTest, ParentJsEventRouter) { StrictMock<MockJsEventRouter> event_router; browser_sync::JsBackend* js_backend = sync_manager_.GetJsBackend(); diff --git a/chrome/browser/sync/engine/syncer.cc b/chrome/browser/sync/engine/syncer.cc index ac20600..2f24ee1 100644 --- a/chrome/browser/sync/engine/syncer.cc +++ b/chrome/browser/sync/engine/syncer.cc @@ -271,9 +271,6 @@ void Syncer::SyncShare(sessions::SyncSession* session, break; } case SYNCER_END: { - VLOG(1) << "Syncer End"; - SyncerEndCommand syncer_end_command; - syncer_end_command.Execute(session); break; } default: @@ -284,11 +281,9 @@ void Syncer::SyncShare(sessions::SyncSession* session, current_step = next_step; } - // Always send out a cycle ended notification, regardless of end-state. - SyncEngineEvent event(SyncEngineEvent::SYNC_CYCLE_ENDED); - sessions::SyncSessionSnapshot snapshot(session->TakeSnapshot()); - event.snapshot = &snapshot; - session->context()->NotifyListeners(event); + VLOG(1) << "Syncer End"; + SyncerEndCommand syncer_end_command; + syncer_end_command.Execute(session); return; } diff --git a/chrome/browser/sync/engine/syncer.h b/chrome/browser/sync/engine/syncer.h index 3178f69..4a86005 100644 --- a/chrome/browser/sync/engine/syncer.h +++ b/chrome/browser/sync/engine/syncer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -12,7 +12,7 @@ #include "base/basictypes.h" #include "base/callback.h" #include "base/gtest_prod_util.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/synchronization/lock.h" #include "chrome/browser/sync/engine/conflict_resolver.h" #include "chrome/browser/sync/engine/syncer_types.h" diff --git a/chrome/browser/sync/engine/syncer_end_command.cc b/chrome/browser/sync/engine/syncer_end_command.cc index dcdfcc1..516bbd0 100644 --- a/chrome/browser/sync/engine/syncer_end_command.cc +++ b/chrome/browser/sync/engine/syncer_end_command.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -15,11 +15,13 @@ SyncerEndCommand::SyncerEndCommand() {} SyncerEndCommand::~SyncerEndCommand() {} void SyncerEndCommand::ExecuteImpl(sessions::SyncSession* session) { - sessions::StatusController* status(session->status_controller()); - status->set_syncing(false); - session->context()->set_previous_session_routing_info( - session->routing_info()); - session->context()->set_last_snapshot(session->TakeSnapshot()); + // Always send out a cycle ended notification, regardless of end-state. + session->status_controller()->set_syncing(false); + SyncEngineEvent event(SyncEngineEvent::SYNC_CYCLE_ENDED); + sessions::SyncSessionSnapshot snapshot(session->TakeSnapshot()); + event.snapshot = &snapshot; + session->context()->NotifyListeners(event); + VLOG(1) << this << " sent sync end snapshot"; } } // namespace browser_sync diff --git a/chrome/browser/sync/engine/syncer_proto_util.cc b/chrome/browser/sync/engine/syncer_proto_util.cc index 5dbb202..a3a6177 100644 --- a/chrome/browser/sync/engine/syncer_proto_util.cc +++ b/chrome/browser/sync/engine/syncer_proto_util.cc @@ -13,6 +13,7 @@ #include "chrome/browser/sync/protocol/service_constants.h" #include "chrome/browser/sync/sessions/sync_session.h" #include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/syncable/syncable-inl.h" #include "chrome/browser/sync/syncable/syncable.h" @@ -78,6 +79,19 @@ void LogResponseProfilingData(const ClientToServerResponse& response) { } // namespace +// static +void SyncerProtoUtil::HandleMigrationDoneResponse( + const sync_pb::ClientToServerResponse* response, + sessions::SyncSession* session) { + LOG_IF(ERROR, 0 >= response->migrated_data_type_id_size()) + << "MIGRATION_DONE but no types specified."; + syncable::ModelTypeSet to_migrate; + for (int i = 0; i < response->migrated_data_type_id_size(); i++) { + to_migrate.insert(syncable::GetModelTypeFromExtensionFieldNumber( + response->migrated_data_type_id(i))); + } + session->status_controller()->set_types_needing_local_migration(to_migrate); +} // static bool SyncerProtoUtil::VerifyResponseBirthday(syncable::Directory* dir, @@ -225,6 +239,9 @@ bool SyncerProtoUtil::PostClientToServerMessage( return false; case ClientToServerResponse::TRANSIENT_ERROR: return false; + case ClientToServerResponse::MIGRATION_DONE: + HandleMigrationDoneResponse(response, session); + return false; case ClientToServerResponse::USER_NOT_ACTIVATED: case ClientToServerResponse::AUTH_INVALID: case ClientToServerResponse::ACCESS_DENIED: diff --git a/chrome/browser/sync/engine/syncer_proto_util.h b/chrome/browser/sync/engine/syncer_proto_util.h index ca68ec3..d55e277 100644 --- a/chrome/browser/sync/engine/syncer_proto_util.h +++ b/chrome/browser/sync/engine/syncer_proto_util.h @@ -102,6 +102,12 @@ class SyncerProtoUtil { static bool VerifyResponseBirthday(syncable::Directory* dir, const sync_pb::ClientToServerResponse* response); + // Builds and sends a SyncEngineEvent to begin migration for types (specified + // in notification). + static void HandleMigrationDoneResponse( + const sync_pb::ClientToServerResponse* response, + sessions::SyncSession* session); + // Post the message using the scm, and do some processing on the returned // headers. Decode the server response. static bool PostAndProcessHeaders(browser_sync::ServerConnectionManager* scm, diff --git a/chrome/browser/sync/engine/syncer_thread.cc b/chrome/browser/sync/engine/syncer_thread.cc index d9240ed..8660373 100644 --- a/chrome/browser/sync/engine/syncer_thread.cc +++ b/chrome/browser/sync/engine/syncer_thread.cc @@ -1,32 +1,14 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/engine/syncer_thread.h" #include <algorithm> -#include <queue> -#include <string> -#include <vector> #include "base/rand_util.h" -#include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "build/build_config.h" -#include "chrome/browser/sync/engine/model_safe_worker.h" -#include "chrome/browser/sync/engine/net/server_connection_manager.h" #include "chrome/browser/sync/engine/syncer.h" -#include "chrome/browser/sync/sessions/sync_session.h" -#include "jingle/notifier/listener/notification_constants.h" - -#if defined(OS_MACOSX) -#include <CoreFoundation/CFNumber.h> -#include <IOKit/IOTypes.h> -#include <IOKit/IOKitLib.h> -#endif - -using std::priority_queue; -using std::min; -using base::Time; + using base::TimeDelta; using base::TimeTicks; @@ -35,862 +17,861 @@ namespace browser_sync { using sessions::SyncSession; using sessions::SyncSessionSnapshot; using sessions::SyncSourceInfo; -using sessions::TypePayloadMap; - -// We use high values here to ensure that failure to receive poll updates from -// the server doesn't result in rapid-fire polling from the client due to low -// local limits. -const int SyncerThread::kDefaultShortPollIntervalSeconds = 3600 * 8; -const int SyncerThread::kDefaultLongPollIntervalSeconds = 3600 * 12; - -// TODO(tim): This is used to regulate the short poll (when notifications are -// disabled) based on user idle time. If it is set to a smaller value than -// the short poll interval, it basically does nothing; for now, this is what -// we want and allows stronger control over the poll rate from the server. We -// should probably re-visit this code later and figure out if user idle time -// is really something we want and make sure it works, if it is. -const int SyncerThread::kDefaultMaxPollIntervalMs = 30 * 60 * 1000; - -// Backoff interval randomization factor. -static const int kBackoffRandomizationFactor = 2; - -const int SyncerThread::kMaxBackoffSeconds = 60 * 60 * 4; // 4 hours. - -SyncerThread::ProtectedFields::ProtectedFields() - : stop_syncer_thread_(false), - pause_requested_(false), - paused_(false), - syncer_(NULL), - connected_(false), - pending_nudge_source_(kUnknown) {} - -SyncerThread::ProtectedFields::~ProtectedFields() {} - -void SyncerThread::NudgeSyncerWithPayloads( - int milliseconds_from_now, - NudgeSource source, - const TypePayloadMap& model_types_with_payloads) { - base::AutoLock lock(lock_); - if (vault_.syncer_ == NULL) { - return; - } +using syncable::ModelTypePayloadMap; +using syncable::ModelTypeBitSet; +using sync_pb::GetUpdatesCallerInfo; - NudgeSyncImpl(milliseconds_from_now, source, model_types_with_payloads); -} +SyncerThread::DelayProvider::DelayProvider() {} +SyncerThread::DelayProvider::~DelayProvider() {} -void SyncerThread::NudgeSyncerWithDataTypes( - int milliseconds_from_now, - NudgeSource source, - const syncable::ModelTypeBitSet& model_types) { - base::AutoLock lock(lock_); - if (vault_.syncer_ == NULL) { - return; - } +SyncerThread::WaitInterval::WaitInterval() {} +SyncerThread::WaitInterval::~WaitInterval() {} - TypePayloadMap model_types_with_payloads = - sessions::MakeTypePayloadMapFromBitSet(model_types, std::string()); - NudgeSyncImpl(milliseconds_from_now, source, model_types_with_payloads); +SyncerThread::SyncSessionJob::SyncSessionJob() {} +SyncerThread::SyncSessionJob::~SyncSessionJob() {} + +SyncerThread::SyncSessionJob::SyncSessionJob(SyncSessionJobPurpose purpose, + base::TimeTicks start, + linked_ptr<sessions::SyncSession> session, bool is_canary_job, + const tracked_objects::Location& nudge_location) : purpose(purpose), + scheduled_start(start), + session(session), + is_canary_job(is_canary_job), + nudge_location(nudge_location) { +} + +TimeDelta SyncerThread::DelayProvider::GetDelay( + const base::TimeDelta& last_delay) { + return SyncerThread::GetRecommendedDelay(last_delay); } -void SyncerThread::NudgeSyncer( - int milliseconds_from_now, +GetUpdatesCallerInfo::GetUpdatesSource GetUpdatesFromNudgeSource( NudgeSource source) { - base::AutoLock lock(lock_); - if (vault_.syncer_ == NULL) { - return; + switch (source) { + case NUDGE_SOURCE_NOTIFICATION: + return GetUpdatesCallerInfo::NOTIFICATION; + case NUDGE_SOURCE_LOCAL: + return GetUpdatesCallerInfo::LOCAL; + case NUDGE_SOURCE_CONTINUATION: + return GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION; + case NUDGE_SOURCE_UNKNOWN: + return GetUpdatesCallerInfo::UNKNOWN; + default: + NOTREACHED(); + return GetUpdatesCallerInfo::UNKNOWN; } - - // Set all enabled datatypes. - ModelSafeRoutingInfo routes; - session_context_->registrar()->GetModelSafeRoutingInfo(&routes); - TypePayloadMap model_types_with_payloads = - sessions::MakeTypePayloadMapFromRoutingInfo(routes, std::string()); - NudgeSyncImpl(milliseconds_from_now, source, model_types_with_payloads); } -SyncerThread::SyncerThread(sessions::SyncSessionContext* context) - : thread_main_started_(false, false), - thread_("SyncEngine_SyncerThread"), - vault_field_changed_(&lock_), - conn_mgr_hookup_(NULL), - syncer_short_poll_interval_seconds_(kDefaultShortPollIntervalSeconds), - syncer_long_poll_interval_seconds_(kDefaultLongPollIntervalSeconds), - syncer_polling_interval_(kDefaultShortPollIntervalSeconds), - syncer_max_interval_(kDefaultMaxPollIntervalMs), - session_context_(context), - disable_idle_detection_(false) { - DCHECK(context); +SyncerThread::WaitInterval::WaitInterval(Mode mode, TimeDelta length) + : mode(mode), had_nudge(false), length(length) { } - if (context->connection_manager()) - WatchConnectionManager(context->connection_manager()); +SyncerThread::SyncerThread(sessions::SyncSessionContext* context, + Syncer* syncer) + : thread_("SyncEngine_SyncerThread"), + syncer_short_poll_interval_seconds_( + TimeDelta::FromSeconds(kDefaultShortPollIntervalSeconds)), + syncer_long_poll_interval_seconds_( + TimeDelta::FromSeconds(kDefaultLongPollIntervalSeconds)), + mode_(NORMAL_MODE), + server_connection_ok_(false), + delay_provider_(new DelayProvider()), + syncer_(syncer), + session_context_(context) { } SyncerThread::~SyncerThread() { - conn_mgr_hookup_.reset(); - delete vault_.syncer_; - CHECK(!thread_.IsRunning()); -} - -// Creates and starts a syncer thread. -// Returns true if it creates a thread or if there's currently a thread running -// and false otherwise. -bool SyncerThread::Start() { - { - base::AutoLock lock(lock_); - if (thread_.IsRunning()) { - return true; - } + DCHECK(!thread_.IsRunning()); +} +void SyncerThread::CheckServerConnectionManagerStatus( + HttpResponse::ServerConnectionCode code) { + + VLOG(1) << "SyncerThread(" << this << ")" << " Server connection changed." + << "Old mode: " << server_connection_ok_ << " Code: " << code; + // Note, be careful when adding cases here because if the SyncerThread + // thinks there is no valid connection as determined by this method, it + // will drop out of *all* forward progress sync loops (it won't poll and it + // will queue up Talk notifications but not actually call SyncShare) until + // some external action causes a ServerConnectionManager to broadcast that + // a valid connection has been re-established. + if (HttpResponse::CONNECTION_UNAVAILABLE == code || + HttpResponse::SYNC_AUTH_ERROR == code) { + server_connection_ok_ = false; + VLOG(1) << "SyncerThread(" << this << ")" << " Server connection changed." + << " new mode:" << server_connection_ok_; + } else if (HttpResponse::SERVER_CONNECTION_OK == code) { + server_connection_ok_ = true; + VLOG(1) << "SyncerThread(" << this << ")" << " Server connection changed." + << " new mode:" << server_connection_ok_; + DoCanaryJob(); + } +} + +void SyncerThread::Start(Mode mode, ModeChangeCallback* callback) { + VLOG(1) << "SyncerThread(" << this << ")" << " Start called from thread " + << MessageLoop::current()->thread_name(); + if (!thread_.IsRunning()) { + VLOG(1) << "SyncerThread(" << this << ")" << " Starting thread with mode " + << mode; if (!thread_.Start()) { - return false; + NOTREACHED() << "Unable to start SyncerThread."; + return; } + WatchConnectionManager(); + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &SyncerThread::SendInitialSnapshot)); } - thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, - &SyncerThread::ThreadMain)); + VLOG(1) << "SyncerThread(" << this << ")" << " Entering start with mode = " + << mode; - // Wait for notification that our task makes it safely onto the message - // loop before returning, so the caller can't call Stop before we're - // actually up and running. This is for consistency with the old pthread - // impl because pthread_create would do this in one step. - thread_main_started_.Wait(); - VLOG(1) << "SyncerThread started."; - return true; + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &SyncerThread::StartImpl, mode, callback)); } -// Stop processing. A max wait of at least 2*server RTT time is recommended. -// Returns true if we stopped, false otherwise. -bool SyncerThread::Stop(int max_wait) { - RequestSyncerExitAndSetThreadStopConditions(); +void SyncerThread::SendInitialSnapshot() { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + scoped_ptr<SyncSession> dummy(new SyncSession(session_context_.get(), this, + SyncSourceInfo(), ModelSafeRoutingInfo(), + std::vector<ModelSafeWorker*>())); + SyncEngineEvent event(SyncEngineEvent::STATUS_CHANGED); + sessions::SyncSessionSnapshot snapshot(dummy->TakeSnapshot()); + event.snapshot = &snapshot; + session_context_->NotifyListeners(event); +} - // This will join, and finish when ThreadMain terminates. - thread_.Stop(); - return true; +void SyncerThread::WatchConnectionManager() { + ServerConnectionManager* scm = session_context_->connection_manager(); + CheckServerConnectionManagerStatus(scm->server_status()); + scm->AddListener(this); } -void SyncerThread::RequestSyncerExitAndSetThreadStopConditions() { - { - base::AutoLock lock(lock_); - // If the thread has been started, then we either already have or are about - // to enter ThreadMainLoop so we have to proceed with shutdown and wait for - // it to finish. If the thread has not been started --and we now own the - // lock-- then we can early out because the caller has not called Start(). - if (!thread_.IsRunning()) - return; +void SyncerThread::StartImpl(Mode mode, ModeChangeCallback* callback) { + VLOG(1) << "SyncerThread(" << this << ")" << " Doing StartImpl with mode " + << mode; - VLOG(1) << "SyncerThread::Stop - setting ThreadMain exit condition to true " - "(vault_.stop_syncer_thread_)"; - // Exit the ThreadMainLoop once the syncer finishes (we tell it to exit - // below). - vault_.stop_syncer_thread_ = true; - if (NULL != vault_.syncer_) { - // Try to early exit the syncer itself, which could be looping inside - // SyncShare. - vault_.syncer_->RequestEarlyExit(); - } + // TODO(lipalani): This will leak if startimpl is never run. Fix it using a + // ThreadSafeRefcounted object. + scoped_ptr<ModeChangeCallback> scoped_callback(callback); + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + DCHECK(!session_context_->account_name().empty()); + DCHECK(syncer_.get()); + mode_ = mode; + AdjustPolling(NULL); // Will kick start poll timer if needed. + if (scoped_callback.get()) + scoped_callback->Run(); - // stop_syncer_thread_ is now true and the Syncer has been told to exit. - // We want to wake up all waiters so they can re-examine state. We signal, - // causing all waiters to try to re-acquire the lock, and then we release - // the lock, and join on our internal thread which should soon run off the - // end of ThreadMain. - vault_field_changed_.Broadcast(); - } + // We just changed our mode. See if there are any pending jobs that we could + // execute in the new mode. + DoPendingJobIfPossible(false); } -bool SyncerThread::RequestPause() { - base::AutoLock lock(lock_); - if (vault_.pause_requested_ || vault_.paused_) - return false; +SyncerThread::JobProcessDecision SyncerThread::DecideWhileInWaitInterval( + const SyncSessionJob& job) { - if (thread_.IsRunning()) { - // Set the pause request. The syncer thread will read this - // request, enter the paused state, and send the PAUSED - // notification. - vault_.pause_requested_ = true; - vault_field_changed_.Broadcast(); - VLOG(1) << "Pause requested."; - } else { - // If the thread is not running, go directly into the paused state - // and notify. - EnterPausedState(); - VLOG(1) << "Paused while not running."; - } - return true; -} + DCHECK(wait_interval_.get()); + DCHECK_NE(job.purpose, SyncSessionJob::CLEAR_USER_DATA); -void SyncerThread::Notify(SyncEngineEvent::EventCause cause) { - session_context_->NotifyListeners(SyncEngineEvent(cause)); -} + VLOG(1) << "SyncerThread(" << this << ")" << " Wait interval mode : " + << wait_interval_->mode << "Wait interval had nudge : " + << wait_interval_->had_nudge << "is canary job : " + << job.is_canary_job; -bool SyncerThread::RequestResume() { - base::AutoLock lock(lock_); - // Only valid to request a resume when we are already paused or we - // have a pause pending. - if (!(vault_.paused_ || vault_.pause_requested_)) - return false; - - if (thread_.IsRunning()) { - if (vault_.pause_requested_) { - // If pause was requested we have not yet paused. In this case, - // the resume cancels the pause request. - vault_.pause_requested_ = false; - vault_field_changed_.Broadcast(); - Notify(SyncEngineEvent::SYNCER_THREAD_RESUMED); - VLOG(1) << "Pending pause canceled by resume."; - } else { - // Unpause and notify. - vault_.paused_ = false; - vault_field_changed_.Broadcast(); - } - } else { - ExitPausedState(); - VLOG(1) << "Resumed while not running."; - } - return true; -} + if (job.purpose == SyncSessionJob::POLL) + return DROP; -void SyncerThread::OnReceivedLongPollIntervalUpdate( - const base::TimeDelta& new_interval) { - syncer_long_poll_interval_seconds_ = static_cast<int>( - new_interval.InSeconds()); -} + DCHECK(job.purpose == SyncSessionJob::NUDGE || + job.purpose == SyncSessionJob::CONFIGURATION); + if (wait_interval_->mode == WaitInterval::THROTTLED) + return SAVE; -void SyncerThread::OnReceivedShortPollIntervalUpdate( - const base::TimeDelta& new_interval) { - syncer_short_poll_interval_seconds_ = static_cast<int>( - new_interval.InSeconds()); -} + DCHECK_EQ(wait_interval_->mode, WaitInterval::EXPONENTIAL_BACKOFF); + if (job.purpose == SyncSessionJob::NUDGE) { + if (mode_ == CONFIGURATION_MODE) + return SAVE; -void SyncerThread::OnSilencedUntil(const base::TimeTicks& silenced_until) { - silenced_until_ = silenced_until; + // If we already had one nudge then just drop this nudge. We will retry + // later when the timer runs out. + return wait_interval_->had_nudge ? DROP : CONTINUE; + } + // This is a config job. + return job.is_canary_job ? CONTINUE : SAVE; } -bool SyncerThread::IsSyncingCurrentlySilenced() { - // We should ignore reads from silenced_until_ under ThreadSanitizer - // since this is a benign race. - ANNOTATE_IGNORE_READS_BEGIN(); - bool ret = (silenced_until_ - TimeTicks::Now()) >= TimeDelta::FromSeconds(0); - ANNOTATE_IGNORE_READS_END(); - return ret; -} +SyncerThread::JobProcessDecision SyncerThread::DecideOnJob( + const SyncSessionJob& job) { + if (job.purpose == SyncSessionJob::CLEAR_USER_DATA) + return CONTINUE; -void SyncerThread::OnShouldStopSyncingPermanently() { - RequestSyncerExitAndSetThreadStopConditions(); - Notify(SyncEngineEvent::STOP_SYNCING_PERMANENTLY); -} + if (wait_interval_.get()) + return DecideWhileInWaitInterval(job); -void SyncerThread::ThreadMainLoop() { - // This is called with lock_ acquired. - lock_.AssertAcquired(); - VLOG(1) << "In thread main loop."; - - // Use the short poll value by default. - vault_.current_wait_interval_.poll_delta = - TimeDelta::FromSeconds(syncer_short_poll_interval_seconds_); - int user_idle_milliseconds = 0; - TimeTicks last_sync_time; - bool initial_sync_for_thread = true; - bool continue_sync_cycle = false; - -#if defined(OS_LINUX) - idle_query_.reset(new IdleQueryLinux()); -#endif - - if (vault_.syncer_ == NULL) { - VLOG(1) << "Syncer thread waiting for database initialization."; - while (vault_.syncer_ == NULL && !vault_.stop_syncer_thread_) - vault_field_changed_.Wait(); - VLOG_IF(1, !(vault_.syncer_ == NULL)) << "Syncer was found after DB " - "started."; + if (mode_ == CONFIGURATION_MODE) { + if (job.purpose == SyncSessionJob::NUDGE) + return SAVE; + else if (job.purpose == SyncSessionJob::CONFIGURATION) + return CONTINUE; + else + return DROP; } - while (!vault_.stop_syncer_thread_) { - // The Wait()s in these conditionals using |vault_| are not TimedWait()s (as - // below) because we cannot poll until these conditions are met, so we wait - // indefinitely. - - // If we are not connected, enter WaitUntilConnectedOrQuit() which - // will return only when the network is connected or a quit is - // requested. Note that it is possible to exit - // WaitUntilConnectedOrQuit() in the paused state which will be - // handled by the next statement. - if (!vault_.connected_ && !initial_sync_for_thread) { - WaitUntilConnectedOrQuit(); - continue; - } + // We are in normal mode. + DCHECK_EQ(mode_, NORMAL_MODE); + DCHECK_NE(job.purpose, SyncSessionJob::CONFIGURATION); - // Check if we should be paused or if a pause was requested. Note - // that we don't check initial_sync_for_thread here since we want - // the pause to happen regardless if it is the initial sync or not. - if (vault_.pause_requested_ || vault_.paused_) { - PauseUntilResumedOrQuit(); - continue; - } + // Freshness condition + if (job.scheduled_start < last_sync_session_end_time_) { + VLOG(1) << "SyncerThread(" << this << ")" + << " Dropping job because of freshness"; + return DROP; + } - const TimeTicks next_poll = last_sync_time + - vault_.current_wait_interval_.poll_delta; - bool throttled = vault_.current_wait_interval_.mode == - WaitInterval::THROTTLED; - // If we are throttled, we must wait. Otherwise, wait until either the next - // nudge (if one exists) or the poll interval. - TimeTicks end_wait = next_poll; - if (!throttled && !vault_.pending_nudge_time_.is_null()) { - end_wait = std::min(end_wait, vault_.pending_nudge_time_); - } - VLOG(1) << "end_wait is " << end_wait.ToInternalValue() - << "\nnext_poll is " << next_poll.ToInternalValue(); - - // We block until the CV is signaled (e.g a control field changed, loss of - // network connection, nudge, spurious, etc), or the poll interval elapses. - TimeDelta sleep_time = end_wait - TimeTicks::Now(); - if (!initial_sync_for_thread && sleep_time > TimeDelta::FromSeconds(0)) { - vault_field_changed_.TimedWait(sleep_time); - - if (TimeTicks::Now() < end_wait) { - // Didn't timeout. Could be a spurious signal, or a signal corresponding - // to an actual change in one of our control fields. By continuing here - // we perform the typical "always recheck conditions when signaled", - // (typically handled by a while(condition_not_met) cv.wait() construct) - // because we jump to the top of the loop. The main difference is we - // recalculate the wait interval, but last_sync_time won't have changed. - // So if we were signaled by a nudge (for ex.) we'll grab the new nudge - // off the queue and wait for that delta. If it was a spurious signal, - // we'll keep waiting for the same moment in time as we just were. - continue; - } - } + if (server_connection_ok_) + return CONTINUE; - // Handle a nudge, caused by either a notification or a local bookmark - // event. This will also update the source of the following SyncMain call. - VLOG(1) << "Calling Sync Main at time " << Time::Now().ToInternalValue(); - bool nudged = false; - scoped_ptr<SyncSession> session; - session.reset(SyncMain(vault_.syncer_, - throttled, continue_sync_cycle, &initial_sync_for_thread, &nudged)); - - // Update timing information for how often these datatypes are triggering - // nudges. - base::TimeTicks now = TimeTicks::Now(); - if (!last_sync_time.is_null()) { - TypePayloadMap::const_iterator iter; - for (iter = session->source().types.begin(); - iter != session->source().types.end(); - ++iter) { - syncable::PostTimeToTypeHistogram(iter->first, - now - last_sync_time); - } - } + VLOG(1) << "SyncerThread(" << this << ")" + << " Bad server connection. Using that to decide on job."; + return job.purpose == SyncSessionJob::NUDGE ? SAVE : DROP; +} + +void SyncerThread::InitOrCoalescePendingJob(const SyncSessionJob& job) { + DCHECK(job.purpose != SyncSessionJob::CONFIGURATION); + if (pending_nudge_.get() == NULL) { + VLOG(1) << "SyncerThread(" << this << ")" + << " Creating a pending nudge job"; + SyncSession* s = job.session.get(); + scoped_ptr<SyncSession> session(new SyncSession(s->context(), + s->delegate(), s->source(), s->routing_info(), s->workers())); - last_sync_time = now; + SyncSessionJob new_job(SyncSessionJob::NUDGE, job.scheduled_start, + make_linked_ptr(session.release()), false, job.nudge_location); + pending_nudge_.reset(new SyncSessionJob(new_job)); - VLOG(1) << "Updating the next polling time after SyncMain"; - vault_.current_wait_interval_ = CalculatePollingWaitTime( - static_cast<int>(vault_.current_wait_interval_.poll_delta.InSeconds()), - &user_idle_milliseconds, &continue_sync_cycle, nudged); + return; } -#if defined(OS_LINUX) - idle_query_.reset(); -#endif -} -void SyncerThread::SetConnected(bool connected) { - DCHECK(!thread_.IsRunning()); - vault_.connected_ = connected; -} + VLOG(1) << "SyncerThread(" << this << ")" << " Coalescing a pending nudge"; + pending_nudge_->session->Coalesce(*(job.session.get())); + pending_nudge_->scheduled_start = job.scheduled_start; -void SyncerThread::SetSyncerPollingInterval(base::TimeDelta interval) { - // TODO(timsteele): Use TimeDelta internally. - syncer_polling_interval_ = static_cast<int>(interval.InSeconds()); + // Unfortunately the nudge location cannot be modified. So it stores the + // location of the first caller. } -void SyncerThread::SetSyncerShortPollInterval(base::TimeDelta interval) { - // TODO(timsteele): Use TimeDelta internally. - syncer_short_poll_interval_seconds_ = - static_cast<int>(interval.InSeconds()); -} +bool SyncerThread::ShouldRunJob(const SyncSessionJob& job) { + JobProcessDecision decision = DecideOnJob(job); + VLOG(1) << "SyncerThread(" << this << ")" << " Should run job, decision: " + << decision << " Job purpose " << job.purpose << "mode " << mode_; + if (decision != SAVE) + return decision == CONTINUE; -void SyncerThread::WaitUntilConnectedOrQuit() { - VLOG(1) << "Syncer thread waiting for connection."; - Notify(SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION); + DCHECK(job.purpose == SyncSessionJob::NUDGE || job.purpose == + SyncSessionJob::CONFIGURATION); - bool is_paused = vault_.paused_; + SaveJob(job); + return false; +} - while (!vault_.connected_ && !vault_.stop_syncer_thread_) { - if (!is_paused && vault_.pause_requested_) { - // If we get a pause request while waiting for a connection, - // enter the paused state. - EnterPausedState(); - is_paused = true; - VLOG(1) << "Syncer thread entering disconnected pause."; - } +void SyncerThread::SaveJob(const SyncSessionJob& job) { + DCHECK(job.purpose != SyncSessionJob::CLEAR_USER_DATA); + if (job.purpose == SyncSessionJob::NUDGE) { + VLOG(1) << "SyncerThread(" << this << ")" << " Saving a nudge job"; + InitOrCoalescePendingJob(job); + } else if (job.purpose == SyncSessionJob::CONFIGURATION){ + VLOG(1) << "SyncerThread(" << this << ")" << " Saving a configuration job"; + DCHECK(wait_interval_.get()); + DCHECK(mode_ == CONFIGURATION_MODE); - if (is_paused && !vault_.paused_) { - ExitPausedState(); - is_paused = false; - VLOG(1) << "Syncer thread exiting disconnected pause."; - } + SyncSession* old = job.session.get(); + SyncSession* s(new SyncSession(session_context_.get(), this, + old->source(), old->routing_info(), old->workers())); + SyncSessionJob new_job(job.purpose, TimeTicks::Now(), + make_linked_ptr(s), false, job.nudge_location); + wait_interval_->pending_configure_job.reset(new SyncSessionJob(new_job)); + } // drop the rest. +} - vault_field_changed_.Wait(); +// Functor for std::find_if to search by ModelSafeGroup. +struct ModelSafeWorkerGroupIs { + explicit ModelSafeWorkerGroupIs(ModelSafeGroup group) : group(group) {} + bool operator()(ModelSafeWorker* w) { + return group == w->GetModelSafeGroup(); } + ModelSafeGroup group; +}; - if (!vault_.stop_syncer_thread_) { - Notify(SyncEngineEvent::SYNCER_THREAD_CONNECTED); - VLOG(1) << "Syncer thread found connection."; +void SyncerThread::ScheduleClearUserData() { + if (!thread_.IsRunning()) { + NOTREACHED(); + return; } + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &SyncerThread::ScheduleClearUserDataImpl)); } -void SyncerThread::PauseUntilResumedOrQuit() { - VLOG(1) << "Syncer thread entering pause."; - // If pause was requested (rather than already being paused), send - // the PAUSED notification. - if (vault_.pause_requested_) - EnterPausedState(); - - // Thread will get stuck here until either a resume is requested - // or shutdown is started. - while (vault_.paused_ && !vault_.stop_syncer_thread_) - vault_field_changed_.Wait(); +void SyncerThread::ScheduleNudge(const TimeDelta& delay, + NudgeSource source, const ModelTypeBitSet& types, + const tracked_objects::Location& nudge_location) { + if (!thread_.IsRunning()) { + NOTREACHED(); + return; + } - // Notify that we have resumed if we are not shutting down. - if (!vault_.stop_syncer_thread_) - ExitPausedState(); + VLOG(1) << "SyncerThread(" << this << ")" << " Nudge scheduled"; - VLOG(1) << "Syncer thread exiting pause."; + ModelTypePayloadMap types_with_payloads = + syncable::ModelTypePayloadMapFromBitSet(types, std::string()); + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &SyncerThread::ScheduleNudgeImpl, delay, + GetUpdatesFromNudgeSource(source), types_with_payloads, false, + nudge_location)); } -void SyncerThread::EnterPausedState() { - lock_.AssertAcquired(); - vault_.pause_requested_ = false; - vault_.paused_ = true; - vault_field_changed_.Broadcast(); - Notify(SyncEngineEvent::SYNCER_THREAD_PAUSED); -} +void SyncerThread::ScheduleNudgeWithPayloads(const TimeDelta& delay, + NudgeSource source, const ModelTypePayloadMap& types_with_payloads, + const tracked_objects::Location& nudge_location) { + if (!thread_.IsRunning()) { + NOTREACHED(); + return; + } + + VLOG(1) << "SyncerThread(" << this << ")" << " Nudge scheduled with payloads"; -void SyncerThread::ExitPausedState() { - lock_.AssertAcquired(); - vault_.paused_ = false; - vault_field_changed_.Broadcast(); - Notify(SyncEngineEvent::SYNCER_THREAD_RESUMED); + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &SyncerThread::ScheduleNudgeImpl, delay, + GetUpdatesFromNudgeSource(source), types_with_payloads, false, + nudge_location)); } -void SyncerThread::DisableIdleDetection() { - disable_idle_detection_ = true; +void SyncerThread::ScheduleClearUserDataImpl() { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + SyncSession* session = new SyncSession(session_context_.get(), this, + SyncSourceInfo(), ModelSafeRoutingInfo(), + std::vector<ModelSafeWorker*>()); + ScheduleSyncSessionJob(TimeDelta::FromSeconds(0), + SyncSessionJob::CLEAR_USER_DATA, session, FROM_HERE); } -// We check how long the user's been idle and sync less often if the machine is -// not in use. The aim is to reduce server load. -SyncerThread::WaitInterval SyncerThread::CalculatePollingWaitTime( - int last_poll_wait, // Time in seconds. - int* user_idle_milliseconds, - bool* continue_sync_cycle, - bool was_nudged) { - lock_.AssertAcquired(); // We access 'vault' in here, so we need the lock. - WaitInterval return_interval; +void SyncerThread::ScheduleNudgeImpl(const TimeDelta& delay, + GetUpdatesCallerInfo::GetUpdatesSource source, + const ModelTypePayloadMap& types_with_payloads, + bool is_canary_job, const tracked_objects::Location& nudge_location) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - // Server initiated throttling trumps everything. - if (!silenced_until_.is_null()) { - // We don't need to reset other state, it can continue where it left off. - return_interval.mode = WaitInterval::THROTTLED; - return_interval.poll_delta = silenced_until_ - TimeTicks::Now(); - return return_interval; - } + VLOG(1) << "SyncerThread(" << this << ")" << " Running Schedule nudge impl"; + // Note we currently nudge for all types regardless of the ones incurring + // the nudge. Doing different would throw off some syncer commands like + // CleanupDisabledTypes. We may want to change this in the future. + SyncSourceInfo info(source, types_with_payloads); - bool is_continuing_sync_cyle = *continue_sync_cycle; - *continue_sync_cycle = false; + SyncSession* session(CreateSyncSession(info)); + SyncSessionJob job(SyncSessionJob::NUDGE, TimeTicks::Now() + delay, + make_linked_ptr(session), is_canary_job, + nudge_location); - // Determine if the syncer has unfinished work to do. - SyncSessionSnapshot* snapshot = session_context_->previous_session_snapshot(); - const bool syncer_has_work_to_do = snapshot && - (snapshot->num_server_changes_remaining > 0 || - snapshot->unsynced_count > 0); - VLOG(1) << "syncer_has_work_to_do is " << syncer_has_work_to_do; + session = NULL; + if (!ShouldRunJob(job)) + return; - // First calculate the expected wait time, figuring in any backoff because of - // user idle time. next_wait is in seconds - syncer_polling_interval_ = (!session_context_->notifications_enabled()) ? - syncer_short_poll_interval_seconds_ : - syncer_long_poll_interval_seconds_; - int default_next_wait = syncer_polling_interval_; - return_interval.poll_delta = TimeDelta::FromSeconds(default_next_wait); - - if (syncer_has_work_to_do) { - // Provide exponential backoff due to consecutive errors, else attempt to - // complete the work as soon as possible. - if (is_continuing_sync_cyle) { - return_interval.mode = WaitInterval::EXPONENTIAL_BACKOFF; - if (was_nudged && vault_.current_wait_interval_.mode == - WaitInterval::EXPONENTIAL_BACKOFF) { - // We were nudged, it failed, and we were already in backoff. - return_interval.had_nudge_during_backoff = true; - // Keep exponent for exponential backoff the same in this case. - return_interval.poll_delta = vault_.current_wait_interval_.poll_delta; - } else { - // We weren't nudged, or we were in a NORMAL wait interval until now. - return_interval.poll_delta = TimeDelta::FromSeconds( - GetRecommendedDelaySeconds(last_poll_wait)); - } - } else { - // No consecutive error. - return_interval.poll_delta = TimeDelta::FromSeconds( - GetRecommendedDelaySeconds(0)); + if (pending_nudge_.get()) { + if (IsBackingOff() && delay > TimeDelta::FromSeconds(1)) { + VLOG(1) << "SyncerThread(" << this << ")" << " Dropping the nudge because" + << "we are in backoff"; + return; } - *continue_sync_cycle = true; - } else if (!session_context_->notifications_enabled()) { - // Ensure that we start exponential backoff from our base polling - // interval when we are not continuing a sync cycle. - last_poll_wait = std::max(last_poll_wait, syncer_polling_interval_); - - // Did the user start interacting with the computer again? - // If so, revise our idle time (and probably next_sync_time) downwards - int new_idle_time = disable_idle_detection_ ? 0 : UserIdleTime(); - if (new_idle_time < *user_idle_milliseconds) { - *user_idle_milliseconds = new_idle_time; + + VLOG(1) << "SyncerThread(" << this << ")" << " Coalescing pending nudge"; + pending_nudge_->session->Coalesce(*(job.session.get())); + + if (!IsBackingOff()) { + VLOG(1) << "SyncerThread(" << this << ")" << " Dropping a nudge because" + << " we are not in backoff and the job was coalesced"; + return; + } else { + VLOG(1) << "SyncerThread(" << this << ")" + << " Rescheduling pending nudge"; + SyncSession* s = pending_nudge_->session.get(); + job.session.reset(new SyncSession(s->context(), s->delegate(), + s->source(), s->routing_info(), s->workers())); + pending_nudge_.reset(); } - return_interval.poll_delta = TimeDelta::FromMilliseconds( - CalculateSyncWaitTime(last_poll_wait * 1000, - *user_idle_milliseconds)); - DCHECK_GE(return_interval.poll_delta.InSeconds(), default_next_wait); } - VLOG(1) << "Sync wait: idle " << default_next_wait - << " non-idle or backoff " << return_interval.poll_delta.InSeconds(); - - return return_interval; + // TODO(lipalani) - pass the job itself to ScheduleSyncSessionJob. + ScheduleSyncSessionJob(delay, SyncSessionJob::NUDGE, job.session.release(), + nudge_location); } -void SyncerThread::ThreadMain() { - base::AutoLock lock(lock_); - // Signal Start() to let it know we've made it safely onto the message loop, - // and unblock it's caller. - thread_main_started_.Signal(); - ThreadMainLoop(); - VLOG(1) << "Syncer thread ThreadMain is done."; - Notify(SyncEngineEvent::SYNCER_THREAD_EXITING); -} +// Helper to extract the routing info and workers corresponding to types in +// |types| from |registrar|. +void GetModelSafeParamsForTypes(const ModelTypeBitSet& types, + ModelSafeWorkerRegistrar* registrar, ModelSafeRoutingInfo* routes, + std::vector<ModelSafeWorker*>* workers) { + ModelSafeRoutingInfo r_tmp; + std::vector<ModelSafeWorker*> w_tmp; + registrar->GetModelSafeRoutingInfo(&r_tmp); + registrar->GetWorkers(&w_tmp); -SyncSession* SyncerThread::SyncMain(Syncer* syncer, bool was_throttled, - bool continue_sync_cycle, bool* initial_sync_for_thread, - bool* was_nudged) { - CHECK(syncer); + bool passive_group_added = false; - // Since we are initiating a new session for which we are the delegate, we - // are not currently silenced so reset this state for the next session which - // may need to use it. - silenced_until_ = base::TimeTicks(); + typedef std::vector<ModelSafeWorker*>::const_iterator iter; + for (size_t i = syncable::FIRST_REAL_MODEL_TYPE; i < types.size(); ++i) { + if (!types.test(i)) + continue; + syncable::ModelType t = syncable::ModelTypeFromInt(i); + DCHECK_EQ(1U, r_tmp.count(t)); + (*routes)[t] = r_tmp[t]; + iter it = std::find_if(w_tmp.begin(), w_tmp.end(), + ModelSafeWorkerGroupIs(r_tmp[t])); + if (it != w_tmp.end()) { + iter it2 = std::find_if(workers->begin(), workers->end(), + ModelSafeWorkerGroupIs(r_tmp[t])); + if (it2 == workers->end()) + workers->push_back(*it); + + if (r_tmp[t] == GROUP_PASSIVE) + passive_group_added = true; + } else { + NOTREACHED(); + } + } + + // Always add group passive. + if (passive_group_added == false) { + iter it = std::find_if(w_tmp.begin(), w_tmp.end(), + ModelSafeWorkerGroupIs(GROUP_PASSIVE)); + if (it != w_tmp.end()) + workers->push_back(*it); + else + NOTREACHED(); + } +} + +void SyncerThread::ScheduleConfig(const ModelTypeBitSet& types) { + if (!thread_.IsRunning()) { + NOTREACHED(); + return; + } + VLOG(1) << "SyncerThread(" << this << ")" << " Scheduling a config"; ModelSafeRoutingInfo routes; std::vector<ModelSafeWorker*> workers; - session_context_->registrar()->GetModelSafeRoutingInfo(&routes); - session_context_->registrar()->GetWorkers(&workers); - SyncSourceInfo info(GetAndResetNudgeSource(was_throttled, - continue_sync_cycle, initial_sync_for_thread, was_nudged)); - scoped_ptr<SyncSession> session; - - base::AutoUnlock unlock(lock_); - do { - session.reset(new SyncSession(session_context_.get(), this, - info, routes, workers)); - VLOG(1) << "Calling SyncShare."; - syncer->SyncShare(session.get()); - } while (session->HasMoreToSync() && silenced_until_.is_null()); - - VLOG(1) << "Done calling SyncShare."; - return session.release(); -} - -SyncSourceInfo SyncerThread::GetAndResetNudgeSource(bool was_throttled, - bool continue_sync_cycle, - bool* initial_sync, - bool* was_nudged) { - bool nudged = false; - NudgeSource nudge_source = kUnknown; - TypePayloadMap model_types_with_payloads; - // Has the previous sync cycle completed? - if (continue_sync_cycle) - nudge_source = kContinuation; - // Update the nudge source if a new nudge has come through during the - // previous sync cycle. - if (!vault_.pending_nudge_time_.is_null()) { - if (!was_throttled) { - nudge_source = vault_.pending_nudge_source_; - model_types_with_payloads = vault_.pending_nudge_types_; - nudged = true; - } - VLOG(1) << "Clearing pending nudge from " << vault_.pending_nudge_source_ - << " at tick " << vault_.pending_nudge_time_.ToInternalValue(); - vault_.pending_nudge_source_ = kUnknown; - vault_.pending_nudge_types_.clear(); - vault_.pending_nudge_time_ = base::TimeTicks(); + GetModelSafeParamsForTypes(types, session_context_->registrar(), + &routes, &workers); + + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + this, &SyncerThread::ScheduleConfigImpl, routes, workers, + GetUpdatesCallerInfo::FIRST_UPDATE)); +} + +void SyncerThread::ScheduleConfigImpl(const ModelSafeRoutingInfo& routing_info, + const std::vector<ModelSafeWorker*>& workers, + const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + + VLOG(1) << "SyncerThread(" << this << ")" << " ScheduleConfigImpl..."; + // TODO(tim): config-specific GetUpdatesCallerInfo value? + SyncSession* session = new SyncSession(session_context_.get(), this, + SyncSourceInfo(source, + syncable::ModelTypePayloadMapFromRoutingInfo( + routing_info, std::string())), + routing_info, workers); + ScheduleSyncSessionJob(TimeDelta::FromSeconds(0), + SyncSessionJob::CONFIGURATION, session, FROM_HERE); +} + +void SyncerThread::ScheduleSyncSessionJob(const base::TimeDelta& delay, + SyncSessionJob::SyncSessionJobPurpose purpose, + sessions::SyncSession* session, + const tracked_objects::Location& nudge_location) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + + SyncSessionJob job(purpose, TimeTicks::Now() + delay, + make_linked_ptr(session), false, nudge_location); + if (purpose == SyncSessionJob::NUDGE) { + VLOG(1) << "SyncerThread(" << this << ")" << " Resetting pending_nudge in" + << " ScheduleSyncSessionJob"; + DCHECK(!pending_nudge_.get() || pending_nudge_->session.get() == session); + pending_nudge_.reset(new SyncSessionJob(job)); } + VLOG(1) << "SyncerThread(" << this << ")" + << " Posting job to execute in DoSyncSessionJob. Job purpose " + << job.purpose; + MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(this, + &SyncerThread::DoSyncSessionJob, job), + delay.InMilliseconds()); +} + +void SyncerThread::SetSyncerStepsForPurpose( + SyncSessionJob::SyncSessionJobPurpose purpose, + SyncerStep* start, SyncerStep* end) { + *end = SYNCER_END; + switch (purpose) { + case SyncSessionJob::CONFIGURATION: + *start = DOWNLOAD_UPDATES; + *end = APPLY_UPDATES; + return; + case SyncSessionJob::CLEAR_USER_DATA: + *start = CLEAR_PRIVATE_DATA; + return; + case SyncSessionJob::NUDGE: + case SyncSessionJob::POLL: + *start = SYNCER_BEGIN; + return; + default: + NOTREACHED(); + } +} - *was_nudged = nudged; - - // TODO(tim): Hack for bug 64136 to correctly tag continuations that result - // from syncer having more work to do. This will be handled properly with - // the message loop based syncer thread, bug 26339. - return MakeSyncSourceInfo(nudged || nudge_source == kContinuation, - nudge_source, model_types_with_payloads, initial_sync); -} - -SyncSourceInfo SyncerThread::MakeSyncSourceInfo(bool nudged, - NudgeSource nudge_source, - const TypePayloadMap& model_types_with_payloads, - bool* initial_sync) { - sync_pb::GetUpdatesCallerInfo::GetUpdatesSource updates_source = - sync_pb::GetUpdatesCallerInfo::UNKNOWN; - if (*initial_sync) { - updates_source = sync_pb::GetUpdatesCallerInfo::FIRST_UPDATE; - *initial_sync = false; - } else if (!nudged) { - updates_source = sync_pb::GetUpdatesCallerInfo::PERIODIC; - } else { - switch (nudge_source) { - case kNotification: - updates_source = sync_pb::GetUpdatesCallerInfo::NOTIFICATION; - break; - case kLocal: - updates_source = sync_pb::GetUpdatesCallerInfo::LOCAL; - break; - case kContinuation: - updates_source = sync_pb::GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION; - break; - case kClearPrivateData: - updates_source = sync_pb::GetUpdatesCallerInfo::CLEAR_PRIVATE_DATA; - break; - case kUnknown: - default: - updates_source = sync_pb::GetUpdatesCallerInfo::UNKNOWN; - break; - } +void SyncerThread::DoSyncSessionJob(const SyncSessionJob& job) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + if (!ShouldRunJob(job)) { + LOG(WARNING) << "Dropping nudge at DoSyncSessionJob, source = " + << job.session->source().updates_source; + return; } - TypePayloadMap sync_source_types; - if (model_types_with_payloads.empty()) { - // No datatypes requested. This must be a poll so set all enabled datatypes. - ModelSafeRoutingInfo routes; - session_context_->registrar()->GetModelSafeRoutingInfo(&routes); - sync_source_types = sessions::MakeTypePayloadMapFromRoutingInfo(routes, - std::string()); + if (job.purpose == SyncSessionJob::NUDGE) { + if (pending_nudge_.get() == NULL || pending_nudge_->session != job.session) + return; // Another nudge must have been scheduled in in the meantime. + pending_nudge_.reset(); + } + VLOG(1) << "SyncerThread(" << this << ")" << " DoSyncSessionJob. job purpose " + << job.purpose; + + SyncerStep begin(SYNCER_BEGIN); + SyncerStep end(SYNCER_END); + SetSyncerStepsForPurpose(job.purpose, &begin, &end); + + bool has_more_to_sync = true; + while (ShouldRunJob(job) && has_more_to_sync) { + VLOG(1) << "SyncerThread(" << this << ")" + << " SyncerThread: Calling SyncShare."; + // Synchronously perform the sync session from this thread. + syncer_->SyncShare(job.session.get(), begin, end); + has_more_to_sync = job.session->HasMoreToSync(); + if (has_more_to_sync) + job.session->ResetTransientState(); + } + VLOG(1) << "SyncerThread(" << this << ")" + << " SyncerThread: Done SyncShare looping."; + FinishSyncSessionJob(job); +} + +void SyncerThread::UpdateCarryoverSessionState(const SyncSessionJob& old_job) { + if (old_job.purpose == SyncSessionJob::CONFIGURATION) { + // Whatever types were part of a configuration task will have had updates + // downloaded. For that reason, we make sure they get recorded in the + // event that they get disabled at a later time. + ModelSafeRoutingInfo r(session_context_->previous_session_routing_info()); + if (!r.empty()) { + ModelSafeRoutingInfo temp_r; + ModelSafeRoutingInfo old_info(old_job.session->routing_info()); + std::set_union(r.begin(), r.end(), old_info.begin(), old_info.end(), + std::insert_iterator<ModelSafeRoutingInfo>(temp_r, temp_r.begin())); + session_context_->set_previous_session_routing_info(temp_r); + } } else { - sync_source_types = model_types_with_payloads; + session_context_->set_previous_session_routing_info( + old_job.session->routing_info()); } +} - return SyncSourceInfo(updates_source, sync_source_types); -} - -void SyncerThread::CreateSyncer(const std::string& dirname) { - base::AutoLock lock(lock_); - VLOG(1) << "Creating syncer up for: " << dirname; - // The underlying database structure is ready, and we should create - // the syncer. - CHECK(vault_.syncer_ == NULL); - vault_.syncer_ = new Syncer(); - vault_field_changed_.Broadcast(); -} - -// Sets |*connected| to false if it is currently true but |code| suggests that -// the current network configuration and/or auth state cannot be used to make -// forward progress, and user intervention (e.g changing server URL or auth -// credentials) is likely necessary. If |*connected| is false, set it to true -// if |code| suggests that we just recently made healthy contact with the -// server. -static inline void CheckConnected(bool* connected, - HttpResponse::ServerConnectionCode code, - base::ConditionVariable* condvar) { - if (*connected) { - // Note, be careful when adding cases here because if the SyncerThread - // thinks there is no valid connection as determined by this method, it - // will drop out of *all* forward progress sync loops (it won't poll and it - // will queue up Talk notifications but not actually call SyncShare) until - // some external action causes a ServerConnectionManager to broadcast that - // a valid connection has been re-established. - if (HttpResponse::CONNECTION_UNAVAILABLE == code || - HttpResponse::SYNC_AUTH_ERROR == code) { - *connected = false; - condvar->Broadcast(); +void SyncerThread::FinishSyncSessionJob(const SyncSessionJob& job) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + // Update timing information for how often datatypes are triggering nudges. + base::TimeTicks now = TimeTicks::Now(); + if (!last_sync_session_end_time_.is_null()) { + ModelTypePayloadMap::const_iterator iter; + for (iter = job.session->source().types.begin(); + iter != job.session->source().types.end(); + ++iter) { + syncable::PostTimeToTypeHistogram(iter->first, + now - last_sync_session_end_time_); } + } + last_sync_session_end_time_ = now; + UpdateCarryoverSessionState(job); + if (IsSyncingCurrentlySilenced()) { + VLOG(1) << "SyncerThread(" << this << ")" + << " We are currently throttled. So not scheduling the next sync."; + SaveJob(job); + return; // Nothing to do. + } + + VLOG(1) << "SyncerThread(" << this << ")" + << " Updating the next polling time after SyncMain"; + ScheduleNextSync(job); +} + +void SyncerThread::ScheduleNextSync(const SyncSessionJob& old_job) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + DCHECK(!old_job.session->HasMoreToSync()); + // Note: |num_server_changes_remaining| > 0 here implies that we received a + // broken response while trying to download all updates, because the Syncer + // will loop until this value is exhausted. Also, if unsynced_handles exist + // but HasMoreToSync is false, this implies that the Syncer determined no + // forward progress was possible at this time (an error, such as an HTTP + // 500, is likely to have occurred during commit). + const bool work_to_do = + old_job.session->status_controller()->num_server_changes_remaining() > 0 + || old_job.session->status_controller()->unsynced_handles().size() > 0; + VLOG(1) << "SyncerThread(" << this << ")" << " syncer has work to do: " + << work_to_do; + + AdjustPolling(&old_job); + + // TODO(tim): Old impl had special code if notifications disabled. Needed? + if (!work_to_do) { + // Success implies backoff relief. Note that if this was a "one-off" job + // (i.e. purpose == SyncSessionJob::CLEAR_USER_DATA), if there was + // work_to_do before it ran this wont have changed, as jobs like this don't + // run a full sync cycle. So we don't need special code here. + wait_interval_.reset(); + VLOG(1) << "SyncerThread(" << this << ")" + << " Job suceeded so not scheduling more jobs"; + return; + } + + if (old_job.session->source().updates_source == + GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION) { + VLOG(1) << "SyncerThread(" << this << ")" + << " Job failed with source continuation"; + // We don't seem to have made forward progress. Start or extend backoff. + HandleConsecutiveContinuationError(old_job); + } else if (IsBackingOff()) { + VLOG(1) << "SyncerThread(" << this << ")" + << " A nudge during backoff failed"; + // We weren't continuing but we're in backoff; must have been a nudge. + DCHECK_EQ(SyncSessionJob::NUDGE, old_job.purpose); + DCHECK(!wait_interval_->had_nudge); + wait_interval_->had_nudge = true; + wait_interval_->timer.Reset(); } else { - if (HttpResponse::SERVER_CONNECTION_OK == code) { - *connected = true; - condvar->Broadcast(); + VLOG(1) << "SyncerThread(" << this << ")" + << " Failed. Schedule a job with continuation as source"; + // We weren't continuing and we aren't in backoff. Schedule a normal + // continuation. + if (old_job.purpose == SyncSessionJob::CONFIGURATION) { + ScheduleConfigImpl(old_job.session->routing_info(), + old_job.session->workers(), + GetUpdatesFromNudgeSource(NUDGE_SOURCE_CONTINUATION)); + } else { + // For all other purposes(nudge and poll) we schedule a retry nudge. + ScheduleNudgeImpl(TimeDelta::FromSeconds(0), + GetUpdatesFromNudgeSource(NUDGE_SOURCE_CONTINUATION), + old_job.session->source().types, false, FROM_HERE); } } } -void SyncerThread::WatchConnectionManager(ServerConnectionManager* conn_mgr) { - conn_mgr_hookup_.reset(NewEventListenerHookup(conn_mgr->channel(), this, - &SyncerThread::HandleServerConnectionEvent)); - CheckConnected(&vault_.connected_, conn_mgr->server_status(), - &vault_field_changed_); +void SyncerThread::AdjustPolling(const SyncSessionJob* old_job) { + DCHECK(thread_.IsRunning()); + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + + TimeDelta poll = (!session_context_->notifications_enabled()) ? + syncer_short_poll_interval_seconds_ : + syncer_long_poll_interval_seconds_; + bool rate_changed = !poll_timer_.IsRunning() || + poll != poll_timer_.GetCurrentDelay(); + + if (old_job && old_job->purpose != SyncSessionJob::POLL && !rate_changed) + poll_timer_.Reset(); + + if (!rate_changed) + return; + + // Adjust poll rate. + poll_timer_.Stop(); + poll_timer_.Start(poll, this, &SyncerThread::PollTimerCallback); } -void SyncerThread::HandleServerConnectionEvent( - const ServerConnectionEvent& event) { - if (ServerConnectionEvent::STATUS_CHANGED == event.what_happened) { - base::AutoLock lock(lock_); - CheckConnected(&vault_.connected_, event.connection_code, - &vault_field_changed_); +void SyncerThread::HandleConsecutiveContinuationError( + const SyncSessionJob& old_job) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + // This if conditions should be compiled out in retail builds. + if (IsBackingOff()) { + DCHECK(wait_interval_->timer.IsRunning() || old_job.is_canary_job); } + SyncSession* old = old_job.session.get(); + SyncSession* s(new SyncSession(session_context_.get(), this, + old->source(), old->routing_info(), old->workers())); + TimeDelta length = delay_provider_->GetDelay( + IsBackingOff() ? wait_interval_->length : TimeDelta::FromSeconds(1)); + + VLOG(1) << "SyncerThread(" << this << ")" + << " In handle continuation error. Old job purpose is " + << old_job.purpose; + VLOG(1) << "SyncerThread(" << this << ")" + << " In Handle continuation error. The time delta(ms) is: " + << length.InMilliseconds(); + + // This will reset the had_nudge variable as well. + wait_interval_.reset(new WaitInterval(WaitInterval::EXPONENTIAL_BACKOFF, + length)); + if (old_job.purpose == SyncSessionJob::CONFIGURATION) { + SyncSessionJob job(old_job.purpose, TimeTicks::Now() + length, + make_linked_ptr(s), false, FROM_HERE); + wait_interval_->pending_configure_job.reset(new SyncSessionJob(job)); + } else { + // We are not in configuration mode. So wait_interval's pending job + // should be null. + DCHECK(wait_interval_->pending_configure_job.get() == NULL); + + // TODO(lipalani) - handle clear user data. + InitOrCoalescePendingJob(old_job); + } + wait_interval_->timer.Start(length, this, &SyncerThread::DoCanaryJob); } -int SyncerThread::GetRecommendedDelaySeconds(int base_delay_seconds) { - if (base_delay_seconds >= kMaxBackoffSeconds) - return kMaxBackoffSeconds; +// static +TimeDelta SyncerThread::GetRecommendedDelay(const TimeDelta& last_delay) { + if (last_delay.InSeconds() >= kMaxBackoffSeconds) + return TimeDelta::FromSeconds(kMaxBackoffSeconds); // This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2 - int backoff_s = - std::max(1, base_delay_seconds * kBackoffRandomizationFactor); + int64 backoff_s = + std::max(static_cast<int64>(1), + last_delay.InSeconds() * kBackoffRandomizationFactor); // Flip a coin to randomize backoff interval by +/- 50%. int rand_sign = base::RandInt(0, 1) * 2 - 1; // Truncation is adequate for rounding here. backoff_s = backoff_s + - (rand_sign * (base_delay_seconds / kBackoffRandomizationFactor)); + (rand_sign * (last_delay.InSeconds() / kBackoffRandomizationFactor)); // Cap the backoff interval. - backoff_s = std::max(1, std::min(backoff_s, kMaxBackoffSeconds)); + backoff_s = std::max(static_cast<int64>(1), + std::min(backoff_s, kMaxBackoffSeconds)); - return backoff_s; + return TimeDelta::FromSeconds(backoff_s); } -// Inputs and return value in milliseconds. -int SyncerThread::CalculateSyncWaitTime(int last_interval, int user_idle_ms) { - // syncer_polling_interval_ is in seconds - int syncer_polling_interval_ms = syncer_polling_interval_ * 1000; - - // This is our default and lower bound. - int next_wait = syncer_polling_interval_ms; - - // Get idle time, bounded by max wait. - int idle = min(user_idle_ms, syncer_max_interval_); +void SyncerThread::Stop() { + VLOG(1) << "SyncerThread(" << this << ")" << " stop called"; + syncer_->RequestEarlyExit(); // Safe to call from any thread. + session_context_->connection_manager()->RemoveListener(this); + thread_.Stop(); +} - // If the user has been idle for a while, we'll start decreasing the poll - // rate. - if (idle >= kPollBackoffThresholdMultiplier * syncer_polling_interval_ms) { - next_wait = std::min(GetRecommendedDelaySeconds( - last_interval / 1000), syncer_max_interval_ / 1000) * 1000; +void SyncerThread::DoCanaryJob() { + VLOG(1) << "SyncerThread(" << this << ")" << " Do canary job"; + DoPendingJobIfPossible(true); +} + +void SyncerThread::DoPendingJobIfPossible(bool is_canary_job) { + SyncSessionJob* job_to_execute = NULL; + if (mode_ == CONFIGURATION_MODE && wait_interval_.get() + && wait_interval_->pending_configure_job.get()) { + VLOG(1) << "SyncerThread(" << this << ")" << " Found pending configure job"; + job_to_execute = wait_interval_->pending_configure_job.get(); + } else if (mode_ == NORMAL_MODE && pending_nudge_.get()) { + VLOG(1) << "SyncerThread(" << this << ")" << " Found pending nudge job"; + // Pending jobs mostly have time from the past. Reset it so this job + // will get executed. + if (pending_nudge_->scheduled_start < TimeTicks::Now()) + pending_nudge_->scheduled_start = TimeTicks::Now(); + + scoped_ptr<SyncSession> session(CreateSyncSession( + pending_nudge_->session->source())); + + // Also the routing info might have been changed since we cached the + // pending nudge. Update it by coalescing to the latest. + pending_nudge_->session->Coalesce(*(session.get())); + // The pending nudge would be cleared in the DoSyncSessionJob function. + job_to_execute = pending_nudge_.get(); } - return next_wait; -} - -// Called with mutex_ already locked. -void SyncerThread::NudgeSyncImpl( - int milliseconds_from_now, - NudgeSource source, - const TypePayloadMap& model_types_with_payloads) { - // TODO(sync): Add the option to reset the backoff state machine. - // This is needed so nudges that are a result of the user's desire - // to download updates for a new data type can be satisfied quickly. - if (vault_.current_wait_interval_.mode == WaitInterval::THROTTLED || - vault_.current_wait_interval_.had_nudge_during_backoff) { - // Drop nudges on the floor if we've already had one since starting this - // stage of exponential backoff or we are throttled. - return; + if (job_to_execute != NULL) { + VLOG(1) << "SyncerThread(" << this << ")" << " Executing pending job"; + SyncSessionJob copy = *job_to_execute; + copy.is_canary_job = is_canary_job; + DoSyncSessionJob(copy); } +} - // Union the current TypePayloadMap with any from nudges that may have already - // posted (coalesce the nudge datatype information). - // TODO(tim): It seems weird to do this if the sources don't match up (e.g. - // if pending_source is kLocal and |source| is kClearPrivateData). - sessions::CoalescePayloads(&vault_.pending_nudge_types_, - model_types_with_payloads); - - const TimeTicks nudge_time = TimeTicks::Now() + - TimeDelta::FromMilliseconds(milliseconds_from_now); - if (nudge_time <= vault_.pending_nudge_time_) { - VLOG(1) << "Nudge for source " << source - << " dropped due to existing later pending nudge"; - return; - } +SyncSession* SyncerThread::CreateSyncSession(const SyncSourceInfo& source) { + ModelSafeRoutingInfo routes; + std::vector<ModelSafeWorker*> workers; + session_context_->registrar()->GetModelSafeRoutingInfo(&routes); + session_context_->registrar()->GetWorkers(&workers); + SyncSourceInfo info(source); - VLOG(1) << "Replacing pending nudge for source " << source - << " at " << nudge_time.ToInternalValue(); + SyncSession* session(new SyncSession(session_context_.get(), this, info, + routes, workers)); - vault_.pending_nudge_source_ = source; - vault_.pending_nudge_time_ = nudge_time; - vault_field_changed_.Broadcast(); + return session; } -void SyncerThread::SetNotificationsEnabled(bool notifications_enabled) { - base::AutoLock lock(lock_); - session_context_->set_notifications_enabled(notifications_enabled); +void SyncerThread::PollTimerCallback() { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + ModelSafeRoutingInfo r; + ModelTypePayloadMap types_with_payloads = + syncable::ModelTypePayloadMapFromRoutingInfo(r, std::string()); + SyncSourceInfo info(GetUpdatesCallerInfo::PERIODIC, types_with_payloads); + SyncSession* s = CreateSyncSession(info); + ScheduleSyncSessionJob(TimeDelta::FromSeconds(0), SyncSessionJob::POLL, s, + FROM_HERE); } -// Returns the amount of time since the user last interacted with the computer, -// in milliseconds -int SyncerThread::UserIdleTime() { -#if defined(OS_WIN) - LASTINPUTINFO last_input_info; - last_input_info.cbSize = sizeof(LASTINPUTINFO); - - // Get time in windows ticks since system start of last activity. - BOOL b = ::GetLastInputInfo(&last_input_info); - if (b == TRUE) - return ::GetTickCount() - last_input_info.dwTime; -#elif defined(OS_MACOSX) - // It would be great to do something like: - // - // return 1000 * - // CGEventSourceSecondsSinceLastEventType( - // kCGEventSourceStateCombinedSessionState, - // kCGAnyInputEventType); - // - // Unfortunately, CGEvent* lives in ApplicationServices, and we're a daemon - // and can't link that high up the food chain. Thus this mucking in IOKit. - - io_service_t hid_service = - IOServiceGetMatchingService(kIOMasterPortDefault, - IOServiceMatching("IOHIDSystem")); - if (!hid_service) { - LOG(WARNING) << "Could not obtain IOHIDSystem"; - return 0; - } +void SyncerThread::Unthrottle() { + DCHECK_EQ(WaitInterval::THROTTLED, wait_interval_->mode); + VLOG(1) << "SyncerThread(" << this << ")" << " Unthrottled.."; + DoCanaryJob(); + wait_interval_.reset(); +} - CFTypeRef object = IORegistryEntryCreateCFProperty(hid_service, - CFSTR("HIDIdleTime"), - kCFAllocatorDefault, - 0); - if (!object) { - LOG(WARNING) << "Could not get IOHIDSystem's HIDIdleTime property"; - IOObjectRelease(hid_service); - return 0; - } +void SyncerThread::Notify(SyncEngineEvent::EventCause cause) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + session_context_->NotifyListeners(SyncEngineEvent(cause)); +} - int64 idle_time; // in nanoseconds - Boolean success = false; - if (CFGetTypeID(object) == CFNumberGetTypeID()) { - success = CFNumberGetValue((CFNumberRef)object, - kCFNumberSInt64Type, - &idle_time); - } else { - LOG(WARNING) << "IOHIDSystem's HIDIdleTime property isn't a number!"; - } +bool SyncerThread::IsBackingOff() const { + return wait_interval_.get() && wait_interval_->mode == + WaitInterval::EXPONENTIAL_BACKOFF; +} - CFRelease(object); - IOObjectRelease(hid_service); +void SyncerThread::OnSilencedUntil(const base::TimeTicks& silenced_until) { + wait_interval_.reset(new WaitInterval(WaitInterval::THROTTLED, + silenced_until - TimeTicks::Now())); + wait_interval_->timer.Start(wait_interval_->length, this, + &SyncerThread::Unthrottle); +} - if (!success) { - LOG(WARNING) << "Could not get IOHIDSystem's HIDIdleTime property's value"; - return 0; - } - return idle_time / 1000000; // nano to milli -#elif defined(OS_LINUX) - if (idle_query_.get()) - return idle_query_->IdleTime(); - return 0; -#else - static bool was_logged = false; - if (!was_logged) { - was_logged = true; - VLOG(1) << "UserIdleTime unimplemented on this platform, synchronization " - "will not throttle when user idle"; - } -#endif +bool SyncerThread::IsSyncingCurrentlySilenced() { + return wait_interval_.get() && wait_interval_->mode == + WaitInterval::THROTTLED; +} - return 0; +void SyncerThread::OnReceivedShortPollIntervalUpdate( + const base::TimeDelta& new_interval) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + syncer_short_poll_interval_seconds_ = new_interval; +} + +void SyncerThread::OnReceivedLongPollIntervalUpdate( + const base::TimeDelta& new_interval) { + DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); + syncer_long_poll_interval_seconds_ = new_interval; +} + +void SyncerThread::OnShouldStopSyncingPermanently() { + VLOG(1) << "SyncerThread(" << this << ")" + << " OnShouldStopSyncingPermanently"; + syncer_->RequestEarlyExit(); // Thread-safe. + Notify(SyncEngineEvent::STOP_SYNCING_PERMANENTLY); +} + +void SyncerThread::OnServerConnectionEvent( + const ServerConnectionEvent2& event) { + thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(this, + &SyncerThread::CheckServerConnectionManagerStatus, + event.connection_code)); +} + +void SyncerThread::set_notifications_enabled(bool notifications_enabled) { + session_context_->set_notifications_enabled(notifications_enabled); } -} // namespace browser_sync +} // browser_sync diff --git a/chrome/browser/sync/engine/syncer_thread.h b/chrome/browser/sync/engine/syncer_thread.h index b8bf724..47e2ac7 100644 --- a/chrome/browser/sync/engine/syncer_thread.h +++ b/chrome/browser/sync/engine/syncer_thread.h @@ -1,75 +1,177 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // // A class to run the syncer on a thread. -// This is the default implementation of SyncerThread whose Stop implementation -// does not support a timeout, but is greatly simplified. #ifndef CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD_H_ #define CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD_H_ #pragma once -#include <list> -#include <string> -#include <vector> - -#include "base/basictypes.h" -#include "base/gtest_prod_util.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" -#include "base/synchronization/condition_variable.h" +#include "base/callback.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/task.h" #include "base/threading/thread.h" #include "base/time.h" -#include "base/synchronization/waitable_event.h" -#include "chrome/browser/sync/engine/syncer_types.h" +#include "base/timer.h" +#include "chrome/browser/sync/engine/nudge_source.h" +#include "chrome/browser/sync/engine/polling_constants.h" +#include "chrome/browser/sync/engine/syncer.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" +#include "chrome/browser/sync/engine/net/server_connection_manager.h" #include "chrome/browser/sync/sessions/sync_session.h" -#include "chrome/browser/sync/syncable/model_type.h" -#include "chrome/common/deprecated/event_sys-inl.h" - -#if defined(OS_LINUX) -#include "chrome/browser/sync/engine/idle_query_linux.h" -#endif - -class EventListenerHookup; +#include "chrome/browser/sync/sessions/sync_session_context.h" namespace browser_sync { -class ModelSafeWorker; -class ServerConnectionManager; -class Syncer; -class URLFactory; struct ServerConnectionEvent; -class SyncerThread : public base::RefCountedThreadSafe<SyncerThread>, - public sessions::SyncSession::Delegate { - FRIEND_TEST_ALL_PREFIXES(SyncerThreadTest, CalculateSyncWaitTime); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadTest, CalculatePollingWaitTime); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, Polling); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, Nudge); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, NudgeWithDataTypes); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, - NudgeWithDataTypesCoalesced); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, NudgeWithPayloads); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, - NudgeWithPayloadsCoalesced); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, Throttling); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, AuthInvalid); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, Pause); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, StartWhenNotConnected); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, PauseWhenNotConnected); - FRIEND_TEST_ALL_PREFIXES(SyncerThreadWithSyncerTest, StopSyncPermanently); - friend class SyncerThreadWithSyncerTest; - friend class SyncerThreadFactory; +class SyncerThread : public sessions::SyncSession::Delegate, + public ServerConnectionEventListener { public: - // Encapsulates the parameters that make up an interval on which the - // syncer thread is sleeping. + enum Mode { + // In this mode, the thread only performs configuration tasks. This is + // designed to make the case where we want to download updates for a + // specific type only, and not continue syncing until we are moved into + // normal mode. + CONFIGURATION_MODE, + // Resumes polling and allows nudges, drops configuration tasks. Runs + // through entire sync cycle. + NORMAL_MODE, + }; + + // Takes ownership of both |context| and |syncer|. + SyncerThread(sessions::SyncSessionContext* context, Syncer* syncer); + virtual ~SyncerThread(); + + typedef Callback0::Type ModeChangeCallback; + + // Change the mode of operation. + // We don't use a lock when changing modes, so we won't cause currently + // scheduled jobs to adhere to the new mode. We could protect it, but it + // doesn't buy very much as a) a session could already be in progress and it + // will continue no matter what, b) the scheduled sessions already contain + // all their required state and won't be affected by potential change at + // higher levels (i.e. the registrar), and c) we service tasks FIFO, so once + // the mode changes all future jobs will be run against the updated mode. + // If supplied, |callback| will be invoked when the mode has been + // changed to |mode| *from the SyncerThread*, and not from the caller + // thread. + void Start(Mode mode, ModeChangeCallback* callback); + + // Joins on the thread as soon as possible (currently running session + // completes). + void Stop(); + + // The meat and potatoes. + void ScheduleNudge(const base::TimeDelta& delay, NudgeSource source, + const syncable::ModelTypeBitSet& types, + const tracked_objects::Location& nudge_location); + void ScheduleNudgeWithPayloads( + const base::TimeDelta& delay, NudgeSource source, + const syncable::ModelTypePayloadMap& types_with_payloads, + const tracked_objects::Location& nudge_location); + void ScheduleConfig(const syncable::ModelTypeBitSet& types); + void ScheduleClearUserData(); + + // Change status of notifications in the SyncSessionContext. + void set_notifications_enabled(bool notifications_enabled); + + // DDOS avoidance function. Calculates how long we should wait before trying + // again after a failed sync attempt, where the last delay was |base_delay|. + // TODO(tim): Look at URLRequestThrottlerEntryInterface. + static base::TimeDelta GetRecommendedDelay(const base::TimeDelta& base_delay); + + // SyncSession::Delegate implementation. + virtual void OnSilencedUntil(const base::TimeTicks& silenced_until); + virtual bool IsSyncingCurrentlySilenced(); + virtual void OnReceivedShortPollIntervalUpdate( + const base::TimeDelta& new_interval); + virtual void OnReceivedLongPollIntervalUpdate( + const base::TimeDelta& new_interval); + virtual void OnShouldStopSyncingPermanently(); + + // ServerConnectionEventListener implementation. + // TODO(tim): schedule a nudge when valid connection detected? in 1 minute? + virtual void OnServerConnectionEvent(const ServerConnectionEvent2& event); + + private: + enum JobProcessDecision { + // Indicates we should continue with the current job. + CONTINUE, + // Indicates that we should save it to be processed later. + SAVE, + // Indicates we should drop this job. + DROP, + }; + + struct SyncSessionJob { + // An enum used to describe jobs for scheduling purposes. + enum SyncSessionJobPurpose { + // Our poll timer schedules POLL jobs periodically based on a server + // assigned poll interval. + POLL, + // A nudge task can come from a variety of components needing to force + // a sync. The source is inferable from |session.source()|. + NUDGE, + // The user invoked a function in the UI to clear their entire account + // and stop syncing (globally). + CLEAR_USER_DATA, + // Typically used for fetching updates for a subset of the enabled types + // during initial sync or reconfiguration. We don't run all steps of + // the sync cycle for these (e.g. CleanupDisabledTypes is skipped). + CONFIGURATION, + }; + SyncSessionJob(); + SyncSessionJob(SyncSessionJobPurpose purpose, base::TimeTicks start, + linked_ptr<sessions::SyncSession> session, bool is_canary_job, + const tracked_objects::Location& nudge_location); + ~SyncSessionJob(); + SyncSessionJobPurpose purpose; + base::TimeTicks scheduled_start; + linked_ptr<sessions::SyncSession> session; + bool is_canary_job; + + // This is the location the nudge came from. used for debugging purpose. + // In case of multiple nudges getting coalesced this stores the first nudge + // that came in. + tracked_objects::Location nudge_location; + }; + friend class SyncerThread2Test; + friend class SyncerThread2WhiteboxTest; + + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, + DropNudgeWhileExponentialBackOff); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, SaveNudge); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, ContinueNudge); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, DropPoll); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, ContinuePoll); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, ContinueConfiguration); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, + SaveConfigurationWhileThrottled); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, + SaveNudgeWhileThrottled); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, + ContinueClearUserDataUnderAllCircumstances); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, + ContinueCanaryJobConfig); + FRIEND_TEST_ALL_PREFIXES(SyncerThread2WhiteboxTest, + ContinueNudgeWhileExponentialBackOff); + + // A component used to get time delays associated with exponential backoff. + // Encapsulated into a class to facilitate testing. + class DelayProvider { + public: + DelayProvider(); + virtual base::TimeDelta GetDelay(const base::TimeDelta& last_delay); + virtual ~DelayProvider(); + private: + DISALLOW_COPY_AND_ASSIGN(DelayProvider); + }; + struct WaitInterval { enum Mode { - // A wait interval whose duration has not been affected by exponential - // backoff. The base case for exponential backoff falls in to this case - // (e.g when the exponent is 1). So far, we don't need a separate case. - // NORMAL intervals are not nudge-rate limited. - NORMAL, // A wait interval whose duration has been affected by exponential // backoff. // EXPONENTIAL_BACKOFF intervals are nudge-rate limited to 1 per interval. @@ -78,286 +180,162 @@ class SyncerThread : public base::RefCountedThreadSafe<SyncerThread>, // during such an interval. THROTTLED, }; + WaitInterval(); + ~WaitInterval(); Mode mode; - // This bool is set to true if we have observed a nudge during during this - // interval and mode == EXPONENTIAL_BACKOFF. - bool had_nudge_during_backoff; - base::TimeDelta poll_delta; // The wait duration until the next poll. - WaitInterval() : mode(NORMAL), had_nudge_during_backoff(false) { } + // This bool is set to true if we have observed a nudge during this + // interval and mode == EXPONENTIAL_BACKOFF. + bool had_nudge; + base::TimeDelta length; + base::OneShotTimer<SyncerThread> timer; + + // Configure jobs are saved only when backing off or throttling. So we + // expose the pointer here. + scoped_ptr<SyncSessionJob> pending_configure_job; + WaitInterval(Mode mode, base::TimeDelta length); }; - enum NudgeSource { - kUnknown = 0, - kNotification, - kLocal, - kContinuation, - kClearPrivateData - }; - // Server can overwrite these values via client commands. - // Standard short poll. This is used when XMPP is off. - static const int kDefaultShortPollIntervalSeconds; - // Long poll is used when XMPP is on. - static const int kDefaultLongPollIntervalSeconds; - // 30 minutes by default. If exponential backoff kicks in, this is the - // longest possible poll interval. - static const int kDefaultMaxPollIntervalMs; - // Maximum interval for exponential backoff. - static const int kMaxBackoffSeconds; - - explicit SyncerThread(sessions::SyncSessionContext* context); - virtual ~SyncerThread(); + // Helper to assemble a job and post a delayed task to sync. + void ScheduleSyncSessionJob( + const base::TimeDelta& delay, + SyncSessionJob::SyncSessionJobPurpose purpose, + sessions::SyncSession* session, + const tracked_objects::Location& nudge_location); - virtual void WatchConnectionManager(ServerConnectionManager* conn_mgr); - - // Starts a syncer thread. - // Returns true if it creates a thread or if there's currently a thread - // running and false otherwise. - virtual bool Start(); - - // Stop processing. |max_wait| doesn't do anything in this version. - virtual bool Stop(int max_wait); - - // Request that the thread pauses. Returns false if the request can - // not be completed (e.g. the thread is not running). When the - // thread actually pauses, a SyncEngineEvent::PAUSED event notification - // will be sent to the relay channel. - virtual bool RequestPause(); - - // Request that the thread resumes from pause. Returns false if the - // request can not be completed (e.g. the thread is not running or - // is not currently paused). When the thread actually resumes, a - // SyncEngineEvent::RESUMED event notification will be sent to the relay - // channel. - virtual bool RequestResume(); - - // Nudges the syncer to sync with a delay specified. This API is for access - // from the SyncerThread's controller and will cause a mutex lock. - virtual void NudgeSyncer(int milliseconds_from_now, NudgeSource source); - - // Same as |NudgeSyncer|, but supports tracking the datatypes that caused - // the nudge to occur. - virtual void NudgeSyncerWithDataTypes( - int milliseconds_from_now, - NudgeSource source, - const syncable::ModelTypeBitSet& model_types); - - // Same as |NudgeSyncer|, but supports including a payload for passing on to - // the download updates command. Datatypes with payloads are also considered - // to have caused a nudged to occur and treated accordingly. - virtual void NudgeSyncerWithPayloads( - int milliseconds_from_now, - NudgeSource source, - const sessions::TypePayloadMap& model_types_with_payloads); - - void SetNotificationsEnabled(bool notifications_enabled); - - // Call this when a directory is opened - void CreateSyncer(const std::string& dirname); - - // DDOS avoidance function. The argument and return value is in seconds - static int GetRecommendedDelaySeconds(int base_delay_seconds); - - protected: - virtual void ThreadMain(); - void ThreadMainLoop(); - - virtual void SetConnected(bool connected); - - virtual void SetSyncerPollingInterval(base::TimeDelta interval); - virtual void SetSyncerShortPollInterval(base::TimeDelta interval); - - // Needed to emulate the behavior of pthread_create, which synchronously - // started the thread and set the value of thread_running_ to true. - // We can't quite match that because we asynchronously post the task, - // which opens a window for Stop to get called before the task actually - // makes it. To prevent this, we block Start() until we're sure it's ok. - base::WaitableEvent thread_main_started_; - - // Handle of the running thread. - base::Thread thread_; + // Invoke the Syncer to perform a sync. + void DoSyncSessionJob(const SyncSessionJob& job); - // Fields that are modified / accessed by multiple threads go in this struct - // for clarity and explicitness. - struct ProtectedFields { - ProtectedFields(); - ~ProtectedFields(); + // Called after the Syncer has performed the sync represented by |job|, to + // reset our state. + void FinishSyncSessionJob(const SyncSessionJob& job); - // False when we want to stop the thread. - bool stop_syncer_thread_; + // Record important state that might be needed in future syncs, such as which + // data types may require cleanup. + void UpdateCarryoverSessionState(const SyncSessionJob& old_job); - // True when a pause was requested. - bool pause_requested_; + // Helper to FinishSyncSessionJob to schedule the next sync operation. + void ScheduleNextSync(const SyncSessionJob& old_job); - // True when the thread is paused. - bool paused_; + // Helper to configure polling intervals. Used by Start and ScheduleNextSync. + void AdjustPolling(const SyncSessionJob* old_job); - Syncer* syncer_; + // Helper to ScheduleNextSync in case of consecutive sync errors. + void HandleConsecutiveContinuationError(const SyncSessionJob& old_job); - // State of the server connection. - bool connected_; + // Determines if it is legal to run |job| by checking current + // operational mode, backoff or throttling, freshness + // (so we don't make redundant syncs), and connection. + bool ShouldRunJob(const SyncSessionJob& job); - // kUnknown if there is no pending nudge. (Theoretically, there - // could be a pending nudge of type kUnknown, so it's better to - // check pending_nudge_time_.) - NudgeSource pending_nudge_source_; + // Decide whether we should CONTINUE, SAVE or DROP the job. + JobProcessDecision DecideOnJob(const SyncSessionJob& job); - // Map of all datatypes that are requesting a nudge. Can be union - // from multiple nudges that are coalesced. In addition, we - // optionally track a payload associated with each datatype (most recent - // payload overwrites old ones). These payloads are used by the download - // updates command and can contain datatype specific information the server - // might use. - sessions::TypePayloadMap pending_nudge_types_; + // Decide on whether to CONTINUE, SAVE or DROP the job when we are in + // backoff mode. + JobProcessDecision DecideWhileInWaitInterval(const SyncSessionJob& job); - // null iff there is no pending nudge. - base::TimeTicks pending_nudge_time_; + // Saves the job for future execution. Note: It drops all the poll jobs. + void SaveJob(const SyncSessionJob& job); - // The wait interval for to the current iteration of our main loop. This is - // only written to by the syncer thread, and since the only reader from a - // different thread (NudgeSync) is called at totally random times, we don't - // really need to access mutually exclusively as the data races that exist - // are intrinsic, but do so anyway and avoid using 'volatile'. - WaitInterval current_wait_interval_; - } vault_; + // Coalesces the current job with the pending nudge. + void InitOrCoalescePendingJob(const SyncSessionJob& job); - // Gets signaled whenever a thread outside of the syncer thread changes a - // protected field in the vault_. - base::ConditionVariable vault_field_changed_; + // 'Impl' here refers to real implementation of public functions, running on + // |thread_|. + void StartImpl(Mode mode, ModeChangeCallback* callback); + void ScheduleNudgeImpl( + const base::TimeDelta& delay, + sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source, + const syncable::ModelTypePayloadMap& types_with_payloads, + bool is_canary_job, const tracked_objects::Location& nudge_location); + void ScheduleConfigImpl(const ModelSafeRoutingInfo& routing_info, + const std::vector<ModelSafeWorker*>& workers, + const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource source); + void ScheduleClearUserDataImpl(); - // Used to lock everything in |vault_|. - base::Lock lock_; + // Returns true if the client is currently in exponential backoff. + bool IsBackingOff() const; - private: - // Threshold multipler for how long before user should be considered idle. - static const int kPollBackoffThresholdMultiplier = 10; + // Helper to signal all listeners registered with |session_context_|. + void Notify(SyncEngineEvent::EventCause cause); - // SyncSession::Delegate implementation. - virtual void OnSilencedUntil(const base::TimeTicks& silenced_until); - virtual bool IsSyncingCurrentlySilenced(); - virtual void OnReceivedShortPollIntervalUpdate( - const base::TimeDelta& new_interval); - virtual void OnReceivedLongPollIntervalUpdate( - const base::TimeDelta& new_interval); - virtual void OnShouldStopSyncingPermanently(); + // Callback to change backoff state. + void DoCanaryJob(); + void Unthrottle(); - void HandleServerConnectionEvent(const ServerConnectionEvent& event); - - // Collect all local state required for a sync and build a SyncSession out of - // it, reset state for the next time, and performs the sync cycle. - // See |GetAndResetNudgeSource| for details on what 'reset' means. - // |was_nudged| is set to true if the session returned is fulfilling a nudge. - // Returns once the session is finished (HasMoreToSync returns false). The - // caller owns the returned SyncSession. - sessions::SyncSession* SyncMain(Syncer* syncer, - bool was_throttled, - bool continue_sync_cycle, - bool* initial_sync_for_thread, - bool* was_nudged); - - // Calculates the next sync wait time and exponential backoff state. - // last_poll_wait is the time duration of the previous polling timeout which - // was used. user_idle_milliseconds is updated by this method, and is a report - // of the full amount of time since the last period of activity for the user. - // The continue_sync_cycle parameter is used to determine whether or not we - // are calculating a polling wait time that is a continuation of an sync cycle - // which terminated while the syncer still had work to do. was_nudged is used - // in case of exponential backoff so we only allow one nudge per backoff - // interval. - WaitInterval CalculatePollingWaitTime( - int last_poll_wait, // in s - int* user_idle_milliseconds, - bool* continue_sync_cycle, - bool was_nudged); - - // Helper to above function, considers effect of user idle time. - virtual int CalculateSyncWaitTime(int last_wait, int user_idle_ms); - - // Resets the source tracking state to a clean slate and returns the current - // state in a SyncSourceInfo. - // The initial sync boolean is updated if read as a sentinel. The following - // two methods work in concert to achieve this goal. - // If |was_throttled| was true, this still discards elapsed nudges, but we - // treat the request as a periodic poll rather than a nudge from a source. - // Builds a SyncSourceInfo and returns whether a nudge occurred in the - // |was_nudged| parameter. - sessions::SyncSourceInfo GetAndResetNudgeSource(bool was_throttled, - bool continue_sync_cycle, - bool* initial_sync, - bool* was_nudged); - - sessions::SyncSourceInfo MakeSyncSourceInfo( - bool nudged, - NudgeSource nudge_source, - const sessions::TypePayloadMap& model_types_with_payloads, - bool* initial_sync); - - int UserIdleTime(); - - void WaitUntilConnectedOrQuit(); - - // The thread will remain in this method until a resume is requested - // or shutdown is started. - void PauseUntilResumedOrQuit(); - - void EnterPausedState(); - - void ExitPausedState(); - - // For unit tests only. - virtual void DisableIdleDetection(); - - // This sets all conditions for syncer thread termination but does not - // actually join threads. It is expected that Stop will be called at some - // time after to fully stop and clean up. - void RequestSyncerExitAndSetThreadStopConditions(); + // Executes the pending job. Called whenever an event occurs that may + // change conditions permitting a job to run. Like when network connection is + // re-established, mode changes etc. + void DoPendingJobIfPossible(bool is_canary_job); - void Notify(SyncEngineEvent::EventCause cause); + // The pointer is owned by the caller. + browser_sync::sessions::SyncSession* CreateSyncSession( + const browser_sync::sessions::SyncSourceInfo& info); + + // Creates a session for a poll and performs the sync. + void PollTimerCallback(); + + // Assign |start| and |end| to appropriate SyncerStep values for the + // specified |purpose|. + void SetSyncerStepsForPurpose(SyncSessionJob::SyncSessionJobPurpose purpose, + SyncerStep* start, + SyncerStep* end); - scoped_ptr<EventListenerHookup> conn_mgr_hookup_; + // Initializes the hookup between the ServerConnectionManager and us. + void WatchConnectionManager(); + + // Used to update |server_connection_ok_|, see below. + void CheckServerConnectionManagerStatus( + HttpResponse::ServerConnectionCode code); + + // Called once the first time thread_ is started to broadcast an initial + // session snapshot containing data like initial_sync_ended. Important when + // the client starts up and does not need to perform an initial sync. + void SendInitialSnapshot(); + + base::Thread thread_; // Modifiable versions of kDefaultLongPollIntervalSeconds which can be // updated by the server. - int syncer_short_poll_interval_seconds_; - int syncer_long_poll_interval_seconds_; - - // The time we wait between polls in seconds. This is used as lower bound on - // our wait time. Updated once per loop from the command line flag. - int syncer_polling_interval_; - - // The upper bound on the nominal wait between polls in seconds. Note that - // this bounds the "nominal" poll interval, while the the actual interval - // also takes previous failures into account. - int syncer_max_interval_; - - // This causes syncer to start syncing ASAP. If the rate of requests is too - // high the request will be silently dropped. mutex_ should be held when - // this is called. - void NudgeSyncImpl( - int milliseconds_from_now, - NudgeSource source, - const sessions::TypePayloadMap& model_types_with_payloads); - -#if defined(OS_LINUX) - // On Linux, we need this information in order to query idle time. - scoped_ptr<IdleQueryLinux> idle_query_; -#endif + base::TimeDelta syncer_short_poll_interval_seconds_; + base::TimeDelta syncer_long_poll_interval_seconds_; - scoped_ptr<sessions::SyncSessionContext> session_context_; + // Periodic timer for polling. See AdjustPolling. + base::RepeatingTimer<SyncerThread> poll_timer_; + + // The mode of operation. We don't use a lock, see Start(...) comment. + Mode mode_; + + // TODO(tim): Bug 26339. This needs to track more than just time I think, + // since the nudges could be for different types. Current impl doesn't care. + base::TimeTicks last_sync_session_end_time_; - // Set whenever the server instructs us to stop sending it requests until - // a specified time, and reset for each call to SyncShare. (Note that the - // WaitInterval::THROTTLED contract is such that we don't call SyncShare at - // all until the "silenced until" embargo expires.) - base::TimeTicks silenced_until_; + // Have we observed a valid server connection? + bool server_connection_ok_; - // Useful for unit tests - bool disable_idle_detection_; + // Tracks in-flight nudges so we can coalesce. + scoped_ptr<SyncSessionJob> pending_nudge_; + + // Current wait state. Null if we're not in backoff and not throttled. + scoped_ptr<WaitInterval> wait_interval_; + + scoped_ptr<DelayProvider> delay_provider_; + + // Invoked to run through the sync cycle. + scoped_ptr<Syncer> syncer_; + + scoped_ptr<sessions::SyncSessionContext> session_context_; DISALLOW_COPY_AND_ASSIGN(SyncerThread); }; + } // namespace browser_sync +// The SyncerThread manages its own internal thread and thus outlives it. We +// don't need refcounting for posting tasks to this internal thread. +DISABLE_RUNNABLE_METHOD_REFCOUNT(browser_sync::SyncerThread); + #endif // CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD_H_ diff --git a/chrome/browser/sync/engine/syncer_thread2.cc b/chrome/browser/sync/engine/syncer_thread2.cc deleted file mode 100644 index ef46360..0000000 --- a/chrome/browser/sync/engine/syncer_thread2.cc +++ /dev/null @@ -1,589 +0,0 @@ -// Copyright (c) 2011 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/sync/engine/syncer_thread2.h" - -#include <algorithm> - -#include "base/rand_util.h" -#include "chrome/browser/sync/engine/syncer.h" - -using base::TimeDelta; -using base::TimeTicks; - -namespace browser_sync { - -using sessions::SyncSession; -using sessions::SyncSessionSnapshot; -using sessions::SyncSourceInfo; -using sessions::TypePayloadMap; -using syncable::ModelTypeBitSet; -using sync_pb::GetUpdatesCallerInfo; - -namespace s3 { - -struct SyncerThread::WaitInterval { - enum Mode { - // A wait interval whose duration has been affected by exponential - // backoff. - // EXPONENTIAL_BACKOFF intervals are nudge-rate limited to 1 per interval. - EXPONENTIAL_BACKOFF, - // A server-initiated throttled interval. We do not allow any syncing - // during such an interval. - THROTTLED, - }; - Mode mode; - - // This bool is set to true if we have observed a nudge during this - // interval and mode == EXPONENTIAL_BACKOFF. - bool had_nudge; - base::TimeDelta length; - base::OneShotTimer<SyncerThread> timer; - WaitInterval(Mode mode, base::TimeDelta length); -}; - -struct SyncerThread::SyncSessionJob { - SyncSessionJobPurpose purpose; - base::TimeTicks scheduled_start; - linked_ptr<sessions::SyncSession> session; -}; - -SyncerThread::DelayProvider::DelayProvider() {} -SyncerThread::DelayProvider::~DelayProvider() {} - -TimeDelta SyncerThread::DelayProvider::GetDelay( - const base::TimeDelta& last_delay) { - return SyncerThread::GetRecommendedDelay(last_delay); -} - -SyncerThread::WaitInterval::WaitInterval(Mode mode, TimeDelta length) - : mode(mode), had_nudge(false), length(length) { } - -SyncerThread::SyncerThread(sessions::SyncSessionContext* context, - Syncer* syncer) - : thread_("SyncEngine_SyncerThread"), - syncer_short_poll_interval_seconds_( - TimeDelta::FromSeconds(kDefaultShortPollIntervalSeconds)), - syncer_long_poll_interval_seconds_( - TimeDelta::FromSeconds(kDefaultLongPollIntervalSeconds)), - mode_(NORMAL_MODE), - server_connection_ok_(false), - delay_provider_(new DelayProvider()), - syncer_(syncer), - session_context_(context) { -} - -SyncerThread::~SyncerThread() { - DCHECK(!thread_.IsRunning()); -} - -void SyncerThread::Start(Mode mode) { - if (!thread_.IsRunning() && !thread_.Start()) { - NOTREACHED() << "Unable to start SyncerThread."; - return; - } - - thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - this, &SyncerThread::StartImpl, mode)); -} - -void SyncerThread::StartImpl(Mode mode) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - DCHECK(!session_context_->account_name().empty()); - DCHECK(syncer_.get()); - mode_ = mode; - AdjustPolling(NULL); // Will kick start poll timer if needed. -} - -bool SyncerThread::ShouldRunJob(SyncSessionJobPurpose purpose, - const TimeTicks& scheduled_start) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - - // Check wait interval. - if (wait_interval_.get()) { - // TODO(tim): Consider different handling for CLEAR_USER_DATA (i.e. permit - // when throttled). - if (wait_interval_->mode == WaitInterval::THROTTLED) - return false; - - DCHECK_EQ(wait_interval_->mode, WaitInterval::EXPONENTIAL_BACKOFF); - if ((purpose != NUDGE) || wait_interval_->had_nudge) - return false; - } - - // Mode / purpose contract (See 'Mode' enum in header). Don't run jobs that - // were intended for a normal sync if we are in configuration mode, and vice - // versa. - switch (mode_) { - case CONFIGURATION_MODE: - if (purpose != CONFIGURATION) - return false; - break; - case NORMAL_MODE: - if (purpose == CONFIGURATION) - return false; - break; - default: - NOTREACHED() << "Unknown SyncerThread Mode: " << mode_; - return false; - } - - // Continuation NUDGE tasks have priority over POLLs because they are the - // only tasks that trigger exponential backoff, so this prevents them from - // being starved from running (e.g. due to a very, very low poll interval, - // such as 0ms). It's rare that this would ever matter in practice. - if (purpose == POLL && (pending_nudge_.get() && - pending_nudge_->session->source().updates_source == - GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION)) { - return false; - } - - // Freshness condition. - if (purpose == NUDGE && - (scheduled_start < last_sync_session_end_time_)) { - return false; - } - - return server_connection_ok_; -} - -GetUpdatesCallerInfo::GetUpdatesSource GetUpdatesFromNudgeSource( - NudgeSource source) { - switch (source) { - case NUDGE_SOURCE_NOTIFICATION: - return GetUpdatesCallerInfo::NOTIFICATION; - case NUDGE_SOURCE_LOCAL: - return GetUpdatesCallerInfo::LOCAL; - case NUDGE_SOURCE_CONTINUATION: - return GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION; - case NUDGE_SOURCE_UNKNOWN: - return GetUpdatesCallerInfo::UNKNOWN; - default: - NOTREACHED(); - return GetUpdatesCallerInfo::UNKNOWN; - } -} - -// Functor for std::find_if to search by ModelSafeGroup. -struct ModelSafeWorkerGroupIs { - explicit ModelSafeWorkerGroupIs(ModelSafeGroup group) : group(group) {} - bool operator()(ModelSafeWorker* w) { - return group == w->GetModelSafeGroup(); - } - ModelSafeGroup group; -}; - -void SyncerThread::ScheduleClearUserData() { - if (!thread_.IsRunning()) { - NOTREACHED(); - return; - } - thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - this, &SyncerThread::ScheduleClearUserDataImpl)); -} - -void SyncerThread::ScheduleNudge(const TimeDelta& delay, - NudgeSource source, const ModelTypeBitSet& types) { - if (!thread_.IsRunning()) { - NOTREACHED(); - return; - } - - TypePayloadMap types_with_payloads = - sessions::MakeTypePayloadMapFromBitSet(types, std::string()); - thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - this, &SyncerThread::ScheduleNudgeImpl, delay, source, - types_with_payloads)); -} - -void SyncerThread::ScheduleNudgeWithPayloads(const TimeDelta& delay, - NudgeSource source, const TypePayloadMap& types_with_payloads) { - if (!thread_.IsRunning()) { - NOTREACHED(); - return; - } - - thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - this, &SyncerThread::ScheduleNudgeImpl, delay, source, - types_with_payloads)); -} - -void SyncerThread::ScheduleClearUserDataImpl() { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - SyncSession* session = new SyncSession(session_context_.get(), this, - SyncSourceInfo(), ModelSafeRoutingInfo(), - std::vector<ModelSafeWorker*>()); - ScheduleSyncSessionJob(TimeDelta::FromSeconds(0), CLEAR_USER_DATA, session); -} - -void SyncerThread::ScheduleNudgeImpl(const TimeDelta& delay, - NudgeSource source, const TypePayloadMap& types_with_payloads) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - TimeTicks rough_start = TimeTicks::Now() + delay; - - // Note we currently nudge for all types regardless of the ones incurring - // the nudge. Doing different would throw off some syncer commands like - // CleanupDisabledTypes. We may want to change this in the future. - ModelSafeRoutingInfo routes; - std::vector<ModelSafeWorker*> workers; - session_context_->registrar()->GetModelSafeRoutingInfo(&routes); - session_context_->registrar()->GetWorkers(&workers); - SyncSourceInfo info(GetUpdatesFromNudgeSource(source), - types_with_payloads); - - scoped_ptr<SyncSession> session(new SyncSession( - session_context_.get(), this, info, routes, workers)); - - if (pending_nudge_.get()) { - if (IsBackingOff() && delay > TimeDelta::FromSeconds(1)) - return; - - pending_nudge_->session->Coalesce(*session.get()); - if (!IsBackingOff()) { - return; - } else { - // Re-schedule the current pending nudge. - SyncSession* s = pending_nudge_->session.get(); - session.reset(new SyncSession(s->context(), s->delegate(), s->source(), - s->routing_info(), s->workers())); - pending_nudge_.reset(); - } - } - ScheduleSyncSessionJob(delay, NUDGE, session.release()); -} - -// Helper to extract the routing info and workers corresponding to types in -// |types| from |registrar|. -void GetModelSafeParamsForTypes(const ModelTypeBitSet& types, - ModelSafeWorkerRegistrar* registrar, ModelSafeRoutingInfo* routes, - std::vector<ModelSafeWorker*>* workers) { - ModelSafeRoutingInfo r_tmp; - std::vector<ModelSafeWorker*> w_tmp; - registrar->GetModelSafeRoutingInfo(&r_tmp); - registrar->GetWorkers(&w_tmp); - - typedef std::vector<ModelSafeWorker*>::const_iterator iter; - for (size_t i = syncable::FIRST_REAL_MODEL_TYPE; i < types.size(); ++i) { - if (!types.test(i)) - continue; - syncable::ModelType t = syncable::ModelTypeFromInt(i); - DCHECK_EQ(1U, r_tmp.count(t)); - (*routes)[t] = r_tmp[t]; - iter it = std::find_if(w_tmp.begin(), w_tmp.end(), - ModelSafeWorkerGroupIs(r_tmp[t])); - if (it != w_tmp.end()) - workers->push_back(*it); - else - NOTREACHED(); - } - - iter it = std::find_if(w_tmp.begin(), w_tmp.end(), - ModelSafeWorkerGroupIs(GROUP_PASSIVE)); - if (it != w_tmp.end()) - workers->push_back(*it); - else - NOTREACHED(); -} - -void SyncerThread::ScheduleConfig(const ModelTypeBitSet& types) { - if (!thread_.IsRunning()) { - NOTREACHED(); - return; - } - - ModelSafeRoutingInfo routes; - std::vector<ModelSafeWorker*> workers; - GetModelSafeParamsForTypes(types, session_context_->registrar(), - &routes, &workers); - - thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( - this, &SyncerThread::ScheduleConfigImpl, routes, workers)); -} - -void SyncerThread::ScheduleConfigImpl(const ModelSafeRoutingInfo& routing_info, - const std::vector<ModelSafeWorker*>& workers) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - - // TODO(tim): config-specific GetUpdatesCallerInfo value? - SyncSession* session = new SyncSession(session_context_.get(), this, - SyncSourceInfo(GetUpdatesCallerInfo::FIRST_UPDATE, - sessions::MakeTypePayloadMapFromRoutingInfo( - routing_info, std::string())), - routing_info, workers); - ScheduleSyncSessionJob(TimeDelta::FromSeconds(0), CONFIGURATION, session); -} - -void SyncerThread::ScheduleSyncSessionJob(const base::TimeDelta& delay, - SyncSessionJobPurpose purpose, sessions::SyncSession* session) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - SyncSessionJob job = {purpose, TimeTicks::Now() + delay, - make_linked_ptr(session)}; - if (purpose == NUDGE) { - DCHECK(!pending_nudge_.get() || pending_nudge_->session.get() == session); - pending_nudge_.reset(new SyncSessionJob(job)); - } - MessageLoop::current()->PostDelayedTask(FROM_HERE, NewRunnableMethod(this, - &SyncerThread::DoSyncSessionJob, job), delay.InMilliseconds()); -} - -void SyncerThread::SetSyncerStepsForPurpose(SyncSessionJobPurpose purpose, - SyncerStep* start, SyncerStep* end) { - *end = SYNCER_END; - switch (purpose) { - case CONFIGURATION: - *start = DOWNLOAD_UPDATES; - *end = APPLY_UPDATES; - return; - case CLEAR_USER_DATA: - *start = CLEAR_PRIVATE_DATA; - return; - case NUDGE: - case POLL: - *start = SYNCER_BEGIN; - return; - default: - NOTREACHED(); - } -} - -void SyncerThread::DoSyncSessionJob(const SyncSessionJob& job) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - - if (job.purpose == NUDGE) { - DCHECK(pending_nudge_.get()); - if (pending_nudge_->session != job.session) - return; // Another nudge must have been scheduled in in the meantime. - pending_nudge_.reset(); - } - - SyncerStep begin(SYNCER_BEGIN); - SyncerStep end(SYNCER_END); - SetSyncerStepsForPurpose(job.purpose, &begin, &end); - - bool has_more_to_sync = true; - bool did_job = false; - while (ShouldRunJob(job.purpose, job.scheduled_start) && has_more_to_sync) { - VLOG(1) << "SyncerThread: Calling SyncShare."; - did_job = true; - // Synchronously perform the sync session from this thread. - syncer_->SyncShare(job.session.get(), begin, end); - has_more_to_sync = job.session->HasMoreToSync(); - if (has_more_to_sync) - job.session->ResetTransientState(); - } - VLOG(1) << "SyncerThread: Done SyncShare looping."; - if (did_job) - FinishSyncSessionJob(job); -} - -void SyncerThread::FinishSyncSessionJob(const SyncSessionJob& job) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - // Update timing information for how often datatypes are triggering nudges. - base::TimeTicks now = TimeTicks::Now(); - if (!last_sync_session_end_time_.is_null()) { - TypePayloadMap::const_iterator iter; - for (iter = job.session->source().types.begin(); - iter != job.session->source().types.end(); - ++iter) { - syncable::PostTimeToTypeHistogram(iter->first, - now - last_sync_session_end_time_); - } - } - last_sync_session_end_time_ = now; - if (IsSyncingCurrentlySilenced()) - return; // Nothing to do. - - VLOG(1) << "Updating the next polling time after SyncMain"; - ScheduleNextSync(job); -} - -void SyncerThread::ScheduleNextSync(const SyncSessionJob& old_job) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - DCHECK(!old_job.session->HasMoreToSync()); - // Note: |num_server_changes_remaining| > 0 here implies that we received a - // broken response while trying to download all updates, because the Syncer - // will loop until this value is exhausted. Also, if unsynced_handles exist - // but HasMoreToSync is false, this implies that the Syncer determined no - // forward progress was possible at this time (an error, such as an HTTP - // 500, is likely to have occurred during commit). - const bool work_to_do = - old_job.session->status_controller()->num_server_changes_remaining() > 0 - || old_job.session->status_controller()->unsynced_handles().size() > 0; - VLOG(1) << "syncer has work to do: " << work_to_do; - - AdjustPolling(&old_job); - - // TODO(tim): Old impl had special code if notifications disabled. Needed? - if (!work_to_do) { - // Success implies backoff relief. Note that if this was a "one-off" job - // (i.e. purpose == CLEAR_USER_DATA), if there was work_to_do before it - // ran this wont have changed, as jobs like this don't run a full sync - // cycle. So we don't need special code here. - wait_interval_.reset(); - return; - } - - if (old_job.session->source().updates_source == - GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION) { - // We don't seem to have made forward progress. Start or extend backoff. - HandleConsecutiveContinuationError(old_job); - } else if (IsBackingOff()) { - // We weren't continuing but we're in backoff; must have been a nudge. - DCHECK_EQ(NUDGE, old_job.purpose); - DCHECK(!wait_interval_->had_nudge); - wait_interval_->had_nudge = true; - wait_interval_->timer.Reset(); - } else { - // We weren't continuing and we aren't in backoff. Schedule a normal - // continuation. - ScheduleNudgeImpl(TimeDelta::FromSeconds(0), NUDGE_SOURCE_CONTINUATION, - old_job.session->source().types); - } -} - -void SyncerThread::AdjustPolling(const SyncSessionJob* old_job) { - DCHECK(thread_.IsRunning()); - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - - TimeDelta poll = (!session_context_->notifications_enabled()) ? - syncer_short_poll_interval_seconds_ : - syncer_long_poll_interval_seconds_; - bool rate_changed = !poll_timer_.IsRunning() || - poll != poll_timer_.GetCurrentDelay(); - - if (old_job && old_job->purpose != POLL && !rate_changed) - poll_timer_.Reset(); - - if (!rate_changed) - return; - - // Adjust poll rate. - poll_timer_.Stop(); - poll_timer_.Start(poll, this, &SyncerThread::PollTimerCallback); -} - -void SyncerThread::HandleConsecutiveContinuationError( - const SyncSessionJob& old_job) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - DCHECK(!IsBackingOff() || !wait_interval_->timer.IsRunning()); - SyncSession* old = old_job.session.get(); - SyncSession* s(new SyncSession(session_context_.get(), this, - old->source(), old->routing_info(), old->workers())); - TimeDelta length = delay_provider_->GetDelay( - IsBackingOff() ? wait_interval_->length : TimeDelta::FromSeconds(1)); - wait_interval_.reset(new WaitInterval(WaitInterval::EXPONENTIAL_BACKOFF, - length)); - SyncSessionJob job = {NUDGE, TimeTicks::Now() + length, - make_linked_ptr(s)}; - pending_nudge_.reset(new SyncSessionJob(job)); - wait_interval_->timer.Start(length, this, &SyncerThread::DoCanaryJob); -} - -// static -TimeDelta SyncerThread::GetRecommendedDelay(const TimeDelta& last_delay) { - if (last_delay.InSeconds() >= kMaxBackoffSeconds) - return TimeDelta::FromSeconds(kMaxBackoffSeconds); - - // This calculates approx. base_delay_seconds * 2 +/- base_delay_seconds / 2 - int64 backoff_s = - std::max(static_cast<int64>(1), - last_delay.InSeconds() * kBackoffRandomizationFactor); - - // Flip a coin to randomize backoff interval by +/- 50%. - int rand_sign = base::RandInt(0, 1) * 2 - 1; - - // Truncation is adequate for rounding here. - backoff_s = backoff_s + - (rand_sign * (last_delay.InSeconds() / kBackoffRandomizationFactor)); - - // Cap the backoff interval. - backoff_s = std::max(static_cast<int64>(1), - std::min(backoff_s, kMaxBackoffSeconds)); - - return TimeDelta::FromSeconds(backoff_s); -} - -void SyncerThread::Stop() { - syncer_->RequestEarlyExit(); // Safe to call from any thread. - thread_.Stop(); - Notify(SyncEngineEvent::SYNCER_THREAD_EXITING); -} - -void SyncerThread::DoCanaryJob() { - DCHECK(pending_nudge_.get()); - wait_interval_->had_nudge = false; - SyncSessionJob copy = {pending_nudge_->purpose, - pending_nudge_->scheduled_start, - pending_nudge_->session}; - DoSyncSessionJob(copy); -} - -void SyncerThread::PollTimerCallback() { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - ModelSafeRoutingInfo r; - std::vector<ModelSafeWorker*> w; - session_context_->registrar()->GetModelSafeRoutingInfo(&r); - session_context_->registrar()->GetWorkers(&w); - TypePayloadMap types_with_payloads = - sessions::MakeTypePayloadMapFromRoutingInfo(r, std::string()); - SyncSourceInfo info(GetUpdatesCallerInfo::PERIODIC, types_with_payloads); - SyncSession* s = new SyncSession(session_context_.get(), this, info, r, w); - ScheduleSyncSessionJob(TimeDelta::FromSeconds(0), POLL, s); -} - -void SyncerThread::Unthrottle() { - DCHECK_EQ(WaitInterval::THROTTLED, wait_interval_->mode); - wait_interval_.reset(); -} - -void SyncerThread::Notify(SyncEngineEvent::EventCause cause) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - session_context_->NotifyListeners(SyncEngineEvent(cause)); -} - -bool SyncerThread::IsBackingOff() const { - return wait_interval_.get() && wait_interval_->mode == - WaitInterval::EXPONENTIAL_BACKOFF; -} - -void SyncerThread::OnSilencedUntil(const base::TimeTicks& silenced_until) { - wait_interval_.reset(new WaitInterval(WaitInterval::THROTTLED, - silenced_until - TimeTicks::Now())); - wait_interval_->timer.Start(wait_interval_->length, this, - &SyncerThread::Unthrottle); -} - -bool SyncerThread::IsSyncingCurrentlySilenced() { - return wait_interval_.get() && wait_interval_->mode == - WaitInterval::THROTTLED; -} - -void SyncerThread::OnReceivedShortPollIntervalUpdate( - const base::TimeDelta& new_interval) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - syncer_short_poll_interval_seconds_ = new_interval; -} - -void SyncerThread::OnReceivedLongPollIntervalUpdate( - const base::TimeDelta& new_interval) { - DCHECK_EQ(MessageLoop::current(), thread_.message_loop()); - syncer_long_poll_interval_seconds_ = new_interval; -} - -void SyncerThread::OnShouldStopSyncingPermanently() { - syncer_->RequestEarlyExit(); // Thread-safe. - Notify(SyncEngineEvent::STOP_SYNCING_PERMANENTLY); -} - -void SyncerThread::OnServerConnectionEvent( - const ServerConnectionEvent& event) { - NOTIMPLEMENTED(); -} - -void SyncerThread::set_notifications_enabled(bool notifications_enabled) { - session_context_->set_notifications_enabled(notifications_enabled); -} - -} // s3 -} // browser_sync diff --git a/chrome/browser/sync/engine/syncer_thread2.h b/chrome/browser/sync/engine/syncer_thread2.h deleted file mode 100644 index 89e0c96..0000000 --- a/chrome/browser/sync/engine/syncer_thread2.h +++ /dev/null @@ -1,227 +0,0 @@ -// Copyright (c) 2011 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. -// -// A class to run the syncer on a thread. -#ifndef CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD2_H_ -#define CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD2_H_ -#pragma once - -#include "base/linked_ptr.h" -#include "base/observer_list.h" -#include "base/scoped_ptr.h" -#include "base/task.h" -#include "base/threading/thread.h" -#include "base/time.h" -#include "base/timer.h" -#include "chrome/browser/sync/engine/nudge_source.h" -#include "chrome/browser/sync/engine/polling_constants.h" -#include "chrome/browser/sync/engine/syncer.h" -#include "chrome/browser/sync/sessions/sync_session.h" -#include "chrome/browser/sync/sessions/sync_session_context.h" - -namespace browser_sync { - -struct ServerConnectionEvent; - -namespace s3 { - -class SyncerThread : public sessions::SyncSession::Delegate { - public: - enum Mode { - // In this mode, the thread only performs configuration tasks. This is - // designed to make the case where we want to download updates for a - // specific type only, and not continue syncing until we are moved into - // normal mode. - CONFIGURATION_MODE, - // Resumes polling and allows nudges, drops configuration tasks. Runs - // through entire sync cycle. - NORMAL_MODE, - }; - - // Takes ownership of both |context| and |syncer|. - SyncerThread(sessions::SyncSessionContext* context, Syncer* syncer); - virtual ~SyncerThread(); - - // Change the mode of operation. - // We don't use a lock when changing modes, so we won't cause currently - // scheduled jobs to adhere to the new mode. We could protect it, but it - // doesn't buy very much as a) a session could already be in progress and it - // will continue no matter what, b) the scheduled sessions already contain - // all their required state and won't be affected by potential change at - // higher levels (i.e. the registrar), and c) we service tasks FIFO, so once - // the mode changes all future jobs will be run against the updated mode. - void Start(Mode mode); - - // Joins on the thread as soon as possible (currently running session - // completes). - void Stop(); - - // The meat and potatoes. - void ScheduleNudge(const base::TimeDelta& delay, NudgeSource source, - const syncable::ModelTypeBitSet& types); - void ScheduleNudgeWithPayloads( - const base::TimeDelta& delay, NudgeSource source, - const sessions::TypePayloadMap& types_with_payloads); - void ScheduleConfig(const syncable::ModelTypeBitSet& types); - void ScheduleClearUserData(); - - // Change status of notifications in the SyncSessionContext. - void set_notifications_enabled(bool notifications_enabled); - - // DDOS avoidance function. Calculates how long we should wait before trying - // again after a failed sync attempt, where the last delay was |base_delay|. - // TODO(tim): Look at URLRequestThrottlerEntryInterface. - static base::TimeDelta GetRecommendedDelay(const base::TimeDelta& base_delay); - - // SyncSession::Delegate implementation. - virtual void OnSilencedUntil(const base::TimeTicks& silenced_until); - virtual bool IsSyncingCurrentlySilenced(); - virtual void OnReceivedShortPollIntervalUpdate( - const base::TimeDelta& new_interval); - virtual void OnReceivedLongPollIntervalUpdate( - const base::TimeDelta& new_interval); - virtual void OnShouldStopSyncingPermanently(); - - private: - friend class SyncerThread2Test; - - // State pertaining to exponential backoff or throttling periods. - struct WaitInterval; - - // An enum used to describe jobs for scheduling purposes. - enum SyncSessionJobPurpose { - // Our poll timer schedules POLL jobs periodically based on a server - // assigned poll interval. - POLL, - // A nudge task can come from a variety of components needing to force - // a sync. The source is inferable from |session.source()|. - NUDGE, - // The user invoked a function in the UI to clear their entire account - // and stop syncing (globally). - CLEAR_USER_DATA, - // Typically used for fetching updates for a subset of the enabled types - // during initial sync or reconfiguration. We don't run all steps of - // the sync cycle for these (e.g. CleanupDisabledTypes is skipped). - CONFIGURATION, - }; - - // Internal state for every sync task that is scheduled. - struct SyncSessionJob; - - // A component used to get time delays associated with exponential backoff. - // Encapsulated into a class to facilitate testing. - class DelayProvider { - public: - DelayProvider(); - virtual base::TimeDelta GetDelay(const base::TimeDelta& last_delay); - virtual ~DelayProvider(); - private: - DISALLOW_COPY_AND_ASSIGN(DelayProvider); - }; - - // Helper to assemble a job and post a delayed task to sync. - void ScheduleSyncSessionJob(const base::TimeDelta& delay, - SyncSessionJobPurpose purpose, - sessions::SyncSession* session); - - // Invoke the Syncer to perform a sync. - void DoSyncSessionJob(const SyncSessionJob& job); - - // Called after the Syncer has performed the sync represented by |job|, to - // reset our state. - void FinishSyncSessionJob(const SyncSessionJob& job); - - // Helper to FinishSyncSessionJob to schedule the next sync operation. - void ScheduleNextSync(const SyncSessionJob& old_job); - - // Helper to configure polling intervals. Used by Start and ScheduleNextSync. - void AdjustPolling(const SyncSessionJob* old_job); - - // Helper to ScheduleNextSync in case of consecutive sync errors. - void HandleConsecutiveContinuationError(const SyncSessionJob& old_job); - - // Determines if it is legal to run a sync job for |purpose| at - // |scheduled_start|. This checks current operational mode, backoff or - // throttling, freshness (so we don't make redundant syncs), and connection. - bool ShouldRunJob(SyncSessionJobPurpose purpose, - const base::TimeTicks& scheduled_start); - - // 'Impl' here refers to real implementation of public functions, running on - // |thread_|. - void StartImpl(Mode mode); - void ScheduleNudgeImpl(const base::TimeDelta& delay, - NudgeSource source, - const sessions::TypePayloadMap& types_with_payloads); - void ScheduleConfigImpl(const ModelSafeRoutingInfo& routing_info, - const std::vector<ModelSafeWorker*>& workers); - void ScheduleClearUserDataImpl(); - - // Returns true if the client is currently in exponential backoff. - bool IsBackingOff() const; - - // Helper to signal all listeners registered with |session_context_|. - void Notify(SyncEngineEvent::EventCause cause); - - // ServerConnectionEventListener implementation. - // TODO(tim): schedule a nudge when valid connection detected? in 1 minute? - virtual void OnServerConnectionEvent(const ServerConnectionEvent& event); - - // Callback to change backoff state. - void DoCanaryJob(); - void Unthrottle(); - - // Creates a session for a poll and performs the sync. - void PollTimerCallback(); - - // Assign |start| and |end| to appropriate SyncerStep values for the - // specified |purpose|. - void SetSyncerStepsForPurpose(SyncSessionJobPurpose purpose, - SyncerStep* start, - SyncerStep* end); - - base::Thread thread_; - - // Modifiable versions of kDefaultLongPollIntervalSeconds which can be - // updated by the server. - base::TimeDelta syncer_short_poll_interval_seconds_; - base::TimeDelta syncer_long_poll_interval_seconds_; - - // Periodic timer for polling. See AdjustPolling. - base::RepeatingTimer<SyncerThread> poll_timer_; - - // The mode of operation. We don't use a lock, see Start(...) comment. - Mode mode_; - - // TODO(tim): Bug 26339. This needs to track more than just time I think, - // since the nudges could be for different types. Current impl doesn't care. - base::TimeTicks last_sync_session_end_time_; - - // Have we observed a valid server connection? - bool server_connection_ok_; - - // Tracks in-flight nudges so we can coalesce. - scoped_ptr<SyncSessionJob> pending_nudge_; - - // Current wait state. Null if we're not in backoff and not throttled. - scoped_ptr<WaitInterval> wait_interval_; - - scoped_ptr<DelayProvider> delay_provider_; - - // Invoked to run through the sync cycle. - scoped_ptr<Syncer> syncer_; - - scoped_ptr<sessions::SyncSessionContext> session_context_; - - DISALLOW_COPY_AND_ASSIGN(SyncerThread); -}; - -} // namespace s3 - -} // namespace browser_sync - -// The SyncerThread manages its own internal thread and thus outlives it. We -// don't need refcounting for posting tasks to this internal thread. -DISABLE_RUNNABLE_METHOD_REFCOUNT(browser_sync::s3::SyncerThread); - -#endif // CHROME_BROWSER_SYNC_ENGINE_SYNCER_THREAD2_H_ diff --git a/chrome/browser/sync/engine/syncer_thread2_unittest.cc b/chrome/browser/sync/engine/syncer_thread2_unittest.cc index 0a54d02..d231a06 100644 --- a/chrome/browser/sync/engine/syncer_thread2_unittest.cc +++ b/chrome/browser/sync/engine/syncer_thread2_unittest.cc @@ -6,8 +6,9 @@ #include "base/test/test_timeouts.h" #include "chrome/browser/sync/engine/mock_model_safe_workers.h" #include "chrome/browser/sync/engine/syncer.h" -#include "chrome/browser/sync/engine/syncer_thread2.h" +#include "chrome/browser/sync/engine/syncer_thread.h" #include "chrome/browser/sync/sessions/test_util.h" +#include "chrome/test/sync/engine/mock_connection_manager.h" #include "chrome/test/sync/engine/test_directory_setter_upper.h" #include "testing/gtest/include/gtest/gtest.h" #include "testing/gmock/include/gmock/gmock.h" @@ -36,8 +37,6 @@ class MockSyncer : public Syncer { SyncerStep)); }; -namespace s3 { - // Used when tests want to record syncing activity to examine later. struct SyncShareRecords { std::vector<TimeTicks> times; @@ -46,7 +45,6 @@ struct SyncShareRecords { // Convenient to use in tests wishing to analyze SyncShare calls over time. static const size_t kMinNumSamples = 5; - class SyncerThread2Test : public testing::Test { public: class MockDelayProvider : public SyncerThread::DelayProvider { @@ -59,18 +57,33 @@ class SyncerThread2Test : public testing::Test { syncer_ = new MockSyncer(); delay_ = NULL; registrar_.reset(MockModelSafeWorkerRegistrar::PassiveBookmarks()); - context_ = new SyncSessionContext(NULL, syncdb_.manager(), + connection_.reset(new MockConnectionManager(syncdb_.manager(), "Test")); + connection_->SetServerReachable(); + context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(), + registrar_.get(), std::vector<SyncEngineEventListener*>()); + context_->set_notifications_enabled(true); + context_->set_account_name("Test"); + syncer_thread_.reset(new SyncerThread(context_, syncer_)); + } + + virtual void SetUpWithTypes(syncable::ModelTypeBitSet types) { + syncdb_.SetUp(); + syncer_ = new MockSyncer(); + delay_ = NULL; + registrar_.reset(MockModelSafeWorkerRegistrar::PassiveForTypes(types)); + connection_.reset(new MockConnectionManager(syncdb_.manager(), "Test")); + connection_->SetServerReachable(); + context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(), registrar_.get(), std::vector<SyncEngineEventListener*>()); context_->set_notifications_enabled(true); context_->set_account_name("Test"); syncer_thread_.reset(new SyncerThread(context_, syncer_)); - // TODO(tim): Once the SCM is hooked up, remove this. - syncer_thread_->server_connection_ok_ = true; } SyncerThread* syncer_thread() { return syncer_thread_.get(); } MockSyncer* syncer() { return syncer_; } MockDelayProvider* delay() { return delay_; } + MockConnectionManager* connection() { return connection_.get(); } TimeDelta zero() { return TimeDelta::FromSeconds(0); } TimeDelta timeout() { return TimeDelta::FromMilliseconds(TestTimeouts::action_timeout_ms()); @@ -96,8 +109,9 @@ class SyncerThread2Test : public testing::Test { bool GetBackoffAndResetTest(base::WaitableEvent* done) { syncable::ModelTypeBitSet nudge_types; - syncer_thread()->Start(SyncerThread::NORMAL_MODE); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types, + FROM_HERE); done->TimedWait(timeout()); TearDown(); done->Reset(); @@ -130,12 +144,17 @@ class SyncerThread2Test : public testing::Test { event->Signal(); } - // Compare a ModelTyepBitSet to a TypePayloadMap, ignoring payload values. - bool CompareModelTypeBitSetToTypePayloadMap( + static void QuitMessageLoop() { + MessageLoop::current()->Quit(); + } + + // Compare a ModelTypeBitSet to a ModelTypePayloadMap, ignoring + // payload values. + bool CompareModelTypeBitSetToModelTypePayloadMap( const syncable::ModelTypeBitSet& lhs, - const sessions::TypePayloadMap& rhs) { + const syncable::ModelTypePayloadMap& rhs) { size_t count = 0; - for (sessions::TypePayloadMap::const_iterator i = rhs.begin(); + for (syncable::ModelTypePayloadMap::const_iterator i = rhs.begin(); i != rhs.end(); ++i, ++count) { if (!lhs.test(i->first)) return false; @@ -145,8 +164,11 @@ class SyncerThread2Test : public testing::Test { return true; } + SyncSessionContext* context() { return context_; } + private: scoped_ptr<SyncerThread> syncer_thread_; + scoped_ptr<MockConnectionManager> connection_; SyncSessionContext* context_; MockSyncer* syncer_; MockDelayProvider* delay_; @@ -178,7 +200,7 @@ ACTION_P(SignalEvent, event) { // Test nudge scheduling. TEST_F(SyncerThread2Test, Nudge) { - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords records; syncable::ModelTypeBitSet model_types; @@ -188,11 +210,12 @@ TEST_F(SyncerThread2Test, Nudge) { .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, &done)))) .RetiresOnSaturation(); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types, + FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records.snapshots.size()); - EXPECT_TRUE(CompareModelTypeBitSetToTypePayloadMap(model_types, + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, records.snapshots[0]->source.updates_source); @@ -204,19 +227,162 @@ TEST_F(SyncerThread2Test, Nudge) { EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records2, 1U, &done)))); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types, + FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records2.snapshots.size()); - EXPECT_TRUE(CompareModelTypeBitSetToTypePayloadMap(model_types, + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, records2.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, records2.snapshots[0]->source.updates_source); } +// Make sure a regular config command is scheduled fine in the absence of any +// errors. +TEST_F(SyncerThread2Test, Config) { + base::WaitableEvent done(false, false); + SyncShareRecords records; + syncable::ModelTypeBitSet model_types; + model_types[syncable::BOOKMARKS] = true; + + EXPECT_CALL(*syncer(), SyncShare(_,_,_)) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), + WithArg<0>(RecordSyncShare(&records, 1U, &done)))); + + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); + + syncer_thread()->ScheduleConfig(model_types); + done.TimedWait(timeout()); + + EXPECT_EQ(1U, records.snapshots.size()); + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, + records.snapshots[0]->source.types)); + EXPECT_EQ(GetUpdatesCallerInfo::FIRST_UPDATE, + records.snapshots[0]->source.updates_source); +} + +// Simulate a failure and make sure the config request is retried. +TEST_F(SyncerThread2Test, ConfigWithBackingOff) { + base::WaitableEvent done(false, false); + base::WaitableEvent* dummy = NULL; + UseMockDelayProvider(); + EXPECT_CALL(*delay(), GetDelay(_)) + .WillRepeatedly(Return(TimeDelta::FromMilliseconds(1))); + SyncShareRecords records; + syncable::ModelTypeBitSet model_types; + model_types[syncable::BOOKMARKS] = true; + + EXPECT_CALL(*syncer(), SyncShare(_,_,_)) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), + WithArg<0>(RecordSyncShare(&records, 1U, dummy)))) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), + WithArg<0>(RecordSyncShare(&records, 1U, &done)))); + + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); + + syncer_thread()->ScheduleConfig(model_types); + done.TimedWait(timeout()); + + EXPECT_EQ(2U, records.snapshots.size()); + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, + records.snapshots[1]->source.types)); + EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION, + records.snapshots[1]->source.updates_source); +} + +// Issue 2 config commands. Second one right after the first has failed +// and make sure LATEST is executed. +TEST_F(SyncerThread2Test, MultipleConfigWithBackingOff) { + syncable::ModelTypeBitSet model_types1, model_types2; + model_types1[syncable::BOOKMARKS] = true; + model_types2[syncable::AUTOFILL] = true; + SetUpWithTypes(model_types1 | model_types2); + base::WaitableEvent done(false, false); + base::WaitableEvent done1(false, false); + base::WaitableEvent* dummy = NULL; + UseMockDelayProvider(); + EXPECT_CALL(*delay(), GetDelay(_)) + .WillRepeatedly(Return(TimeDelta::FromMilliseconds(30))); + SyncShareRecords records; + + EXPECT_CALL(*syncer(), SyncShare(_,_,_)) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), + WithArg<0>(RecordSyncShare(&records, 1U, dummy)))) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), + WithArg<0>(RecordSyncShare(&records, 1U, &done1)))) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), + WithArg<0>(RecordSyncShare(&records, 1U, &done)))); + + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); + + syncer_thread()->ScheduleConfig(model_types1); + + // done1 indicates the first config failed. + done1.TimedWait(timeout()); + syncer_thread()->ScheduleConfig(model_types2); + done.TimedWait(timeout()); + + EXPECT_EQ(3U, records.snapshots.size()); + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types2, + records.snapshots[2]->source.types)); + EXPECT_EQ(GetUpdatesCallerInfo::FIRST_UPDATE, + records.snapshots[2]->source.updates_source); +} + +// Issue a nudge when the config has failed. Make sure both the config and +// nudge are executed. +TEST_F(SyncerThread2Test, NudgeWithConfigWithBackingOff) { + syncable::ModelTypeBitSet model_types; + model_types[syncable::BOOKMARKS] = true; + base::WaitableEvent done(false, false); + base::WaitableEvent done1(false, false); + base::WaitableEvent done2(false, false); + base::WaitableEvent* dummy = NULL; + UseMockDelayProvider(); + EXPECT_CALL(*delay(), GetDelay(_)) + .WillRepeatedly(Return(TimeDelta::FromMilliseconds(50))); + SyncShareRecords records; + + EXPECT_CALL(*syncer(), SyncShare(_,_,_)) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), + WithArg<0>(RecordSyncShare(&records, 1U, dummy)))) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateCommitFailed), + WithArg<0>(RecordSyncShare(&records, 1U, &done1)))) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), + WithArg<0>(RecordSyncShare(&records, 1U, &done2)))) + .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), + WithArg<0>(RecordSyncShare(&records, 1U, &done)))); + + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); + + syncer_thread()->ScheduleConfig(model_types); + done1.TimedWait(timeout()); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, model_types, + FROM_HERE); + + // done2 indicates config suceeded. Now change the mode so nudge can execute. + done2.TimedWait(timeout()); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); + done.TimedWait(timeout()); + EXPECT_EQ(4U, records.snapshots.size()); + + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, + records.snapshots[2]->source.types)); + EXPECT_EQ(GetUpdatesCallerInfo::SYNC_CYCLE_CONTINUATION, + records.snapshots[2]->source.updates_source); + + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(model_types, + records.snapshots[3]->source.types)); + EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, + records.snapshots[3]->source.updates_source); + +} + + // Test that nudges are coalesced. TEST_F(SyncerThread2Test, NudgeCoalescing) { - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords r; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) @@ -226,28 +392,31 @@ TEST_F(SyncerThread2Test, NudgeCoalescing) { types1[syncable::BOOKMARKS] = true; types2[syncable::AUTOFILL] = true; types3[syncable::THEMES] = true; - TimeDelta delay = TimeDelta::FromMilliseconds(20); + TimeDelta delay = TimeDelta::FromMilliseconds( + TestTimeouts::tiny_timeout_ms()); TimeTicks optimal_time = TimeTicks::Now() + delay; - syncer_thread()->ScheduleNudge(delay, NUDGE_SOURCE_UNKNOWN, types1); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types2); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_NOTIFICATION, types3); + syncer_thread()->ScheduleNudge(delay, NUDGE_SOURCE_UNKNOWN, types1, + FROM_HERE); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types2, + FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r.snapshots.size()); EXPECT_GE(r.times[0], optimal_time); - EXPECT_TRUE(CompareModelTypeBitSetToTypePayloadMap(types1 | types2 | types3, - r.snapshots[0]->source.types)); - EXPECT_EQ(GetUpdatesCallerInfo::NOTIFICATION, + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap( + types1 | types2, r.snapshots[0]->source.types)); + EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, r.snapshots[0]->source.updates_source); SyncShareRecords r2; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&r2, 1U, &done)))); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_NOTIFICATION, types3); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_NOTIFICATION, types3, + FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r2.snapshots.size()); - EXPECT_TRUE(CompareModelTypeBitSetToTypePayloadMap(types3, + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(types3, r2.snapshots[0]->source.types)); EXPECT_EQ(GetUpdatesCallerInfo::NOTIFICATION, r2.snapshots[0]->source.updates_source); @@ -255,10 +424,10 @@ TEST_F(SyncerThread2Test, NudgeCoalescing) { // Test nudge scheduling. TEST_F(SyncerThread2Test, NudgeWithPayloads) { - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords records; - sessions::TypePayloadMap model_types_with_payloads; + syncable::ModelTypePayloadMap model_types_with_payloads; model_types_with_payloads[syncable::BOOKMARKS] = "test"; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) @@ -266,7 +435,7 @@ TEST_F(SyncerThread2Test, NudgeWithPayloads) { WithArg<0>(RecordSyncShare(&records, 1U, &done)))) .RetiresOnSaturation(); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL, - model_types_with_payloads); + model_types_with_payloads, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records.snapshots.size()); @@ -282,7 +451,7 @@ TEST_F(SyncerThread2Test, NudgeWithPayloads) { .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records2, 1U, &done)))); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL, - model_types_with_payloads); + model_types_with_payloads, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, records2.snapshots.size()); @@ -293,34 +462,32 @@ TEST_F(SyncerThread2Test, NudgeWithPayloads) { // Test that nudges are coalesced. TEST_F(SyncerThread2Test, NudgeWithPayloadsCoalescing) { - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); SyncShareRecords r; EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&r, 1U, &done)))); - sessions::TypePayloadMap types1, types2, types3; + syncable::ModelTypePayloadMap types1, types2, types3; types1[syncable::BOOKMARKS] = "test1"; types2[syncable::AUTOFILL] = "test2"; types3[syncable::THEMES] = "test3"; - TimeDelta delay = TimeDelta::FromMilliseconds(20); + TimeDelta delay = TimeDelta::FromMilliseconds( + TestTimeouts::tiny_timeout_ms()); TimeTicks optimal_time = TimeTicks::Now() + delay; syncer_thread()->ScheduleNudgeWithPayloads(delay, NUDGE_SOURCE_UNKNOWN, - types1); + types1, FROM_HERE); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_LOCAL, - types2); - syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_NOTIFICATION, - types3); + types2, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r.snapshots.size()); EXPECT_GE(r.times[0], optimal_time); - sessions::TypePayloadMap coalesced_types; - sessions::CoalescePayloads(&coalesced_types, types1); - sessions::CoalescePayloads(&coalesced_types, types2); - sessions::CoalescePayloads(&coalesced_types, types3); + syncable::ModelTypePayloadMap coalesced_types; + syncable::CoalescePayloads(&coalesced_types, types1); + syncable::CoalescePayloads(&coalesced_types, types2); EXPECT_EQ(coalesced_types, r.snapshots[0]->source.types); - EXPECT_EQ(GetUpdatesCallerInfo::NOTIFICATION, + EXPECT_EQ(GetUpdatesCallerInfo::LOCAL, r.snapshots[0]->source.updates_source); SyncShareRecords r2; @@ -328,7 +495,7 @@ TEST_F(SyncerThread2Test, NudgeWithPayloadsCoalescing) { .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&r2, 1U, &done)))); syncer_thread()->ScheduleNudgeWithPayloads(zero(), NUDGE_SOURCE_NOTIFICATION, - types3); + types3, FROM_HERE); done.TimedWait(timeout()); EXPECT_EQ(1U, r2.snapshots.size()); EXPECT_EQ(types3, r2.snapshots[0]->source.types); @@ -347,7 +514,7 @@ TEST_F(SyncerThread2Test, Polling) { WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll_interval; - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); @@ -366,7 +533,7 @@ TEST_F(SyncerThread2Test, PollNotificationsDisabled) { WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll_interval; - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); @@ -387,7 +554,7 @@ TEST_F(SyncerThread2Test, PollIntervalUpdate) { WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll1 + poll2; - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); @@ -396,13 +563,14 @@ TEST_F(SyncerThread2Test, PollIntervalUpdate) { // Test that a sync session is run through to completion. TEST_F(SyncerThread2Test, HasMoreToSync) { - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); base::WaitableEvent done(false, false); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(Invoke(sessions::test_util::SimulateHasMoreToSync)) .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), SignalEvent(&done))); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet()); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), + FROM_HERE); done.TimedWait(timeout()); // If more nudges are scheduled, they'll be waited on by TearDown, and would // cause our expectation to break. @@ -419,11 +587,12 @@ TEST_F(SyncerThread2Test, ThrottlingDoesThrottle) { EXPECT_CALL(*syncer(), SyncShare(_,_,_)) .WillOnce(WithArg<0>(sessions::test_util::SimulateThrottled(throttle))); - syncer_thread()->Start(SyncerThread::NORMAL_MODE); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types, + FROM_HERE); FlushLastTask(&done); - syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE); + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(types); FlushLastTask(&done); } @@ -445,7 +614,7 @@ TEST_F(SyncerThread2Test, ThrottlingExpires) { WithArg<0>(RecordSyncShare(&records, kMinNumSamples, &done)))); TimeTicks optimal_start = TimeTicks::Now() + poll + throttle1; - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); @@ -460,13 +629,15 @@ TEST_F(SyncerThread2Test, ConfigurationMode) { base::WaitableEvent* dummy = NULL; syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); EXPECT_CALL(*syncer(), SyncShare(_,_,_)) - .WillOnce(DoAll(Invoke(sessions::test_util::SimulateSuccess), + .WillOnce((Invoke(sessions::test_util::SimulateSuccess), WithArg<0>(RecordSyncShare(&records, 1U, dummy)))); - syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE); + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncable::ModelTypeBitSet nudge_types; nudge_types[syncable::AUTOFILL] = true; - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types, + FROM_HERE); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, nudge_types, + FROM_HERE); syncable::ModelTypeBitSet config_types; config_types[syncable::BOOKMARKS] = true; @@ -477,7 +648,7 @@ TEST_F(SyncerThread2Test, ConfigurationMode) { syncer_thread()->Stop(); EXPECT_EQ(1U, records.snapshots.size()); - EXPECT_TRUE(CompareModelTypeBitSetToTypePayloadMap(config_types, + EXPECT_TRUE(CompareModelTypeBitSetToModelTypePayloadMap(config_types, records.snapshots[0]->source.types)); } @@ -538,7 +709,7 @@ TEST_F(SyncerThread2Test, BackoffDropsJobs) { EXPECT_CALL(*delay(), GetDelay(_)) .WillRepeatedly(Return(TimeDelta::FromDays(1))); - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); ASSERT_TRUE(done.TimedWait(timeout())); done.Reset(); @@ -555,7 +726,8 @@ TEST_F(SyncerThread2Test, BackoffDropsJobs) { // We schedule a nudge with enough delay (10X poll interval) that at least // one or two polls would have taken place. The nudge should succeed. - syncer_thread()->ScheduleNudge(poll * 10, NUDGE_SOURCE_LOCAL, types); + syncer_thread()->ScheduleNudge(poll * 10, NUDGE_SOURCE_LOCAL, types, + FROM_HERE); ASSERT_TRUE(done.TimedWait(timeout())); done.Reset(); @@ -568,13 +740,15 @@ TEST_F(SyncerThread2Test, BackoffDropsJobs) { EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(0); EXPECT_CALL(*delay(), GetDelay(_)).Times(0); - syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE); + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(types); FlushLastTask(&done); - syncer_thread()->Start(SyncerThread::NORMAL_MODE); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types, + FROM_HERE); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, types, + FROM_HERE); FlushLastTask(&done); } @@ -604,7 +778,7 @@ TEST_F(SyncerThread2Test, BackoffElevation) { .RetiresOnSaturation(); EXPECT_CALL(*delay(), GetDelay(Eq(fourth))).WillOnce(Return(fifth)); - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); ASSERT_TRUE(done.TimedWait(timeout())); EXPECT_GE(r.times[2] - r.times[1], second); @@ -632,7 +806,7 @@ TEST_F(SyncerThread2Test, BackoffRelief) { // Optimal start for the post-backoff poll party. TimeTicks optimal_start = TimeTicks::Now() + poll + backoff; - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); @@ -671,8 +845,9 @@ TEST_F(SyncerThread2Test, SyncerSteps) { base::WaitableEvent done(false, false); EXPECT_CALL(*syncer(), SyncShare(_, SYNCER_BEGIN, SYNCER_END)) .Times(1); - syncer_thread()->Start(SyncerThread::NORMAL_MODE); - syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet()); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), + FROM_HERE); FlushLastTask(&done); syncer_thread()->Stop(); Mock::VerifyAndClearExpectations(syncer()); @@ -680,15 +855,14 @@ TEST_F(SyncerThread2Test, SyncerSteps) { // ClearUserData. EXPECT_CALL(*syncer(), SyncShare(_, CLEAR_PRIVATE_DATA, SYNCER_END)) .Times(1); - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); syncer_thread()->ScheduleClearUserData(); FlushLastTask(&done); syncer_thread()->Stop(); Mock::VerifyAndClearExpectations(syncer()); - // Configuration. EXPECT_CALL(*syncer(), SyncShare(_, DOWNLOAD_UPDATES, APPLY_UPDATES)); - syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE); + syncer_thread()->Start(SyncerThread::CONFIGURATION_MODE, NULL); syncer_thread()->ScheduleConfig(ModelTypeBitSet()); FlushLastTask(&done); syncer_thread()->Stop(); @@ -700,7 +874,7 @@ TEST_F(SyncerThread2Test, SyncerSteps) { .WillRepeatedly(SignalEvent(&done)); const TimeDelta poll(TimeDelta::FromMilliseconds(10)); syncer_thread()->OnReceivedLongPollIntervalUpdate(poll); - syncer_thread()->Start(SyncerThread::NORMAL_MODE); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); done.TimedWait(timeout()); syncer_thread()->Stop(); Mock::VerifyAndClearExpectations(syncer()); @@ -714,15 +888,46 @@ TEST_F(SyncerThread2Test, DISABLED_NoConfigDuringNormal) { // Test that starting the syncer thread without a valid connection doesn't // break things when a connection is detected. -// Test config tasks don't run during normal mode. -// TODO(tim): Implement this test and then the functionality! -TEST_F(SyncerThread2Test, DISABLED_StartWhenNotConnected) { +TEST_F(SyncerThread2Test, StartWhenNotConnected) { + base::WaitableEvent done(false, false); + MessageLoop cur; + connection()->SetServerNotReachable(); + EXPECT_CALL(*syncer(), SyncShare(_,_,_)).WillOnce(SignalEvent(&done)); + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), + FROM_HERE); + FlushLastTask(&done); + + connection()->SetServerReachable(); + cur.PostTask(FROM_HERE, NewRunnableFunction( + &SyncerThread2Test::QuitMessageLoop)); + cur.Run(); + // By now, the server connection event should have been posted to the + // SyncerThread. + FlushLastTask(&done); + done.TimedWait(timeout()); +} + +TEST_F(SyncerThread2Test, SetsPreviousRoutingInfo) { + base::WaitableEvent done(false, false); + ModelSafeRoutingInfo info; + EXPECT_TRUE(info == context()->previous_session_routing_info()); + ModelSafeRoutingInfo expected; + context()->registrar()->GetModelSafeRoutingInfo(&expected); + ASSERT_FALSE(expected.empty()); + EXPECT_CALL(*syncer(), SyncShare(_,_,_)).Times(1); + + syncer_thread()->Start(SyncerThread::NORMAL_MODE, NULL); + syncer_thread()->ScheduleNudge(zero(), NUDGE_SOURCE_LOCAL, ModelTypeBitSet(), + FROM_HERE); + FlushLastTask(&done); + syncer_thread()->Stop(); + + EXPECT_TRUE(expected == context()->previous_session_routing_info()); } -} // namespace s3 } // namespace browser_sync // SyncerThread won't outlive the test! -DISABLE_RUNNABLE_METHOD_REFCOUNT(browser_sync::s3::SyncerThread2Test); - +DISABLE_RUNNABLE_METHOD_REFCOUNT(browser_sync::SyncerThread2Test); diff --git a/chrome/browser/sync/engine/syncer_thread2_whitebox_unittest.cc b/chrome/browser/sync/engine/syncer_thread2_whitebox_unittest.cc new file mode 100644 index 0000000..e041aaa --- /dev/null +++ b/chrome/browser/sync/engine/syncer_thread2_whitebox_unittest.cc @@ -0,0 +1,231 @@ +// Copyright (c) 2011 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/time.h" +#include "chrome/browser/sync/engine/mock_model_safe_workers.h" +#include "chrome/browser/sync/engine/syncer_thread.h" +#include "chrome/browser/sync/sessions/sync_session_context.h" +#include "chrome/browser/sync/sessions/test_util.h" +#include "chrome/test/sync/engine/mock_connection_manager.h" +#include "chrome/test/sync/engine/test_directory_setter_upper.h" +#include "testing/gtest/include/gtest/gtest.h" +#include "testing/gmock/include/gmock/gmock.h" + +using base::TimeDelta; +using base::TimeTicks; + +namespace browser_sync { +using sessions::SyncSessionContext; +using browser_sync::Syncer; + +class SyncerThread2WhiteboxTest : public testing::Test { + public: + virtual void SetUp() { + syncdb_.SetUp(); + Syncer* syncer = new Syncer(); + registrar_.reset(MockModelSafeWorkerRegistrar::PassiveBookmarks()); + context_ = new SyncSessionContext(connection_.get(), syncdb_.manager(), + registrar_.get(), std::vector<SyncEngineEventListener*>()); + context_->set_notifications_enabled(true); + context_->set_account_name("Test"); + syncer_thread_.reset(new SyncerThread(context_, syncer)); + } + + virtual void TearDown() { + syncdb_.TearDown(); + } + + void SetMode(SyncerThread::Mode mode) { + syncer_thread_->mode_ = mode; + } + + void SetLastSyncedTime(base::TimeTicks ticks) { + syncer_thread_->last_sync_session_end_time_ = ticks; + } + + void SetServerConnection(bool connected) { + syncer_thread_->server_connection_ok_ = connected; + } + + void ResetWaitInterval() { + syncer_thread_->wait_interval_.reset(); + } + + void SetWaitIntervalToThrottled() { + syncer_thread_->wait_interval_.reset(new SyncerThread::WaitInterval( + SyncerThread::WaitInterval::THROTTLED, TimeDelta::FromSeconds(1))); + } + + void SetWaitIntervalToExponentialBackoff() { + syncer_thread_->wait_interval_.reset( + new SyncerThread::WaitInterval( + SyncerThread::WaitInterval::EXPONENTIAL_BACKOFF, + TimeDelta::FromSeconds(1))); + } + + SyncerThread::JobProcessDecision DecideOnJob( + const SyncerThread::SyncSessionJob& job) { + return syncer_thread_->DecideOnJob(job); + } + + void InitializeSyncerOnNormalMode() { + SetMode(SyncerThread::NORMAL_MODE); + ResetWaitInterval(); + SetServerConnection(true); + SetLastSyncedTime(base::TimeTicks::Now()); + } + + SyncerThread::JobProcessDecision CreateAndDecideJob( + SyncerThread::SyncSessionJob::SyncSessionJobPurpose purpose) { + struct SyncerThread::SyncSessionJob job; + job.purpose = purpose; + job.scheduled_start = TimeTicks::Now(); + return DecideOnJob(job); + } + + protected: + scoped_ptr<SyncerThread> syncer_thread_; + + private: + scoped_ptr<MockConnectionManager> connection_; + SyncSessionContext* context_; + //MockDelayProvider* delay_; + scoped_ptr<MockModelSafeWorkerRegistrar> registrar_; + MockDirectorySetterUpper syncdb_; +}; + +TEST_F(SyncerThread2WhiteboxTest, SaveNudge) { + InitializeSyncerOnNormalMode(); + + // Now set the mode to configure. + SetMode(SyncerThread::CONFIGURATION_MODE); + + SyncerThread::JobProcessDecision decision = + CreateAndDecideJob(SyncerThread::SyncSessionJob::NUDGE); + + EXPECT_EQ(decision, SyncerThread::SAVE); +} + +TEST_F(SyncerThread2WhiteboxTest, ContinueNudge) { + InitializeSyncerOnNormalMode(); + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::NUDGE); + + EXPECT_EQ(decision, SyncerThread::CONTINUE); +} + +TEST_F(SyncerThread2WhiteboxTest, DropPoll) { + InitializeSyncerOnNormalMode(); + SetMode(SyncerThread::CONFIGURATION_MODE); + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::POLL); + + EXPECT_EQ(decision, SyncerThread::DROP); +} + +TEST_F(SyncerThread2WhiteboxTest, ContinuePoll) { + InitializeSyncerOnNormalMode(); + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::POLL); + + EXPECT_EQ(decision, SyncerThread::CONTINUE); +} + +TEST_F(SyncerThread2WhiteboxTest, ContinueConfiguration) { + InitializeSyncerOnNormalMode(); + SetMode(SyncerThread::CONFIGURATION_MODE); + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::CONFIGURATION); + + EXPECT_EQ(decision, SyncerThread::CONTINUE); +} + +TEST_F(SyncerThread2WhiteboxTest, SaveConfigurationWhileThrottled) { + InitializeSyncerOnNormalMode(); + SetMode(SyncerThread::CONFIGURATION_MODE); + + SetWaitIntervalToThrottled(); + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::CONFIGURATION); + + EXPECT_EQ(decision, SyncerThread::SAVE); +} + +TEST_F(SyncerThread2WhiteboxTest, SaveNudgeWhileThrottled) { + InitializeSyncerOnNormalMode(); + SetMode(SyncerThread::CONFIGURATION_MODE); + + SetWaitIntervalToThrottled(); + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::NUDGE); + + EXPECT_EQ(decision, SyncerThread::SAVE); + +} + +TEST_F(SyncerThread2WhiteboxTest, ContinueClearUserDataUnderAllCircumstances) { + InitializeSyncerOnNormalMode(); + + SetMode(SyncerThread::CONFIGURATION_MODE); + SetWaitIntervalToThrottled(); + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::CLEAR_USER_DATA); + EXPECT_EQ(decision, SyncerThread::CONTINUE); + + SetMode(SyncerThread::NORMAL_MODE); + SetWaitIntervalToExponentialBackoff(); + decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::CLEAR_USER_DATA); + EXPECT_EQ(decision, SyncerThread::CONTINUE); +} + +TEST_F(SyncerThread2WhiteboxTest, ContinueNudgeWhileExponentialBackOff) { + InitializeSyncerOnNormalMode(); + SetMode(SyncerThread::NORMAL_MODE); + SetWaitIntervalToExponentialBackoff(); + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::NUDGE); + + EXPECT_EQ(decision, SyncerThread::CONTINUE); +} + +TEST_F(SyncerThread2WhiteboxTest, DropNudgeWhileExponentialBackOff) { + InitializeSyncerOnNormalMode(); + SetMode(SyncerThread::NORMAL_MODE); + SetWaitIntervalToExponentialBackoff(); + + syncer_thread_->wait_interval_->had_nudge = true; + + SyncerThread::JobProcessDecision decision = CreateAndDecideJob( + SyncerThread::SyncSessionJob::NUDGE); + + EXPECT_EQ(decision, SyncerThread::DROP); +} + +TEST_F(SyncerThread2WhiteboxTest, ContinueCanaryJobConfig) { + InitializeSyncerOnNormalMode(); + SetMode(SyncerThread::CONFIGURATION_MODE); + SetWaitIntervalToExponentialBackoff(); + + struct SyncerThread::SyncSessionJob job; + job.purpose = SyncerThread::SyncSessionJob::CONFIGURATION; + job.scheduled_start = TimeTicks::Now(); + job.is_canary_job = true; + SyncerThread::JobProcessDecision decision = DecideOnJob(job); + + EXPECT_EQ(decision, SyncerThread::CONTINUE); +} + +} // namespace browser_sync + +// SyncerThread won't outlive the test! +DISABLE_RUNNABLE_METHOD_REFCOUNT( + browser_sync::SyncerThread2WhiteboxTest); diff --git a/chrome/browser/sync/engine/syncer_thread_adapter.cc b/chrome/browser/sync/engine/syncer_thread_adapter.cc deleted file mode 100644 index 34b3a00..0000000 --- a/chrome/browser/sync/engine/syncer_thread_adapter.cc +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) 2011 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/time.h" -#include "chrome/browser/sync/engine/syncer_thread_adapter.h" -#include "chrome/browser/sync/syncable/model_type.h" - -using base::TimeDelta; -using syncable::ModelTypeBitSet; - -namespace browser_sync { - -SyncerThreadAdapter::SyncerThreadAdapter(sessions::SyncSessionContext* context, - bool using_new_impl) - : legacy_(NULL), new_impl_(NULL), using_new_impl_(using_new_impl) { - if (using_new_impl_) { - new_impl_.reset(new s3::SyncerThread(context, new Syncer())); - new_impl_->Start(s3::SyncerThread::CONFIGURATION_MODE); - } else { - legacy_ = new SyncerThread(context); - } -} - -SyncerThreadAdapter::~SyncerThreadAdapter() { - legacy_ = NULL; - new_impl_.reset(); -} - -void SyncerThreadAdapter::WatchConnectionManager( - ServerConnectionManager* conn_mgr) { - DCHECK(!using_new_impl_); - legacy_->WatchConnectionManager(conn_mgr); -} - -bool SyncerThreadAdapter::Start() { - if (using_new_impl_) { - new_impl_->Start(s3::SyncerThread::NORMAL_MODE); - return true; - } else { - return legacy_->Start(); - } -} - -bool SyncerThreadAdapter::Stop(int max_wait) { - if (using_new_impl_) { - new_impl_->Stop(); - return true; - } else { - return legacy_->Stop(max_wait); - } -} - -bool SyncerThreadAdapter::RequestPause() { - DCHECK(!using_new_impl_); - return legacy_->RequestPause(); -} - -bool SyncerThreadAdapter::RequestResume() { - DCHECK(!using_new_impl_); - return legacy_->RequestResume(); -} - -s3::NudgeSource LegacyToNewSyncerThreadSource(SyncerThread::NudgeSource s) { - switch(s) { - case SyncerThread::kNotification: - return s3::NUDGE_SOURCE_NOTIFICATION; - case SyncerThread::kContinuation: - return s3::NUDGE_SOURCE_CONTINUATION; - case SyncerThread::kLocal: - return s3::NUDGE_SOURCE_LOCAL; - case SyncerThread::kUnknown: - return s3::NUDGE_SOURCE_UNKNOWN; - default: - NOTREACHED(); - return s3::NUDGE_SOURCE_UNKNOWN; - } -} - -void SyncerThreadAdapter::NudgeSyncer(int milliseconds_from_now, - SyncerThread::NudgeSource source) { - if (using_new_impl_) { - if (source == SyncerThread::kClearPrivateData) { - new_impl_->ScheduleClearUserData(); - return; - } - new_impl_->ScheduleNudge( - TimeDelta::FromMilliseconds(milliseconds_from_now), - LegacyToNewSyncerThreadSource(source), ModelTypeBitSet()); - } else { - legacy_->NudgeSyncer(milliseconds_from_now, source); - } -} - -void SyncerThreadAdapter::NudgeSyncerWithDataTypes( - int milliseconds_from_now, - SyncerThread::NudgeSource source, - const syncable::ModelTypeBitSet& model_types) { - DCHECK_NE(SyncerThread::kClearPrivateData, source); - if (using_new_impl_) { - new_impl_->ScheduleNudge( - TimeDelta::FromMilliseconds(milliseconds_from_now), - LegacyToNewSyncerThreadSource(source), model_types); - } else { - legacy_->NudgeSyncerWithDataTypes(milliseconds_from_now, source, - model_types); - } -} - -void SyncerThreadAdapter::NudgeSyncerWithPayloads( - int milliseconds_from_now, - SyncerThread::NudgeSource source, - const sessions::TypePayloadMap& model_types_with_payloads) { - DCHECK_NE(SyncerThread::kClearPrivateData, source); - if (using_new_impl_) { - new_impl_->ScheduleNudgeWithPayloads( - TimeDelta::FromMilliseconds(milliseconds_from_now), - LegacyToNewSyncerThreadSource(source), model_types_with_payloads); - } else { - legacy_->NudgeSyncerWithPayloads(milliseconds_from_now, source, - model_types_with_payloads); - } -} - -void SyncerThreadAdapter::SetNotificationsEnabled(bool enabled) { - if (using_new_impl_) - new_impl_->set_notifications_enabled(enabled); - else - legacy_->SetNotificationsEnabled(enabled); -} - -void SyncerThreadAdapter::CreateSyncer(const std::string& dirname) { - if (!using_new_impl_) - legacy_->CreateSyncer(dirname); - // No-op if using new impl. -} - -s3::SyncerThread* SyncerThreadAdapter::new_impl() { - return new_impl_.get(); -} - -} // namespace browser_sync diff --git a/chrome/browser/sync/engine/syncer_thread_adapter.h b/chrome/browser/sync/engine/syncer_thread_adapter.h deleted file mode 100644 index a47bb1a..0000000 --- a/chrome/browser/sync/engine/syncer_thread_adapter.h +++ /dev/null @@ -1,50 +0,0 @@ -// Copyright (c) 2011 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/ref_counted.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/engine/net/server_connection_manager.h" -#include "chrome/browser/sync/engine/syncer_thread.h" -#include "chrome/browser/sync/engine/syncer_thread2.h" -#include "chrome/browser/sync/sessions/sync_session_context.h" -#include "chrome/browser/sync/syncable/model_type.h" - -namespace browser_sync { - -class SyncerThreadAdapter { - public: - SyncerThreadAdapter(sessions::SyncSessionContext* context, - bool using_new_impl); - ~SyncerThreadAdapter(); - - // We mimic the SyncerThread interface (an adaptee). - void WatchConnectionManager(ServerConnectionManager* conn_mgr); - bool Start(); - bool Stop(int max_wait); - bool RequestPause(); - bool RequestResume(); - void NudgeSyncer(int milliseconds_from_now, - SyncerThread::NudgeSource source); - void NudgeSyncerWithDataTypes( - int milliseconds_from_now, - SyncerThread::NudgeSource source, - const syncable::ModelTypeBitSet& model_types); - void NudgeSyncerWithPayloads( - int milliseconds_from_now, - SyncerThread::NudgeSource source, - const sessions::TypePayloadMap& model_types_with_payloads); - void SetNotificationsEnabled(bool enabled); - void CreateSyncer(const std::string& dirname); - - // Expose the new impl to leverage new apis through the adapter. - s3::SyncerThread* new_impl(); - private: - scoped_refptr<SyncerThread> legacy_; - scoped_ptr<s3::SyncerThread> new_impl_; - bool using_new_impl_; - - DISALLOW_COPY_AND_ASSIGN(SyncerThreadAdapter); -}; - -} // namespace browser_sync diff --git a/chrome/browser/sync/engine/syncer_thread_unittest.cc b/chrome/browser/sync/engine/syncer_thread_unittest.cc index f5d8049..7a37f38 100644 --- a/chrome/browser/sync/engine/syncer_thread_unittest.cc +++ b/chrome/browser/sync/engine/syncer_thread_unittest.cc @@ -1,1245 +1,3 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 <list> -#include <map> - -#include "base/scoped_ptr.h" -#include "base/synchronization/lock.h" -#include "base/time.h" -#include "base/synchronization/waitable_event.h" -#include "chrome/browser/sync/engine/model_safe_worker.h" -#include "chrome/browser/sync/engine/syncer_thread.h" -#include "chrome/browser/sync/engine/syncer_types.h" -#include "chrome/browser/sync/sessions/sync_session_context.h" -#include "chrome/browser/sync/util/channel.h" -#include "chrome/test/sync/engine/mock_connection_manager.h" -#include "chrome/test/sync/engine/test_directory_setter_upper.h" -#include "chrome/test/sync/sessions/test_scoped_session_event_listener.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using base::TimeTicks; -using base::TimeDelta; -using base::WaitableEvent; -using testing::_; -using testing::AnyNumber; -using testing::Field; - -namespace browser_sync { -using sessions::ErrorCounters; -using sessions::TestScopedSessionEventListener; -using sessions::SyncSessionContext; -using sessions::SyncSessionSnapshot; -using sessions::SyncerStatus; - -typedef testing::Test SyncerThreadTest; -typedef SyncerThread::WaitInterval WaitInterval; - -ACTION_P(SignalEvent, event) { - event->Signal(); -} - -SyncSessionSnapshot SessionSnapshotForTest( - int64 num_server_changes_remaining, - int64 unsynced_count) { - std::string download_progress_markers[syncable::MODEL_TYPE_COUNT]; - for (int i = syncable::FIRST_REAL_MODEL_TYPE; - i < syncable::MODEL_TYPE_COUNT; - ++i) { - syncable::ModelType type(syncable::ModelTypeFromInt(i)); - sync_pb::DataTypeProgressMarker token; - token.set_data_type_id( - syncable::GetExtensionFieldNumberFromModelType(type)); - token.set_token("foobar"); - token.SerializeToString(&download_progress_markers[i]); - } - return SyncSessionSnapshot(SyncerStatus(), ErrorCounters(), - num_server_changes_remaining, false, - syncable::ModelTypeBitSet(), download_progress_markers, - false, false, unsynced_count, 0, false, sessions::SyncSourceInfo()); -} - -class ListenerMock : public SyncEngineEventListener { - public: - MOCK_METHOD1(OnSyncEngineEvent, void(const SyncEngineEvent&)); -}; - -class SyncerThreadWithSyncerTest : public testing::Test, - public ModelSafeWorkerRegistrar, - public SyncEngineEventListener { - public: - SyncerThreadWithSyncerTest() - : max_wait_time_(TimeDelta::FromSeconds(10)), - sync_cycle_ended_event_(false, false) {} - virtual void SetUp() { - metadb_.SetUp(); - connection_.reset(new MockConnectionManager(metadb_.manager(), - metadb_.name())); - worker_ = new ModelSafeWorker(); - std::vector<SyncEngineEventListener*> listeners; - listeners.push_back(this); - context_ = new SyncSessionContext(connection_.get(), metadb_.manager(), - this, listeners); - context_->set_account_name(metadb_.name()); - syncer_thread_ = new SyncerThread(context_); - syncer_thread_->SetConnected(true); - syncable::ModelTypeBitSet expected_types; - expected_types[syncable::BOOKMARKS] = true; - connection_->ExpectGetUpdatesRequestTypes(expected_types); - } - virtual void TearDown() { - context_ = NULL; - syncer_thread_ = NULL; - connection_.reset(); - metadb_.TearDown(); - } - - // ModelSafeWorkerRegistrar implementation. - virtual void GetWorkers(std::vector<ModelSafeWorker*>* out) { - out->push_back(worker_.get()); - } - - virtual void GetModelSafeRoutingInfo(ModelSafeRoutingInfo* out) { - // We're just testing the sync engine here, so we shunt everything to - // the SyncerThread. - (*out)[syncable::BOOKMARKS] = GROUP_PASSIVE; - } - - ManuallyOpenedTestDirectorySetterUpper* metadb() { return &metadb_; } - MockConnectionManager* connection() { return connection_.get(); } - SyncerThread* syncer_thread() { return syncer_thread_; } - - // Waits an indefinite amount of sync cycles for the syncer thread to become - // throttled. Only call this if a throttle is supposed to occur! - bool WaitForThrottle() { - int max_cycles = 5; - while (max_cycles && !syncer_thread()->IsSyncingCurrentlySilenced()) { - sync_cycle_ended_event_.TimedWait(max_wait_time_); - max_cycles--; - } - - return syncer_thread()->IsSyncingCurrentlySilenced(); - } - - void WaitForDisconnect() { - // Wait for the SyncerThread to detect loss of connection, up to a max of - // 10 seconds to timeout the test. - base::AutoLock lock(syncer_thread()->lock_); - TimeTicks start = TimeTicks::Now(); - TimeDelta ten_seconds = TimeDelta::FromSeconds(10); - while (syncer_thread()->vault_.connected_) { - syncer_thread()->vault_field_changed_.TimedWait(ten_seconds); - if (TimeTicks::Now() - start > ten_seconds) - break; - } - EXPECT_FALSE(syncer_thread()->vault_.connected_); - } - - bool Pause(ListenerMock* listener) { - WaitableEvent event(false, false); - { - base::AutoLock lock(syncer_thread()->lock_); - EXPECT_CALL(*listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_PAUSED))). - WillOnce(SignalEvent(&event)); - } - if (!syncer_thread()->RequestPause()) - return false; - return event.TimedWait(max_wait_time_); - } - - bool Resume(ListenerMock* listener) { - WaitableEvent event(false, false); - { - base::AutoLock lock(syncer_thread()->lock_); - EXPECT_CALL(*listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_RESUMED))). - WillOnce(SignalEvent(&event)); - } - if (!syncer_thread()->RequestResume()) - return false; - return event.TimedWait(max_wait_time_); - } - - void PreventThreadFromPolling() { - const TimeDelta poll_interval = TimeDelta::FromMinutes(5); - syncer_thread()->SetSyncerShortPollInterval(poll_interval); - } - - // Compare a provided TypePayloadMap to the pending nudge info stored in the - // SyncerThread vault. - bool CompareNudgeTypesToVault(const sessions::TypePayloadMap& lhs) { - const sessions::TypePayloadMap& vault_nudge_types = - syncer_thread()->vault_.pending_nudge_types_; - return lhs == vault_nudge_types; - } - - // Compare a provided ModelTypeBitset to the pending nudge info stored in the - // SyncerThread vault. Nudge info in vault must not have any non-empty - // payloads. - bool CompareNudgeTypesBitSetToVault(const syncable::ModelTypeBitSet& lhs) { - sessions::TypePayloadMap model_types_with_payloads = - sessions::MakeTypePayloadMapFromBitSet(lhs, std::string()); - size_t count = 0; - for (sessions::TypePayloadMap::const_iterator i = - syncer_thread()->vault_.pending_nudge_types_.begin(); - i != syncer_thread()->vault_.pending_nudge_types_.end(); - ++i, ++count) { - if (!lhs.test(i->first)) - return false; - } - if (lhs.count() != count) - return false; - return true; - } - - - private: - - virtual void OnSyncEngineEvent(const SyncEngineEvent& event) { - if (event.what_happened == SyncEngineEvent::SYNC_CYCLE_ENDED) - sync_cycle_ended_event_.Signal(); - } - - protected: - TimeDelta max_wait_time_; - SyncSessionContext* context_; - - private: - ManuallyOpenedTestDirectorySetterUpper metadb_; - scoped_ptr<MockConnectionManager> connection_; - scoped_refptr<SyncerThread> syncer_thread_; - scoped_refptr<ModelSafeWorker> worker_; - base::WaitableEvent sync_cycle_ended_event_; - DISALLOW_COPY_AND_ASSIGN(SyncerThreadWithSyncerTest); -}; - -class SyncShareIntercept - : public MockConnectionManager::ResponseCodeOverrideRequestor, - public MockConnectionManager::MidCommitObserver { - public: - SyncShareIntercept() : sync_occured_(false, false), - allow_multiple_interceptions_(true) {} - virtual ~SyncShareIntercept() {} - virtual void Observe() { - if (!allow_multiple_interceptions_ && !times_sync_occured_.empty()) - FAIL() << "Multiple sync shares occured."; - times_sync_occured_.push_back(TimeTicks::Now()); - sync_occured_.Signal(); - } - - // ResponseCodeOverrideRequestor implementation. This assumes any override - // requested is intended to silence the SyncerThread. - virtual void OnOverrideComplete() { - // We should not see any syncing. - allow_multiple_interceptions_ = false; - times_sync_occured_.clear(); - } - - void WaitForSyncShare(int at_least_this_many, TimeDelta max_wait) { - while (at_least_this_many-- > 0) - sync_occured_.TimedWait(max_wait); - } - std::vector<TimeTicks> times_sync_occured() const { - return times_sync_occured_; - } - - void Reset() { - allow_multiple_interceptions_ = true; - times_sync_occured_.clear(); - sync_occured_.Reset(); - } - private: - std::vector<TimeTicks> times_sync_occured_; - base::WaitableEvent sync_occured_; - bool allow_multiple_interceptions_; - DISALLOW_COPY_AND_ASSIGN(SyncShareIntercept); -}; - -TEST_F(SyncerThreadTest, Construction) { - SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL, - std::vector<SyncEngineEventListener*>()); - scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context)); -} - -TEST_F(SyncerThreadTest, StartStop) { - SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL, - std::vector<SyncEngineEventListener*>()); - scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context)); - EXPECT_TRUE(syncer_thread->Start()); - EXPECT_TRUE(syncer_thread->Stop(2000)); - - // Do it again for good measure. I caught some bugs by adding this so - // I would recommend keeping it. - EXPECT_TRUE(syncer_thread->Start()); - EXPECT_TRUE(syncer_thread->Stop(2000)); -} - -TEST(SyncerThread, GetRecommendedDelay) { - EXPECT_LE(0, SyncerThread::GetRecommendedDelaySeconds(0)); - EXPECT_LE(1, SyncerThread::GetRecommendedDelaySeconds(1)); - EXPECT_LE(50, SyncerThread::GetRecommendedDelaySeconds(50)); - EXPECT_LE(10, SyncerThread::GetRecommendedDelaySeconds(10)); - EXPECT_EQ(SyncerThread::kMaxBackoffSeconds, - SyncerThread::GetRecommendedDelaySeconds( - SyncerThread::kMaxBackoffSeconds)); - EXPECT_EQ(SyncerThread::kMaxBackoffSeconds, - SyncerThread::GetRecommendedDelaySeconds( - SyncerThread::kMaxBackoffSeconds+1)); -} - -TEST_F(SyncerThreadTest, CalculateSyncWaitTime) { - SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL, - std::vector<SyncEngineEventListener*>()); - scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context)); - syncer_thread->DisableIdleDetection(); - - // Syncer_polling_interval_ is less than max poll interval. - TimeDelta syncer_polling_interval = TimeDelta::FromSeconds(1); - - syncer_thread->SetSyncerPollingInterval(syncer_polling_interval); - - // user_idle_ms is less than 10 * (syncer_polling_interval*1000). - ASSERT_EQ(syncer_polling_interval.InMilliseconds(), - syncer_thread->CalculateSyncWaitTime(1000, 0)); - ASSERT_EQ(syncer_polling_interval.InMilliseconds(), - syncer_thread->CalculateSyncWaitTime(1000, 1)); - - // user_idle_ms is ge than 10 * (syncer_polling_interval*1000). - int last_poll_time = 2000; - ASSERT_TRUE(last_poll_time <= - syncer_thread->CalculateSyncWaitTime(last_poll_time, 10000)); - ASSERT_TRUE(last_poll_time * 3 >= - syncer_thread->CalculateSyncWaitTime(last_poll_time, 10000)); - ASSERT_TRUE(last_poll_time <= - syncer_thread->CalculateSyncWaitTime(last_poll_time, 100000)); - ASSERT_TRUE(last_poll_time * 3 >= - syncer_thread->CalculateSyncWaitTime(last_poll_time, 100000)); - - // Maximum backoff time should be syncer_max_interval. - int near_threshold = SyncerThread::kDefaultMaxPollIntervalMs / 2 - 1; - int threshold = SyncerThread::kDefaultMaxPollIntervalMs; - int over_threshold = SyncerThread::kDefaultMaxPollIntervalMs + 1; - ASSERT_TRUE(near_threshold <= - syncer_thread->CalculateSyncWaitTime(near_threshold, 10000)); - ASSERT_TRUE(SyncerThread::kDefaultMaxPollIntervalMs >= - syncer_thread->CalculateSyncWaitTime(near_threshold, 10000)); - ASSERT_TRUE(SyncerThread::kDefaultMaxPollIntervalMs == - syncer_thread->CalculateSyncWaitTime(threshold, 10000)); - ASSERT_TRUE(SyncerThread::kDefaultMaxPollIntervalMs == - syncer_thread->CalculateSyncWaitTime(over_threshold, 10000)); - - // Possible idle time must be capped by syncer_max_interval. - int over_sync_max_interval = - SyncerThread::kDefaultMaxPollIntervalMs + 1; - syncer_polling_interval = TimeDelta::FromSeconds( - over_sync_max_interval / 100); // so 1000* is right - syncer_thread->SetSyncerPollingInterval(syncer_polling_interval); - ASSERT_EQ(syncer_polling_interval.InSeconds() * 1000, - syncer_thread->CalculateSyncWaitTime(1000, over_sync_max_interval)); - syncer_polling_interval = TimeDelta::FromSeconds(1); - syncer_thread->SetSyncerPollingInterval(syncer_polling_interval); - ASSERT_TRUE(last_poll_time <= - syncer_thread->CalculateSyncWaitTime(last_poll_time, - over_sync_max_interval)); - ASSERT_TRUE(last_poll_time * 3 >= - syncer_thread->CalculateSyncWaitTime(last_poll_time, - over_sync_max_interval)); -} - -TEST_F(SyncerThreadTest, CalculatePollingWaitTime) { - // Set up the environment. - int user_idle_milliseconds_param = 0; - SyncSessionContext* context = new SyncSessionContext(NULL, NULL, NULL, - std::vector<SyncEngineEventListener*>()); - scoped_refptr<SyncerThread> syncer_thread(new SyncerThread(context)); - syncer_thread->DisableIdleDetection(); - // Hold the lock to appease asserts in code. - base::AutoLock lock(syncer_thread->lock_); - - // Notifications disabled should result in a polling interval of - // kDefaultShortPollInterval. - { - context->set_notifications_enabled(false); - bool continue_sync_cycle_param = false; - - // No work and no backoff. - WaitInterval interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_EQ(SyncerThread::kDefaultShortPollIntervalSeconds, - interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_FALSE(continue_sync_cycle_param); - - // In this case the continue_sync_cycle is turned off. - continue_sync_cycle_param = true; - interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_EQ(SyncerThread::kDefaultShortPollIntervalSeconds, - interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_FALSE(continue_sync_cycle_param); - } - - // Notifications enabled should result in a polling interval of - // SyncerThread::kDefaultLongPollIntervalSeconds. - { - context->set_notifications_enabled(true); - bool continue_sync_cycle_param = false; - - // No work and no backoff. - WaitInterval interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds, - interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_FALSE(continue_sync_cycle_param); - - // In this case the continue_sync_cycle is turned off. - continue_sync_cycle_param = true; - interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds, - interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_FALSE(continue_sync_cycle_param); - } - - // There are two states which can cause a continuation, either the updates - // available do not match the updates received, or the unsynced count is - // non-zero. - { - // More server changes remaining to download. - context->set_last_snapshot(SessionSnapshotForTest(1, 0)); - bool continue_sync_cycle_param = false; - - WaitInterval interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_LE(0, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - continue_sync_cycle_param = false; - interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_GE(3, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_LE(0, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - - interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_GE(2, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - // Now simulate no more server changes remaining. - context->set_last_snapshot(SessionSnapshotForTest(0, 0)); - interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds, - interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_FALSE(continue_sync_cycle_param); - } - - { - // Now try with unsynced local items. - context->set_last_snapshot(SessionSnapshotForTest(0, 1)); - bool continue_sync_cycle_param = false; - - WaitInterval interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_LE(0, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - continue_sync_cycle_param = false; - interval = syncer_thread->CalculatePollingWaitTime( - 0, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_GE(2, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - context->set_last_snapshot(SessionSnapshotForTest(0, 0)); - interval = syncer_thread->CalculatePollingWaitTime( - 4, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds, - interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_FALSE(continue_sync_cycle_param); - } - - // Regression for exponential backoff reset when the syncer is nudged. - { - context->set_last_snapshot(SessionSnapshotForTest(0, 1)); - bool continue_sync_cycle_param = false; - - // Expect move from default polling interval to exponential backoff due to - // unsynced_count != 0. - WaitInterval interval = syncer_thread->CalculatePollingWaitTime( - 3600, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_LE(0, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - continue_sync_cycle_param = false; - interval = syncer_thread->CalculatePollingWaitTime( - 3600, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_GE(2, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - // Expect exponential backoff. - interval = syncer_thread->CalculatePollingWaitTime( - 2, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_LE(2, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - interval = syncer_thread->CalculatePollingWaitTime( - 2, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_GE(6, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - syncer_thread->vault_.current_wait_interval_ = interval; - - interval = syncer_thread->CalculatePollingWaitTime( - static_cast<int>(interval.poll_delta.InSeconds()), - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - true); - - // Don't change poll on a failed nudge during backoff. - ASSERT_TRUE(syncer_thread->vault_.current_wait_interval_.poll_delta == - interval.poll_delta); - ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode); - ASSERT_TRUE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - // If we got a nudge and we weren't in backoff mode, we see exponential - // backoff. - syncer_thread->vault_.current_wait_interval_.mode = WaitInterval::NORMAL; - interval = syncer_thread->CalculatePollingWaitTime( - 2, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - true); - - // 5 and 3 are bounds on the backoff randomization formula given input of 2. - ASSERT_GE(5, interval.poll_delta.InSeconds()); - ASSERT_LE(3, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - // And if another interval expires, we get a bigger backoff. - WaitInterval new_interval = syncer_thread->CalculatePollingWaitTime( - static_cast<int>(interval.poll_delta.InSeconds()), - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - false); - - ASSERT_GE(12, new_interval.poll_delta.InSeconds()); - ASSERT_LE(5, new_interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::EXPONENTIAL_BACKOFF, interval.mode); - ASSERT_FALSE(new_interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - // A nudge resets the continue_sync_cycle_param value, so our backoff - // should return to the minimum. - continue_sync_cycle_param = false; - interval = syncer_thread->CalculatePollingWaitTime( - 3600, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - true); - - ASSERT_LE(0, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - continue_sync_cycle_param = false; - interval = syncer_thread->CalculatePollingWaitTime( - 3600, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - true); - - ASSERT_GE(2, interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_TRUE(continue_sync_cycle_param); - - // Setting unsynced_count = 0 returns us to the default polling interval. - context->set_last_snapshot(SessionSnapshotForTest(0, 0)); - interval = syncer_thread->CalculatePollingWaitTime( - 4, - &user_idle_milliseconds_param, - &continue_sync_cycle_param, - true); - - ASSERT_EQ(SyncerThread::kDefaultLongPollIntervalSeconds, - interval.poll_delta.InSeconds()); - ASSERT_EQ(WaitInterval::NORMAL, interval.mode); - ASSERT_FALSE(interval.had_nudge_during_backoff); - ASSERT_FALSE(continue_sync_cycle_param); - } -} - -TEST_F(SyncerThreadWithSyncerTest, Polling) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - - const TimeDelta poll_interval = TimeDelta::FromSeconds(1); - syncer_thread()->SetSyncerShortPollInterval(poll_interval); - EXPECT_TRUE(syncer_thread()->Start()); - - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - - TimeDelta two_polls = poll_interval + poll_interval; - // We could theoretically return immediately from the wait if the interceptor - // was already signaled for a SyncShare (the first one comes quick). - interceptor.WaitForSyncShare(1, two_polls); - EXPECT_FALSE(interceptor.times_sync_occured().empty()); - - // Wait for at least 2 more SyncShare operations. - interceptor.WaitForSyncShare(2, two_polls); - EXPECT_TRUE(syncer_thread()->Stop(2000)); - - // Now analyze the run. - std::vector<TimeTicks> data = interceptor.times_sync_occured(); - - EXPECT_GE(data.size(), static_cast<unsigned int>(3)); - for (unsigned int i = 0; i < data.size() - 1; i++) { - TimeTicks optimal_next_sync = data[i] + poll_interval; - EXPECT_TRUE(data[i + 1] >= optimal_next_sync); - // This should be reliable, as there are no blocking or I/O operations - // except the explicit 2 second wait, so if it takes longer than this - // there is a problem. - EXPECT_TRUE(data[i + 1] < optimal_next_sync + poll_interval); - } -} - -TEST_F(SyncerThreadWithSyncerTest, Nudge) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - // We don't want a poll to happen during this test (except the first one). - PreventThreadFromPolling(); - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - const TimeDelta poll_interval = TimeDelta::FromMinutes(5); - interceptor.WaitForSyncShare(1, poll_interval + poll_interval); - - EXPECT_EQ(static_cast<unsigned int>(1), - interceptor.times_sync_occured().size()); - // The SyncerThread should be waiting for the poll now. Nudge it to sync - // immediately (5ms). - syncer_thread()->NudgeSyncer(5, SyncerThread::kUnknown); - interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1)); - EXPECT_EQ(static_cast<unsigned int>(2), - interceptor.times_sync_occured().size()); - - // SyncerThread should be waiting again. Signal it to stop. - EXPECT_TRUE(syncer_thread()->Stop(2000)); -} - -TEST_F(SyncerThreadWithSyncerTest, NudgeWithDataTypes) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - // We don't want a poll to happen during this test (except the first one). - PreventThreadFromPolling(); - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - const TimeDelta poll_interval = TimeDelta::FromMinutes(5); - interceptor.WaitForSyncShare(1, poll_interval + poll_interval); - EXPECT_EQ(static_cast<unsigned int>(1), - interceptor.times_sync_occured().size()); - - // The SyncerThread should be waiting for the poll now. Nudge it to sync - // immediately (5ms). - syncable::ModelTypeBitSet model_types; - model_types[syncable::BOOKMARKS] = true; - - // Paused so we can verify the nudge types safely. - syncer_thread()->RequestPause(); - syncer_thread()->NudgeSyncerWithDataTypes(5, - SyncerThread::kUnknown, - model_types); - EXPECT_TRUE(CompareNudgeTypesBitSetToVault(model_types)); - syncer_thread()->RequestResume(); - - interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1)); - EXPECT_EQ(static_cast<unsigned int>(2), - interceptor.times_sync_occured().size()); - - // SyncerThread should be waiting again. Signal it to stop. - EXPECT_TRUE(syncer_thread()->Stop(2000)); - EXPECT_TRUE(syncer_thread()->vault_.pending_nudge_types_.empty()); -} - -TEST_F(SyncerThreadWithSyncerTest, NudgeWithDataTypesCoalesced) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - // We don't want a poll to happen during this test (except the first one). - PreventThreadFromPolling(); - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - const TimeDelta poll_interval = TimeDelta::FromMinutes(5); - interceptor.WaitForSyncShare(1, poll_interval + poll_interval); - EXPECT_EQ(static_cast<unsigned int>(1), - interceptor.times_sync_occured().size()); - - // The SyncerThread should be waiting for the poll now. Nudge it to sync - // immediately (5ms). - syncable::ModelTypeBitSet model_types; - model_types[syncable::BOOKMARKS] = true; - - // Paused so we can verify the nudge types safely. - syncer_thread()->RequestPause(); - syncer_thread()->NudgeSyncerWithDataTypes(100, - SyncerThread::kUnknown, - model_types); - EXPECT_TRUE(CompareNudgeTypesBitSetToVault(model_types)); - - model_types[syncable::BOOKMARKS] = false; - model_types[syncable::AUTOFILL] = true; - syncer_thread()->NudgeSyncerWithDataTypes(0, - SyncerThread::kUnknown, - model_types); - - // Reset BOOKMARKS for expectations. - model_types[syncable::BOOKMARKS] = true; - EXPECT_TRUE(CompareNudgeTypesBitSetToVault(model_types)); - - syncer_thread()->RequestResume(); - - interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1)); - EXPECT_EQ(static_cast<unsigned int>(2), - interceptor.times_sync_occured().size()); - - // SyncerThread should be waiting again. Signal it to stop. - EXPECT_TRUE(syncer_thread()->Stop(2000)); - EXPECT_TRUE(syncer_thread()->vault_.pending_nudge_types_.empty()); -} - -TEST_F(SyncerThreadWithSyncerTest, NudgeWithPayloads) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - // We don't want a poll to happen during this test (except the first one). - PreventThreadFromPolling(); - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - const TimeDelta poll_interval = TimeDelta::FromMinutes(5); - interceptor.WaitForSyncShare(1, poll_interval + poll_interval); - EXPECT_EQ(static_cast<unsigned int>(1), - interceptor.times_sync_occured().size()); - - // The SyncerThread should be waiting for the poll now. Nudge it to sync - // immediately (5ms). - sessions::TypePayloadMap nudge_types; - nudge_types[syncable::BOOKMARKS] = "test"; - connection()->ExpectGetUpdatesRequestPayloads(nudge_types); - - // Paused so we can verify the nudge types safely. - syncer_thread()->RequestPause(); - syncer_thread()->NudgeSyncerWithPayloads(5, - SyncerThread::kUnknown, - nudge_types); - EXPECT_TRUE(CompareNudgeTypesToVault(nudge_types)); - syncer_thread()->RequestResume(); - - interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1)); - EXPECT_EQ(static_cast<unsigned int>(2), - interceptor.times_sync_occured().size()); - - // SyncerThread should be waiting again. Signal it to stop. - EXPECT_TRUE(syncer_thread()->Stop(2000)); - EXPECT_TRUE(syncer_thread()->vault_.pending_nudge_types_.empty()); -} - -TEST_F(SyncerThreadWithSyncerTest, NudgeWithPayloadsCoalesced) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - // We don't want a poll to happen during this test (except the first one). - PreventThreadFromPolling(); - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - const TimeDelta poll_interval = TimeDelta::FromMinutes(5); - interceptor.WaitForSyncShare(1, poll_interval + poll_interval); - EXPECT_EQ(static_cast<unsigned int>(1), - interceptor.times_sync_occured().size()); - - // The SyncerThread should be waiting for the poll now. Nudge it to sync - // immediately (5ms). - sessions::TypePayloadMap nudge_types; - nudge_types[syncable::BOOKMARKS] = "books"; - - // Paused so we can verify the nudge types safely. - syncer_thread()->RequestPause(); - syncer_thread()->NudgeSyncerWithPayloads(100, - SyncerThread::kUnknown, - nudge_types); - EXPECT_TRUE(CompareNudgeTypesToVault(nudge_types)); - - nudge_types.erase(syncable::BOOKMARKS); - nudge_types[syncable::AUTOFILL] = "auto"; - syncer_thread()->NudgeSyncerWithPayloads(0, - SyncerThread::kUnknown, - nudge_types); - - // Reset BOOKMARKS for expectations. - nudge_types[syncable::BOOKMARKS] = "books"; - EXPECT_TRUE(CompareNudgeTypesToVault(nudge_types)); - connection()->ExpectGetUpdatesRequestPayloads(nudge_types); - - syncer_thread()->RequestResume(); - - interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1)); - EXPECT_EQ(static_cast<unsigned int>(2), - interceptor.times_sync_occured().size()); - - // SyncerThread should be waiting again. Signal it to stop. - EXPECT_TRUE(syncer_thread()->Stop(2000)); - EXPECT_TRUE(syncer_thread()->vault_.pending_nudge_types_.empty()); -} - -TEST_F(SyncerThreadWithSyncerTest, Throttling) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - const TimeDelta poll_interval = TimeDelta::FromMilliseconds(10); - syncer_thread()->SetSyncerShortPollInterval(poll_interval); - - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - - // Wait for some healthy syncing. - interceptor.WaitForSyncShare(4, poll_interval + poll_interval); - - // Tell the server to throttle a single request, which should be all it takes - // to silence our syncer (for 2 hours, so we shouldn't hit that in this test). - // This will atomically visit the interceptor so it can switch to throttled - // mode and fail on multiple requests. - connection()->ThrottleNextRequest(&interceptor); - - // Try to trigger a sync (we have a really short poll interval already). - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - - // Wait until the syncer thread reports that it is throttled. Any further - // sync share interceptions will result in failure. If things are broken, - // we may never halt. - ASSERT_TRUE(WaitForThrottle()); - EXPECT_TRUE(syncer_thread()->IsSyncingCurrentlySilenced()); - - EXPECT_TRUE(syncer_thread()->Stop(2000)); -} - -TEST_F(SyncerThreadWithSyncerTest, StopSyncPermanently) { - // The SyncerThread should request an exit from the Syncer and set - // conditions for termination. - const TimeDelta poll_interval = TimeDelta::FromMilliseconds(10); - syncer_thread()->SetSyncerShortPollInterval(poll_interval); - - ListenerMock listener; - WaitableEvent sync_cycle_ended_event(false, false); - WaitableEvent syncer_thread_exiting_event(false, false); - TestScopedSessionEventListener reg(context_, &listener); - - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::STATUS_CHANGED))). - Times(AnyNumber()); - - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - Times(AnyNumber()). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::STOP_SYNCING_PERMANENTLY))); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_EXITING))). - WillOnce(SignalEvent(&syncer_thread_exiting_event)); - - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - - connection()->set_store_birthday("NotYourLuckyDay"); - ASSERT_TRUE(syncer_thread_exiting_event.TimedWait(max_wait_time_)); - EXPECT_TRUE(syncer_thread()->Stop(0)); -} - -TEST_F(SyncerThreadWithSyncerTest, AuthInvalid) { - SyncShareIntercept interceptor; - connection()->SetMidCommitObserver(&interceptor); - const TimeDelta poll_interval = TimeDelta::FromMilliseconds(1); - - syncer_thread()->SetSyncerShortPollInterval(poll_interval); - EXPECT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - - // Wait for some healthy syncing. - interceptor.WaitForSyncShare(2, TimeDelta::FromSeconds(10)); - EXPECT_GE(interceptor.times_sync_occured().size(), 2U); - - // Atomically start returning auth invalid and set the interceptor to fail - // on any sync. - connection()->FailWithAuthInvalid(&interceptor); - WaitForDisconnect(); - - // Try to trigger a sync (the interceptor will assert if one occurs). - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - - // Wait several poll intervals but don't expect any syncing besides the cycle - // that lost the connection. - interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(1)); - EXPECT_EQ(1U, interceptor.times_sync_occured().size()); - - // Simulate a valid re-authentication and expect resumption of syncing. - interceptor.Reset(); - ASSERT_TRUE(interceptor.times_sync_occured().empty()); - connection()->StopFailingWithAuthInvalid(NULL); - ServerConnectionEvent e = {ServerConnectionEvent::STATUS_CHANGED, - HttpResponse::SERVER_CONNECTION_OK, - true}; - connection()->channel()->NotifyListeners(e); - - interceptor.WaitForSyncShare(1, TimeDelta::FromSeconds(10)); - EXPECT_FALSE(interceptor.times_sync_occured().empty()); - - EXPECT_TRUE(syncer_thread()->Stop(2000)); -} - -// TODO(zea): Disabled, along with PauseWhenNotConnected, due to stalling on -// windows, preventing further sync unit tests from running. See crbug/39070. -TEST_F(SyncerThreadWithSyncerTest, DISABLED_Pause) { - WaitableEvent sync_cycle_ended_event(false, false); - WaitableEvent paused_event(false, false); - WaitableEvent resumed_event(false, false); - PreventThreadFromPolling(); - - ListenerMock listener; - TestScopedSessionEventListener reg(context_, &listener); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::STATUS_CHANGED))). - Times(AnyNumber()); - - // Wait for the initial sync to complete. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_EXITING))); - - ASSERT_TRUE(syncer_thread()->Start()); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - - // Request a pause. - ASSERT_TRUE(Pause(&listener)); - - // Resuming the pause. - ASSERT_TRUE(Resume(&listener)); - - // Not paused, should fail. - EXPECT_FALSE(syncer_thread()->RequestResume()); - - // Request a pause. - ASSERT_TRUE(Pause(&listener)); - - // Nudge the syncer, this should do nothing while we are paused. - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - - // Resuming will cause the nudge to be processed and a sync cycle to run. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - ASSERT_TRUE(Resume(&listener)); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - - EXPECT_TRUE(syncer_thread()->Stop(2000)); -} - -TEST_F(SyncerThreadWithSyncerTest, StartWhenNotConnected) { - WaitableEvent sync_cycle_ended_event(false, false); - WaitableEvent event(false, false); - ListenerMock listener; - TestScopedSessionEventListener reg(context_, &listener); - PreventThreadFromPolling(); - - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::STATUS_CHANGED))). - Times(AnyNumber()); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_EXITING))); - - connection()->SetServerNotReachable(); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - - // Syncer thread will always go through once cycle at the start, - // then it will wait for a connection. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION))). - WillOnce(SignalEvent(&event)); - ASSERT_TRUE(syncer_thread()->Start()); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - ASSERT_TRUE(event.TimedWait(max_wait_time_)); - - // Connect, will put the syncer thread into its usually poll wait. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_CONNECTED))). - WillOnce(SignalEvent(&event)); - connection()->SetServerReachable(); - ASSERT_TRUE(event.TimedWait(max_wait_time_)); - - // Nudge the syncer to complete a cycle. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - - EXPECT_TRUE(syncer_thread()->Stop(2000)); -} - -// See TODO comment on the "Pause" test above. -TEST_F(SyncerThreadWithSyncerTest, DISABLED_PauseWhenNotConnected) { - WaitableEvent sync_cycle_ended_event(false, false); - WaitableEvent event(false, false); - ListenerMock listener; - TestScopedSessionEventListener reg(context_, &listener); - PreventThreadFromPolling(); - - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::STATUS_CHANGED))). - Times(AnyNumber()); - - // Put the thread into a "waiting for connection" state. - connection()->SetServerNotReachable(); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION))). - WillOnce(SignalEvent(&event)); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - - ASSERT_TRUE(syncer_thread()->Start()); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - ASSERT_TRUE(event.TimedWait(max_wait_time_)); - - // Pause and resume the thread while waiting for a connection. - ASSERT_TRUE(Pause(&listener)); - ASSERT_TRUE(Resume(&listener)); - - // Make a connection and let the syncer cycle. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_CONNECTED))). - WillOnce(SignalEvent(&event)); - connection()->SetServerReachable(); - ASSERT_TRUE(event.TimedWait(max_wait_time_)); - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - - // Disconnect and get into the waiting for a connection state. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_WAITING_FOR_CONNECTION))). - WillOnce(SignalEvent(&event)); - connection()->SetServerNotReachable(); - ASSERT_TRUE(event.TimedWait(max_wait_time_)); - - // Pause so we can test getting a connection while paused. - ASSERT_TRUE(Pause(&listener)); - - // Get a connection then resume. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_CONNECTED))). - WillOnce(SignalEvent(&event)); - connection()->SetServerReachable(); - ASSERT_TRUE(event.TimedWait(max_wait_time_)); - - ASSERT_TRUE(Resume(&listener)); - - // Cycle the syncer to show we are not longer paused. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_EXITING))); - - syncer_thread()->NudgeSyncer(0, SyncerThread::kUnknown); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - - EXPECT_TRUE(syncer_thread()->Stop(2000)); -} - -TEST_F(SyncerThreadWithSyncerTest, PauseResumeWhenNotRunning) { - WaitableEvent sync_cycle_ended_event(false, false); - WaitableEvent event(false, false); - ListenerMock listener; - TestScopedSessionEventListener reg(context_, &listener); - PreventThreadFromPolling(); - - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::STATUS_CHANGED))). - Times(AnyNumber()); - - // Pause and resume the syncer while not running - ASSERT_TRUE(Pause(&listener)); - ASSERT_TRUE(Resume(&listener)); - - // Pause the thread then start the syncer. - ASSERT_TRUE(Pause(&listener)); - metadb()->Open(); - syncer_thread()->CreateSyncer(metadb()->name()); - ASSERT_TRUE(syncer_thread()->Start()); - - // Resume and let the syncer cycle. - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNC_CYCLE_ENDED))). - WillOnce(SignalEvent(&sync_cycle_ended_event)); - EXPECT_CALL(listener, OnSyncEngineEvent( - Field(&SyncEngineEvent::what_happened, - SyncEngineEvent::SYNCER_THREAD_EXITING))); - - ASSERT_TRUE(Resume(&listener)); - ASSERT_TRUE(sync_cycle_ended_event.TimedWait(max_wait_time_)); - EXPECT_TRUE(syncer_thread()->Stop(2000)); -} - -} // namespace browser_sync diff --git a/chrome/browser/sync/engine/syncer_types.cc b/chrome/browser/sync/engine/syncer_types.cc new file mode 100644 index 0000000..8bc5524 --- /dev/null +++ b/chrome/browser/sync/engine/syncer_types.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2011 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/sync/engine/syncer_types.h" + +namespace browser_sync { + +SyncEngineEvent::SyncEngineEvent(EventCause cause) : what_happened(cause), + snapshot(NULL) { +} + +SyncEngineEvent::~SyncEngineEvent() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/engine/syncer_types.h b/chrome/browser/sync/engine/syncer_types.h index 0e05e0e..57e8855 100644 --- a/chrome/browser/sync/engine/syncer_types.h +++ b/chrome/browser/sync/engine/syncer_types.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,7 +10,7 @@ #include <vector> #include "base/observer_list.h" -#include "chrome/browser/sync/util/channel.h" +#include "chrome/browser/sync/syncable/model_type.h" namespace syncable { class BaseTransaction; @@ -83,30 +83,6 @@ struct SyncEngineEvent { SYNC_CYCLE_ENDED, //////////////////////////////////////////////////////////////// - // SyncerThread generated events. - - // This event is sent when the thread is paused in response to a - // pause request. - // TODO(tim): Deprecated. - SYNCER_THREAD_PAUSED, - - // This event is sent when the thread is resumed in response to a - // resume request. - // TODO(tim): Deprecated. - SYNCER_THREAD_RESUMED, - - // This event is sent when the thread is waiting for a connection - // to be established. - SYNCER_THREAD_WAITING_FOR_CONNECTION, - - // This event is sent when a connection has been established and - // the thread continues. - SYNCER_THREAD_CONNECTED, - - // Sent when the main syncer loop finishes. - SYNCER_THREAD_EXITING, - - //////////////////////////////////////////////////////////////// // Generated in response to specific protocol actions or events. // New token in updated_token. @@ -124,9 +100,8 @@ struct SyncEngineEvent { CLEAR_SERVER_DATA_FAILED, }; - explicit SyncEngineEvent(EventCause cause) : what_happened(cause), - snapshot(NULL) { -} + explicit SyncEngineEvent(EventCause cause); + ~SyncEngineEvent(); EventCause what_happened; diff --git a/chrome/browser/sync/engine/syncer_unittest.cc b/chrome/browser/sync/engine/syncer_unittest.cc index 0aa7e35..243756a 100644 --- a/chrome/browser/sync/engine/syncer_unittest.cc +++ b/chrome/browser/sync/engine/syncer_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -13,7 +13,7 @@ #include <string> #include "base/callback.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/string_number_conversions.h" #include "build/build_config.h" #include "chrome/browser/sync/engine/conflict_resolver.h" @@ -154,8 +154,8 @@ class SyncerTest : public testing::Test, std::vector<ModelSafeWorker*> workers; GetModelSafeRoutingInfo(&info); GetWorkers(&workers); - sessions::TypePayloadMap types = - sessions::MakeTypePayloadMapFromRoutingInfo(info, std::string()); + syncable::ModelTypePayloadMap types = + syncable::ModelTypePayloadMapFromRoutingInfo(info, std::string()); return new SyncSession(context_.get(), this, sessions::SyncSourceInfo(sync_pb::GetUpdatesCallerInfo::UNKNOWN, types), info, workers); diff --git a/chrome/browser/sync/engine/syncer_util.cc b/chrome/browser/sync/engine/syncer_util.cc index c1bed35..c20e206 100644 --- a/chrome/browser/sync/engine/syncer_util.cc +++ b/chrome/browser/sync/engine/syncer_util.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -639,28 +639,6 @@ void SyncerUtil::AddPredecessorsThenItem( std::reverse(commit_ids->begin() + initial_size, commit_ids->end()); } -// TODO(ncarter): This is redundant to some code in GetCommitIdsCommand. Unify -// them. -// static -void SyncerUtil::AddUncommittedParentsAndTheirPredecessors( - syncable::BaseTransaction* trans, - syncable::MetahandleSet* inserted_items, - std::vector<syncable::Id>* commit_ids, - syncable::Id parent_id) { - size_t intial_commit_ids_size = commit_ids->size(); - // Climb the tree adding entries leaf -> root. - while (!parent_id.ServerKnows()) { - Entry parent(trans, GET_BY_ID, parent_id); - CHECK(parent.good()) << "Bad user-only parent in item path."; - if (!AddItemThenPredecessors(trans, &parent, IS_UNSYNCED, inserted_items, - commit_ids)) - break; // Parent was already present in |inserted_items|. - parent_id = parent.Get(PARENT_ID); - } - // Reverse what we added to get the correct order. - std::reverse(commit_ids->begin() + intial_commit_ids_size, commit_ids->end()); -} - // static void SyncerUtil::MarkDeletedChildrenSynced( const syncable::ScopedDirLookup &dir, diff --git a/chrome/browser/sync/engine/syncer_util.h b/chrome/browser/sync/engine/syncer_util.h index b1d5e50..90de980 100644 --- a/chrome/browser/sync/engine/syncer_util.h +++ b/chrome/browser/sync/engine/syncer_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -127,12 +127,6 @@ class SyncerUtil { syncable::MetahandleSet* inserted_items, std::vector<syncable::Id>* commit_ids); - static void AddUncommittedParentsAndTheirPredecessors( - syncable::BaseTransaction* trans, - syncable::MetahandleSet* inserted_items, - std::vector<syncable::Id>* commit_ids, - syncable::Id parent_id); - static void MarkDeletedChildrenSynced( const syncable::ScopedDirLookup &dir, std::set<syncable::Id>* deleted_folders); diff --git a/chrome/browser/sync/engine/update_applicator.cc b/chrome/browser/sync/engine/update_applicator.cc index 6922c0f..04eca43 100644 --- a/chrome/browser/sync/engine/update_applicator.cc +++ b/chrome/browser/sync/engine/update_applicator.cc @@ -91,10 +91,21 @@ void UpdateApplicator::Advance() { } bool UpdateApplicator::SkipUpdate(const syncable::Entry& entry) { - ModelSafeGroup g = - GetGroupForModelType(entry.GetServerModelType(), routing_info_); + syncable::ModelType type = entry.GetServerModelType(); + ModelSafeGroup g = GetGroupForModelType(type, routing_info_); + // The extra routing_info count check here is to support GetUpdateses for + // a subset of the globally enabled types, and not attempt to update items + // if their type isn't permitted in the current run. These would typically + // be unapplied items from a previous sync. if (g != group_filter_) return true; + if (g == GROUP_PASSIVE && + !routing_info_.count(type) && + type != syncable::UNSPECIFIED && + type != syncable::TOP_LEVEL_FOLDER) { + VLOG(1) << "Skipping update application, type not permitted."; + return true; + } return false; } diff --git a/chrome/browser/sync/glue/app_data_type_controller.cc b/chrome/browser/sync/glue/app_data_type_controller.cc index 453d5e2..cf025f6 100644 --- a/chrome/browser/sync/glue/app_data_type_controller.cc +++ b/chrome/browser/sync/glue/app_data_type_controller.cc @@ -1,17 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/app_data_type_controller.h" #include "base/metrics/histogram.h" -#include "base/logging.h" -#include "base/time.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" -#include "chrome/browser/sync/unrecoverable_error_handler.h" -#include "content/browser/browser_thread.h" namespace browser_sync { @@ -19,110 +14,42 @@ AppDataTypeController::AppDataTypeController( ProfileSyncFactory* profile_sync_factory, Profile* profile, ProfileSyncService* sync_service) - : profile_sync_factory_(profile_sync_factory), - profile_(profile), - sync_service_(sync_service), - state_(NOT_RUNNING) { - DCHECK(profile_sync_factory); - DCHECK(sync_service); + : FrontendDataTypeController(profile_sync_factory, + profile, + sync_service) { } AppDataTypeController::~AppDataTypeController() { } -void AppDataTypeController::Start(StartCallback* start_callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(start_callback); - if (state_ != NOT_RUNNING) { - start_callback->Run(BUSY); - delete start_callback; - return; - } +syncable::ModelType AppDataTypeController::type() const { + return syncable::APPS; +} - start_callback_.reset(start_callback); +bool AppDataTypeController::StartModels() { + profile_->InitExtensions(true); + return true; +} - profile_->InitExtensions(); +void AppDataTypeController::CreateSyncComponents() { ProfileSyncFactory::SyncComponents sync_components = profile_sync_factory_->CreateAppSyncComponents(sync_service_, this); model_associator_.reset(sync_components.model_associator); change_processor_.reset(sync_components.change_processor); - - bool sync_has_nodes = false; - if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { - StartFailed(UNRECOVERABLE_ERROR); - return; - } - - base::TimeTicks start_time = base::TimeTicks::Now(); - bool merge_success = model_associator_->AssociateModels(); - UMA_HISTOGRAM_TIMES("Sync.AppAssociationTime", - base::TimeTicks::Now() - start_time); - if (!merge_success) { - StartFailed(ASSOCIATION_FAILED); - return; - } - - sync_service_->ActivateDataType(this, change_processor_.get()); - state_ = RUNNING; - FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK); -} - -void AppDataTypeController::Stop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (change_processor_ != NULL) - sync_service_->DeactivateDataType(this, change_processor_.get()); - - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - - change_processor_.reset(); - model_associator_.reset(); - start_callback_.reset(); - - state_ = NOT_RUNNING; -} - -bool AppDataTypeController::enabled() { - return true; -} - -syncable::ModelType AppDataTypeController::type() { - return syncable::APPS; -} - -browser_sync::ModelSafeGroup AppDataTypeController::model_safe_group() { - return browser_sync::GROUP_UI; -} - -const char* AppDataTypeController::name() const { - // For logging only. - return "app"; -} - -DataTypeController::State AppDataTypeController::state() { - return state_; } -void AppDataTypeController::OnUnrecoverableError( +void AppDataTypeController::RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); UMA_HISTOGRAM_COUNTS("Sync.AppRunFailures", 1); - sync_service_->OnUnrecoverableError(from_here, message); } -void AppDataTypeController::FinishStart(StartResult result) { - start_callback_->Run(result); - start_callback_.reset(); +void AppDataTypeController::RecordAssociationTime(base::TimeDelta time) { + UMA_HISTOGRAM_TIMES("Sync.AppAssociationTime", time); } -void AppDataTypeController::StartFailed(StartResult result) { - model_associator_.reset(); - change_processor_.reset(); - start_callback_->Run(result); - start_callback_.reset(); +void AppDataTypeController::RecordStartFailure(StartResult result) { UMA_HISTOGRAM_ENUMERATION("Sync.AppStartFailures", result, MAX_START_RESULT); diff --git a/chrome/browser/sync/glue/app_data_type_controller.h b/chrome/browser/sync/glue/app_data_type_controller.h index 4bd2750..93bd517 100644 --- a/chrome/browser/sync/glue/app_data_type_controller.h +++ b/chrome/browser/sync/glue/app_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,20 +8,11 @@ #include <string> -#include "base/basictypes.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/glue/data_type_controller.h" - -class Profile; -class ProfileSyncFactory; -class ProfileSyncService; +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" namespace browser_sync { -class AssociatorInterface; -class ChangeProcessor; - -class AppDataTypeController : public DataTypeController { +class AppDataTypeController : public FrontendDataTypeController { public: AppDataTypeController( ProfileSyncFactory* profile_sync_factory, @@ -30,41 +21,17 @@ class AppDataTypeController : public DataTypeController { virtual ~AppDataTypeController(); // DataTypeController implementation. - virtual void Start(StartCallback* start_callback); - - virtual void Stop(); - - virtual bool enabled(); - - virtual syncable::ModelType type(); - - virtual browser_sync::ModelSafeGroup model_safe_group(); - - virtual const char* name() const; - - virtual State state(); + virtual syncable::ModelType type() const; - // UnrecoverableErrorHandler interface. - virtual void OnUnrecoverableError( + private: + // DataTypeController implementations. + virtual bool StartModels(); + virtual void CreateSyncComponents(); + virtual void RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message); - - private: - // Helper method to run the stashed start callback with a given result. - void FinishStart(StartResult result); - - // Cleans up state and calls callback when start fails. - void StartFailed(StartResult result); - - ProfileSyncFactory* profile_sync_factory_; - Profile* profile_; - ProfileSyncService* sync_service_; - - State state_; - - scoped_ptr<StartCallback> start_callback_; - scoped_ptr<AssociatorInterface> model_associator_; - scoped_ptr<ChangeProcessor> change_processor_; + virtual void RecordAssociationTime(base::TimeDelta time); + virtual void RecordStartFailure(StartResult result); DISALLOW_COPY_AND_ASSIGN(AppDataTypeController); }; diff --git a/chrome/browser/sync/glue/autofill_change_processor.cc b/chrome/browser/sync/glue/autofill_change_processor.cc index aef3b99..00be75c 100644 --- a/chrome/browser/sync/glue/autofill_change_processor.cc +++ b/chrome/browser/sync/glue/autofill_change_processor.cc @@ -19,7 +19,7 @@ #include "chrome/browser/webdata/web_data_service.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/guid.h" -#include "chrome/common/notification_service.h" +#include "content/common/notification_service.h" namespace browser_sync { @@ -108,7 +108,7 @@ void AutofillChangeProcessor::ObserveAutofillEntriesChanged( } std::vector<base::Time> timestamps; - if (!web_database_->GetAutofillTimestamps( + if (!web_database_->GetAutofillTable()->GetAutofillTimestamps( change->key().name(), change->key().value(), ×tamps)) { @@ -145,7 +145,7 @@ void AutofillChangeProcessor::ObserveAutofillEntriesChanged( } std::vector<base::Time> timestamps; - if (!web_database_->GetAutofillTimestamps( + if (!web_database_->GetAutofillTable()->GetAutofillTimestamps( change->key().name(), change->key().value(), ×tamps)) { @@ -173,8 +173,10 @@ void AutofillChangeProcessor::RemoveSyncNode(const std::string& tag, sync_api::WriteNode sync_node(trans); int64 sync_id = model_associator_->GetSyncIdFromChromeId(tag); if (sync_api::kInvalidId == sync_id) { - std::string err = "Unexpected notification for: " + tag; - error_handler()->OnUnrecoverableError(FROM_HERE, err); + // This could happen because web db might have duplicates and when an entry + // and its duplicate is deleted. + LOG(WARNING) << + "Bogus delete notification generate for autofill entry " + tag; return; } else { if (!sync_node.InitByIdLookup(sync_id)) { @@ -288,7 +290,7 @@ void AutofillChangeProcessor::CommitChangesFromSyncModel() { autofill_changes_.clear(); // Make changes - if (!web_database_->UpdateAutofillEntries(new_entries)) { + if (!web_database_->GetAutofillTable()->UpdateAutofillEntries(new_entries)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Could not update autofill entries."); return; @@ -301,7 +303,7 @@ void AutofillChangeProcessor::CommitChangesFromSyncModel() { void AutofillChangeProcessor::ApplySyncAutofillEntryDelete( const sync_pb::AutofillSpecifics& autofill) { - if (!web_database_->RemoveFormElement( + if (!web_database_->GetAutofillTable()->RemoveFormElement( UTF8ToUTF16(autofill.name()), UTF8ToUTF16(autofill.value()))) { error_handler()->OnUnrecoverableError(FROM_HERE, "Could not remove autofill node."); @@ -344,11 +346,11 @@ void AutofillChangeProcessor::ApplySyncAutofillProfileChange( DCHECK(false) << "Guid generated is invalid " << guid; return; } - scoped_ptr<AutoFillProfile> p(new AutoFillProfile); + scoped_ptr<AutofillProfile> p(new AutofillProfile); p->set_guid(guid); AutofillModelAssociator::FillProfileWithServerData(p.get(), profile); - if (!web_database_->AddAutoFillProfile(*p.get())) { + if (!web_database_->GetAutofillTable()->AddAutofillProfile(*p.get())) { NOTREACHED() << "Couldn't add autofill profile: " << guid; return; } @@ -364,16 +366,18 @@ void AutofillChangeProcessor::ApplySyncAutofillProfileChange( "model association has not happened"); return; } - AutoFillProfile *temp_ptr; - if (!web_database_->GetAutoFillProfile(*guid, &temp_ptr)) { + AutofillProfile *temp_ptr; + if (!web_database_->GetAutofillTable()->GetAutofillProfile( + *guid, &temp_ptr)) { LOG(ERROR) << "Autofill profile not found for " << *guid; return; } - scoped_ptr<AutoFillProfile> p(temp_ptr); + scoped_ptr<AutofillProfile> p(temp_ptr); AutofillModelAssociator::FillProfileWithServerData(p.get(), profile); - if (!web_database_->UpdateAutoFillProfile(*(p.get()))) { + if (!web_database_->GetAutofillTable()->UpdateAutofillProfile( + *(p.get()))) { LOG(ERROR) << "Couldn't update autofill profile: " << guid; return; } @@ -393,7 +397,7 @@ void AutofillChangeProcessor::ApplySyncAutofillProfileDelete( return; } - if (!web_database_->RemoveAutoFillProfile(*guid)) { + if (!web_database_->GetAutofillTable()->RemoveAutofillProfile(*guid)) { LOG(ERROR) << "Could not remove the profile"; return; } diff --git a/chrome/browser/sync/glue/autofill_change_processor.h b/chrome/browser/sync/glue/autofill_change_processor.h index 3b5ff35..914feca 100644 --- a/chrome/browser/sync/glue/autofill_change_processor.h +++ b/chrome/browser/sync/glue/autofill_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -16,8 +16,8 @@ #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/webdata/web_data_service.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" class AutofillCreditCardChange; class AutofillEntry; diff --git a/chrome/browser/sync/glue/autofill_data_type_controller.cc b/chrome/browser/sync/glue/autofill_data_type_controller.cc index 37582fe..fa5dde4 100644 --- a/chrome/browser/sync/glue/autofill_data_type_controller.cc +++ b/chrome/browser/sync/glue/autofill_data_type_controller.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -14,8 +14,8 @@ #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/webdata/web_data_service.h" -#include "chrome/common/notification_service.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_service.h" namespace browser_sync { @@ -39,6 +39,12 @@ AutofillDataTypeController::AutofillDataTypeController( AutofillDataTypeController::~AutofillDataTypeController() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + + // TODO(zea): remove once crbug.com/61804 is resolved. + CHECK_EQ(state_, NOT_RUNNING) << "AutofillDataTypeController destroyed " + << "without being stopped."; + CHECK(!change_processor_.get()) << "AutofillDataTypeController destroyed " + << "while holding a change processor."; } void AutofillDataTypeController::Start(StartCallback* start_callback) { @@ -46,7 +52,7 @@ void AutofillDataTypeController::Start(StartCallback* start_callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(start_callback); if (state() != NOT_RUNNING) { - start_callback->Run(BUSY); + start_callback->Run(BUSY, FROM_HERE); delete start_callback; return; } @@ -100,6 +106,11 @@ void AutofillDataTypeController::Observe(NotificationType type, &AutofillDataTypeController::StartImpl)); } +// TODO(sync): Blocking the UI thread at shutdown is bad. If we had a way of +// distinguishing chrome shutdown from sync shutdown, we should be able to avoid +// this (http://crbug.com/55662). Further, all this functionality should be +// abstracted to a higher layer, where we could ensure all datatypes are doing +// the same thing (http://crbug.com/76232). void AutofillDataTypeController::Stop() { VLOG(1) << "Stopping autofill data type controller."; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); @@ -116,13 +127,15 @@ void AutofillDataTypeController::Stop() { } // Wait for the model association to abort. abort_association_complete_.Wait(); - StartDoneImpl(ABORTED, STOPPING); + StartDoneImpl(ABORTED, STOPPING, FROM_HERE); } // If Stop() is called while Start() is waiting for the personal // data manager or web data service to load, abort the start. if (state_ == MODEL_STARTING) - StartDoneImpl(ABORTED, STOPPING); + StartDoneImpl(ABORTED, STOPPING, FROM_HERE); + + DCHECK(!start_callback_.get()); // Deactivate the change processor on the UI thread. We dont want to listen // for any more changes or process them from server. @@ -140,27 +153,33 @@ void AutofillDataTypeController::Stop() { // particular, during shutdown we may attempt to destroy the // profile_sync_service before we've removed its observers (BUG 61804). datatype_stopped_.Wait(); + } else if (change_processor_.get()) { + // TODO(zea): remove once crbug.com/61804 is resolved. + LOG(FATAL) << "AutofillDataTypeController::Stop() called after DB thread" + << " killed."; } + CHECK(!change_processor_.get()) << "AutofillChangeProcessor not released."; } bool AutofillDataTypeController::enabled() { return true; } -syncable::ModelType AutofillDataTypeController::type() { +syncable::ModelType AutofillDataTypeController::type() const { return syncable::AUTOFILL; } -browser_sync::ModelSafeGroup AutofillDataTypeController::model_safe_group() { +browser_sync::ModelSafeGroup AutofillDataTypeController::model_safe_group() + const { return browser_sync::GROUP_DB; } -const char* AutofillDataTypeController::name() const { +std::string AutofillDataTypeController::name() const { // For logging only. return "autofill"; } -DataTypeController::State AutofillDataTypeController::state() { +DataTypeController::State AutofillDataTypeController::state() const { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); return state_; } @@ -199,6 +218,11 @@ void AutofillDataTypeController::StartImpl() { change_processor_.reset(sync_components.change_processor); } + if (!model_associator_->CryptoReadyIfNecessary()) { + StartFailed(NEEDS_CRYPTO); + return; + } + bool sync_has_nodes = false; if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { StartFailed(UNRECOVERABLE_ERROR); @@ -234,18 +258,20 @@ void AutofillDataTypeController::StartDone( this, &AutofillDataTypeController::StartDoneImpl, result, - new_state)); + new_state, + FROM_HERE)); } } void AutofillDataTypeController::StartDoneImpl( DataTypeController::StartResult result, - DataTypeController::State new_state) { + DataTypeController::State new_state, + const tracked_objects::Location& location) { VLOG(1) << "Autofill data type controller StartDoneImpl called."; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); set_state(new_state); - start_callback_->Run(result); + start_callback_->Run(result, location); start_callback_.reset(); if (result == UNRECOVERABLE_ERROR || result == ASSOCIATION_FAILED) { diff --git a/chrome/browser/sync/glue/autofill_data_type_controller.h b/chrome/browser/sync/glue/autofill_data_type_controller.h index 05b964c..8df5fb8 100644 --- a/chrome/browser/sync/glue/autofill_data_type_controller.h +++ b/chrome/browser/sync/glue/autofill_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,7 +9,7 @@ #include <string> #include "base/basictypes.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/synchronization/waitable_event.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/profile_sync_factory.h" @@ -43,13 +43,13 @@ class AutofillDataTypeController : public DataTypeController, virtual bool enabled(); - virtual syncable::ModelType type(); + virtual syncable::ModelType type() const; - virtual browser_sync::ModelSafeGroup model_safe_group(); + virtual browser_sync::ModelSafeGroup model_safe_group() const; - virtual const char* name() const; + virtual std::string name() const; - virtual State state(); + virtual State state() const; // UnrecoverableHandler implementation virtual void OnUnrecoverableError(const tracked_objects::Location& from_here, @@ -74,7 +74,8 @@ class AutofillDataTypeController : public DataTypeController, private: void StartImpl(); void StartDone(StartResult result, State state); - void StartDoneImpl(StartResult result, State state); + void StartDoneImpl(StartResult result, State state, + const tracked_objects::Location& location); void StopImpl(); void StartFailed(StartResult result); void OnUnrecoverableErrorImpl(const tracked_objects::Location& from_here, diff --git a/chrome/browser/sync/glue/autofill_data_type_controller_unittest.cc b/chrome/browser/sync/glue/autofill_data_type_controller_unittest.cc index 6d36e39..01deb74 100644 --- a/chrome/browser/sync/glue/autofill_data_type_controller_unittest.cc +++ b/chrome/browser/sync/glue/autofill_data_type_controller_unittest.cc @@ -1,13 +1,13 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" #include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" #include "base/synchronization/waitable_event.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/glue/autofill_data_type_controller.h" @@ -17,10 +17,10 @@ #include "chrome/browser/sync/profile_sync_service_mock.h" #include "chrome/browser/sync/profile_sync_test_util.h" #include "chrome/browser/webdata/web_data_service.h" -#include "chrome/common/notification_source.h" -#include "chrome/common/notification_type.h" #include "chrome/test/profile_mock.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_source.h" +#include "content/common/notification_type.h" #include "testing/gmock/include/gmock/gmock.h" using base::WaitableEvent; @@ -46,7 +46,8 @@ ACTION_P(SignalEvent, event) { class StartCallback { public: - MOCK_METHOD1(Run, void(DataTypeController::StartResult result)); + MOCK_METHOD2(Run, void(DataTypeController::StartResult result, + const tracked_objects::Location& location)); }; class PersonalDataManagerMock : public PersonalDataManager { @@ -118,7 +119,8 @@ class AutofillDataTypeControllerTest : public testing::Test { WillOnce(Return( ProfileSyncFactory::SyncComponents(model_associator_, change_processor_))); - + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels()). @@ -160,7 +162,7 @@ TEST_F(AutofillDataTypeControllerTest, StartPDMAndWDSReady) { SetStartExpectations(); SetAssociateExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, autofill_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)). + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)). WillOnce(QuitUIMessageLoop()); autofill_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); MessageLoop::current()->Run(); @@ -179,7 +181,7 @@ TEST_F(AutofillDataTypeControllerTest, AbortWhilePDMStarting) { EXPECT_EQ(DataTypeController::MODEL_STARTING, autofill_dtc_->state()); EXPECT_CALL(service_, DeactivateDataType(_, _)).Times(0); - EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED, _)); autofill_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, autofill_dtc_->state()); } @@ -197,7 +199,7 @@ TEST_F(AutofillDataTypeControllerTest, AbortWhileWDSStarting) { EXPECT_EQ(DataTypeController::MODEL_STARTING, autofill_dtc_->state()); EXPECT_CALL(service_, DeactivateDataType(_, _)).Times(0); - EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED, _)); autofill_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, autofill_dtc_->state()); } @@ -221,6 +223,8 @@ TEST_F(AutofillDataTypeControllerTest, AbortWhileAssociatingNotActivated) { // that signals the event and lets the DB thread continue. WaitableEvent pause_db_thread(false, false); WaitableEvent wait_for_db_thread_pause(false, false); + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillOnce(DoAll( SignalEvent(&wait_for_db_thread_pause), @@ -235,7 +239,7 @@ TEST_F(AutofillDataTypeControllerTest, AbortWhileAssociatingNotActivated) { EXPECT_EQ(DataTypeController::ASSOCIATING, autofill_dtc_->state()); EXPECT_CALL(service_, DeactivateDataType(_, _)).Times(0); - EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED, _)); autofill_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, autofill_dtc_->state()); } @@ -250,6 +254,8 @@ TEST_F(AutofillDataTypeControllerTest, AbortWhileAssociatingActivated) { WillOnce(Return( ProfileSyncFactory::SyncComponents(model_associator_, change_processor_))); + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels()). @@ -273,7 +279,7 @@ TEST_F(AutofillDataTypeControllerTest, AbortWhileAssociatingActivated) { EXPECT_EQ(DataTypeController::ASSOCIATING, autofill_dtc_->state()); SetStopExpectations(); - EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED, _)); autofill_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, autofill_dtc_->state()); } diff --git a/chrome/browser/sync/glue/autofill_model_associator.cc b/chrome/browser/sync/glue/autofill_model_associator.cc index 5ecefc6..493e372 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.cc +++ b/chrome/browser/sync/glue/autofill_model_associator.cc @@ -35,8 +35,8 @@ struct AutofillModelAssociator::DataBundle { std::set<AutofillKey> current_entries; std::vector<AutofillEntry> new_entries; std::set<string16> current_profiles; - std::vector<AutoFillProfile*> updated_profiles; - std::vector<AutoFillProfile*> new_profiles; // We own these pointers. + std::vector<AutofillProfile*> updated_profiles; + std::vector<AutofillProfile*> new_profiles; // We own these pointers. ~DataBundle() { STLDeleteElements(&new_profiles); } }; @@ -118,15 +118,15 @@ bool AutofillModelAssociator::TraverseAndAssociateChromeAutofillEntries( bool AutofillModelAssociator::LoadAutofillData( std::vector<AutofillEntry>* entries, - std::vector<AutoFillProfile*>* profiles) { + std::vector<AutofillProfile*>* profiles) { if (IsAbortPending()) return false; - if (!web_database_->GetAllAutofillEntries(entries)) + if (!web_database_->GetAutofillTable()->GetAllAutofillEntries(entries)) return false; if (IsAbortPending()) return false; - if (!web_database_->GetAutoFillProfiles(profiles)) + if (!web_database_->GetAutofillTable()->GetAutofillProfiles(profiles)) return false; return true; @@ -142,7 +142,7 @@ bool AutofillModelAssociator::AssociateModels() { // TODO(zork): Attempt to load the model association from storage. std::vector<AutofillEntry> entries; - ScopedVector<AutoFillProfile> profiles; + ScopedVector<AutofillProfile> profiles; if (!LoadAutofillData(&entries, &profiles.get())) { LOG(ERROR) << "Could not get the autofill data from WebDatabase."; @@ -178,7 +178,7 @@ bool AutofillModelAssociator::AssociateModels() { // the autofill database after closing the write transaction, since // this is the only thread that writes to the database. We also don't have // to worry about the sync model getting out of sync, because changes are - // propogated to the ChangeProcessor on this thread. + // propagated to the ChangeProcessor on this thread. if (!SaveChangesToWebData(bundle)) { LOG(ERROR) << "Failed to update autofill entries."; return false; @@ -206,21 +206,24 @@ bool AutofillModelAssociator::SaveChangesToWebData(const DataBundle& bundle) { return false; if (bundle.new_entries.size() && - !web_database_->UpdateAutofillEntries(bundle.new_entries)) { + !web_database_->GetAutofillTable()->UpdateAutofillEntries( + bundle.new_entries)) { return false; } for (size_t i = 0; i < bundle.new_profiles.size(); i++) { if (IsAbortPending()) return false; - if (!web_database_->AddAutoFillProfile(*bundle.new_profiles[i])) + if (!web_database_->GetAutofillTable()->AddAutofillProfile( + *bundle.new_profiles[i])) return false; } for (size_t i = 0; i < bundle.updated_profiles.size(); i++) { if (IsAbortPending()) return false; - if (!web_database_->UpdateAutoFillProfile(*bundle.updated_profiles[i])) + if (!web_database_->GetAutofillTable()->UpdateAutofillProfile( + *bundle.updated_profiles[i])) return false; } return true; @@ -230,7 +233,7 @@ bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, DataBundle* bundle, - const std::vector<AutoFillProfile*>& all_profiles_from_db) { + const std::vector<AutofillProfile*>& all_profiles_from_db) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); bool autofill_profile_not_migrated = HasNotMigratedYet(write_trans); @@ -239,12 +242,12 @@ bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( VLOG(2) << "[AUTOFILL MIGRATION]" << "Printing profiles from web db"; - for (std::vector<AutoFillProfile*>::const_iterator ix = + for (std::vector<AutofillProfile*>::const_iterator ix = all_profiles_from_db.begin(); ix != all_profiles_from_db.end(); ++ix) { - AutoFillProfile* p = *ix; + AutofillProfile* p = *ix; VLOG(2) << "[AUTOFILL MIGRATION] " - << p->GetFieldText(AutofillType(NAME_FIRST)) - << p->GetFieldText(AutofillType(NAME_LAST)); + << p->GetInfo(NAME_FIRST) + << p->GetInfo(NAME_LAST); } } @@ -288,8 +291,8 @@ bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( // Define the functor to be used as the predicate in find_if call. struct CompareProfiles - : public std::binary_function<AutoFillProfile*, AutoFillProfile*, bool> { - bool operator() (AutoFillProfile* p1, AutoFillProfile* p2) const { + : public std::binary_function<AutofillProfile*, AutofillProfile*, bool> { + bool operator() (AutofillProfile* p1, AutofillProfile* p2) const { if (p1->Compare(*p2) == 0) return true; else @@ -297,11 +300,11 @@ struct CompareProfiles } }; -AutoFillProfile* AutofillModelAssociator::FindCorrespondingNodeFromWebDB( +AutofillProfile* AutofillModelAssociator::FindCorrespondingNodeFromWebDB( const sync_pb::AutofillProfileSpecifics& profile, - const std::vector<AutoFillProfile*>& all_profiles_from_db) { + const std::vector<AutofillProfile*>& all_profiles_from_db) { static std::string guid(guid::GenerateGUID()); - AutoFillProfile p; + AutofillProfile p; p.set_guid(guid); if (!FillProfileWithServerData(&p, profile)) { // Not a big deal. We encountered an error. Just say this profile does not @@ -311,7 +314,7 @@ AutoFillProfile* AutofillModelAssociator::FindCorrespondingNodeFromWebDB( } // Now instantiate the functor and call find_if. - std::vector<AutoFillProfile*>::const_iterator ix = + std::vector<AutofillProfile*>::const_iterator ix = std::find_if(all_profiles_from_db.begin(), all_profiles_from_db.end(), std::bind2nd(CompareProfiles(), &p)); @@ -342,11 +345,11 @@ void AutofillModelAssociator::AddNativeProfileIfNeeded( const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, const sync_api::ReadNode& node, - const std::vector<AutoFillProfile*>& all_profiles_from_db) { + const std::vector<AutofillProfile*>& all_profiles_from_db) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - AutoFillProfile* profile_in_web_db = FindCorrespondingNodeFromWebDB( + AutofillProfile* profile_in_web_db = FindCorrespondingNodeFromWebDB( profile, all_profiles_from_db); if (profile_in_web_db != NULL) { @@ -365,7 +368,7 @@ void AutofillModelAssociator::AddNativeProfileIfNeeded( return; } Associate(&guid, node.GetId()); - AutoFillProfile* p = new AutoFillProfile(guid); + AutofillProfile* p = new AutofillProfile(guid); FillProfileWithServerData(p, profile); bundle->new_profiles.push_back(p); } @@ -498,18 +501,18 @@ bool AutofillModelAssociator::MergeTimestamps( // the local value if they differ, and return whether the merge happened. bool MergeField(FormGroup* f, AutofillFieldType t, const std::string& specifics_field) { - if (UTF16ToUTF8(f->GetFieldText(AutofillType(t))) == specifics_field) + if (UTF16ToUTF8(f->GetInfo(t)) == specifics_field) return false; - f->SetInfo(AutofillType(t), UTF8ToUTF16(specifics_field)); + f->SetInfo(t, UTF8ToUTF16(specifics_field)); return true; } // static bool AutofillModelAssociator::FillProfileWithServerData( - AutoFillProfile* merge_into, + AutofillProfile* merge_into, const sync_pb::AutofillProfileSpecifics& specifics) { bool diff = false; - AutoFillProfile* p = merge_into; + AutofillProfile* p = merge_into; const sync_pb::AutofillProfileSpecifics& s(specifics); diff = MergeField(p, NAME_FIRST, s.name_first()) || diff; diff = MergeField(p, NAME_LAST, s.name_last()) || diff; @@ -575,4 +578,13 @@ bool AutofillModelAssociator::HasNotMigratedYet( return false; } +bool AutofillModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + syncable::ModelTypeSet encrypted_types; + sync_service_->GetEncryptedDataTypes(&encrypted_types); + return encrypted_types.count(syncable::AUTOFILL) == 0 || + sync_service_->IsCryptographerReady(&trans); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_model_associator.h b/chrome/browser/sync/glue/autofill_model_associator.h index 09cfcdc..0709c89 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.h +++ b/chrome/browser/sync/glue/autofill_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -12,7 +12,7 @@ #include <vector> #include "base/basictypes.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "base/synchronization/lock.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/engine/syncapi.h" @@ -20,7 +20,7 @@ #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/webdata/autofill_entry.h" -class AutoFillProfile; +class AutofillProfile; class ProfileSyncService; class WebDatabase; @@ -66,6 +66,9 @@ class AutofillModelAssociator // See ModelAssociator interface. virtual void AbortAssociation(); + // See ModelAssociator interface. + virtual bool CryptoReadyIfNecessary(); + // Not implemented. virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id); @@ -93,7 +96,7 @@ class AutofillModelAssociator const std::vector<base::Time>& timestamps, std::vector<base::Time>* new_timestamps); static bool FillProfileWithServerData( - AutoFillProfile* merge_into, + AutofillProfile* merge_into, const sync_pb::AutofillProfileSpecifics& specifics); // TODO(georgey) : add the same processing for CC info (already in protocol @@ -108,9 +111,9 @@ class AutofillModelAssociator protected: // Given a profile from sync db it tries to match the profile against // one in web db. it ignores the guid and compares the actual data. - AutoFillProfile* FindCorrespondingNodeFromWebDB( + AutofillProfile* FindCorrespondingNodeFromWebDB( const sync_pb::AutofillProfileSpecifics& profile, - const std::vector<AutoFillProfile*>& all_profiles_from_db); + const std::vector<AutofillProfile*>& all_profiles_from_db); private: typedef std::map<std::string, int64> AutofillToSyncIdMap; @@ -122,7 +125,7 @@ class AutofillModelAssociator // Helper to query WebDatabase for the current autofill state. bool LoadAutofillData(std::vector<AutofillEntry>* entries, - std::vector<AutoFillProfile*>* profiles); + std::vector<AutofillProfile*>* profiles); // We split up model association first by autofill sub-type (entries, and // profiles. There is a Traverse* method for each of these. @@ -132,12 +135,12 @@ class AutofillModelAssociator const std::vector<AutofillEntry>& all_entries_from_db, std::set<AutofillKey>* current_entries, std::vector<AutofillEntry>* new_entries); - bool TraverseAndAssociateChromeAutoFillProfiles( + bool TraverseAndAssociateChromeAutofillProfiles( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, - const std::vector<AutoFillProfile*>& all_profiles_from_db, + const std::vector<AutofillProfile*>& all_profiles_from_db, std::set<string16>* current_profiles, - std::vector<AutoFillProfile*>* updated_profiles); + std::vector<AutofillProfile*>* updated_profiles); // Once the above traversals are complete, we traverse the sync model to // associate all remaining nodes. @@ -145,7 +148,7 @@ class AutofillModelAssociator sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, DataBundle* bundle, - const std::vector<AutoFillProfile*>& all_profiles_from_db); + const std::vector<AutofillProfile*>& all_profiles_from_db); // Helper to persist any changes that occured during model association to // the WebDatabase. @@ -157,13 +160,13 @@ class AutofillModelAssociator DataBundle* bundle, const sync_api::ReadNode& node); - // Helper to insert an AutoFillProfile into the WebDatabase (e.g. in response + // Helper to insert an AutofillProfile into the WebDatabase (e.g. in response // to encountering a sync node that doesn't exist yet locally). void AddNativeProfileIfNeeded( const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, const sync_api::ReadNode& node, - const std::vector<AutoFillProfile*>& all_profiles_from_db); + const std::vector<AutofillProfile*>& all_profiles_from_db); // Called at various points in model association to determine if the // user requested an abort. diff --git a/chrome/browser/sync/glue/autofill_profile_change_processor.cc b/chrome/browser/sync/glue/autofill_profile_change_processor.cc index ccf9c5a..833e58e 100644 --- a/chrome/browser/sync/glue/autofill_profile_change_processor.cc +++ b/chrome/browser/sync/glue/autofill_profile_change_processor.cc @@ -19,9 +19,9 @@ #include "chrome/browser/webdata/autofill_change.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/guid.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_service.h" +#include "content/common/notification_type.h" namespace browser_sync { @@ -179,7 +179,7 @@ void AutofillProfileChangeProcessor::CommitChangesFromSyncModel() { for (unsigned int i = 0;i < autofill_changes_.size(); ++i) { if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == autofill_changes_[i].action_) { - if (!web_database_->RemoveAutoFillProfile( + if (!web_database_->GetAutofillTable()->RemoveAutofillProfile( autofill_changes_[i].profile_specifics_.guid())) { LOG(ERROR) << "could not delete the profile " << autofill_changes_[i].profile_specifics_.guid(); @@ -218,10 +218,10 @@ void AutofillProfileChangeProcessor::ApplyAutofillProfileChange( profile_specifics.guid(); return; } - AutoFillProfile p(profile_specifics.guid()); + AutofillProfile p(profile_specifics.guid()); AutofillProfileModelAssociator::OverwriteProfileWithServerData(&p, profile_specifics); - if (!web_database_->AddAutoFillProfile(p)) { + if (!web_database_->GetAutofillTable()->AddAutofillProfile(p)) { LOG(ERROR) << "could not add autofill profile for guid " << p.guid(); break; } @@ -232,18 +232,20 @@ void AutofillProfileChangeProcessor::ApplyAutofillProfileChange( break; } case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: { - AutoFillProfile *p; - if (!web_database_->GetAutoFillProfile(profile_specifics.guid(), &p)) { + AutofillProfile *p; + if (!web_database_->GetAutofillTable()->GetAutofillProfile( + profile_specifics.guid(), &p)) { LOG(ERROR) << "Could not find the autofill profile to update for " << profile_specifics.guid(); break; } - scoped_ptr<AutoFillProfile> autofill_pointer(p); + scoped_ptr<AutofillProfile> autofill_pointer(p); AutofillProfileModelAssociator::OverwriteProfileWithServerData( autofill_pointer.get(), profile_specifics); - if (!web_database_->UpdateAutoFillProfile(*(autofill_pointer.get()))) { + if (!web_database_->GetAutofillTable()->UpdateAutofillProfile( + *(autofill_pointer.get()))) { LOG(ERROR) << "Could not update autofill profile for " << profile_specifics.guid(); break; @@ -278,7 +280,7 @@ void AutofillProfileChangeProcessor::RemoveSyncNode(const std::string& guid, void AutofillProfileChangeProcessor::AddAutofillProfileSyncNode( sync_api::WriteTransaction* trans, sync_api::BaseNode& autofill_profile_root, - const AutoFillProfile& profile) { + const AutofillProfile& profile) { std::string guid = profile.guid(); @@ -315,7 +317,7 @@ void AutofillProfileChangeProcessor::StopObserving() { } void AutofillProfileChangeProcessor::WriteAutofillProfile( - const AutoFillProfile& profile, + const AutofillProfile& profile, sync_api::WriteNode* node) { sync_pb::AutofillProfileSpecifics specifics; @@ -326,32 +328,27 @@ void AutofillProfileChangeProcessor::WriteAutofillProfile( DCHECK(guid::IsValidGUID(profile.guid())); specifics.set_guid(profile.guid()); - specifics.set_name_first(UTF16ToUTF8( - profile.GetFieldText(AutofillType(NAME_FIRST)))); - specifics.set_name_middle(UTF16ToUTF8( - profile.GetFieldText(AutofillType(NAME_MIDDLE)))); - specifics.set_name_last( - UTF16ToUTF8(profile.GetFieldText(AutofillType(NAME_LAST)))); + specifics.set_name_first(UTF16ToUTF8(profile.GetInfo(NAME_FIRST))); + specifics.set_name_middle(UTF16ToUTF8(profile.GetInfo(NAME_MIDDLE))); + specifics.set_name_last(UTF16ToUTF8(profile.GetInfo(NAME_LAST))); specifics.set_address_home_line1( - UTF16ToUTF8(profile.GetFieldText(AutofillType(ADDRESS_HOME_LINE1)))); + UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_LINE1))); specifics.set_address_home_line2( - UTF16ToUTF8(profile.GetFieldText(AutofillType(ADDRESS_HOME_LINE2)))); - specifics.set_address_home_city(UTF16ToUTF8(profile.GetFieldText( - AutofillType(ADDRESS_HOME_CITY)))); - specifics.set_address_home_state(UTF16ToUTF8(profile.GetFieldText( - AutofillType(ADDRESS_HOME_STATE)))); - specifics.set_address_home_country(UTF16ToUTF8(profile.GetFieldText( - AutofillType(ADDRESS_HOME_COUNTRY)))); - specifics.set_address_home_zip(UTF16ToUTF8(profile.GetFieldText( - AutofillType(ADDRESS_HOME_ZIP)))); - specifics.set_email_address(UTF16ToUTF8(profile.GetFieldText( - AutofillType(EMAIL_ADDRESS)))); - specifics.set_company_name(UTF16ToUTF8(profile.GetFieldText( - AutofillType(COMPANY_NAME)))); - specifics.set_phone_fax_whole_number(UTF16ToUTF8(profile.GetFieldText( - AutofillType(PHONE_FAX_WHOLE_NUMBER)))); - specifics.set_phone_home_whole_number(UTF16ToUTF8(profile.GetFieldText( - AutofillType(PHONE_HOME_WHOLE_NUMBER)))); + UTF16ToUTF8(profile.GetInfo(ADDRESS_HOME_LINE2))); + specifics.set_address_home_city(UTF16ToUTF8(profile.GetInfo( + ADDRESS_HOME_CITY))); + specifics.set_address_home_state(UTF16ToUTF8(profile.GetInfo( + ADDRESS_HOME_STATE))); + specifics.set_address_home_country(UTF16ToUTF8(profile.GetInfo( + ADDRESS_HOME_COUNTRY))); + specifics.set_address_home_zip(UTF16ToUTF8(profile.GetInfo( + ADDRESS_HOME_ZIP))); + specifics.set_email_address(UTF16ToUTF8(profile.GetInfo(EMAIL_ADDRESS))); + specifics.set_company_name(UTF16ToUTF8(profile.GetInfo(COMPANY_NAME))); + specifics.set_phone_fax_whole_number(UTF16ToUTF8(profile.GetInfo( + PHONE_FAX_WHOLE_NUMBER))); + specifics.set_phone_home_whole_number(UTF16ToUTF8(profile.GetInfo( + PHONE_HOME_WHOLE_NUMBER))); node->SetAutofillProfileSpecifics(specifics); } diff --git a/chrome/browser/sync/glue/autofill_profile_change_processor.h b/chrome/browser/sync/glue/autofill_profile_change_processor.h index a1d44a3..9b08d3d 100644 --- a/chrome/browser/sync/glue/autofill_profile_change_processor.h +++ b/chrome/browser/sync/glue/autofill_profile_change_processor.h @@ -16,10 +16,10 @@ #include "chrome/browser/sync/unrecoverable_error_handler.h" #include "chrome/browser/webdata/autofill_change.h" #include "chrome/browser/webdata/web_database.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_service.h" +#include "content/common/notification_type.h" namespace browser_sync { @@ -48,7 +48,7 @@ class AutofillProfileChangeProcessor : public ChangeProcessor, const NotificationDetails& details); - static void WriteAutofillProfile(const AutoFillProfile& profile, + static void WriteAutofillProfile(const AutofillProfile& profile, sync_api::WriteNode* node); protected: @@ -63,7 +63,7 @@ class AutofillProfileChangeProcessor : public ChangeProcessor, AutofillProfileChangeRecord( sync_api::SyncManager::ChangeRecord::Action action, int64 id, - const sync_pb::AutofillProfileSpecifics profile_specifics) + const sync_pb::AutofillProfileSpecifics& profile_specifics) : action_(action), id_(id), profile_specifics_(profile_specifics) {} @@ -73,7 +73,7 @@ class AutofillProfileChangeProcessor : public ChangeProcessor, virtual void AddAutofillProfileSyncNode(sync_api::WriteTransaction* trans, sync_api::BaseNode& autofill_profile_root, - const AutoFillProfile& profile); + const AutofillProfile& profile); void ActOnChange(AutofillProfileChange* change, sync_api::WriteTransaction* trans, diff --git a/chrome/browser/sync/glue/autofill_profile_data_type_controller.cc b/chrome/browser/sync/glue/autofill_profile_data_type_controller.cc index 1b5214b..2203197 100644 --- a/chrome/browser/sync/glue/autofill_profile_data_type_controller.cc +++ b/chrome/browser/sync/glue/autofill_profile_data_type_controller.cc @@ -19,11 +19,11 @@ AutofillProfileDataTypeController::AutofillProfileDataTypeController( AutofillProfileDataTypeController::~AutofillProfileDataTypeController() {} -syncable::ModelType AutofillProfileDataTypeController::type() { +syncable::ModelType AutofillProfileDataTypeController::type() const { return syncable::AUTOFILL_PROFILE; } -const char* AutofillProfileDataTypeController::name() const { +std::string AutofillProfileDataTypeController::name() const { // For logging only. return "autofill_profile"; } diff --git a/chrome/browser/sync/glue/autofill_profile_data_type_controller.h b/chrome/browser/sync/glue/autofill_profile_data_type_controller.h index 83cafc3..d97a904 100644 --- a/chrome/browser/sync/glue/autofill_profile_data_type_controller.h +++ b/chrome/browser/sync/glue/autofill_profile_data_type_controller.h @@ -19,9 +19,9 @@ class AutofillProfileDataTypeController : public AutofillDataTypeController { ProfileSyncService* sync_service); virtual ~AutofillProfileDataTypeController(); - virtual syncable::ModelType type(); + virtual syncable::ModelType type() const; - virtual const char* name() const; + virtual std::string name() const; protected: virtual ProfileSyncFactory::SyncComponents CreateSyncComponents( diff --git a/chrome/browser/sync/glue/autofill_profile_model_associator.cc b/chrome/browser/sync/glue/autofill_profile_model_associator.cc index 1cadaf0..a982eba 100644 --- a/chrome/browser/sync/glue/autofill_profile_model_associator.cc +++ b/chrome/browser/sync/glue/autofill_profile_model_associator.cc @@ -45,25 +45,25 @@ AutofillProfileModelAssociator::AutofillProfileModelAssociator() number_of_profiles_created_(0) { } -bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( +bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutofillProfiles( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, - const std::vector<AutoFillProfile*>& all_profiles_from_db, + const std::vector<AutofillProfile*>& all_profiles_from_db, std::set<std::string>* current_profiles, - std::vector<AutoFillProfile*>* updated_profiles, - std::vector<AutoFillProfile*>* new_profiles, + std::vector<AutofillProfile*>* updated_profiles, + std::vector<AutofillProfile*>* new_profiles, std::vector<std::string>* profiles_to_delete) { if (VLOG_IS_ON(2)) { VLOG(2) << "[AUTOFILL MIGRATION]" << "Printing profiles from web db"; - for (std::vector<AutoFillProfile*>::const_iterator ix = + for (std::vector<AutofillProfile*>::const_iterator ix = all_profiles_from_db.begin(); ix != all_profiles_from_db.end(); ++ix) { - AutoFillProfile* p = *ix; + AutofillProfile* p = *ix; VLOG(2) << "[AUTOFILL MIGRATION] " - << p->GetFieldText(AutofillType(NAME_FIRST)) - << p->GetFieldText(AutofillType(NAME_LAST)) + << p->GetInfo(NAME_FIRST) + << p->GetInfo(NAME_LAST) << p->guid(); } } @@ -72,8 +72,8 @@ bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( << "Looking for the above data in sync db.."; // Alias the all_profiles_from_db so we fit in 80 characters - const std::vector<AutoFillProfile*>& profiles(all_profiles_from_db); - for (std::vector<AutoFillProfile*>::const_iterator ix = profiles.begin(); + const std::vector<AutofillProfile*>& profiles(all_profiles_from_db); + for (std::vector<AutofillProfile*>::const_iterator ix = profiles.begin(); ix != profiles.end(); ++ix) { std::string guid((*ix)->guid()); @@ -90,8 +90,8 @@ bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( current_profiles->find(guid) == current_profiles->end()) { VLOG(2) << "[AUTOFILL MIGRATION]" << " Found in sync db: " - << (*ix)->GetFieldText(AutofillType(NAME_FIRST)) - << (*ix)->GetFieldText(AutofillType(NAME_LAST)) + << (*ix)->GetInfo(NAME_FIRST) + << (*ix)->GetInfo(NAME_LAST) << (*ix)->guid() << " so associating with node id " << node.GetId(); const sync_pb::AutofillProfileSpecifics& autofill( @@ -125,11 +125,11 @@ bool AutofillProfileModelAssociator::GetSyncIdForTaggedNode( } bool AutofillProfileModelAssociator::LoadAutofillData( - std::vector<AutoFillProfile*>* profiles) { + std::vector<AutofillProfile*>* profiles) { if (IsAbortPending()) return false; - if (!web_database_->GetAutoFillProfiles(profiles)) + if (!web_database_->GetAutofillTable()->GetAutofillProfiles(profiles)) return false; return true; @@ -143,7 +143,7 @@ bool AutofillProfileModelAssociator::AssociateModels() { abort_association_pending_ = false; } - ScopedVector<AutoFillProfile> profiles; + ScopedVector<AutofillProfile> profiles; if (!LoadAutofillData(&profiles.get())) { LOG(ERROR) << "Could not get the autofill data from WebDatabase."; @@ -166,7 +166,7 @@ bool AutofillProfileModelAssociator::AssociateModels() { return false; } - if (!TraverseAndAssociateChromeAutoFillProfiles(&trans, autofill_root, + if (!TraverseAndAssociateChromeAutofillProfiles(&trans, autofill_root, profiles.get(), &bundle.current_profiles, &bundle.updated_profiles, &bundle.new_profiles, @@ -210,9 +210,9 @@ bool AutofillProfileModelAssociator::DisassociateModels() { bool AutofillProfileModelAssociator::MergeField(FormGroup* f, AutofillFieldType t, const std::string& specifics_field) { - if (UTF16ToUTF8(f->GetFieldText(AutofillType(t))) == specifics_field) + if (UTF16ToUTF8(f->GetInfo(t)) == specifics_field) return false; - f->SetInfo(AutofillType(t), UTF8ToUTF16(specifics_field)); + f->SetInfo(t, UTF8ToUTF16(specifics_field)); return true; } bool AutofillProfileModelAssociator::SyncModelHasUserCreatedNodes( @@ -233,10 +233,10 @@ bool AutofillProfileModelAssociator::SyncModelHasUserCreatedNodes( } // static bool AutofillProfileModelAssociator::OverwriteProfileWithServerData( - AutoFillProfile* merge_into, + AutofillProfile* merge_into, const sync_pb::AutofillProfileSpecifics& specifics) { bool diff = false; - AutoFillProfile* p = merge_into; + AutofillProfile* p = merge_into; const sync_pb::AutofillProfileSpecifics& s(specifics); diff = MergeField(p, NAME_FIRST, s.name_first()) || diff; diff = MergeField(p, NAME_LAST, s.name_last()) || diff; @@ -260,12 +260,12 @@ bool AutofillProfileModelAssociator::OverwriteProfileWithServerData( int64 AutofillProfileModelAssociator::FindSyncNodeWithProfile( sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, - const AutoFillProfile& profile_from_db, + const AutofillProfile& profile_from_db, std::set<std::string>* current_profiles) { int64 sync_child_id = autofill_root.GetFirstChildId(); while (sync_child_id != sync_api::kInvalidId) { ReadNode read_node(trans); - AutoFillProfile p; + AutofillProfile p; if (!read_node.InitByIdLookup(sync_child_id)) { LOG(ERROR) << "unable to find the id given by getfirst child " << sync_child_id; @@ -290,8 +290,8 @@ int64 AutofillProfileModelAssociator::FindSyncNodeWithProfile( bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, - const AutoFillProfile& profile, - std::vector<AutoFillProfile*>* new_profiles, + const AutofillProfile& profile, + std::vector<AutofillProfile*>* new_profiles, std::set<std::string>* current_profiles, std::vector<std::string>* profiles_to_delete) { @@ -315,7 +315,7 @@ bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( autofill_specifics.guid(); return false; } - AutoFillProfile* p = new AutoFillProfile(autofill_specifics.guid()); + AutofillProfile* p = new AutofillProfile(autofill_specifics.guid()); OverwriteProfileWithServerData(p, autofill_specifics); new_profiles->push_back(p); std::string guid = autofill_specifics.guid(); @@ -323,8 +323,8 @@ bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( current_profiles->insert(autofill_specifics.guid()); VLOG(2) << "[AUTOFILL MIGRATION]" << "Found in sync db but with a different guid: " - << UTF16ToUTF8(profile.GetFieldText(AutofillType(NAME_FIRST))) - << UTF16ToUTF8(profile.GetFieldText(AutofillType(NAME_LAST))) + << UTF16ToUTF8(profile.GetInfo(NAME_FIRST)) + << UTF16ToUTF8(profile.GetInfo(NAME_LAST)) << "New guid " << autofill_specifics.guid() << " sync node id " << sync_node_id << " so associating. Profile to be deleted " << profile.guid(); @@ -342,8 +342,8 @@ bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( node.SetTitle(UTF8ToWide(profile.guid())); VLOG(2) << "[AUTOFILL MIGRATION]" << "NOT Found in sync db " - << UTF16ToUTF8(profile.GetFieldText(AutofillType(NAME_FIRST))) - << UTF16ToUTF8(profile.GetFieldText(AutofillType(NAME_LAST))) + << UTF16ToUTF8(profile.GetInfo(NAME_FIRST)) + << UTF16ToUTF8(profile.GetInfo(NAME_LAST)) << profile.guid() << " so creating a new sync node. Sync node id " << node.GetId(); @@ -405,7 +405,7 @@ void AutofillProfileModelAssociator::AddNativeProfileIfNeeded( bundle->current_profiles.end()) { std::string guid(profile.guid()); Associate(&guid, node.GetId()); - AutoFillProfile* p = new AutoFillProfile(profile.guid()); + AutofillProfile* p = new AutofillProfile(profile.guid()); OverwriteProfileWithServerData(p, profile); bundle->new_profiles.push_back(p); VLOG(2) << "[AUTOFILL MIGRATION] " @@ -426,21 +426,24 @@ bool AutofillProfileModelAssociator::SaveChangesToWebData( for (size_t i = 0; i < bundle.new_profiles.size(); i++) { if (IsAbortPending()) return false; - if (!web_database_->AddAutoFillProfile(*bundle.new_profiles[i])) + if (!web_database_->GetAutofillTable()->AddAutofillProfile( + *bundle.new_profiles[i])) return false; } for (size_t i = 0; i < bundle.updated_profiles.size(); i++) { if (IsAbortPending()) return false; - if (!web_database_->UpdateAutoFillProfile(*bundle.updated_profiles[i])) + if (!web_database_->GetAutofillTable()->UpdateAutofillProfile( + *bundle.updated_profiles[i])) return false; } for (size_t i = 0; i< bundle.profiles_to_delete.size(); ++i) { if (IsAbortPending()) return false; - if (!web_database_->RemoveAutoFillProfile(bundle.profiles_to_delete[i])) + if (!web_database_->GetAutofillTable()->RemoveAutofillProfile( + bundle.profiles_to_delete[i])) return false; } return true; @@ -501,6 +504,14 @@ AutofillProfileModelAssociator::DataBundle::~DataBundle() { STLDeleteElements(&new_profiles); } +bool AutofillProfileModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + syncable::ModelTypeSet encrypted_types; + sync_service_->GetEncryptedDataTypes(&encrypted_types); + return encrypted_types.count(syncable::AUTOFILL_PROFILE) == 0 || + sync_service_->IsCryptographerReady(&trans); +} } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_profile_model_associator.h b/chrome/browser/sync/glue/autofill_profile_model_associator.h index 377d597..c8b99ac 100644 --- a/chrome/browser/sync/glue/autofill_profile_model_associator.h +++ b/chrome/browser/sync/glue/autofill_profile_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -12,7 +12,7 @@ #include <vector> #include "base/basictypes.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "base/synchronization/lock.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/engine/syncapi.h" @@ -20,7 +20,7 @@ #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/webdata/autofill_entry.h" -class AutoFillProfile; +class AutofillProfile; class ProfileSyncService; class WebDatabase; @@ -72,6 +72,9 @@ class AutofillProfileModelAssociator // See ModelAssociator interface. virtual void AbortAssociation(); + // See ModelAssociator interface. + virtual bool CryptoReadyIfNecessary(); + virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id); virtual bool InitSyncNodeFromChromeId(const std::string& node_id, @@ -94,35 +97,35 @@ class AutofillProfileModelAssociator virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id); static bool OverwriteProfileWithServerData( - AutoFillProfile* merge_into, + AutofillProfile* merge_into, const sync_pb::AutofillProfileSpecifics& specifics); protected: AutofillProfileModelAssociator(); - bool TraverseAndAssociateChromeAutoFillProfiles( + bool TraverseAndAssociateChromeAutofillProfiles( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, - const std::vector<AutoFillProfile*>& all_profiles_from_db, + const std::vector<AutofillProfile*>& all_profiles_from_db, std::set<std::string>* current_profiles, - std::vector<AutoFillProfile*>* updated_profiles, - std::vector<AutoFillProfile*>* new_profiles, + std::vector<AutofillProfile*>* updated_profiles, + std::vector<AutofillProfile*>* new_profiles, std::vector<std::string>* profiles_to_delete); - // Helper to insert an AutoFillProfile into the WebDatabase (e.g. in response + // Helper to insert an AutofillProfile into the WebDatabase (e.g. in response // to encountering a sync node that doesn't exist yet locally). virtual void AddNativeProfileIfNeeded( const sync_pb::AutofillProfileSpecifics& profile, DataBundle* bundle, const sync_api::ReadNode& node); - // Helper to insert a sync node for the given AutoFillProfile (e.g. in + // Helper to insert a sync node for the given AutofillProfile (e.g. in // response to encountering a native profile that doesn't exist yet in the // cloud). virtual bool MakeNewAutofillProfileSyncNodeIfNeeded( sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, - const AutoFillProfile& profile, - std::vector<AutoFillProfile*>* new_profiles, + const AutofillProfile& profile, + std::vector<AutofillProfile*>* new_profiles, std::set<std::string>* current_profiles, std::vector<std::string>* profiles_to_delete); @@ -142,7 +145,7 @@ class AutofillProfileModelAssociator // struct DataBundle; // Helper to query WebDatabase for the current autofill state. - bool LoadAutofillData(std::vector<AutoFillProfile*>* profiles); + bool LoadAutofillData(std::vector<AutofillProfile*>* profiles); static bool MergeField(FormGroup* f, AutofillFieldType t, @@ -158,7 +161,7 @@ class AutofillProfileModelAssociator int64 FindSyncNodeWithProfile(sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, - const AutoFillProfile& profile, + const AutofillProfile& profile, std::set<std::string>* current_profiles); ProfileSyncService* sync_service_; @@ -186,8 +189,8 @@ struct AutofillProfileModelAssociator::DataBundle { std::set<std::string> current_profiles; std::vector<std::string> profiles_to_delete; - std::vector<AutoFillProfile*> updated_profiles; - std::vector<AutoFillProfile*> new_profiles; // We own these pointers. + std::vector<AutofillProfile*> updated_profiles; + std::vector<AutofillProfile*> new_profiles; // We own these pointers. }; } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_profile_model_associator_unittest.cc b/chrome/browser/sync/glue/autofill_profile_model_associator_unittest.cc index 46287fd..e1792a6 100644 --- a/chrome/browser/sync/glue/autofill_profile_model_associator_unittest.cc +++ b/chrome/browser/sync/glue/autofill_profile_model_associator_unittest.cc @@ -18,7 +18,7 @@ using ::testing::ReturnRef; using ::testing::Pointee; using ::testing::Ref; using ::testing::Invoke; -class AutoFillProfile; +class AutofillProfile; using browser_sync::AutofillProfileModelAssociator; @@ -31,15 +31,15 @@ class MockAutofillProfileModelAssociator MockAutofillProfileModelAssociator() { } virtual ~MockAutofillProfileModelAssociator() {} - bool TraverseAndAssociateChromeAutoFillProfilesWrapper( + bool TraverseAndAssociateChromeAutofillProfilesWrapper( sync_api::WriteTransaction* write_trans, const sync_api::ReadNode& autofill_root, - const std::vector<AutoFillProfile*>& all_profiles_from_db, + const std::vector<AutofillProfile*>& all_profiles_from_db, std::set<std::string>* current_profiles, - std::vector<AutoFillProfile*>* updated_profiles, - std::vector<AutoFillProfile*>* new_profiles, + std::vector<AutofillProfile*>* updated_profiles, + std::vector<AutofillProfile*>* new_profiles, std::vector<std::string>* profiles_to_delete) { - return TraverseAndAssociateChromeAutoFillProfiles(write_trans, + return TraverseAndAssociateChromeAutofillProfiles(write_trans, autofill_root, all_profiles_from_db, current_profiles, @@ -52,13 +52,13 @@ class MockAutofillProfileModelAssociator DataBundle*, const sync_api::ReadNode&)); MOCK_METHOD2(OverwriteProfileWithServerData, - bool(AutoFillProfile*, + bool(AutofillProfile*, const sync_pb::AutofillProfileSpecifics&)); MOCK_METHOD6(MakeNewAutofillProfileSyncNodeIfNeeded, bool(sync_api::WriteTransaction*, const sync_api::BaseNode&, - const AutoFillProfile&, - std::vector<AutoFillProfile*>*, + const AutofillProfile&, + std::vector<AutofillProfile*>*, std::set<std::string>*, std::vector<std::string>*)); MOCK_METHOD2(Associate, void(const std::string*, int64)); @@ -94,7 +94,7 @@ class AutofillProfileModelAssociatorTest : public testing::Test { TEST_F(AutofillProfileModelAssociatorTest, TestAssociateProfileInWebDBWithSyncDB) { - ScopedVector<AutoFillProfile> profiles_from_web_db; + ScopedVector<AutofillProfile> profiles_from_web_db; std::string guid = "EDC609ED-7EEE-4F27-B00C-423242A9C44B"; sync_pb::EntitySpecifics specifics; @@ -108,7 +108,7 @@ TEST_F(AutofillProfileModelAssociatorTest, // This will be released inside the function // TraverseAndAssociateChromeAutofillProfiles - AutoFillProfile *profile = new AutoFillProfile(guid); + AutofillProfile *profile = new AutofillProfile(guid); // Set up the entry kernel with what we want. EntryKernel kernel; @@ -125,7 +125,7 @@ TEST_F(AutofillProfileModelAssociatorTest, profiles_from_web_db.push_back(profile); - associator_.TraverseAndAssociateChromeAutoFillProfilesWrapper(&write_trans, + associator_.TraverseAndAssociateChromeAutofillProfilesWrapper(&write_trans, read_node, profiles_from_web_db.get(), ¤t_profiles, @@ -137,7 +137,7 @@ TEST_F(AutofillProfileModelAssociatorTest, } TEST_F(AutofillProfileModelAssociatorTest, TestAssociatingMissingWebDBProfile) { - ScopedVector<AutoFillProfile> profiles_from_web_db; + ScopedVector<AutofillProfile> profiles_from_web_db; MockDirectory mock_directory; MockWriteTransaction write_trans(&mock_directory); @@ -149,7 +149,7 @@ TEST_F(AutofillProfileModelAssociatorTest, TestAssociatingMissingWebDBProfile) { std::string guid = "EDC609ED-7EEE-4F27-B00C-423242A9C44A"; std::set<std::string> current_profiles; - AutoFillProfile *profile = new AutoFillProfile(guid); + AutofillProfile *profile = new AutofillProfile(guid); EXPECT_CALL(associator_, MakeNewAutofillProfileSyncNodeIfNeeded(&write_trans, @@ -162,7 +162,7 @@ TEST_F(AutofillProfileModelAssociatorTest, TestAssociatingMissingWebDBProfile) { profiles_from_web_db.push_back(profile); - associator_.TraverseAndAssociateChromeAutoFillProfilesWrapper(&write_trans, + associator_.TraverseAndAssociateChromeAutofillProfilesWrapper(&write_trans, autofill_root, profiles_from_web_db.get(), ¤t_profiles, @@ -254,8 +254,7 @@ TEST_F(AutofillProfileModelAssociatorTest, TestNeedToAddNativeProfile) { read_node); EXPECT_EQ(bundle.new_profiles.size(), (unsigned int)1); - EXPECT_EQ( - bundle.new_profiles.front()->GetFieldText(AutofillType(NAME_FIRST)), - UTF8ToUTF16(first_name)); + EXPECT_EQ(bundle.new_profiles.front()->GetInfo(NAME_FIRST), + UTF8ToUTF16(first_name)); } diff --git a/chrome/browser/sync/glue/bookmark_change_processor.cc b/chrome/browser/sync/glue/bookmark_change_processor.cc index ad673ef..0172cd2 100644 --- a/chrome/browser/sync/glue/bookmark_change_processor.cc +++ b/chrome/browser/sync/glue/bookmark_change_processor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/bookmark_change_processor.h" @@ -61,7 +61,7 @@ void BookmarkChangeProcessor::UpdateSyncNodeProperties( void BookmarkChangeProcessor::EncodeFavicon(const BookmarkNode* src, BookmarkModel* model, std::vector<unsigned char>* dst) { - const SkBitmap& favicon = model->GetFavIcon(src); + const SkBitmap& favicon = model->GetFavicon(src); dst->clear(); @@ -95,7 +95,7 @@ void BookmarkChangeProcessor::RemoveSyncNodeHierarchy( sync_api::WriteTransaction trans(share_handle()); // Later logic assumes that |topmost| has been unlinked. - DCHECK(!topmost->GetParent()); + DCHECK(!topmost->parent()); // A BookmarkModel deletion event means that |node| and all its children were // deleted. Sync backend expects children to be deleted individually, so we do @@ -107,19 +107,19 @@ void BookmarkChangeProcessor::RemoveSyncNodeHierarchy( int index = 0; while (node) { // The top of |index_stack| should always be |node|'s index. - DCHECK(!node->GetParent() || (node->GetParent()->IndexOfChild(node) == + DCHECK(!node->parent() || (node->parent()->GetIndexOf(node) == index_stack.top())); - if (index == node->GetChildCount()) { + if (index == node->child_count()) { // If we've processed all of |node|'s children, delete |node| and move // on to its successor. RemoveOneSyncNode(&trans, node); - node = node->GetParent(); + node = node->parent(); index = index_stack.top() + 1; // (top() + 0) was what we removed. index_stack.pop(); } else { // If |node| has an unprocessed child, process it next after pushing the // current state onto the stack. - DCHECK_LT(index, node->GetChildCount()); + DCHECK_LT(index, node->child_count()); index_stack.push(index); node = node->GetChild(index); index = 0; @@ -163,8 +163,7 @@ int64 BookmarkChangeProcessor::CreateSyncNode(const BookmarkNode* parent, sync_api::WriteNode sync_child(trans); // Actually create the node with the appropriate initial position. - if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator, - error_handler)) { + if (!PlaceSyncNode(CREATE, parent, index, trans, &sync_child, associator)) { error_handler->OnUnrecoverableError(FROM_HERE, "Sync node creation failed; recovery unlikely"); return sync_api::kInvalidId; @@ -212,10 +211,10 @@ void BookmarkChangeProcessor::BookmarkNodeChanged(BookmarkModel* model, DCHECK_EQ(sync_node.GetIsFolder(), node->is_folder()); DCHECK_EQ(model_associator_->GetChromeNodeFromSyncId( sync_node.GetParentId()), - node->GetParent()); + node->parent()); // This node's index should be one more than the predecessor's index. - DCHECK_EQ(node->GetParent()->IndexOfChild(node), - CalculateBookmarkModelInsertionIndex(node->GetParent(), + DCHECK_EQ(node->parent()->GetIndexOf(node), + CalculateBookmarkModelInsertionIndex(node->parent(), &sync_node)); } @@ -242,13 +241,13 @@ void BookmarkChangeProcessor::BookmarkNodeMoved(BookmarkModel* model, } if (!PlaceSyncNode(MOVE, new_parent, new_index, &trans, &sync_node, - model_associator_, error_handler())) { + model_associator_)) { error_handler()->OnUnrecoverableError(FROM_HERE, std::string()); return; } } -void BookmarkChangeProcessor::BookmarkNodeFavIconLoaded(BookmarkModel* model, +void BookmarkChangeProcessor::BookmarkNodeFaviconLoaded(BookmarkModel* model, const BookmarkNode* node) { DCHECK(running()); BookmarkNodeChanged(model, node); @@ -262,7 +261,7 @@ void BookmarkChangeProcessor::BookmarkNodeChildrenReordered( // The given node's children got reordered. We need to reorder all the // children of the corresponding sync node. - for (int i = 0; i < node->GetChildCount(); ++i) { + for (int i = 0; i < node->child_count(); ++i) { sync_api::WriteNode sync_child(&trans); if (!model_associator_->InitSyncNodeFromChromeId(node->GetChild(i)->id(), &sync_child)) { @@ -273,7 +272,7 @@ void BookmarkChangeProcessor::BookmarkNodeChildrenReordered( model_associator_->GetSyncIdFromChromeId(node->id())); if (!PlaceSyncNode(MOVE, node, i, &trans, &sync_child, - model_associator_, error_handler())) { + model_associator_)) { error_handler()->OnUnrecoverableError(FROM_HERE, std::string()); return; } @@ -283,12 +282,10 @@ void BookmarkChangeProcessor::BookmarkNodeChildrenReordered( // static bool BookmarkChangeProcessor::PlaceSyncNode(MoveOrCreate operation, const BookmarkNode* parent, int index, sync_api::WriteTransaction* trans, - sync_api::WriteNode* dst, BookmarkModelAssociator* associator, - UnrecoverableErrorHandler* error_handler) { + sync_api::WriteNode* dst, BookmarkModelAssociator* associator) { sync_api::ReadNode sync_parent(trans); if (!associator->InitSyncNodeFromChromeId(parent->id(), &sync_parent)) { LOG(WARNING) << "Parent lookup failed"; - error_handler->OnUnrecoverableError(FROM_HERE, std::string()); return false; } @@ -342,8 +339,8 @@ int BookmarkChangeProcessor::CalculateBookmarkModelInsertionIndex( const BookmarkNode* predecessor = model_associator_->GetChromeNodeFromSyncId(predecessor_id); DCHECK(predecessor); - DCHECK_EQ(predecessor->GetParent(), parent); - return parent->IndexOfChild(predecessor) + 1; + DCHECK_EQ(predecessor->parent(), parent); + return parent->GetIndexOf(predecessor) + 1; } // ApplyModelChanges is called by the sync backend after changes have been made @@ -396,21 +393,25 @@ void BookmarkChangeProcessor::ApplyChangesFromSyncModel( // Children of a deleted node should not be deleted; they may be // reparented by a later change record. Move them to a temporary place. DCHECK(dst) << "Could not find node to be deleted"; - const BookmarkNode* parent = dst->GetParent(); - if (dst->GetChildCount()) { + if (!dst) // Can't do anything if we can't find the chrome node. + continue; + const BookmarkNode* parent = dst->parent(); + if (dst->child_count()) { if (!foster_parent) { - foster_parent = model->AddGroup(model->other_node(), - model->other_node()->GetChildCount(), - string16()); + foster_parent = model->AddFolder(model->other_node(), + model->other_node()->child_count(), + string16()); } - for (int i = dst->GetChildCount() - 1; i >= 0; --i) { + for (int i = dst->child_count() - 1; i >= 0; --i) { model->Move(dst->GetChild(i), foster_parent, - foster_parent->GetChildCount()); + foster_parent->child_count()); } } - DCHECK_EQ(dst->GetChildCount(), 0) << "Node being deleted has children"; + DCHECK_EQ(dst->child_count(), 0) << "Node being deleted has children"; model_associator_->Disassociate(changes[i].id); - model->Remove(parent, parent->IndexOfChild(dst)); + int index = parent->GetIndexOf(dst); + if (index > -1) + model->Remove(parent, index); dst = NULL; } else { DCHECK_EQ((changes[i].action == @@ -430,9 +431,9 @@ void BookmarkChangeProcessor::ApplyChangesFromSyncModel( // Clean up the temporary node. if (foster_parent) { // There should be no nodes left under the foster parent. - DCHECK_EQ(foster_parent->GetChildCount(), 0); - model->Remove(foster_parent->GetParent(), - foster_parent->GetParent()->IndexOfChild(foster_parent)); + DCHECK_EQ(foster_parent->child_count(), 0); + model->Remove(foster_parent->parent(), + foster_parent->parent()->GetIndexOf(foster_parent)); foster_parent = NULL; } @@ -472,7 +473,7 @@ const BookmarkNode* BookmarkChangeProcessor::CreateOrUpdateBookmarkNode( model->SetURL(dst, src->GetURL()); model->SetTitle(dst, WideToUTF16Hack(src->GetTitle())); - SetBookmarkFavicon(src, dst, model->profile()); + SetBookmarkFavicon(src, dst, model); } return dst; @@ -487,17 +488,17 @@ const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode( BookmarkModel* model, int index) { DCHECK(parent); - DCHECK(index >= 0 && index <= parent->GetChildCount()); + DCHECK(index >= 0 && index <= parent->child_count()); const BookmarkNode* node; if (sync_node->GetIsFolder()) { - node = model->AddGroup(parent, index, - WideToUTF16Hack(sync_node->GetTitle())); + node = model->AddFolder(parent, index, + WideToUTF16Hack(sync_node->GetTitle())); } else { node = model->AddURL(parent, index, WideToUTF16Hack(sync_node->GetTitle()), sync_node->GetURL()); - SetBookmarkFavicon(sync_node, node, model->profile()); + SetBookmarkFavicon(sync_node, node, model); } return node; } @@ -507,13 +508,14 @@ const BookmarkNode* BookmarkChangeProcessor::CreateBookmarkNode( bool BookmarkChangeProcessor::SetBookmarkFavicon( sync_api::BaseNode* sync_node, const BookmarkNode* bookmark_node, - Profile* profile) { + BookmarkModel* bookmark_model) { std::vector<unsigned char> icon_bytes_vector; sync_node->GetFaviconBytes(&icon_bytes_vector); if (icon_bytes_vector.empty()) return false; - ApplyBookmarkFavicon(bookmark_node, profile, icon_bytes_vector); + ApplyBookmarkFavicon(bookmark_node, bookmark_model->profile(), + icon_bytes_vector); return true; } @@ -536,10 +538,11 @@ void BookmarkChangeProcessor::ApplyBookmarkFavicon( FaviconService* favicon_service = profile->GetFaviconService(Profile::EXPLICIT_ACCESS); - history->AddPage(bookmark_node->GetURL(), history::SOURCE_SYNCED); + history->AddPageNoVisitForBookmark(bookmark_node->GetURL()); favicon_service->SetFavicon(bookmark_node->GetURL(), fake_icon_url, - icon_bytes_vector); + icon_bytes_vector, + history::FAVICON); } // static diff --git a/chrome/browser/sync/glue/bookmark_change_processor.h b/chrome/browser/sync/glue/bookmark_change_processor.h index 461cbb0..debc776 100644 --- a/chrome/browser/sync/glue/bookmark_change_processor.h +++ b/chrome/browser/sync/glue/bookmark_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -14,8 +14,6 @@ #include "chrome/browser/sync/glue/bookmark_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" -class ProfileSyncService; - namespace browser_sync { // This class is responsible for taking changes from the BookmarkModel @@ -47,7 +45,7 @@ class BookmarkChangeProcessor : public BookmarkModelObserver, const BookmarkNode* node); virtual void BookmarkNodeChanged(BookmarkModel* model, const BookmarkNode* node); - virtual void BookmarkNodeFavIconLoaded(BookmarkModel* model, + virtual void BookmarkNodeFaviconLoaded(BookmarkModel* model, const BookmarkNode* node); virtual void BookmarkNodeChildrenReordered(BookmarkModel* model, const BookmarkNode* node); @@ -75,7 +73,7 @@ class BookmarkChangeProcessor : public BookmarkModelObserver, // for the bookmark in question. static bool SetBookmarkFavicon(sync_api::BaseNode* sync_node, const BookmarkNode* bookmark_node, - Profile* profile); + BookmarkModel* model); // Applies the favicon data in |icon_bytes_vector| to |bookmark_node|. // |profile| is the profile that contains the HistoryService and BookmarkModel @@ -140,8 +138,7 @@ class BookmarkChangeProcessor : public BookmarkModelObserver, int index, sync_api::WriteTransaction* trans, sync_api::WriteNode* dst, - BookmarkModelAssociator* associator, - UnrecoverableErrorHandler* error_handler); + BookmarkModelAssociator* associator); // Copy properties (but not position) from |src| to |dst|. static void UpdateSyncNodeProperties(const BookmarkNode* src, diff --git a/chrome/browser/sync/glue/bookmark_data_type_controller.cc b/chrome/browser/sync/glue/bookmark_data_type_controller.cc index 93d1430..8cd3d5b 100644 --- a/chrome/browser/sync/glue/bookmark_data_type_controller.cc +++ b/chrome/browser/sync/glue/bookmark_data_type_controller.cc @@ -1,22 +1,18 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/bookmark_data_type_controller.h" -#include "base/logging.h" #include "base/metrics/histogram.h" -#include "base/time.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sync/glue/bookmark_change_processor.h" -#include "chrome/browser/sync/glue/bookmark_model_associator.h" -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_source.h" -#include "chrome/common/notification_type.h" +#include "chrome/browser/sync/profile_sync_service.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_details.h" +#include "content/common/notification_source.h" +#include "content/common/notification_type.h" namespace browser_sync { @@ -24,96 +20,31 @@ BookmarkDataTypeController::BookmarkDataTypeController( ProfileSyncFactory* profile_sync_factory, Profile* profile, ProfileSyncService* sync_service) - : profile_sync_factory_(profile_sync_factory), - profile_(profile), - sync_service_(sync_service), - state_(NOT_RUNNING) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(profile_sync_factory); - DCHECK(profile); - DCHECK(sync_service); + : FrontendDataTypeController(profile_sync_factory, + profile, + sync_service) { } BookmarkDataTypeController::~BookmarkDataTypeController() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); } -void BookmarkDataTypeController::Start(StartCallback* start_callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(start_callback); - if (state_ != NOT_RUNNING) { - start_callback->Run(BUSY); - delete start_callback; - return; - } - - start_callback_.reset(start_callback); - - if (!enabled()) { - FinishStart(NOT_ENABLED); - return; - } - - state_ = MODEL_STARTING; - +// We want to start the bookmark model before we begin associating. +bool BookmarkDataTypeController::StartModels() { // If the bookmarks model is loaded, continue with association. BookmarkModel* bookmark_model = profile_->GetBookmarkModel(); if (bookmark_model && bookmark_model->IsLoaded()) { - Associate(); - return; + return true; // Continue to Associate(). } // Add an observer and continue when the bookmarks model is loaded. registrar_.Add(this, NotificationType::BOOKMARK_MODEL_LOADED, Source<Profile>(sync_service_->profile())); + return false; // Don't continue Start. } -void BookmarkDataTypeController::Stop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - // If Stop() is called while Start() is waiting for the bookmark - // model to load, abort the start. - if (state_ == MODEL_STARTING) - FinishStart(ABORTED); - +// Cleanup for our extra registrar usage. +void BookmarkDataTypeController::CleanupState() { registrar_.RemoveAll(); - if (change_processor_ != NULL) - sync_service_->DeactivateDataType(this, change_processor_.get()); - - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - - change_processor_.reset(); - model_associator_.reset(); - - state_ = NOT_RUNNING; -} - -bool BookmarkDataTypeController::enabled() { - return true; -} - -syncable::ModelType BookmarkDataTypeController::type() { - return syncable::BOOKMARKS; -} - -browser_sync::ModelSafeGroup BookmarkDataTypeController::model_safe_group() { - return browser_sync::GROUP_UI; -} - -const char* BookmarkDataTypeController::name() const { - // For logging only. - return "bookmark"; -} - -DataTypeController::State BookmarkDataTypeController::state() { - return state_; -} - -void BookmarkDataTypeController::OnUnrecoverableError( - const tracked_objects::Location& from_here, const std::string& message) { - // The ProfileSyncService will invoke our Stop() method in response to this. - UMA_HISTOGRAM_COUNTS("Sync.BookmarkRunFailures", 1); - sync_service_->OnUnrecoverableError(from_here, message); } void BookmarkDataTypeController::Observe(NotificationType type, @@ -122,52 +53,33 @@ void BookmarkDataTypeController::Observe(NotificationType type, DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK_EQ(NotificationType::BOOKMARK_MODEL_LOADED, type.value); registrar_.RemoveAll(); + DCHECK_EQ(state_, MODEL_STARTING); + state_ = ASSOCIATING; Associate(); } -void BookmarkDataTypeController::Associate() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK_EQ(state_, MODEL_STARTING); - state_ = ASSOCIATING; +syncable::ModelType BookmarkDataTypeController::type() const { + return syncable::BOOKMARKS; +} - ProfileSyncFactory::SyncComponents sync_components = - profile_sync_factory_->CreateBookmarkSyncComponents(sync_service_, this); +void BookmarkDataTypeController::CreateSyncComponents() { + ProfileSyncFactory::SyncComponents sync_components = profile_sync_factory_-> + CreateBookmarkSyncComponents(sync_service_, this); model_associator_.reset(sync_components.model_associator); change_processor_.reset(sync_components.change_processor); +} - bool sync_has_nodes = false; - if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { - StartFailed(UNRECOVERABLE_ERROR); - return; - } - - base::TimeTicks start_time = base::TimeTicks::Now(); - bool merge_success = model_associator_->AssociateModels(); - UMA_HISTOGRAM_TIMES("Sync.BookmarkAssociationTime", - base::TimeTicks::Now() - start_time); - if (!merge_success) { - StartFailed(ASSOCIATION_FAILED); - return; - } - - sync_service_->ActivateDataType(this, change_processor_.get()); - state_ = RUNNING; - FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK); +void BookmarkDataTypeController::RecordUnrecoverableError( + const tracked_objects::Location& from_here, + const std::string& message) { + UMA_HISTOGRAM_COUNTS("Sync.BookmarkRunFailures", 1); } -void BookmarkDataTypeController::FinishStart(StartResult result) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - start_callback_->Run(result); - start_callback_.reset(); +void BookmarkDataTypeController::RecordAssociationTime(base::TimeDelta time) { + UMA_HISTOGRAM_TIMES("Sync.BookmarkAssociationTime", time); } -void BookmarkDataTypeController::StartFailed(StartResult result) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - model_associator_.reset(); - change_processor_.reset(); - state_ = NOT_RUNNING; - start_callback_->Run(result); - start_callback_.reset(); +void BookmarkDataTypeController::RecordStartFailure(StartResult result) { UMA_HISTOGRAM_ENUMERATION("Sync.BookmarkStartFailures", result, MAX_START_RESULT); diff --git a/chrome/browser/sync/glue/bookmark_data_type_controller.h b/chrome/browser/sync/glue/bookmark_data_type_controller.h index 0716b44..8741e84 100644 --- a/chrome/browser/sync/glue/bookmark_data_type_controller.h +++ b/chrome/browser/sync/glue/bookmark_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,26 +8,18 @@ #include <string> -#include "base/basictypes.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_observer.h" +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" class NotificationDetails; class NotificationType; class NotificationSource; -class Profile; -class ProfileSyncService; -class ProfileSyncFactory; namespace browser_sync { -class AssociatorInterface; -class ChangeProcessor; - // A class that manages the startup and shutdown of bookmark sync. -class BookmarkDataTypeController : public DataTypeController, +class BookmarkDataTypeController : public FrontendDataTypeController, public NotificationObserver { public: BookmarkDataTypeController( @@ -36,24 +28,8 @@ class BookmarkDataTypeController : public DataTypeController, ProfileSyncService* sync_service); virtual ~BookmarkDataTypeController(); - // DataTypeController interface. - virtual void Start(StartCallback* start_callback); - - virtual void Stop(); - - virtual bool enabled(); - - virtual syncable::ModelType type(); - - virtual browser_sync::ModelSafeGroup model_safe_group(); - - virtual const char* name() const; - - virtual State state(); - - // UnrecoverableErrorHandler interface. - virtual void OnUnrecoverableError(const tracked_objects::Location& from_here, - const std::string& message); + // FrontendDataTypeController interface. + virtual syncable::ModelType type() const; // NotificationObserver interface. virtual void Observe(NotificationType type, @@ -61,24 +37,16 @@ class BookmarkDataTypeController : public DataTypeController, const NotificationDetails& details); private: - // Runs model association and change processor registration. - void Associate(); - - // Helper method to run the stashed start callback with a given result. - void FinishStart(StartResult result); - - // Cleans up state and calls callback when star fails. - void StartFailed(StartResult result); - - ProfileSyncFactory* profile_sync_factory_; - Profile* profile_; - ProfileSyncService* sync_service_; - - State state_; + // FrontendDataTypeController interface. + virtual bool StartModels(); + virtual void CleanupState(); + virtual void CreateSyncComponents(); + virtual void RecordUnrecoverableError( + const tracked_objects::Location& from_here, + const std::string& message); + virtual void RecordAssociationTime(base::TimeDelta time); + virtual void RecordStartFailure(StartResult result); - scoped_ptr<StartCallback> start_callback_; - scoped_ptr<AssociatorInterface> model_associator_; - scoped_ptr<ChangeProcessor> change_processor_; NotificationRegistrar registrar_; DISALLOW_COPY_AND_ASSIGN(BookmarkDataTypeController); diff --git a/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc b/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc index 40edc78..af20af5 100644 --- a/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc +++ b/chrome/browser/sync/glue/bookmark_data_type_controller_unittest.cc @@ -1,12 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" #include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/task.h" #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/profiles/profile.h" @@ -15,11 +15,11 @@ #include "chrome/browser/sync/glue/model_associator_mock.h" #include "chrome/browser/sync/profile_sync_factory_mock.h" #include "chrome/browser/sync/profile_sync_service_mock.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_source.h" -#include "chrome/common/notification_type.h" #include "chrome/test/profile_mock.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_service.h" +#include "content/common/notification_source.h" +#include "content/common/notification_type.h" #include "testing/gmock/include/gmock/gmock.h" using browser_sync::BookmarkDataTypeController; @@ -34,7 +34,8 @@ using testing::SetArgumentPointee; class StartCallback { public: - MOCK_METHOD1(Run, void(DataTypeController::StartResult result)); + MOCK_METHOD2(Run, void(DataTypeController::StartResult result, + const tracked_objects::Location& location)); }; class BookmarkModelMock : public BookmarkModel { @@ -68,6 +69,8 @@ class BookmarkDataTypeControllerTest : public testing::Test { } void SetAssociateExpectations() { + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); @@ -99,7 +102,7 @@ TEST_F(BookmarkDataTypeControllerTest, StartBookmarkModelReady) { EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, bookmark_dtc_->state()); } @@ -109,7 +112,7 @@ TEST_F(BookmarkDataTypeControllerTest, StartBookmarkModelNotReady) { EXPECT_CALL(bookmark_model_, IsLoaded()).WillRepeatedly(Return(false)); SetAssociateExpectations(); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::MODEL_STARTING, bookmark_dtc_->state()); @@ -126,7 +129,7 @@ TEST_F(BookmarkDataTypeControllerTest, StartFirstRun) { SetAssociateExpectations(); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -134,7 +137,7 @@ TEST_F(BookmarkDataTypeControllerTest, StartBusy) { SetStartExpectations(); EXPECT_CALL(bookmark_model_, IsLoaded()).WillRepeatedly(Return(false)); - EXPECT_CALL(start_callback_, Run(DataTypeController::BUSY)); + EXPECT_CALL(start_callback_, Run(DataTypeController::BUSY, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -145,7 +148,7 @@ TEST_F(BookmarkDataTypeControllerTest, StartOk) { EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -153,12 +156,14 @@ TEST_F(BookmarkDataTypeControllerTest, StartAssociationFailed) { SetStartExpectations(); // Set up association to fail. EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _)); + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels()). WillRepeatedly(Return(false)); - EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state()); } @@ -168,9 +173,11 @@ TEST_F(BookmarkDataTypeControllerTest, SetStartExpectations(); // Set up association to fail with an unrecoverable error. EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _)); + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); - EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR)); + EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state()); } @@ -179,7 +186,7 @@ TEST_F(BookmarkDataTypeControllerTest, StartAborted) { SetStartExpectations(); EXPECT_CALL(bookmark_model_, IsLoaded()).WillRepeatedly(Return(false)); - EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ABORTED, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); bookmark_dtc_->Stop(); EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state()); @@ -192,7 +199,7 @@ TEST_F(BookmarkDataTypeControllerTest, Stop) { EXPECT_EQ(DataTypeController::NOT_RUNNING, bookmark_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, bookmark_dtc_->state()); bookmark_dtc_->Stop(); @@ -209,7 +216,7 @@ TEST_F(BookmarkDataTypeControllerTest, OnUnrecoverableError) { &BookmarkDataTypeController::Stop)); SetStopExpectations(); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); bookmark_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); // This should cause bookmark_dtc_->Stop() to be called. bookmark_dtc_->OnUnrecoverableError(FROM_HERE, "Test"); diff --git a/chrome/browser/sync/glue/bookmark_model_associator.cc b/chrome/browser/sync/glue/bookmark_model_associator.cc index 743623d..c839501 100644 --- a/chrome/browser/sync/glue/bookmark_model_associator.cc +++ b/chrome/browser/sync/glue/bookmark_model_associator.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -13,9 +13,10 @@ #include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/engine/syncapi.h" -#include "chrome/browser/sync/syncable/autofill_migration.h" #include "chrome/browser/sync/glue/bookmark_change_processor.h" -#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/syncable/autofill_migration.h" +#include "chrome/browser/sync/syncable/nigori_util.h" +#include "chrome/browser/sync/util/cryptographer.h" #include "content/browser/browser_thread.h" namespace browser_sync { @@ -86,7 +87,7 @@ class BookmarkNodeFinder { BookmarkNodeFinder::BookmarkNodeFinder(const BookmarkNode* parent_node) : parent_node_(parent_node) { - for (int i = 0; i < parent_node_->GetChildCount(); ++i) { + for (int i = 0; i < parent_node_->child_count(); ++i) { child_nodes_.insert(parent_node_->GetChild(i)); } } @@ -95,7 +96,7 @@ const BookmarkNode* BookmarkNodeFinder::FindBookmarkNode( const sync_api::BaseNode& sync_node) { // Create a bookmark node from the given sync node. BookmarkNode temp_node(sync_node.GetURL()); - temp_node.SetTitle(WideToUTF16Hack(sync_node.GetTitle())); + temp_node.set_title(WideToUTF16Hack(sync_node.GetTitle())); if (sync_node.GetIsFolder()) temp_node.set_type(BookmarkNode::FOLDER); else @@ -146,7 +147,7 @@ void BookmarkNodeIdIndex::AddAll(const BookmarkNode* node) { if (!node->is_folder()) return; - for (int i = 0; i < node->GetChildCount(); ++i) + for (int i = 0; i < node->child_count(); ++i) AddAll(node->GetChild(i)); } @@ -156,15 +157,18 @@ const BookmarkNode* BookmarkNodeIdIndex::Find(int64 id) const { } BookmarkModelAssociator::BookmarkModelAssociator( - ProfileSyncService* sync_service, - UnrecoverableErrorHandler* persist_ids_error_handler) - : sync_service_(sync_service), - persist_ids_error_handler_(persist_ids_error_handler), + BookmarkModel* bookmark_model, + sync_api::UserShare* user_share, + UnrecoverableErrorHandler* unrecoverable_error_handler) + : bookmark_model_(bookmark_model), + user_share_(user_share), + unrecoverable_error_handler_(unrecoverable_error_handler), ALLOW_THIS_IN_INITIALIZER_LIST(persist_associations_(this)), number_of_new_sync_nodes_created_at_association_(0) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(sync_service_); - DCHECK(persist_ids_error_handler_); + DCHECK(bookmark_model_); + DCHECK(user_share_); + DCHECK(unrecoverable_error_handler_); } BookmarkModelAssociator::~BookmarkModelAssociator() { @@ -237,7 +241,7 @@ bool BookmarkModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { return false; } - sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + sync_api::ReadTransaction trans(user_share_); sync_api::ReadNode bookmark_bar_node(&trans); if (!bookmark_bar_node.InitByIdLookup(bookmark_bar_sync_id)) { @@ -287,7 +291,7 @@ bool BookmarkModelAssociator::AssociateTaggedPermanentNode( bool BookmarkModelAssociator::GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) { - sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + sync_api::ReadTransaction trans(user_share_); sync_api::ReadNode sync_node(&trans); if (!sync_node.InitByTagLookup(tag.c_str())) return false; @@ -326,34 +330,34 @@ bool BookmarkModelAssociator::BuildAssociations() { // This algorithm will not do well if the folder name has changes but the // children under them are all the same. - BookmarkModel* model = sync_service_->profile()->GetBookmarkModel(); - DCHECK(model->IsLoaded()); + DCHECK(bookmark_model_->IsLoaded()); // To prime our association, we associate the top-level nodes, Bookmark Bar // and Other Bookmarks. - if (!AssociateTaggedPermanentNode(model->other_node(), kOtherBookmarksTag)) { + if (!AssociateTaggedPermanentNode(bookmark_model_->other_node(), + kOtherBookmarksTag)) { LOG(ERROR) << "Server did not create top-level nodes. Possibly we " << "are running against an out-of-date server?"; return false; } - if (!AssociateTaggedPermanentNode(model->GetBookmarkBarNode(), + if (!AssociateTaggedPermanentNode(bookmark_model_->GetBookmarkBarNode(), kBookmarkBarTag)) { LOG(ERROR) << "Server did not create top-level nodes. Possibly we " << "are running against an out-of-date server?"; return false; } int64 bookmark_bar_sync_id = GetSyncIdFromChromeId( - model->GetBookmarkBarNode()->id()); + bookmark_model_->GetBookmarkBarNode()->id()); DCHECK(bookmark_bar_sync_id != sync_api::kInvalidId); int64 other_bookmarks_sync_id = GetSyncIdFromChromeId( - model->other_node()->id()); + bookmark_model_->other_node()->id()); DCHECK(other_bookmarks_sync_id != sync_api::kInvalidId); std::stack<int64> dfs_stack; dfs_stack.push(other_bookmarks_sync_id); dfs_stack.push(bookmark_bar_sync_id); - sync_api::WriteTransaction trans(sync_service_->GetUserShare()); + sync_api::WriteTransaction trans(user_share_); while (!dfs_stack.empty()) { int64 sync_parent_id = dfs_stack.top(); @@ -382,17 +386,17 @@ bool BookmarkModelAssociator::BuildAssociations() { const BookmarkNode* child_node = NULL; child_node = node_finder.FindBookmarkNode(sync_child_node); if (child_node) { - model->Move(child_node, parent_node, index); + bookmark_model_->Move(child_node, parent_node, index); // Set the favicon for bookmark node from sync node or vice versa. if (BookmarkChangeProcessor::SetBookmarkFavicon( - &sync_child_node, child_node, sync_service_->profile())) { - BookmarkChangeProcessor::SetSyncNodeFavicon(child_node, model, - &sync_child_node); + &sync_child_node, child_node, bookmark_model_)) { + BookmarkChangeProcessor::SetSyncNodeFavicon( + child_node, bookmark_model_, &sync_child_node); } } else { // Create a new bookmark node for the sync node. child_node = BookmarkChangeProcessor::CreateBookmarkNode( - &sync_child_node, parent_node, model, index); + &sync_child_node, parent_node, bookmark_model_, index); } Associate(child_node, sync_child_id); if (sync_child_node.GetIsFolder()) @@ -407,24 +411,16 @@ bool BookmarkModelAssociator::BuildAssociations() { // the right positions: from 0 to index - 1. // So the children starting from index in the parent bookmark node are the // ones that are not present in the parent sync node. So create them. - for (int i = index; i < parent_node->GetChildCount(); ++i) { + for (int i = index; i < parent_node->child_count(); ++i) { sync_child_id = BookmarkChangeProcessor::CreateSyncNode( - parent_node, model, i, &trans, this, sync_service_); + parent_node, bookmark_model_, i, &trans, this, + unrecoverable_error_handler_); if (parent_node->GetChild(i)->is_folder()) dfs_stack.push(sync_child_id); number_of_new_sync_nodes_created_at_association_++; } } - if (sync_service_->GetAutofillMigrationState() != - syncable::MIGRATED) { - syncable::AutofillMigrationDebugInfo debug_info; - debug_info.bookmarks_added_during_migration = - number_of_new_sync_nodes_created_at_association_; - sync_service_->SetAutofillMigrationDebugInfo( - syncable::AutofillMigrationDebugInfo::BOOKMARK_ADDED, - debug_info); - } return true; } @@ -448,7 +444,7 @@ void BookmarkModelAssociator::PersistAssociations() { return; } - sync_api::WriteTransaction trans(sync_service_->GetUserShare()); + sync_api::WriteTransaction trans(user_share_); DirtyAssociationsSyncIds::iterator iter; for (iter = dirty_associations_sync_ids_.begin(); iter != dirty_associations_sync_ids_.end(); @@ -456,7 +452,7 @@ void BookmarkModelAssociator::PersistAssociations() { int64 sync_id = *iter; sync_api::WriteNode sync_node(&trans); if (!sync_node.InitByIdLookup(sync_id)) { - persist_ids_error_handler_->OnUnrecoverableError(FROM_HERE, + unrecoverable_error_handler_->OnUnrecoverableError(FROM_HERE, "Could not lookup bookmark node for ID persistence."); return; } @@ -470,11 +466,10 @@ void BookmarkModelAssociator::PersistAssociations() { } bool BookmarkModelAssociator::LoadAssociations() { - BookmarkModel* model = sync_service_->profile()->GetBookmarkModel(); - DCHECK(model->IsLoaded()); + DCHECK(bookmark_model_->IsLoaded()); // If the bookmarks changed externally, our previous associations may not be // valid; so return false. - if (model->file_changed()) + if (bookmark_model_->file_changed()) return false; // Our persisted associations should be valid. Try to populate id association @@ -495,14 +490,14 @@ bool BookmarkModelAssociator::LoadAssociations() { // Build a bookmark node ID index since we are going to repeatedly search for // bookmark nodes by their IDs. BookmarkNodeIdIndex id_index; - id_index.AddAll(model->GetBookmarkBarNode()); - id_index.AddAll(model->other_node()); + id_index.AddAll(bookmark_model_->GetBookmarkBarNode()); + id_index.AddAll(bookmark_model_->other_node()); std::stack<int64> dfs_stack; dfs_stack.push(other_bookmarks_id); dfs_stack.push(bookmark_bar_id); - sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + sync_api::ReadTransaction trans(user_share_); // Count total number of nodes in sync model so that we can compare that // with the total number of nodes in the bookmark model. @@ -526,8 +521,8 @@ bool BookmarkModelAssociator::LoadAssociations() { // Don't try to call NodesMatch on permanent nodes like bookmark bar and // other bookmarks. They are not expected to match. - if (node != model->GetBookmarkBarNode() && - node != model->other_node() && + if (node != bookmark_model_->GetBookmarkBarNode() && + node != bookmark_model_->other_node() && !NodesMatch(node, &sync_parent)) return false; @@ -554,4 +549,13 @@ bool BookmarkModelAssociator::LoadAssociations() { return sync_node_count == id_index.count(); } +bool BookmarkModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(user_share_); + const syncable::ModelTypeSet& encrypted_types = + GetEncryptedDataTypes(trans.GetWrappedTrans()); + return encrypted_types.count(syncable::BOOKMARKS) == 0 || + trans.GetCryptographer()->is_ready(); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/bookmark_model_associator.h b/chrome/browser/sync/glue/bookmark_model_associator.h index 035f684..ac0da1c 100644 --- a/chrome/browser/sync/glue/bookmark_model_associator.h +++ b/chrome/browser/sync/glue/bookmark_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -15,16 +15,16 @@ #include "chrome/browser/sync/unrecoverable_error_handler.h" #include "chrome/browser/sync/glue/model_associator.h" +class BookmarkModel; class BookmarkNode; namespace sync_api { class BaseNode; class BaseTransaction; class ReadNode; +struct UserShare; } -class ProfileSyncService; - namespace browser_sync { class BookmarkChangeProcessor; @@ -37,8 +37,10 @@ class BookmarkModelAssociator : public PerDataTypeAssociatorInterface<BookmarkNode, int64> { public: static syncable::ModelType model_type() { return syncable::BOOKMARKS; } - BookmarkModelAssociator(ProfileSyncService* sync_service, - UnrecoverableErrorHandler* persist_ids_error_handler); + BookmarkModelAssociator( + BookmarkModel* bookmark_model, + sync_api::UserShare* user_share, + UnrecoverableErrorHandler* unrecoverable_error_handler); virtual ~BookmarkModelAssociator(); // AssociatorInterface implementation. @@ -83,15 +85,15 @@ class BookmarkModelAssociator // thread. } + // See ModelAssociator interface. + virtual bool CryptoReadyIfNecessary(); + protected: // Stores the id of the node with the given tag in |sync_id|. // Returns of that node was found successfully. // Tests override this. virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id); - // Used by TestBookmarkModelAssociator. - ProfileSyncService* sync_service() { return sync_service_; } - private: typedef std::map<int64, int64> BookmarkIdToSyncIdMap; typedef std::map<int64, const BookmarkNode*> SyncIdToBookmarkNodeMap; @@ -123,8 +125,9 @@ class BookmarkModelAssociator bool NodesMatch(const BookmarkNode* bookmark, const sync_api::BaseNode* sync_node) const; - ProfileSyncService* sync_service_; - UnrecoverableErrorHandler* persist_ids_error_handler_; + BookmarkModel* bookmark_model_; + sync_api::UserShare* user_share_; + UnrecoverableErrorHandler* unrecoverable_error_handler_; BookmarkIdToSyncIdMap id_map_; SyncIdToBookmarkNodeMap id_map_inverse_; // Stores sync ids for dirty associations. diff --git a/chrome/browser/sync/glue/data_type_controller.h b/chrome/browser/sync/glue/data_type_controller.h index adedad7..3108310 100644 --- a/chrome/browser/sync/glue/data_type_controller.h +++ b/chrome/browser/sync/glue/data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,9 +6,11 @@ #define CHROME_BROWSER_SYNC_GLUE_DATA_TYPE_CONTROLLER_H__ #pragma once +#include <string> #include <map> #include "base/callback.h" +#include "base/tracked.h" #include "chrome/browser/sync/engine/model_safe_worker.h" #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/unrecoverable_error_handler.h" @@ -51,7 +53,8 @@ class DataTypeController MAX_START_RESULT }; - typedef Callback1<StartResult>::Type StartCallback; + typedef Callback2<StartResult, + const tracked_objects::Location&>::Type StartCallback; typedef std::map<syncable::ModelType, scoped_refptr<DataTypeController> > TypeMap; @@ -71,23 +74,19 @@ class DataTypeController // result. virtual void Stop() = 0; - // Returns true if the user has indicated that they want this data - // type to be enabled. - virtual bool enabled() = 0; - // Unique model type for this data type controller. - virtual syncable::ModelType type() = 0; + virtual syncable::ModelType type() const = 0; // Name of this data type. For logging purposes only. - virtual const char* name() const = 0; + virtual std::string name() const = 0; // The model safe group of this data type. This should reflect the // thread that should be used to modify the data type's native // model. - virtual browser_sync::ModelSafeGroup model_safe_group() = 0; + virtual browser_sync::ModelSafeGroup model_safe_group() const = 0; // Current state of the data type controller. - virtual State state() = 0; + virtual State state() const = 0; protected: friend struct BrowserThread::DeleteOnThread<BrowserThread::UI>; diff --git a/chrome/browser/sync/glue/data_type_controller_mock.h b/chrome/browser/sync/glue/data_type_controller_mock.h index 83d0b6b..afde5cb 100644 --- a/chrome/browser/sync/glue/data_type_controller_mock.h +++ b/chrome/browser/sync/glue/data_type_controller_mock.h @@ -19,10 +19,10 @@ class DataTypeControllerMock : public DataTypeController { MOCK_METHOD1(Start, void(StartCallback* start_callback)); MOCK_METHOD0(Stop, void()); MOCK_METHOD0(enabled, bool()); - MOCK_METHOD0(type, syncable::ModelType()); - MOCK_CONST_METHOD0(name, const char*()); - MOCK_METHOD0(model_safe_group, browser_sync::ModelSafeGroup()); - MOCK_METHOD0(state, State()); + MOCK_CONST_METHOD0(type, syncable::ModelType()); + MOCK_CONST_METHOD0(name, std::string()); + MOCK_CONST_METHOD0(model_safe_group, browser_sync::ModelSafeGroup()); + MOCK_CONST_METHOD0(state, State()); MOCK_METHOD2(OnUnrecoverableError, void(const tracked_objects::Location&, const std::string&)); }; diff --git a/chrome/browser/sync/glue/data_type_manager.cc b/chrome/browser/sync/glue/data_type_manager.cc new file mode 100644 index 0000000..993230b --- /dev/null +++ b/chrome/browser/sync/glue/data_type_manager.cc @@ -0,0 +1,15 @@ +// Copyright (c) 2011 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/sync/glue/data_type_manager.h" + +namespace browser_sync { + +DataTypeManager::ConfigureResultWithErrorLocation:: + ~ConfigureResultWithErrorLocation() {} + +DataTypeManager::ConfigureResultWithErrorLocation:: + ConfigureResultWithErrorLocation() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/data_type_manager.h b/chrome/browser/sync/glue/data_type_manager.h index 99a9c29..4c97bee 100644 --- a/chrome/browser/sync/glue/data_type_manager.h +++ b/chrome/browser/sync/glue/data_type_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,6 +8,7 @@ #include <set> +#include "base/memory/scoped_ptr.h" #include "base/task.h" #include "chrome/browser/sync/glue/data_type_controller.h" #include "chrome/browser/sync/syncable/model_type.h" @@ -20,17 +21,13 @@ class DataTypeManager { public: enum State { STOPPED, // No data types are currently running. - RESTARTING, // Configuration has been changed... DOWNLOAD_PENDING, // Not implemented yet: Waiting for the syncer to // complete the initial download of new data // types. - // TODO(tim): Deprecated. Bug 26339. - PAUSE_PENDING, // Waiting for the sync backend to pause. CONFIGURING, // Data types are being started. - - // TODO(tim): Deprecated. Bug 26339. - RESUME_PENDING, // Waiting for the sync backend to resume. + BLOCKED, // We can't move forward with configuration because some + // external action must take place (i.e. passphrase). CONFIGURED, // All enabled data types are running. STOPPING // Data types are being stopped. @@ -47,6 +44,32 @@ class DataTypeManager { typedef std::set<syncable::ModelType> TypeSet; + // In case of an error the location is filled with the location the + // error originated from. In case of a success the error location value + // is to be not used. + // TODO(tim): We should rename this / ConfigureResult to something more + // flexible like SyncConfigureDoneDetails. + struct ConfigureResultWithErrorLocation { + ConfigureResult result; + TypeSet requested_types; + scoped_ptr<tracked_objects::Location> location; + + ConfigureResultWithErrorLocation(); + ConfigureResultWithErrorLocation(const ConfigureResult& result, + const tracked_objects::Location& location, + const TypeSet& requested_types) + : result(result), + requested_types(requested_types) { + this->location.reset(new tracked_objects::Location( + location.function_name(), + location.file_name(), + location.line_number())); + } + + ~ConfigureResultWithErrorLocation(); + }; + + virtual ~DataTypeManager() {} // Begins asynchronous configuration of data types. Any currently diff --git a/chrome/browser/sync/glue/data_type_manager_impl.cc b/chrome/browser/sync/glue/data_type_manager_impl.cc index bb89c6e..ef7936b 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl.cc +++ b/chrome/browser/sync/glue/data_type_manager_impl.cc @@ -1,36 +1,44 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/data_type_manager_impl.h" + #include <algorithm> #include <functional> +#include "base/compiler_specific.h" #include "base/logging.h" #include "base/message_loop.h" -#include "base/task.h" #include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/browser/sync/glue/data_type_manager_impl.h" #include "chrome/browser/sync/glue/sync_backend_host.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_source.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_details.h" +#include "content/common/notification_service.h" +#include "content/common/notification_source.h" namespace browser_sync { namespace { static const syncable::ModelType kStartOrder[] = { + syncable::NIGORI, // Listed for completeness. syncable::BOOKMARKS, syncable::PREFERENCES, syncable::AUTOFILL, syncable::AUTOFILL_PROFILE, + syncable::EXTENSIONS, + syncable::APPS, syncable::THEMES, syncable::TYPED_URLS, syncable::PASSWORDS, syncable::SESSIONS, }; +COMPILE_ASSERT(arraysize(kStartOrder) == + syncable::MODEL_TYPE_COUNT - syncable::FIRST_REAL_MODEL_TYPE, + kStartOrder_IncorrectSize); + // Comparator used when sorting data type controllers. class SortComparator : public std::binary_function<DataTypeController*, DataTypeController*, @@ -50,18 +58,14 @@ class SortComparator : public std::binary_function<DataTypeController*, } // namespace -DataTypeManagerImpl::DataTypeManagerImpl( - SyncBackendHost* backend, +DataTypeManagerImpl::DataTypeManagerImpl(SyncBackendHost* backend, const DataTypeController::TypeMap& controllers) : backend_(backend), controllers_(controllers), state_(DataTypeManager::STOPPED), - current_dtc_(NULL), - pause_pending_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(method_factory_(this)) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + needs_reconfigure_(false), + method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { DCHECK(backend_); - DCHECK_GT(arraysize(kStartOrder), 0U); // Ensure all data type controllers are stopped. for (DataTypeController::TypeMap::const_iterator it = controllers_.begin(); it != controllers_.end(); ++it) { @@ -73,7 +77,25 @@ DataTypeManagerImpl::DataTypeManagerImpl( start_order_[kStartOrder[i]] = i; } -DataTypeManagerImpl::~DataTypeManagerImpl() { +DataTypeManagerImpl::~DataTypeManagerImpl() {} + +bool DataTypeManagerImpl::GetControllersNeedingStart( + std::vector<DataTypeController*>* needs_start) { + // Add any data type controllers into the needs_start_ list that are + // currently NOT_RUNNING or STOPPING. + bool found_any = false; + for (TypeSet::const_iterator it = last_requested_types_.begin(); + it != last_requested_types_.end(); ++it) { + DataTypeController::TypeMap::const_iterator dtc = controllers_.find(*it); + if (dtc != controllers_.end() && + (dtc->second->state() == DataTypeController::NOT_RUNNING || + dtc->second->state() == DataTypeController::STOPPING)) { + found_any = true; + if (needs_start) + needs_start->push_back(dtc->second.get()); + } + } + return found_any; } void DataTypeManagerImpl::Configure(const TypeSet& desired_types) { @@ -84,22 +106,17 @@ void DataTypeManagerImpl::Configure(const TypeSet& desired_types) { return; } - backend_->UpdateEnabledTypes(desired_types); - last_requested_types_ = desired_types; - // Add any data type controllers into the needs_start_ list that are - // currently NOT_RUNNING or STOPPING. - needs_start_.clear(); - for (TypeSet::const_iterator it = desired_types.begin(); - it != desired_types.end(); ++it) { - DataTypeController::TypeMap::const_iterator dtc = controllers_.find(*it); - if (dtc != controllers_.end() && - (dtc->second->state() == DataTypeController::NOT_RUNNING || - dtc->second->state() == DataTypeController::STOPPING)) { - needs_start_.push_back(dtc->second.get()); - VLOG(1) << "Will start " << dtc->second->name(); - } + // Only proceed if we're in a steady state or blocked. + if (state_ != STOPPED && state_ != CONFIGURED && state_ != BLOCKED) { + VLOG(1) << "Received configure request while configuration in flight. " + << "Postponing until current configuration complete."; + needs_reconfigure_ = true; + return; } + + needs_start_.clear(); + GetControllersNeedingStart(&needs_start_); // Sort these according to kStartOrder. std::sort(needs_start_.begin(), needs_start_.end(), @@ -124,36 +141,18 @@ void DataTypeManagerImpl::Configure(const TypeSet& desired_types) { needs_stop_.end(), SortComparator(&start_order_)); - // If nothing changed, we're done. - if (needs_start_.empty() && needs_stop_.empty()) { - state_ = CONFIGURED; - NotifyStart(); - NotifyDone(OK); - return; - } - + // Restart to start/stop data types and notify the backend that the + // desired types have changed (need to do this even if there aren't any + // types to start/stop, because it could be that some types haven't + // started due to crypto errors but the backend host needs to know that we're + // disabling them anyway). Restart(); } void DataTypeManagerImpl::Restart() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); VLOG(1) << "Restarting..."; - // If we are currently waiting for an asynchronous process to - // complete, change our state to RESTARTING so those processes know - // that we want to start over when they finish. - if (state_ == DOWNLOAD_PENDING || state_ == PAUSE_PENDING || - state_ == CONFIGURING || state_ == RESUME_PENDING) { - state_ = RESTARTING; - return; - } - if (pause_pending_) { // Catch for http://crbug.com/73218. - NOTREACHED() << "Attempted to restart DataTypeManager while already " - << " configuring."; - return; - } - DCHECK(state_ == STOPPED || state_ == RESTARTING || state_ == CONFIGURED); - current_dtc_ = NULL; + DCHECK(state_ == STOPPED || state_ == CONFIGURED || state_ == BLOCKED); // Starting from a "steady state" (stopped or configured) state // should send a start notification. @@ -174,122 +173,96 @@ void DataTypeManagerImpl::Restart() { controllers_, last_requested_types_, method_factory_.NewRunnableMethod(&DataTypeManagerImpl::DownloadReady)); - - // If there were new types needing download, a nudge will have been sent and - // we should be in DOWNLOAD_PENDING. In that case we start the syncer thread - // (which is idempotent) to fetch these updates. - // However, we could actually be in PAUSE_PENDING here as if no new types - // were needed, our DownloadReady callback will have fired and we will have - // requested a pause already (so starting the syncer thread will still not - // let it make forward progress as the pause needs to be resumed by us). - // Because both the pause and start syncing commands are posted FCFS to the - // core thread, there is no race between the pause and the start; the pause - // will always win, so we will always start paused if we don't need to - // download new types. Thus, in almost all cases, the syncer thread DOES NOT - // start before model association. But... - // - // TODO(tim): Bug 47957. There is still subtle badness here. If we just - // restarted the browser and were upgraded in between, we may be in a state - // where a bunch of data types do have initial sync ended, but a new guy - // does not. In this case, what we really want is to _only_ download updates - // for that new type and not the ones that have already finished and we've - // presumably associated before. What happens now is the syncer is nudged - // and it does a sync from timestamp 0 for only the new types, and sends the - // OnSyncCycleCompleted event, which is how we get the DownloadReady call. - // We request a pause at this point, but it is done asynchronously. So in - // theory, the syncer could charge forward with another sync (for _all_ - // types) before the pause is serviced, which could be bad for associating - // models as we'll merge the present cloud with the immediate past, which - // opens the door to bugs like "bookmark came back from dead". A lot more - // stars have to align now for this to happen, but it's still there. - backend_->StartSyncingWithServer(); } void DataTypeManagerImpl::DownloadReady() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(state_ == DOWNLOAD_PENDING || state_ == RESTARTING); + DCHECK(state_ == DOWNLOAD_PENDING); - // If we had a restart while waiting for downloads, just restart. - // Note: Restart() can cause DownloadReady to be directly invoked, so we post - // a task to avoid re-entrancy issues. - if (state_ == RESTARTING) { - MessageLoop::current()->PostTask(FROM_HERE, - method_factory_.NewRunnableMethod(&DataTypeManagerImpl::Restart)); - return; - } - - // Pause the sync backend before starting the data types. - state_ = PAUSE_PENDING; - PauseSyncer(); + state_ = CONFIGURING; + StartNextType(); } void DataTypeManagerImpl::StartNextType() { // If there are any data types left to start, start the one at the // front of the list. if (!needs_start_.empty()) { - current_dtc_ = needs_start_[0]; - VLOG(1) << "Starting " << current_dtc_->name(); - current_dtc_->Start( + VLOG(1) << "Starting " << needs_start_[0]->name(); + needs_start_[0]->Start( NewCallback(this, &DataTypeManagerImpl::TypeStartCallback)); return; } - // If no more data types need starting, we're done. Resume the sync - // backend to finish. DCHECK_EQ(state_, CONFIGURING); - state_ = RESUME_PENDING; - ResumeSyncer(); + + if (needs_reconfigure_) { + // An attempt was made to reconfigure while we were already configuring. + // This can be because a passphrase was accepted or the user changed the + // set of desired types. Either way, |last_requested_types_| will contain + // the most recent set of desired types, so we just call configure. + // Note: we do this whether or not GetControllersNeedingStart is true, + // because we may need to stop datatypes. + SetBlockedAndNotify(); + needs_reconfigure_ = false; + VLOG(1) << "Reconfiguring due to previous configure attempt occuring while" + << " busy."; + + // Unwind the stack before executing configure. The method configure and its + // callees are not re-entrant. + MessageLoop::current()->PostTask(FROM_HERE, + method_factory_.NewRunnableMethod(&DataTypeManagerImpl::Configure, + last_requested_types_)); + return; + } + + // Do a fresh calculation to see if controllers need starting to account for + // things like encryption, which may still need to be sorted out before we + // can announce we're "Done" configuration entirely. + if (GetControllersNeedingStart(NULL)) { + SetBlockedAndNotify(); + return; + } + + // If no more data types need starting, we're done. + state_ = CONFIGURED; + NotifyDone(OK, FROM_HERE); } void DataTypeManagerImpl::TypeStartCallback( - DataTypeController::StartResult result) { + DataTypeController::StartResult result, + const tracked_objects::Location& location) { // When the data type controller invokes this callback, it must be // on the UI thread. DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(current_dtc_); - // If configuration changed while this data type was starting, we - // need to reset. Resume the syncer. - if (state_ == RESTARTING) { - ResumeSyncer(); + if (state_ == STOPPING) { + // If we reach this callback while stopping, this means that + // DataTypeManager::Stop() was called while the current data type + // was starting. Now that it has finished starting, we can finish + // stopping the DataTypeManager. This is considered an ABORT. + FinishStopAndNotify(ABORTED, FROM_HERE); + return; + } else if (state_ == STOPPED) { + // If our state_ is STOPPED, we have already stopped all of the data + // types. We should not be getting callbacks from stopped data types. + LOG(ERROR) << "Start callback called by stopped data type!"; return; } // We're done with the data type at the head of the list -- remove it. - DataTypeController* started_dtc = current_dtc_; + DataTypeController* started_dtc = needs_start_[0]; DCHECK(needs_start_.size()); DCHECK_EQ(needs_start_[0], started_dtc); needs_start_.erase(needs_start_.begin()); - current_dtc_ = NULL; - // If we reach this callback while stopping, this means that - // DataTypeManager::Stop() was called while the current data type - // was starting. Now that it has finished starting, we can finish - // stopping the DataTypeManager. This is considered an ABORT. - if (state_ == STOPPING) { - FinishStopAndNotify(ABORTED); - return; - } + if (result == DataTypeController::NEEDS_CRYPTO) { - // If our state_ is STOPPED, we have already stopped all of the data - // types. We should not be getting callbacks from stopped data types. - if (state_ == STOPPED) { - LOG(ERROR) << "Start callback called by stopped data type!"; - return; } - + // If the type started normally, continue to the next type. // If the type is waiting for the cryptographer, continue to the next type. // Once the cryptographer is ready, we'll attempt to restart this type. - if (result == DataTypeController::NEEDS_CRYPTO) { - VLOG(1) << "Waiting for crypto " << started_dtc->name(); - StartNextType(); - return; - } - - // If the type started normally, continue to the next type. - if (result == DataTypeController::OK || + if (result == DataTypeController::NEEDS_CRYPTO || + result == DataTypeController::OK || result == DataTypeController::OK_FIRST_RUN) { - VLOG(1) << "Started " << started_dtc->name(); StartNextType(); return; } @@ -313,7 +286,7 @@ void DataTypeManagerImpl::TypeStartCallback( NOTREACHED(); break; } - FinishStopAndNotify(configure_result); + FinishStopAndNotify(configure_result, location); } void DataTypeManagerImpl::Stop() { @@ -326,53 +299,33 @@ void DataTypeManagerImpl::Stop() { // which will synchronously invoke the start callback. if (state_ == CONFIGURING) { state_ = STOPPING; - current_dtc_->Stop(); - return; - } - // If Stop() is called while waiting for pause or resume, we no - // longer care about this. - bool aborted = false; - if (state_ == PAUSE_PENDING) { - pause_pending_ = false; - RemoveObserver(NotificationType::SYNC_PAUSED); - aborted = true; - } - if (state_ == RESUME_PENDING) { - RemoveObserver(NotificationType::SYNC_RESUMED); - aborted = true; - } + DCHECK_LT(0U, needs_start_.size()); + needs_start_[0]->Stop(); - // If Stop() is called while waiting for download, cancel all - // outstanding tasks. - if (state_ == DOWNLOAD_PENDING) { - method_factory_.RevokeAll(); - aborted = true; + // By this point, the datatype should have invoked the start callback, + // triggering FinishStop to be called, and the state to reach STOPPED. If we + // aren't STOPPED, it means that a datatype controller didn't call the start + // callback appropriately. + DCHECK_EQ(STOPPED, state_); + return; } + const bool download_pending = state_ == DOWNLOAD_PENDING; state_ = STOPPING; - if (aborted) - FinishStopAndNotify(ABORTED); - else - FinishStop(); -} - -const DataTypeController::TypeMap& DataTypeManagerImpl::controllers() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - return controllers_; -} + if (download_pending) { + // If Stop() is called while waiting for download, cancel all + // outstanding tasks. + method_factory_.RevokeAll(); + FinishStopAndNotify(ABORTED, FROM_HERE); + return; + } -DataTypeManager::State DataTypeManagerImpl::state() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - return state_; + FinishStop(); } void DataTypeManagerImpl::FinishStop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(state_== CONFIGURING || - state_ == STOPPING || - state_ == PAUSE_PENDING || - state_ == RESUME_PENDING); + DCHECK(state_== CONFIGURING || state_ == STOPPING || state_ == BLOCKED); // Simply call the Stop() method on all running data types. for (DataTypeController::TypeMap::const_iterator it = controllers_.begin(); it != controllers_.end(); ++it) { @@ -386,100 +339,43 @@ void DataTypeManagerImpl::FinishStop() { state_ = STOPPED; } -void DataTypeManagerImpl::FinishStopAndNotify(ConfigureResult result) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +void DataTypeManagerImpl::FinishStopAndNotify(ConfigureResult result, + const tracked_objects::Location& location) { FinishStop(); - NotifyDone(result); -} - -void DataTypeManagerImpl::Observe(NotificationType type, - const NotificationSource& source, - const NotificationDetails& details) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - switch (type.value) { - case NotificationType::SYNC_PAUSED: - DCHECK(state_ == PAUSE_PENDING || state_ == RESTARTING); - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - pause_pending_ = false; - - RemoveObserver(NotificationType::SYNC_PAUSED); - - // If the state changed to RESTARTING while waiting to be - // paused, resume the syncer so we can restart. - if (state_ == RESTARTING) { - ResumeSyncer(); - return; - } - - state_ = CONFIGURING; - StartNextType(); - break; - case NotificationType::SYNC_RESUMED: - DCHECK(state_ == RESUME_PENDING || state_ == RESTARTING); - RemoveObserver(NotificationType::SYNC_RESUMED); - - // If we are resuming because of a restart, continue the restart. - if (state_ == RESTARTING) { - Restart(); - return; - } - - state_ = CONFIGURED; - NotifyDone(OK); - break; - default: - NOTREACHED(); - } -} - -void DataTypeManagerImpl::AddObserver(NotificationType type) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - notification_registrar_.Add(this, - type, - NotificationService::AllSources()); -} - -void DataTypeManagerImpl::RemoveObserver(NotificationType type) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - notification_registrar_.Remove(this, - type, - NotificationService::AllSources()); + NotifyDone(result, location); } void DataTypeManagerImpl::NotifyStart() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); NotificationService::current()->Notify( NotificationType::SYNC_CONFIGURE_START, Source<DataTypeManager>(this), NotificationService::NoDetails()); } -void DataTypeManagerImpl::NotifyDone(ConfigureResult result) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +void DataTypeManagerImpl::NotifyDone(ConfigureResult result, + const tracked_objects::Location& location) { + ConfigureResultWithErrorLocation result_with_location(result, location, + last_requested_types_); NotificationService::current()->Notify( NotificationType::SYNC_CONFIGURE_DONE, Source<DataTypeManager>(this), - Details<ConfigureResult>(&result)); + Details<ConfigureResultWithErrorLocation>(&result_with_location)); } -void DataTypeManagerImpl::ResumeSyncer() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - AddObserver(NotificationType::SYNC_RESUMED); - if (!backend_->RequestResume()) { - RemoveObserver(NotificationType::SYNC_RESUMED); - FinishStopAndNotify(UNRECOVERABLE_ERROR); - } +const DataTypeController::TypeMap& DataTypeManagerImpl::controllers() { + return controllers_; } -void DataTypeManagerImpl::PauseSyncer() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - AddObserver(NotificationType::SYNC_PAUSED); - pause_pending_ = true; - if (!backend_->RequestPause()) { - pause_pending_ = false; - RemoveObserver(NotificationType::SYNC_PAUSED); - FinishStopAndNotify(UNRECOVERABLE_ERROR); - } +DataTypeManager::State DataTypeManagerImpl::state() { + return state_; +} + +void DataTypeManagerImpl::SetBlockedAndNotify() { + state_ = BLOCKED; + NotificationService::current()->Notify( + NotificationType::SYNC_CONFIGURE_BLOCKED, + Source<DataTypeManager>(this), + NotificationService::NoDetails()); } } // namespace browser_sync diff --git a/chrome/browser/sync/glue/data_type_manager_impl.h b/chrome/browser/sync/glue/data_type_manager_impl.h index cc9e956..841af2b 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl.h +++ b/chrome/browser/sync/glue/data_type_manager_impl.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -13,39 +13,24 @@ #include "base/basictypes.h" #include "base/task.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_type.h" - -class NotificationSource; -class NotificationDetails; namespace browser_sync { class DataTypeController; class SyncBackendHost; -class DataTypeManagerImpl : public DataTypeManager, - public NotificationObserver { +class DataTypeManagerImpl : public DataTypeManager { public: DataTypeManagerImpl(SyncBackendHost* backend, - const DataTypeController::TypeMap& controllers); + const DataTypeController::TypeMap& controllers); virtual ~DataTypeManagerImpl(); // DataTypeManager interface. virtual void Configure(const TypeSet& desired_types); - virtual void Stop(); - virtual const DataTypeController::TypeMap& controllers(); - virtual State state(); - // NotificationObserver implementation. - virtual void Observe(NotificationType type, - const NotificationSource& source, - const NotificationDetails& details); - private: // Starts the next data type in the kStartOrder list, indicated by // the current_type_ member. If there are no more data types to @@ -53,38 +38,40 @@ class DataTypeManagerImpl : public DataTypeManager, void StartNextType(); // Callback passed to each data type controller on startup. - void TypeStartCallback(DataTypeController::StartResult result); + void TypeStartCallback(DataTypeController::StartResult result, + const tracked_objects::Location& from_here); // Stops all data types. void FinishStop(); - void FinishStopAndNotify(ConfigureResult result); + void FinishStopAndNotify(ConfigureResult result, + const tracked_objects::Location& location); + + // Returns true if any last_requested_types_ currently needs to start model + // association. If non-null, fills |needs_start| with all such controllers. + bool GetControllersNeedingStart( + std::vector<DataTypeController*>* needs_start); void Restart(); void DownloadReady(); - void AddObserver(NotificationType type); - void RemoveObserver(NotificationType type); void NotifyStart(); - void NotifyDone(ConfigureResult result); - void ResumeSyncer(); - void PauseSyncer(); + void NotifyDone(ConfigureResult result, + const tracked_objects::Location& location); + void SetBlockedAndNotify(); SyncBackendHost* backend_; // Map of all data type controllers that are available for sync. // This list is determined at startup by various command line flags. const DataTypeController::TypeMap controllers_; State state_; - DataTypeController* current_dtc_; std::map<syncable::ModelType, int> start_order_; TypeSet last_requested_types_; std::vector<DataTypeController*> needs_start_; std::vector<DataTypeController*> needs_stop_; - // Safety check to ensure we never request more than one pause. This can be - // true while state_ != PAUSE_PENDING if we attempt to restart while in - // PAUSE_PENDING state. See http://crbug.com/73218. - bool pause_pending_; + // Whether an attempt to reconfigure was made while we were busy configuring. + // The |last_requested_types_| will reflect the newest set of requested types. + bool needs_reconfigure_; - NotificationRegistrar notification_registrar_; ScopedRunnableMethodFactory<DataTypeManagerImpl> method_factory_; DISALLOW_COPY_AND_ASSIGN(DataTypeManagerImpl); diff --git a/chrome/browser/sync/glue/data_type_manager_impl2.cc b/chrome/browser/sync/glue/data_type_manager_impl2.cc deleted file mode 100644 index af816fb..0000000 --- a/chrome/browser/sync/glue/data_type_manager_impl2.cc +++ /dev/null @@ -1,337 +0,0 @@ -// Copyright (c) 2011 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/sync/glue/data_type_manager_impl2.h" - -#include "base/compiler_specific.h" -#include "base/logging.h" -#include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/browser/sync/glue/sync_backend_host.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_source.h" -#include "content/browser/browser_thread.h" - -namespace browser_sync { - -namespace { - -static const syncable::ModelType kStartOrder[] = { - syncable::NIGORI, // Listed for completeness. - syncable::BOOKMARKS, - syncable::PREFERENCES, - syncable::AUTOFILL, - syncable::AUTOFILL_PROFILE, - syncable::EXTENSIONS, - syncable::APPS, - syncable::THEMES, - syncable::TYPED_URLS, - syncable::PASSWORDS, - syncable::SESSIONS, -}; - -COMPILE_ASSERT(arraysize(kStartOrder) == - syncable::MODEL_TYPE_COUNT - syncable::FIRST_REAL_MODEL_TYPE, - kStartOrder_IncorrectSize); - -// Comparator used when sorting data type controllers. -class SortComparator : public std::binary_function<DataTypeController*, - DataTypeController*, - bool> { - public: - explicit SortComparator(std::map<syncable::ModelType, int>* order) - : order_(order) { } - - // Returns true if lhs precedes rhs. - bool operator() (DataTypeController* lhs, DataTypeController* rhs) { - return (*order_)[lhs->type()] < (*order_)[rhs->type()]; - } - - private: - std::map<syncable::ModelType, int>* order_; -}; - -} // namespace - -DataTypeManagerImpl2::DataTypeManagerImpl2(SyncBackendHost* backend, - const DataTypeController::TypeMap& controllers) - : backend_(backend), - controllers_(controllers), - state_(DataTypeManager::STOPPED), - current_dtc_(NULL), - method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)) { - DCHECK(backend_); - // Ensure all data type controllers are stopped. - for (DataTypeController::TypeMap::const_iterator it = controllers_.begin(); - it != controllers_.end(); ++it) { - DCHECK_EQ(DataTypeController::NOT_RUNNING, (*it).second->state()); - } - - // Build a ModelType -> order map for sorting. - for (int i = 0; i < static_cast<int>(arraysize(kStartOrder)); i++) - start_order_[kStartOrder[i]] = i; -} - -DataTypeManagerImpl2::~DataTypeManagerImpl2() {} - -void DataTypeManagerImpl2::Configure(const TypeSet& desired_types) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (state_ == STOPPING) { - // You can not set a configuration while stopping. - LOG(ERROR) << "Configuration set while stopping."; - return; - } - - last_requested_types_ = desired_types; - // Add any data type controllers into the needs_start_ list that are - // currently NOT_RUNNING or STOPPING. - needs_start_.clear(); - for (TypeSet::const_iterator it = desired_types.begin(); - it != desired_types.end(); ++it) { - DataTypeController::TypeMap::const_iterator dtc = controllers_.find(*it); - if (dtc != controllers_.end() && - (dtc->second->state() == DataTypeController::NOT_RUNNING || - dtc->second->state() == DataTypeController::STOPPING)) { - needs_start_.push_back(dtc->second.get()); - VLOG(1) << "Will start " << dtc->second->name(); - } - } - // Sort these according to kStartOrder. - std::sort(needs_start_.begin(), - needs_start_.end(), - SortComparator(&start_order_)); - - // Add any data type controllers into that needs_stop_ list that are - // currently MODEL_STARTING, ASSOCIATING, or RUNNING. - needs_stop_.clear(); - for (DataTypeController::TypeMap::const_iterator it = controllers_.begin(); - it != controllers_.end(); ++it) { - DataTypeController* dtc = (*it).second; - if (desired_types.count(dtc->type()) == 0 && ( - dtc->state() == DataTypeController::MODEL_STARTING || - dtc->state() == DataTypeController::ASSOCIATING || - dtc->state() == DataTypeController::RUNNING)) { - needs_stop_.push_back(dtc); - VLOG(1) << "Will stop " << dtc->name(); - } - } - // Sort these according to kStartOrder. - std::sort(needs_stop_.begin(), - needs_stop_.end(), - SortComparator(&start_order_)); - - // If nothing changed, we're done. - if (needs_start_.empty() && needs_stop_.empty()) { - state_ = CONFIGURED; - NotifyStart(); - NotifyDone(OK); - return; - } - - Restart(); -} - -void DataTypeManagerImpl2::Restart() { - VLOG(1) << "Restarting..."; - - // If we are currently waiting for an asynchronous process to - // complete, change our state to RESTARTING so those processes know - // that we want to start over when they finish. - if (state_ == DOWNLOAD_PENDING || state_ == CONFIGURING) { - state_ = RESTARTING; - return; - } - - DCHECK(state_ == STOPPED || state_ == RESTARTING || state_ == CONFIGURED); - current_dtc_ = NULL; - - // Starting from a "steady state" (stopped or configured) state - // should send a start notification. - if (state_ == STOPPED || state_ == CONFIGURED) - NotifyStart(); - - // Stop requested data types. - for (size_t i = 0; i < needs_stop_.size(); ++i) { - VLOG(1) << "Stopping " << needs_stop_[i]->name(); - needs_stop_[i]->Stop(); - } - needs_stop_.clear(); - - // Tell the backend about the new set of data types we wish to sync. - // The task will be invoked when updates are downloaded. - state_ = DOWNLOAD_PENDING; - backend_->ConfigureDataTypes( - controllers_, - last_requested_types_, - method_factory_.NewRunnableMethod(&DataTypeManagerImpl2::DownloadReady)); -} - -void DataTypeManagerImpl2::DownloadReady() { - DCHECK(state_ == DOWNLOAD_PENDING || state_ == RESTARTING); - - // If we had a restart while waiting for downloads, just restart. - // Note: Restart() can cause DownloadReady to be directly invoked, so we post - // a task to avoid re-entrancy issues. - if (state_ == RESTARTING) { - MessageLoop::current()->PostTask(FROM_HERE, - method_factory_.NewRunnableMethod(&DataTypeManagerImpl2::Restart)); - return; - } - - state_ = CONFIGURING; - StartNextType(); -} - -void DataTypeManagerImpl2::StartNextType() { - // If there are any data types left to start, start the one at the - // front of the list. - if (!needs_start_.empty()) { - current_dtc_ = needs_start_[0]; - VLOG(1) << "Starting " << current_dtc_->name(); - current_dtc_->Start( - NewCallback(this, &DataTypeManagerImpl2::TypeStartCallback)); - return; - } - - // If no more data types need starting, we're done. - DCHECK_EQ(state_, CONFIGURING); - state_ = CONFIGURED; - NotifyDone(OK); -} - -void DataTypeManagerImpl2::TypeStartCallback( - DataTypeController::StartResult result) { - // When the data type controller invokes this callback, it must be - // on the UI thread. - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(current_dtc_); - - if (state_ == RESTARTING) { - // If configuration changed while this data type was starting, we - // need to reset. - Restart(); - return; - } else if (state_ == STOPPING) { - // If we reach this callback while stopping, this means that - // DataTypeManager::Stop() was called while the current data type - // was starting. Now that it has finished starting, we can finish - // stopping the DataTypeManager. This is considered an ABORT. - FinishStopAndNotify(ABORTED); - return; - } else if (state_ == STOPPED) { - // If our state_ is STOPPED, we have already stopped all of the data - // types. We should not be getting callbacks from stopped data types. - LOG(ERROR) << "Start callback called by stopped data type!"; - return; - } - - // We're done with the data type at the head of the list -- remove it. - DataTypeController* started_dtc = current_dtc_; - DCHECK(needs_start_.size()); - DCHECK_EQ(needs_start_[0], started_dtc); - needs_start_.erase(needs_start_.begin()); - current_dtc_ = NULL; - - // If the type started normally, continue to the next type. - // If the type is waiting for the cryptographer, continue to the next type. - // Once the cryptographer is ready, we'll attempt to restart this type. - if (result == DataTypeController::NEEDS_CRYPTO || - result == DataTypeController::OK || - result == DataTypeController::OK_FIRST_RUN) { - StartNextType(); - return; - } - - // Any other result is a fatal error. Shut down any types we've - // managed to start up to this point and pass the result to the - // callback. - VLOG(1) << "Failed " << started_dtc->name(); - ConfigureResult configure_result = DataTypeManager::ABORTED; - switch (result) { - case DataTypeController::ABORTED: - configure_result = DataTypeManager::ABORTED; - break; - case DataTypeController::ASSOCIATION_FAILED: - configure_result = DataTypeManager::ASSOCIATION_FAILED; - break; - case DataTypeController::UNRECOVERABLE_ERROR: - configure_result = DataTypeManager::UNRECOVERABLE_ERROR; - break; - default: - NOTREACHED(); - break; - } - FinishStopAndNotify(configure_result); -} - -void DataTypeManagerImpl2::Stop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - if (state_ == STOPPED) - return; - - // If we are currently configuring, then the current type is in a - // partially started state. Abort the startup of the current type, - // which will synchronously invoke the start callback. - if (state_ == CONFIGURING) { - state_ = STOPPING; - current_dtc_->Stop(); - return; - } - - const bool download_pending = state_ == DOWNLOAD_PENDING; - state_ = STOPPING; - if (download_pending) { - // If Stop() is called while waiting for download, cancel all - // outstanding tasks. - method_factory_.RevokeAll(); - FinishStopAndNotify(ABORTED); - return; - } - - FinishStop(); -} - -void DataTypeManagerImpl2::FinishStop() { - DCHECK(state_== CONFIGURING || state_ == STOPPING); - // Simply call the Stop() method on all running data types. - for (DataTypeController::TypeMap::const_iterator it = controllers_.begin(); - it != controllers_.end(); ++it) { - DataTypeController* dtc = (*it).second; - if (dtc->state() != DataTypeController::NOT_RUNNING && - dtc->state() != DataTypeController::STOPPING) { - dtc->Stop(); - VLOG(1) << "Stopped " << dtc->name(); - } - } - state_ = STOPPED; -} - -void DataTypeManagerImpl2::FinishStopAndNotify(ConfigureResult result) { - FinishStop(); - NotifyDone(result); -} - -void DataTypeManagerImpl2::NotifyStart() { - NotificationService::current()->Notify( - NotificationType::SYNC_CONFIGURE_START, - Source<DataTypeManager>(this), - NotificationService::NoDetails()); -} - -void DataTypeManagerImpl2::NotifyDone(ConfigureResult result) { - NotificationService::current()->Notify( - NotificationType::SYNC_CONFIGURE_DONE, - Source<DataTypeManager>(this), - Details<ConfigureResult>(&result)); -} - -const DataTypeController::TypeMap& DataTypeManagerImpl2::controllers() { - return controllers_; -} - -DataTypeManager::State DataTypeManagerImpl2::state() { - return state_; -} - -} // namespace browser_sync diff --git a/chrome/browser/sync/glue/data_type_manager_impl2.h b/chrome/browser/sync/glue/data_type_manager_impl2.h deleted file mode 100644 index 570a38b..0000000 --- a/chrome/browser/sync/glue/data_type_manager_impl2.h +++ /dev/null @@ -1,70 +0,0 @@ -// Copyright (c) 2011 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_GLUE_DATA_TYPE_MANAGER_IMPL2_H__ -#define CHROME_BROWSER_SYNC_GLUE_DATA_TYPE_MANAGER_IMPL2_H__ -#pragma once - -#include "chrome/browser/sync/glue/data_type_manager.h" - -#include <map> -#include <vector> - -#include "base/basictypes.h" -#include "base/task.h" - -namespace browser_sync { - -class DataTypeController; -class SyncBackendHost; - -class DataTypeManagerImpl2 : public DataTypeManager { - public: - DataTypeManagerImpl2(SyncBackendHost* backend, - const DataTypeController::TypeMap& controllers); - virtual ~DataTypeManagerImpl2(); - - // DataTypeManager interface. - virtual void Configure(const TypeSet& desired_types); - virtual void Stop(); - virtual const DataTypeController::TypeMap& controllers(); - virtual State state(); - - private: - // Starts the next data type in the kStartOrder list, indicated by - // the current_type_ member. If there are no more data types to - // start, the stashed start_callback_ is invoked. - void StartNextType(); - - // Callback passed to each data type controller on startup. - void TypeStartCallback(DataTypeController::StartResult result); - - // Stops all data types. - void FinishStop(); - void FinishStopAndNotify(ConfigureResult result); - - void Restart(); - void DownloadReady(); - void NotifyStart(); - void NotifyDone(ConfigureResult result); - - SyncBackendHost* backend_; - // Map of all data type controllers that are available for sync. - // This list is determined at startup by various command line flags. - const DataTypeController::TypeMap controllers_; - State state_; - DataTypeController* current_dtc_; - std::map<syncable::ModelType, int> start_order_; - TypeSet last_requested_types_; - std::vector<DataTypeController*> needs_start_; - std::vector<DataTypeController*> needs_stop_; - - ScopedRunnableMethodFactory<DataTypeManagerImpl2> method_factory_; - - DISALLOW_COPY_AND_ASSIGN(DataTypeManagerImpl2); -}; - -} // namespace browser_sync - -#endif // CHROME_BROWSER_SYNC_GLUE_DATA_TYPE_MANAGER_IMPL2_H__ diff --git a/chrome/browser/sync/glue/data_type_manager_impl2_unittest.cc b/chrome/browser/sync/glue/data_type_manager_impl2_unittest.cc index 4f9f9b3..999579c 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl2_unittest.cc +++ b/chrome/browser/sync/glue/data_type_manager_impl2_unittest.cc @@ -2,12 +2,12 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "chrome/browser/sync/glue/data_type_manager_impl2.h" +#include "chrome/browser/sync/glue/data_type_manager_impl.h" #include <set> +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/stl_util-inl.h" #include "base/task.h" #include "chrome/browser/sync/glue/data_type_controller.h" @@ -25,24 +25,42 @@ #include "testing/gtest/include/gtest/gtest.h" using browser_sync::DataTypeManager; -using browser_sync::DataTypeManagerImpl2; +using browser_sync::DataTypeManagerImpl; using browser_sync::DataTypeController; using browser_sync::DataTypeControllerMock; using browser_sync::SyncBackendHostMock; using testing::_; +using testing::AtLeast; using testing::DoAll; using testing::DoDefault; using testing::InSequence; +using testing::Invoke; +using testing::InvokeWithoutArgs; +using testing::InvokeWithoutArgs; +using testing::Mock; using testing::Property; using testing::Pointee; using testing::Return; using testing::SaveArg; ACTION_P(InvokeCallback, callback_result) { - arg0->Run(callback_result); + arg0->Run(callback_result, FROM_HERE); delete arg0; } +ACTION_P2(InvokeCallbackPointer, callback, argument) { + callback->Run(argument, FROM_HERE); + delete callback; +} + +DataTypeManager::ConfigureResult GetResult( + const NotificationDetails& details) { + DataTypeManager::ConfigureResultWithErrorLocation* result_with_location = + Details<DataTypeManager::ConfigureResultWithErrorLocation>( + details).ptr(); + return result_with_location->result; +} + class DataTypeManagerImpl2Test : public testing::Test { public: DataTypeManagerImpl2Test() @@ -79,10 +97,14 @@ class DataTypeManagerImpl2Test : public testing::Test { DataTypeControllerMock* MakePasswordDTC() { DataTypeControllerMock* dtc = new DataTypeControllerMock(); + SetPasswordDTCExpectations(dtc); + return dtc; + } + + void SetPasswordDTCExpectations(DataTypeControllerMock* dtc) { EXPECT_CALL(*dtc, enabled()).WillRepeatedly(Return(true)); EXPECT_CALL(*dtc, type()).WillRepeatedly(Return(syncable::PASSWORDS)); EXPECT_CALL(*dtc, name()).WillRepeatedly(Return("passwords")); - return dtc; } void SetStartStopExpectations(DataTypeControllerMock* mock_dtc) { @@ -126,12 +148,12 @@ class DataTypeManagerImpl2Test : public testing::Test { _, _)); } + void SetConfigureDoneExpectation(DataTypeManager::ConfigureResult result) { EXPECT_CALL( observer_, Observe(NotificationType(NotificationType::SYNC_CONFIGURE_DONE), _, - Property(&Details<DataTypeManager::ConfigureResult>::ptr, - Pointee(result)))); + ::testing::ResultOf(&GetResult, result))); } MessageLoopForUI message_loop_; @@ -144,7 +166,7 @@ class DataTypeManagerImpl2Test : public testing::Test { }; TEST_F(DataTypeManagerImpl2Test, NoControllers) { - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::OK); dtm.Configure(types_); @@ -158,7 +180,7 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureOne) { SetStartStopExpectations(bookmark_dtc); controllers_[syncable::BOOKMARKS] = bookmark_dtc; EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); types_.insert(syncable::BOOKMARKS); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::OK); @@ -174,7 +196,7 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureOneStopWhileStarting) { DataTypeController::MODEL_STARTING); controllers_[syncable::BOOKMARKS] = bookmark_dtc; EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); types_.insert(syncable::BOOKMARKS); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::OK); @@ -189,7 +211,7 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureOneStopWhileAssociating) { SetBusyStartStopExpectations(bookmark_dtc, DataTypeController::ASSOCIATING); controllers_[syncable::BOOKMARKS] = bookmark_dtc; EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); types_.insert(syncable::BOOKMARKS); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::OK); @@ -201,22 +223,37 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureOneStopWhileAssociating) { TEST_F(DataTypeManagerImpl2Test, OneWaitingForCrypto) { DataTypeControllerMock* password_dtc = MakePasswordDTC(); - EXPECT_CALL(*password_dtc, state()). + EXPECT_CALL(*password_dtc, state()).Times(AtLeast(2)). WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); EXPECT_CALL(*password_dtc, Start(_)). WillOnce(InvokeCallback((DataTypeController::NEEDS_CRYPTO))); - EXPECT_CALL(*password_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); controllers_[syncable::PASSWORDS] = password_dtc; EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); types_.insert(syncable::PASSWORDS); SetConfigureStartExpectation(); + + dtm.Configure(types_); + EXPECT_EQ(DataTypeManager::BLOCKED, dtm.state()); + + Mock::VerifyAndClearExpectations(&backend_); + Mock::VerifyAndClearExpectations(&observer_); + Mock::VerifyAndClearExpectations(password_dtc); + SetConfigureDoneExpectation(DataTypeManager::OK); + SetPasswordDTCExpectations(password_dtc); + EXPECT_CALL(*password_dtc, state()). + WillOnce(Return(DataTypeController::NOT_RUNNING)). + WillRepeatedly(Return(DataTypeController::RUNNING)); + EXPECT_CALL(*password_dtc, Start(_)). + WillOnce(InvokeCallback((DataTypeController::OK))); + EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); dtm.Configure(types_); + EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); + EXPECT_CALL(*password_dtc, Stop()).Times(1); dtm.Stop(); EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); } @@ -230,7 +267,7 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureOneThenAnother) { controllers_[syncable::PREFERENCES] = preference_dtc; EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(2); - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); types_.insert(syncable::BOOKMARKS); SetConfigureStartExpectation(); @@ -257,7 +294,7 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureOneThenSwitch) { controllers_[syncable::PREFERENCES] = preference_dtc; EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(2); - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); types_.insert(syncable::BOOKMARKS); SetConfigureStartExpectation(); @@ -276,6 +313,18 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureOneThenSwitch) { EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); } +void DoConfigureDataTypes( + const DataTypeController::TypeMap& data_type_controllers, + const syncable::ModelTypeSet& types, + CancelableTask* ready_task) { + ready_task->Run(); + delete ready_task; +} + +void QuitMessageLoop() { + MessageLoop::current()->Quit(); +} + TEST_F(DataTypeManagerImpl2Test, ConfigureWhileOneInFlight) { DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); // Save the callback here so we can interrupt startup. @@ -298,8 +347,12 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureWhileOneInFlight) { SetStartStopExpectations(preference_dtc); controllers_[syncable::PREFERENCES] = preference_dtc; - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(2); - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); + EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)) + .WillOnce(Invoke(DoConfigureDataTypes)) + .WillOnce(DoAll(Invoke(DoConfigureDataTypes), + InvokeWithoutArgs(QuitMessageLoop))); + types_.insert(syncable::BOOKMARKS); SetConfigureStartExpectation(); @@ -310,9 +363,11 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureWhileOneInFlight) { // preferences and continue starting bookmarks. types_.insert(syncable::PREFERENCES); dtm.Configure(types_); - callback->Run(DataTypeController::OK); + callback->Run(DataTypeController::OK, FROM_HERE); delete callback; + MessageLoop::current()->Run(); + EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); dtm.Stop(); @@ -328,7 +383,7 @@ TEST_F(DataTypeManagerImpl2Test, OneFailingController) { WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); controllers_[syncable::BOOKMARKS] = bookmark_dtc; - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::ASSOCIATION_FAILED); EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); @@ -348,12 +403,11 @@ TEST_F(DataTypeManagerImpl2Test, StopWhileInFlight) { DataTypeController::StartCallback* callback; EXPECT_CALL(*preference_dtc, Start(_)). WillOnce(SaveArg<0>(&callback)); - EXPECT_CALL(*preference_dtc, Stop()).Times(1); EXPECT_CALL(*preference_dtc, state()). WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); controllers_[syncable::PREFERENCES] = preference_dtc; - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::ABORTED); EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); @@ -366,9 +420,9 @@ TEST_F(DataTypeManagerImpl2Test, StopWhileInFlight) { EXPECT_EQ(DataTypeManager::CONFIGURING, dtm.state()); // Call stop before the preference callback is invoked. + EXPECT_CALL(*preference_dtc, Stop()). + WillOnce(InvokeCallbackPointer(callback, DataTypeController::ABORTED)); dtm.Stop(); - callback->Run(DataTypeController::ABORTED); - delete callback; EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); } @@ -385,7 +439,7 @@ TEST_F(DataTypeManagerImpl2Test, SecondControllerFails) { WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); controllers_[syncable::PREFERENCES] = preference_dtc; - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::ASSOCIATION_FAILED); EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); @@ -405,7 +459,7 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureWhileDownloadPending) { SetStartStopExpectations(preference_dtc); controllers_[syncable::PREFERENCES] = preference_dtc; - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::OK); CancelableTask* task; @@ -424,14 +478,10 @@ TEST_F(DataTypeManagerImpl2Test, ConfigureWhileDownloadPending) { types_.insert(syncable::PREFERENCES); dtm.Configure(types_); - // Should now be RESTARTING. - EXPECT_EQ(DataTypeManager::RESTARTING, dtm.state()); - // Running the task will queue a restart task to the message loop, and // eventually get us configured. task->Run(); delete task; - EXPECT_EQ(DataTypeManager::RESTARTING, dtm.state()); MessageLoop::current()->RunAllPending(); EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); @@ -444,7 +494,7 @@ TEST_F(DataTypeManagerImpl2Test, StopWhileDownloadPending) { SetNotUsedExpectations(bookmark_dtc); controllers_[syncable::BOOKMARKS] = bookmark_dtc; - DataTypeManagerImpl2 dtm(&backend_, controllers_); + DataTypeManagerImpl dtm(&backend_, controllers_); SetConfigureStartExpectation(); SetConfigureDoneExpectation(DataTypeManager::ABORTED); CancelableTask* task; diff --git a/chrome/browser/sync/glue/data_type_manager_impl_unittest.cc b/chrome/browser/sync/glue/data_type_manager_impl_unittest.cc index f0cab02..7a37f38 100644 --- a/chrome/browser/sync/glue/data_type_manager_impl_unittest.cc +++ b/chrome/browser/sync/glue/data_type_manager_impl_unittest.cc @@ -1,668 +1,3 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 <set> - -#include "base/message_loop.h" -#include "base/scoped_ptr.h" -#include "base/stl_util-inl.h" -#include "base/task.h" -#include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/browser/sync/glue/data_type_controller_mock.h" -#include "chrome/browser/sync/glue/data_type_manager_impl.h" -#include "chrome/browser/sync/glue/sync_backend_host_mock.h" -#include "chrome/browser/sync/profile_sync_test_util.h" -#include "chrome/browser/sync/syncable/model_type.h" -#include "content/browser/browser_thread.h" -#include "content/common/notification_details.h" -#include "content/common/notification_observer_mock.h" -#include "content/common/notification_registrar.h" -#include "content/common/notification_service.h" -#include "content/common/notification_type.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -using browser_sync::DataTypeManager; -using browser_sync::DataTypeManagerImpl; -using browser_sync::DataTypeController; -using browser_sync::DataTypeControllerMock; -using browser_sync::SyncBackendHostMock; -using testing::_; -using testing::DoAll; -using testing::DoDefault; -using testing::InSequence; -using testing::Property; -using testing::Pointee; -using testing::Return; -using testing::SaveArg; - -ACTION_P(InvokeCallback, callback_result) { - arg0->Run(callback_result); - delete arg0; -} - -class DataTypeManagerImplTest : public testing::Test { - public: - DataTypeManagerImplTest() - : ui_thread_(BrowserThread::UI, &message_loop_) {} - - virtual ~DataTypeManagerImplTest() { - } - - protected: - virtual void SetUp() { - registrar_.Add(&observer_, - NotificationType::SYNC_CONFIGURE_START, - NotificationService::AllSources()); - registrar_.Add(&observer_, - NotificationType::SYNC_CONFIGURE_DONE, - NotificationService::AllSources()); - } - - DataTypeControllerMock* MakeBookmarkDTC() { - DataTypeControllerMock* dtc = new DataTypeControllerMock(); - EXPECT_CALL(*dtc, enabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(*dtc, type()).WillRepeatedly(Return(syncable::BOOKMARKS)); - EXPECT_CALL(*dtc, name()).WillRepeatedly(Return("bookmark")); - return dtc; - } - - DataTypeControllerMock* MakePreferenceDTC() { - DataTypeControllerMock* dtc = new DataTypeControllerMock(); - EXPECT_CALL(*dtc, enabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(*dtc, type()).WillRepeatedly(Return(syncable::PREFERENCES)); - EXPECT_CALL(*dtc, name()).WillRepeatedly(Return("preference")); - return dtc; - } - - DataTypeControllerMock* MakePasswordDTC() { - DataTypeControllerMock* dtc = new DataTypeControllerMock(); - EXPECT_CALL(*dtc, enabled()).WillRepeatedly(Return(true)); - EXPECT_CALL(*dtc, type()).WillRepeatedly(Return(syncable::PASSWORDS)); - EXPECT_CALL(*dtc, name()).WillRepeatedly(Return("passwords")); - return dtc; - } - - void SetStartStopExpectations(DataTypeControllerMock* mock_dtc) { - InSequence seq; - EXPECT_CALL(*mock_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - EXPECT_CALL(*mock_dtc, Start(_)). - WillOnce(InvokeCallback((DataTypeController::OK))); - EXPECT_CALL(*mock_dtc, state()). - WillRepeatedly(Return(DataTypeController::RUNNING)); - EXPECT_CALL(*mock_dtc, Stop()).Times(1); - EXPECT_CALL(*mock_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - } - - void SetBusyStartStopExpectations(DataTypeControllerMock* mock_dtc, - DataTypeController::State busy_state) { - InSequence seq; - EXPECT_CALL(*mock_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - EXPECT_CALL(*mock_dtc, Start(_)). - WillOnce(InvokeCallback((DataTypeController::OK))); - EXPECT_CALL(*mock_dtc, state()). - WillRepeatedly(Return(busy_state)); - EXPECT_CALL(*mock_dtc, Stop()).Times(1); - EXPECT_CALL(*mock_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - } - - void SetNotUsedExpectations(DataTypeControllerMock* mock_dtc) { - EXPECT_CALL(*mock_dtc, Start(_)).Times(0); - EXPECT_CALL(*mock_dtc, Stop()).Times(0); - EXPECT_CALL(*mock_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - } - - void SetConfigureStartExpectation() { - EXPECT_CALL( - observer_, - Observe(NotificationType(NotificationType::SYNC_CONFIGURE_START), - _, _)); - } - - void SetConfigureDoneExpectation(DataTypeManager::ConfigureResult result) { - EXPECT_CALL( - observer_, - Observe(NotificationType(NotificationType::SYNC_CONFIGURE_DONE), _, - Property(&Details<DataTypeManager::ConfigureResult>::ptr, - Pointee(result)))); - } - - void SetBackendExpectations(int times) { - EXPECT_CALL(backend_, UpdateEnabledTypes(_)).Times(times); - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(times); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(times); - EXPECT_CALL(backend_, RequestPause()).Times(times); - EXPECT_CALL(backend_, RequestResume()).Times(times); - } - - MessageLoopForUI message_loop_; - BrowserThread ui_thread_; - DataTypeController::TypeMap controllers_; - SyncBackendHostMock backend_; - NotificationObserverMock observer_; - NotificationRegistrar registrar_; - std::set<syncable::ModelType> types_; -}; - -TEST_F(DataTypeManagerImplTest, NoControllers) { - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - EXPECT_CALL(backend_, UpdateEnabledTypes(_)); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureOne) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - SetBackendExpectations(1); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureOneStopWhileStarting) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetBusyStartStopExpectations(bookmark_dtc, - DataTypeController::MODEL_STARTING); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - SetBackendExpectations(1); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureOneStopWhileAssociating) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetBusyStartStopExpectations(bookmark_dtc, DataTypeController::ASSOCIATING); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - SetBackendExpectations(1); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, OneWaitingForCrypto) { - DataTypeControllerMock* password_dtc = MakePasswordDTC(); - EXPECT_CALL(*password_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - EXPECT_CALL(*password_dtc, Start(_)). - WillOnce(InvokeCallback((DataTypeController::NEEDS_CRYPTO))); - EXPECT_CALL(*password_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - - controllers_[syncable::PASSWORDS] = password_dtc; - SetBackendExpectations(1); - - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::PASSWORDS); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - - -TEST_F(DataTypeManagerImplTest, ConfigureOneThenAnother) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - SetStartStopExpectations(preference_dtc); - controllers_[syncable::PREFERENCES] = preference_dtc; - - SetBackendExpectations(2); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - - types_.insert(syncable::PREFERENCES); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureOneThenSwitch) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - SetStartStopExpectations(preference_dtc); - controllers_[syncable::PREFERENCES] = preference_dtc; - - SetBackendExpectations(2); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - - types_.clear(); - types_.insert(syncable::PREFERENCES); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureWhileOneInFlight) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - // Save the callback here so we can interrupt startup. - DataTypeController::StartCallback* callback; - { - InSequence seq; - EXPECT_CALL(*bookmark_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - EXPECT_CALL(*bookmark_dtc, Start(_)). - WillOnce(SaveArg<0>(&callback)); - EXPECT_CALL(*bookmark_dtc, state()). - WillRepeatedly(Return(DataTypeController::RUNNING)); - EXPECT_CALL(*bookmark_dtc, Stop()).Times(1); - EXPECT_CALL(*bookmark_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - } - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - SetStartStopExpectations(preference_dtc); - controllers_[syncable::PREFERENCES] = preference_dtc; - - SetBackendExpectations(2); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - - // At this point, the bookmarks dtc should be in flight. Add - // preferences and continue starting bookmarks. - types_.insert(syncable::PREFERENCES); - dtm.Configure(types_); - callback->Run(DataTypeController::OK); - delete callback; - - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureWhilePausePending) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - SetStartStopExpectations(preference_dtc); - controllers_[syncable::PREFERENCES] = preference_dtc; - - // Don't notify the first time pause is called. - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(2); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(2); - EXPECT_CALL(backend_, RequestPause()). - WillOnce(Return(true)). - WillOnce(DoDefault()); - EXPECT_CALL(backend_, RequestResume()).Times(2); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::PAUSE_PENDING, dtm.state()); - - // Configure while pause pending. - types_.insert(syncable::PREFERENCES); - dtm.Configure(types_); - - // Should now be RESTARTING. - EXPECT_EQ(DataTypeManager::RESTARTING, dtm.state()); - - // Send the SYNC_PAUSED notification. This will allow the DTM to - // wake up and restart itself with the new configuration. - NotificationService::current()->Notify(NotificationType::SYNC_PAUSED, - NotificationService::AllSources(), - NotificationService::NoDetails()); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, StopWhilePausePending) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetNotUsedExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(1); - // Never notify when RequestPause is called. - EXPECT_CALL(backend_, RequestPause()).WillOnce(Return(true)); - EXPECT_CALL(backend_, RequestResume()).Times(0); - DataTypeManagerImpl dtm(&backend_, controllers_); - - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::ABORTED); - types_.insert(syncable::BOOKMARKS); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::PAUSE_PENDING, dtm.state()); - - // Stop while pause pending. - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); - - // We should be able to safely handle a SYNC_PAUSED notification. - NotificationService::current()->Notify(NotificationType::SYNC_PAUSED, - NotificationService::AllSources(), - NotificationService::NoDetails()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureWhileResumePending) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - SetStartStopExpectations(preference_dtc); - controllers_[syncable::PREFERENCES] = preference_dtc; - - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(2); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(2); - EXPECT_CALL(backend_, RequestPause()).Times(2); - // Don't notify the first time resume is called. - EXPECT_CALL(backend_, RequestResume()). - WillOnce(Return(true)). - WillOnce(DoDefault()); - DataTypeManagerImpl dtm(&backend_, controllers_); - types_.insert(syncable::BOOKMARKS); - - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::RESUME_PENDING, dtm.state()); - - // Configure while resume pending. - types_.insert(syncable::PREFERENCES); - dtm.Configure(types_); - - // Should now be RESTARTING. - EXPECT_EQ(DataTypeManager::RESTARTING, dtm.state()); - - // Send the SYNC_PAUSED notification. This will allow the DTM to - // wake up and restart itself with the new configuration. - NotificationService::current()->Notify(NotificationType::SYNC_RESUMED, - NotificationService::AllSources(), - NotificationService::NoDetails()); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, StopWhileResumePending) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(1); - EXPECT_CALL(backend_, RequestPause()).Times(1); - // Never notify pause resumed. - EXPECT_CALL(backend_, RequestResume()).WillOnce(Return(true)); - DataTypeManagerImpl dtm(&backend_, controllers_); - - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::ABORTED); - types_.insert(syncable::BOOKMARKS); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::RESUME_PENDING, dtm.state()); - - // Stop while pause pending. - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); - - // We should be able to safely handle a SYNC_RESUMED notification. - NotificationService::current()->Notify(NotificationType::SYNC_RESUMED, - NotificationService::AllSources(), - NotificationService::NoDetails()); -} - -TEST_F(DataTypeManagerImplTest, OneFailingController) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - EXPECT_CALL(*bookmark_dtc, Start(_)). - WillOnce(InvokeCallback((DataTypeController::ASSOCIATION_FAILED))); - EXPECT_CALL(*bookmark_dtc, Stop()).Times(0); - EXPECT_CALL(*bookmark_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::ASSOCIATION_FAILED); - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(1); - EXPECT_CALL(backend_, RequestPause()).Times(1); - EXPECT_CALL(backend_, RequestResume()).Times(0); - - types_.insert(syncable::BOOKMARKS); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, StopWhileInFlight) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - // Save the callback here so we can interrupt startup. - DataTypeController::StartCallback* callback; - EXPECT_CALL(*preference_dtc, Start(_)). - WillOnce(SaveArg<0>(&callback)); - EXPECT_CALL(*preference_dtc, Stop()).Times(1); - EXPECT_CALL(*preference_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - controllers_[syncable::PREFERENCES] = preference_dtc; - - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::ABORTED); - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(1); - EXPECT_CALL(backend_, RequestPause()).Times(1); - EXPECT_CALL(backend_, RequestResume()).Times(0); - - types_.insert(syncable::BOOKMARKS); - types_.insert(syncable::PREFERENCES); - dtm.Configure(types_); - // Configure should stop in the CONFIGURING state because we are - // waiting for the preferences callback to be invoked. - EXPECT_EQ(DataTypeManager::CONFIGURING, dtm.state()); - - // Call stop before the preference callback is invoked. - dtm.Stop(); - callback->Run(DataTypeController::ABORTED); - delete callback; - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, SecondControllerFails) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - EXPECT_CALL(*preference_dtc, Start(_)). - WillOnce(InvokeCallback((DataTypeController::ASSOCIATION_FAILED))); - EXPECT_CALL(*preference_dtc, Stop()).Times(0); - EXPECT_CALL(*preference_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - controllers_[syncable::PREFERENCES] = preference_dtc; - - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::ASSOCIATION_FAILED); - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(1); - EXPECT_CALL(backend_, RequestPause()).Times(1); - EXPECT_CALL(backend_, RequestResume()).Times(0); - - types_.insert(syncable::BOOKMARKS); - types_.insert(syncable::PREFERENCES); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, PauseFailed) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - EXPECT_CALL(*bookmark_dtc, Start(_)).Times(0); - EXPECT_CALL(*bookmark_dtc, state()). - WillRepeatedly(Return(DataTypeController::NOT_RUNNING)); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::UNRECOVERABLE_ERROR); - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(1); - EXPECT_CALL(backend_, RequestPause()).WillOnce(Return(false)); - EXPECT_CALL(backend_, RequestResume()).Times(0); - - types_.insert(syncable::BOOKMARKS); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ResumeFailed) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::UNRECOVERABLE_ERROR); - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)).Times(1); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(1); - EXPECT_CALL(backend_, RequestPause()).Times(1); - EXPECT_CALL(backend_, RequestResume()).WillOnce(Return(false)); - - types_.insert(syncable::BOOKMARKS); - dtm.Configure(types_); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, ConfigureWhileDownloadPending) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetStartStopExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeControllerMock* preference_dtc = MakePreferenceDTC(); - SetStartStopExpectations(preference_dtc); - controllers_[syncable::PREFERENCES] = preference_dtc; - - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::OK); - CancelableTask* task; - // Grab the task the first time this is called so we can configure - // before it is finished. - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)). - WillOnce(SaveArg<2>(&task)). - WillOnce(DoDefault()); - EXPECT_CALL(backend_, StartSyncingWithServer()).Times(2); - EXPECT_CALL(backend_, RequestPause()).Times(1); - EXPECT_CALL(backend_, RequestResume()).Times(1); - - types_.insert(syncable::BOOKMARKS); - dtm.Configure(types_); - // Configure should stop in the DOWNLOAD_PENDING state because we - // are waiting for the download ready task to be run. - EXPECT_EQ(DataTypeManager::DOWNLOAD_PENDING, dtm.state()); - - types_.insert(syncable::PREFERENCES); - dtm.Configure(types_); - - // Should now be RESTARTING. - EXPECT_EQ(DataTypeManager::RESTARTING, dtm.state()); - - // Running the task will queue a restart task to the message loop, and - // eventually get us configured. - task->Run(); - delete task; - EXPECT_EQ(DataTypeManager::RESTARTING, dtm.state()); - MessageLoop::current()->RunAllPending(); - EXPECT_EQ(DataTypeManager::CONFIGURED, dtm.state()); - - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); -} - -TEST_F(DataTypeManagerImplTest, StopWhileDownloadPending) { - DataTypeControllerMock* bookmark_dtc = MakeBookmarkDTC(); - SetNotUsedExpectations(bookmark_dtc); - controllers_[syncable::BOOKMARKS] = bookmark_dtc; - - DataTypeManagerImpl dtm(&backend_, controllers_); - SetConfigureStartExpectation(); - SetConfigureDoneExpectation(DataTypeManager::ABORTED); - CancelableTask* task; - // Grab the task the first time this is called so we can stop - // before it is finished. - EXPECT_CALL(backend_, ConfigureDataTypes(_, _, _)). - WillOnce(SaveArg<2>(&task)); - EXPECT_CALL(backend_, StartSyncingWithServer()); - EXPECT_CALL(backend_, RequestPause()).Times(0); - EXPECT_CALL(backend_, RequestResume()).Times(0); - - types_.insert(syncable::BOOKMARKS); - dtm.Configure(types_); - // Configure should stop in the DOWNLOAD_PENDING state because we - // are waiting for the download ready task to be run. - EXPECT_EQ(DataTypeManager::DOWNLOAD_PENDING, dtm.state()); - - dtm.Stop(); - EXPECT_EQ(DataTypeManager::STOPPED, dtm.state()); - - // It should be perfectly safe to run this task even though the DTM - // has been stopped. - task->Run(); - delete task; -} diff --git a/chrome/browser/sync/glue/data_type_manager_mock.cc b/chrome/browser/sync/glue/data_type_manager_mock.cc index 9cc445e..0afef59 100644 --- a/chrome/browser/sync/glue/data_type_manager_mock.cc +++ b/chrome/browser/sync/glue/data_type_manager_mock.cc @@ -2,12 +2,14 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include "base/tracked.h" #include "chrome/browser/sync/glue/data_type_manager_mock.h" namespace browser_sync { DataTypeManagerMock::DataTypeManagerMock() - : result_(OK) { + : result_(OK, FROM_HERE, syncable::ModelTypeSet()) { + // By default, calling Configure will send a SYNC_CONFIGURE_START // and SYNC_CONFIGURE_DONE notification with a DataTypeManager::OK // detail. diff --git a/chrome/browser/sync/glue/data_type_manager_mock.h b/chrome/browser/sync/glue/data_type_manager_mock.h index 05a3665..eba4d61 100644 --- a/chrome/browser/sync/glue/data_type_manager_mock.h +++ b/chrome/browser/sync/glue/data_type_manager_mock.h @@ -8,16 +8,17 @@ #include "chrome/browser/sync/glue/data_type_manager.h" #include "chrome/browser/sync/profile_sync_test_util.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_details.h" +#include "content/common/notification_service.h" +#include "content/common/notification_type.h" #include "testing/gmock/include/gmock/gmock.h" ACTION_P3(NotifyFromDataTypeManagerWithResult, dtm, type, result) { NotificationService::current()->Notify( type, Source<browser_sync::DataTypeManager>(dtm), - Details<browser_sync::DataTypeManager::ConfigureResult>(result)); + Details<browser_sync::DataTypeManager::ConfigureResultWithErrorLocation>( + result)); } ACTION_P2(NotifyFromDataTypeManager, dtm, type) { @@ -39,7 +40,7 @@ class DataTypeManagerMock : public DataTypeManager { MOCK_METHOD0(state, State()); private: - DataTypeManager::ConfigureResult result_; + browser_sync::DataTypeManager::ConfigureResultWithErrorLocation result_; }; } // namespace browser_sync diff --git a/chrome/browser/sync/glue/database_model_worker_unittest.cc b/chrome/browser/sync/glue/database_model_worker_unittest.cc index 0a70ef6..40fc978 100644 --- a/chrome/browser/sync/glue/database_model_worker_unittest.cc +++ b/chrome/browser/sync/glue/database_model_worker_unittest.cc @@ -1,10 +1,10 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/threading/thread.h" #include "base/timer.h" #include "chrome/browser/sync/glue/database_model_worker.h" diff --git a/chrome/browser/sync/glue/do_optimistic_refresh_task.h b/chrome/browser/sync/glue/do_optimistic_refresh_task.h index f98d436..31cdebc 100644 --- a/chrome/browser/sync/glue/do_optimistic_refresh_task.h +++ b/chrome/browser/sync/glue/do_optimistic_refresh_task.h @@ -1,11 +1,11 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved.
+// Copyright (c) 2011 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_GLUE_DO_OPTIMISTIC_REFRESH_TASK_H_
#define CHROME_BROWSER_SYNC_GLUE_DO_OPTIMISTIC_REFRESH_TASK_H_
#pragma once
-#include "base/ref_counted.h"
+#include "base/memory/ref_counted.h"
#include "chrome/browser/autofill/personal_data_manager.h"
namespace browser_sync {
diff --git a/chrome/browser/sync/glue/extension_change_processor.cc b/chrome/browser/sync/glue/extension_change_processor.cc index 7cf1a2e..dd0da34 100644 --- a/chrome/browser/sync/glue/extension_change_processor.cc +++ b/chrome/browser/sync/glue/extension_change_processor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,15 +9,17 @@ #include "base/logging.h" #include "base/stl_util-inl.h" -#include "chrome/browser/profiles/profile.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_sync_data.h" +#include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/glue/extension_sync.h" #include "chrome/browser/sync/glue/extension_util.h" +#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/extension_specifics.pb.h" #include "chrome/common/extensions/extension.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_source.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_details.h" +#include "content/common/notification_source.h" namespace browser_sync { @@ -26,7 +28,9 @@ ExtensionChangeProcessor::ExtensionChangeProcessor( UnrecoverableErrorHandler* error_handler) : ChangeProcessor(error_handler), traits_(traits), - profile_(NULL) { + profile_(NULL), + extension_service_(NULL), + user_share_(NULL) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(error_handler); } @@ -65,7 +69,7 @@ void ExtensionChangeProcessor::Observe(NotificationType type, const std::string& id = uninstalled_extension_info->extension_id; VLOG(1) << "Removing server data for uninstalled extension " << id << " of type " << uninstalled_extension_info->extension_type; - RemoveServerData(traits_, id, profile_->GetProfileSyncService()); + RemoveServerData(traits_, id, user_share_); } } else { const Extension* extension = NULL; @@ -81,8 +85,8 @@ void ExtensionChangeProcessor::Observe(NotificationType type, return; } std::string error; - if (!UpdateServerData(traits_, *extension, - profile_->GetProfileSyncService(), &error)) { + if (!UpdateServerData(traits_, *extension, *extension_service_, + user_share_, &error)) { error_handler()->OnUnrecoverableError(FROM_HERE, error); } } @@ -96,10 +100,9 @@ void ExtensionChangeProcessor::ApplyChangesFromSyncModel( if (!running()) { return; } - ExtensionService* extensions_service = - GetExtensionServiceFromProfile(profile_); for (int i = 0; i < change_count; ++i) { const sync_api::SyncManager::ChangeRecord& change = changes[i]; + sync_pb::ExtensionSpecifics specifics; switch (change.action) { case sync_api::SyncManager::ChangeRecord::ACTION_ADD: case sync_api::SyncManager::ChangeRecord::ACTION_UPDATE: { @@ -112,30 +115,14 @@ void ExtensionChangeProcessor::ApplyChangesFromSyncModel( return; } DCHECK_EQ(node.GetModelType(), traits_.model_type); - const sync_pb::ExtensionSpecifics& specifics = - (*traits_.extension_specifics_getter)(node); - if (!IsExtensionSpecificsValid(specifics)) { - std::string error = - std::string("Invalid server specifics: ") + - ExtensionSpecificsToString(specifics); - error_handler()->OnUnrecoverableError(FROM_HERE, error); - return; - } - StopObserving(); - UpdateClient(traits_, specifics, extensions_service); - StartObserving(); + specifics = (*traits_.extension_specifics_getter)(node); break; } case sync_api::SyncManager::ChangeRecord::ACTION_DELETE: { - sync_pb::ExtensionSpecifics specifics; - if ((*traits_.extension_specifics_entity_getter)( + if (!(*traits_.extension_specifics_entity_getter)( change.specifics, &specifics)) { - StopObserving(); - RemoveFromClient(traits_, specifics.id(), extensions_service); - StartObserving(); - } else { std::stringstream error; - error << "Could not get extension ID for deleted node " + error << "Could not get extension specifics from deleted node " << change.id; error_handler()->OnUnrecoverableError(FROM_HERE, error.str()); LOG(DFATAL) << error.str(); @@ -143,13 +130,32 @@ void ExtensionChangeProcessor::ApplyChangesFromSyncModel( break; } } + ExtensionSyncData sync_data; + if (!GetExtensionSyncData(specifics, &sync_data)) { + // TODO(akalin): Should probably recover or drop. + std::string error = + std::string("Invalid server specifics: ") + + ExtensionSpecificsToString(specifics); + error_handler()->OnUnrecoverableError(FROM_HERE, error); + return; + } + sync_data.uninstalled = + (change.action == sync_api::SyncManager::ChangeRecord::ACTION_DELETE); + StopObserving(); + extension_service_->ProcessSyncData(sync_data, + traits_.is_valid_and_syncable); + StartObserving(); } } void ExtensionChangeProcessor::StartImpl(Profile* profile) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(profile); profile_ = profile; + extension_service_ = profile_->GetExtensionService(); + user_share_ = profile_->GetProfileSyncService()->GetUserShare(); + DCHECK(profile_); + DCHECK(extension_service_); + DCHECK(user_share_); StartObserving(); } @@ -157,6 +163,8 @@ void ExtensionChangeProcessor::StopImpl() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); StopObserving(); profile_ = NULL; + extension_service_ = NULL; + user_share_ = NULL; } void ExtensionChangeProcessor::StartObserving() { diff --git a/chrome/browser/sync/glue/extension_change_processor.h b/chrome/browser/sync/glue/extension_change_processor.h index 4d50f8c..cf75a2c 100644 --- a/chrome/browser/sync/glue/extension_change_processor.h +++ b/chrome/browser/sync/glue/extension_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,14 +10,11 @@ #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/change_processor.h" #include "chrome/browser/sync/glue/extension_sync_traits.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_type.h" -#include "chrome/common/notification_registrar.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_type.h" -class ExtensionService; -class NotificationDetails; -class NotificationSource; -class Profile; +class ExtensionServiceInterface; namespace browser_sync { @@ -60,8 +57,11 @@ class ExtensionChangeProcessor : public ChangeProcessor, NotificationRegistrar notification_registrar_; const ExtensionSyncTraits traits_; - // Owner of the ExtensionService. Non-NULL iff |running()| is true. + + // Non-NULL iff |running()| is true. Profile* profile_; + ExtensionServiceInterface* extension_service_; + sync_api::UserShare* user_share_; DISALLOW_COPY_AND_ASSIGN(ExtensionChangeProcessor); }; diff --git a/chrome/browser/sync/glue/extension_data_type_controller.cc b/chrome/browser/sync/glue/extension_data_type_controller.cc index 47b9427..07d4d1c 100644 --- a/chrome/browser/sync/glue/extension_data_type_controller.cc +++ b/chrome/browser/sync/glue/extension_data_type_controller.cc @@ -1,17 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/extension_data_type_controller.h" -#include "base/logging.h" #include "base/metrics/histogram.h" -#include "base/time.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" -#include "chrome/browser/sync/unrecoverable_error_handler.h" -#include "content/browser/browser_thread.h" namespace browser_sync { @@ -19,110 +14,42 @@ ExtensionDataTypeController::ExtensionDataTypeController( ProfileSyncFactory* profile_sync_factory, Profile* profile, ProfileSyncService* sync_service) - : profile_sync_factory_(profile_sync_factory), - profile_(profile), - sync_service_(sync_service), - state_(NOT_RUNNING) { - DCHECK(profile_sync_factory); - DCHECK(sync_service); + : FrontendDataTypeController(profile_sync_factory, + profile, + sync_service) { } ExtensionDataTypeController::~ExtensionDataTypeController() { } -void ExtensionDataTypeController::Start(StartCallback* start_callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(start_callback); - if (state_ != NOT_RUNNING) { - start_callback->Run(BUSY); - delete start_callback; - return; - } +syncable::ModelType ExtensionDataTypeController::type() const { + return syncable::EXTENSIONS; +} - start_callback_.reset(start_callback); +bool ExtensionDataTypeController::StartModels() { + profile_->InitExtensions(true); + return true; +} - profile_->InitExtensions(); +void ExtensionDataTypeController::CreateSyncComponents() { ProfileSyncFactory::SyncComponents sync_components = profile_sync_factory_->CreateExtensionSyncComponents(sync_service_, - this); + this); model_associator_.reset(sync_components.model_associator); change_processor_.reset(sync_components.change_processor); - - bool sync_has_nodes = false; - if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { - StartFailed(UNRECOVERABLE_ERROR); - return; - } - - base::TimeTicks start_time = base::TimeTicks::Now(); - bool merge_success = model_associator_->AssociateModels(); - UMA_HISTOGRAM_TIMES("Sync.ExtensionAssociationTime", - base::TimeTicks::Now() - start_time); - if (!merge_success) { - StartFailed(ASSOCIATION_FAILED); - return; - } - - sync_service_->ActivateDataType(this, change_processor_.get()); - state_ = RUNNING; - FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK); -} - -void ExtensionDataTypeController::Stop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (change_processor_ != NULL) - sync_service_->DeactivateDataType(this, change_processor_.get()); - - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - - change_processor_.reset(); - model_associator_.reset(); - start_callback_.reset(); - - state_ = NOT_RUNNING; -} - -bool ExtensionDataTypeController::enabled() { - return true; -} - -syncable::ModelType ExtensionDataTypeController::type() { - return syncable::EXTENSIONS; -} - -browser_sync::ModelSafeGroup ExtensionDataTypeController::model_safe_group() { - return browser_sync::GROUP_UI; -} - -const char* ExtensionDataTypeController::name() const { - // For logging only. - return "extension"; -} - -DataTypeController::State ExtensionDataTypeController::state() { - return state_; } -void ExtensionDataTypeController::OnUnrecoverableError( +void ExtensionDataTypeController::RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); UMA_HISTOGRAM_COUNTS("Sync.ExtensionRunFailures", 1); - sync_service_->OnUnrecoverableError(from_here, message); } -void ExtensionDataTypeController::FinishStart(StartResult result) { - start_callback_->Run(result); - start_callback_.reset(); +void ExtensionDataTypeController::RecordAssociationTime(base::TimeDelta time) { + UMA_HISTOGRAM_TIMES("Sync.ExtensionAssociationTime", time); } -void ExtensionDataTypeController::StartFailed(StartResult result) { - model_associator_.reset(); - change_processor_.reset(); - start_callback_->Run(result); - start_callback_.reset(); +void ExtensionDataTypeController::RecordStartFailure(StartResult result) { UMA_HISTOGRAM_ENUMERATION("Sync.ExtensionStartFailures", result, MAX_START_RESULT); diff --git a/chrome/browser/sync/glue/extension_data_type_controller.h b/chrome/browser/sync/glue/extension_data_type_controller.h index 0c031df..761db08 100644 --- a/chrome/browser/sync/glue/extension_data_type_controller.h +++ b/chrome/browser/sync/glue/extension_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,20 +8,11 @@ #include <string> -#include "base/basictypes.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/glue/data_type_controller.h" - -class Profile; -class ProfileSyncFactory; -class ProfileSyncService; +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" namespace browser_sync { -class AssociatorInterface; -class ChangeProcessor; - -class ExtensionDataTypeController : public DataTypeController { +class ExtensionDataTypeController : public FrontendDataTypeController { public: ExtensionDataTypeController( ProfileSyncFactory* profile_sync_factory, @@ -30,41 +21,17 @@ class ExtensionDataTypeController : public DataTypeController { virtual ~ExtensionDataTypeController(); // DataTypeController implementation. - virtual void Start(StartCallback* start_callback); - - virtual void Stop(); - - virtual bool enabled(); - - virtual syncable::ModelType type(); - - virtual browser_sync::ModelSafeGroup model_safe_group(); - - virtual const char* name() const; - - virtual State state(); + virtual syncable::ModelType type() const; - // UnrecoverableErrorHandler interface. - virtual void OnUnrecoverableError( + private: + // DataTypeController implementations. + virtual bool StartModels(); + virtual void CreateSyncComponents(); + virtual void RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message); - - private: - // Helper method to run the stashed start callback with a given result. - void FinishStart(StartResult result); - - // Cleans up state and calls callback when start fails. - void StartFailed(StartResult result); - - ProfileSyncFactory* profile_sync_factory_; - Profile* profile_; - ProfileSyncService* sync_service_; - - State state_; - - scoped_ptr<StartCallback> start_callback_; - scoped_ptr<AssociatorInterface> model_associator_; - scoped_ptr<ChangeProcessor> change_processor_; + virtual void RecordAssociationTime(base::TimeDelta time); + virtual void RecordStartFailure(StartResult result); DISALLOW_COPY_AND_ASSIGN(ExtensionDataTypeController); }; diff --git a/chrome/browser/sync/glue/extension_data_type_controller_unittest.cc b/chrome/browser/sync/glue/extension_data_type_controller_unittest.cc index 29220b1..4a6953c 100644 --- a/chrome/browser/sync/glue/extension_data_type_controller_unittest.cc +++ b/chrome/browser/sync/glue/extension_data_type_controller_unittest.cc @@ -1,12 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" #include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/task.h" #include "chrome/browser/sync/glue/extension_data_type_controller.h" #include "chrome/browser/sync/glue/change_processor_mock.h" @@ -28,7 +28,8 @@ using testing::SetArgumentPointee; class StartCallback { public: - MOCK_METHOD1(Run, void(DataTypeController::StartResult result)); + MOCK_METHOD2(Run, void(DataTypeController::StartResult result, + const tracked_objects::Location& location)); }; class ExtensionDataTypeControllerTest : public testing::Test { @@ -54,6 +55,8 @@ class ExtensionDataTypeControllerTest : public testing::Test { } void SetAssociateExpectations() { + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels()). @@ -85,7 +88,7 @@ TEST_F(ExtensionDataTypeControllerTest, Start) { SetAssociateExpectations(); SetActivateExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, extension_dtc_->state()); } @@ -96,7 +99,7 @@ TEST_F(ExtensionDataTypeControllerTest, StartFirstRun) { SetActivateExpectations(); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN, _)); extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -107,7 +110,7 @@ TEST_F(ExtensionDataTypeControllerTest, StartOk) { EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -117,7 +120,7 @@ TEST_F(ExtensionDataTypeControllerTest, StartAssociationFailed) { EXPECT_CALL(*model_associator_, AssociateModels()). WillRepeatedly(Return(false)); - EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED, _)); extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state()); } @@ -126,9 +129,11 @@ TEST_F(ExtensionDataTypeControllerTest, StartAssociationTriggersUnrecoverableError) { SetStartExpectations(); // Set up association to fail with an unrecoverable error. + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); - EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR)); + EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR, _)); extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state()); } @@ -141,7 +146,7 @@ TEST_F(ExtensionDataTypeControllerTest, Stop) { EXPECT_EQ(DataTypeController::NOT_RUNNING, extension_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, extension_dtc_->state()); extension_dtc_->Stop(); @@ -160,7 +165,7 @@ TEST_F(ExtensionDataTypeControllerTest, OnUnrecoverableError) { &ExtensionDataTypeController::Stop)); SetStopExpectations(); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); extension_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); // This should cause extension_dtc_->Stop() to be called. extension_dtc_->OnUnrecoverableError(FROM_HERE, "Test"); diff --git a/chrome/browser/sync/glue/extension_model_associator.cc b/chrome/browser/sync/glue/extension_model_associator.cc index c381705..383d819 100644 --- a/chrome/browser/sync/glue/extension_model_associator.cc +++ b/chrome/browser/sync/glue/extension_model_associator.cc @@ -1,23 +1,30 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/extension_model_associator.h" #include "base/logging.h" +#include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/extension_data.h" #include "chrome/browser/sync/glue/extension_sync_traits.h" #include "chrome/browser/sync/glue/extension_sync.h" +#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/extension_specifics.pb.h" +#include "chrome/browser/sync/syncable/nigori_util.h" #include "content/browser/browser_thread.h" namespace browser_sync { ExtensionModelAssociator::ExtensionModelAssociator( - const ExtensionSyncTraits& traits, ProfileSyncService* sync_service) - : traits_(traits), sync_service_(sync_service) { + const ExtensionSyncTraits& traits, + ExtensionServiceInterface* extension_service, + sync_api::UserShare* user_share) + : traits_(traits), extension_service_(extension_service), + user_share_(user_share) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(sync_service_); + DCHECK(extension_service_); + DCHECK(user_share_); } ExtensionModelAssociator::~ExtensionModelAssociator() { @@ -27,10 +34,12 @@ ExtensionModelAssociator::~ExtensionModelAssociator() { bool ExtensionModelAssociator::AssociateModels() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); ExtensionDataMap extension_data_map; - if (!SlurpExtensionData(traits_, sync_service_, &extension_data_map)) { + if (!SlurpExtensionData( + traits_, *extension_service_, user_share_, &extension_data_map)) { return false; } - if (!FlushExtensionData(traits_, extension_data_map, sync_service_)) { + if (!FlushExtensionData( + traits_, extension_data_map, extension_service_, user_share_)) { return false; } @@ -45,7 +54,16 @@ bool ExtensionModelAssociator::DisassociateModels() { bool ExtensionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - return RootNodeHasChildren(traits_.root_node_tag, sync_service_, has_nodes); + return RootNodeHasChildren(traits_.root_node_tag, user_share_, has_nodes); +} + +bool ExtensionModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(user_share_); + const syncable::ModelTypeSet& encrypted_types = + GetEncryptedDataTypes(trans.GetWrappedTrans()); + return encrypted_types.count(traits_.model_type) == 0 || + trans.GetCryptographer()->is_ready(); } } // namespace browser_sync diff --git a/chrome/browser/sync/glue/extension_model_associator.h b/chrome/browser/sync/glue/extension_model_associator.h index 0063d2d..ab4eb9c 100644 --- a/chrome/browser/sync/glue/extension_model_associator.h +++ b/chrome/browser/sync/glue/extension_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -11,7 +11,11 @@ #include "chrome/browser/sync/glue/model_associator.h" #include "chrome/browser/sync/syncable/model_type.h" -class ProfileSyncService; +class ExtensionServiceInterface; + +namespace sync_api { +struct UserShare; +} // namespace sync_api namespace browser_sync { @@ -21,7 +25,8 @@ class ExtensionModelAssociator : public AssociatorInterface { public: // Does not take ownership of sync_service. ExtensionModelAssociator(const ExtensionSyncTraits& traits, - ProfileSyncService* sync_service); + ExtensionServiceInterface* extension_service, + sync_api::UserShare* user_share); virtual ~ExtensionModelAssociator(); // Used by profile_sync_test_util.h. @@ -35,11 +40,11 @@ class ExtensionModelAssociator : public AssociatorInterface { // No implementation needed, this associator runs on the main // thread. } - + virtual bool CryptoReadyIfNecessary(); private: const ExtensionSyncTraits traits_; - // Weak pointer. Always non-NULL. - ProfileSyncService* sync_service_; + ExtensionServiceInterface* const extension_service_; + sync_api::UserShare* const user_share_; DISALLOW_COPY_AND_ASSIGN(ExtensionModelAssociator); }; diff --git a/chrome/browser/sync/glue/extension_sync.cc b/chrome/browser/sync/glue/extension_sync.cc index 3933515..1aedc4a 100644 --- a/chrome/browser/sync/glue/extension_sync.cc +++ b/chrome/browser/sync/glue/extension_sync.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,6 +9,7 @@ #include "base/logging.h" #include "chrome/browser/extensions/extension_updater.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_sync_data.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/extension_data.h" @@ -19,11 +20,11 @@ namespace browser_sync { bool RootNodeHasChildren(const char* tag, - ProfileSyncService* sync_service, + sync_api::UserShare* user_share, bool* has_children) { CHECK(has_children); *has_children = false; - sync_api::ReadTransaction trans(sync_service->GetUserShare()); + sync_api::ReadTransaction trans(user_share); sync_api::ReadNode node(&trans); if (!node.InitByTagLookup(tag)) { LOG(ERROR) << "Root node with tag " << tag << " does not exist"; @@ -33,22 +34,8 @@ bool RootNodeHasChildren(const char* tag, return true; } -ExtensionService* GetExtensionServiceFromProfile( - Profile* profile) { - CHECK(profile); - ExtensionService* extensions_service = profile->GetExtensionService(); - CHECK(extensions_service); - return extensions_service; -} - namespace { -ExtensionService* GetExtensionServiceFromProfileSyncService( - ProfileSyncService* sync_service) { - CHECK(sync_service); - return GetExtensionServiceFromProfile(sync_service->profile()); -} - // Updates the value in |extension_data_map| from the given data, // creating an entry if necessary. Returns a pointer to the // updated/created ExtensionData object. @@ -80,7 +67,7 @@ ExtensionData* SetOrCreateExtensionData( void ReadClientDataFromExtensionList( const ExtensionList& extensions, IsValidAndSyncablePredicate is_valid_and_syncable, - ExtensionService* extensions_service, + const ExtensionServiceInterface& extensions_service, std::set<std::string>* unsynced_extensions, ExtensionDataMap* extension_data_map) { for (ExtensionList::const_iterator it = extensions.begin(); @@ -89,7 +76,7 @@ void ReadClientDataFromExtensionList( const Extension& extension = **it; if (is_valid_and_syncable(extension)) { sync_pb::ExtensionSpecifics client_specifics; - GetExtensionSpecifics(extension, extensions_service->extension_prefs(), + GetExtensionSpecifics(extension, extensions_service, &client_specifics); DcheckIsExtensionSpecificsValid(client_specifics); const ExtensionData& extension_data = @@ -110,17 +97,17 @@ void ReadClientDataFromExtensionList( // enabled and disabled extensions from |extensions_service|. void SlurpClientData( IsValidAndSyncablePredicate is_valid_and_syncable, - ExtensionService* extensions_service, + const ExtensionServiceInterface& extensions_service, std::set<std::string>* unsynced_extensions, ExtensionDataMap* extension_data_map) { - const ExtensionList* extensions = extensions_service->extensions(); + const ExtensionList* extensions = extensions_service.extensions(); CHECK(extensions); ReadClientDataFromExtensionList( *extensions, is_valid_and_syncable, extensions_service, unsynced_extensions, extension_data_map); const ExtensionList* disabled_extensions = - extensions_service->disabled_extensions(); + extensions_service.disabled_extensions(); CHECK(disabled_extensions); ReadClientDataFromExtensionList( *disabled_extensions, is_valid_and_syncable, extensions_service, @@ -145,9 +132,9 @@ bool SlurpServerData( const char* root_node_tag, const ExtensionSpecificsGetter extension_specifics_getter, const std::set<std::string>& unsynced_extensions, - ProfileSyncService* sync_service, + sync_api::UserShare* user_share, ExtensionDataMap* extension_data_map) { - sync_api::WriteTransaction trans(sync_service->GetUserShare()); + sync_api::WriteTransaction trans(user_share); sync_api::ReadNode root(&trans); if (!root.InitByTagLookup(root_node_tag)) { LOG(ERROR) << GetRootNodeDoesNotExistError(root_node_tag); @@ -188,10 +175,9 @@ bool SlurpServerData( } // namespace bool SlurpExtensionData(const ExtensionSyncTraits& traits, - ProfileSyncService* sync_service, + const ExtensionServiceInterface& extensions_service, + sync_api::UserShare* user_share, ExtensionDataMap* extension_data_map) { - ExtensionService* extensions_service = - GetExtensionServiceFromProfileSyncService(sync_service); std::set<std::string> unsynced_extensions; // Read client-side data first so server data takes precedence, and @@ -202,7 +188,7 @@ bool SlurpExtensionData(const ExtensionSyncTraits& traits, if (!SlurpServerData( traits.root_node_tag, traits.extension_specifics_getter, - unsynced_extensions, sync_service, extension_data_map)) { + unsynced_extensions, user_share, extension_data_map)) { return false; } return true; @@ -249,91 +235,20 @@ bool UpdateServer( return true; } -// Tries to update the client data from the given extension data. -// extension_data->ServerNeedsUpdate() must not hold and -// extension_data->ClientNeedsUpdate() must hold before this function -// is called. If the update was successful, -// extension_data->ClientNeedsUpdate() will be false after this -// function is called. Otherwise, the extension needs updating to a -// new version. -void TryUpdateClient( - IsValidAndSyncablePredicate is_valid_and_syncable, - ExtensionService* extensions_service, - ExtensionData* extension_data) { - DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER)); - DCHECK(extension_data->NeedsUpdate(ExtensionData::CLIENT)); - const sync_pb::ExtensionSpecifics& specifics = - extension_data->merged_data(); - DcheckIsExtensionSpecificsValid(specifics); - const std::string& id = specifics.id(); - const Extension* extension = extensions_service->GetExtensionById(id, true); - if (extension) { - if (!is_valid_and_syncable(*extension)) { - LOG(DFATAL) << "TryUpdateClient() called for non-syncable extension " - << extension->id(); - return; - } - SetExtensionProperties(specifics, extensions_service, extension); - { - sync_pb::ExtensionSpecifics extension_specifics; - GetExtensionSpecifics(*extension, extensions_service->extension_prefs(), - &extension_specifics); - DCHECK(AreExtensionSpecificsUserPropertiesEqual( - specifics, extension_specifics)) - << ExtensionSpecificsToString(specifics) << ", " - << ExtensionSpecificsToString(extension_specifics); - } - if (!IsExtensionOutdated(*extension, specifics)) { - extension_data->ResolveData(ExtensionData::CLIENT); - DCHECK(!extension_data->NeedsUpdate(ExtensionData::CLIENT)); - } - } else { - GURL update_url(specifics.update_url()); - // TODO(akalin): Replace silent update with a list of enabled - // permissions. - extensions_service->AddPendingExtensionFromSync( - id, update_url, - is_valid_and_syncable, - true, // install_silently - specifics.enabled(), - specifics.incognito_enabled()); - } - DCHECK(!extension_data->NeedsUpdate(ExtensionData::SERVER)); -} - -// Kick off a run of the extension updater. -// -// TODO(akalin): Combine this with the similar function in -// theme_util.cc. -void NudgeExtensionUpdater(ExtensionService* extensions_service) { - ExtensionUpdater* extension_updater = extensions_service->updater(); - // Auto-updates should now be on always (see the construction of the - // ExtensionService in ProfileImpl::InitExtensions()). - if (extension_updater) { - extension_updater->CheckNow(); - } else { - LOG(DFATAL) << "Extension updater unexpectedly NULL; " - << "auto-updates may be turned off"; - } -} - } // namespace bool FlushExtensionData(const ExtensionSyncTraits& traits, const ExtensionDataMap& extension_data_map, - ProfileSyncService* sync_service) { - sync_api::WriteTransaction trans(sync_service->GetUserShare()); + ExtensionServiceInterface* extensions_service, + sync_api::UserShare* user_share) { + sync_api::WriteTransaction trans(user_share); sync_api::ReadNode root(&trans); if (!root.InitByTagLookup(traits.root_node_tag)) { LOG(ERROR) << GetRootNodeDoesNotExistError(traits.root_node_tag); return false; } - ExtensionService* extensions_service = - GetExtensionServiceFromProfileSyncService(sync_service); - // Update server and client as necessary. - bool should_nudge_extension_updater = false; for (ExtensionDataMap::const_iterator it = extension_data_map.begin(); it != extension_data_map.end(); ++it) { ExtensionData extension_data = it->second; @@ -346,26 +261,22 @@ bool FlushExtensionData(const ExtensionSyncTraits& traits, } } DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER)); - if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) { - TryUpdateClient(traits.is_valid_and_syncable, - extensions_service, &extension_data); - if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) { - should_nudge_extension_updater = true; - } + ExtensionSyncData sync_data; + if (!GetExtensionSyncData(extension_data.merged_data(), &sync_data)) { + // TODO(akalin): Should probably recover or drop. + NOTREACHED(); + return false; } - DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER)); + extensions_service->ProcessSyncData(sync_data, + traits.is_valid_and_syncable); } - - if (should_nudge_extension_updater) { - NudgeExtensionUpdater(extensions_service); - } - return true; } bool UpdateServerData(const ExtensionSyncTraits& traits, const Extension& extension, - ProfileSyncService* sync_service, + const ExtensionServiceInterface& extensions_service, + sync_api::UserShare* user_share, std::string* error) { const std::string& id = extension.id(); if (!traits.is_valid_and_syncable(extension)) { @@ -376,16 +287,14 @@ bool UpdateServerData(const ExtensionSyncTraits& traits, return false; } - ExtensionService* extensions_service = - GetExtensionServiceFromProfileSyncService(sync_service); sync_pb::ExtensionSpecifics client_data; - GetExtensionSpecifics(extension, extensions_service->extension_prefs(), + GetExtensionSpecifics(extension, extensions_service, &client_data); DcheckIsExtensionSpecificsValid(client_data); ExtensionData extension_data = ExtensionData::FromData(ExtensionData::CLIENT, client_data); - sync_api::WriteTransaction trans(sync_service->GetUserShare()); + sync_api::WriteTransaction trans(user_share); sync_api::ReadNode node(&trans); if (node.InitByClientTagLookup(traits.model_type, id)) { @@ -423,8 +332,8 @@ bool UpdateServerData(const ExtensionSyncTraits& traits, void RemoveServerData(const ExtensionSyncTraits& traits, const std::string& id, - ProfileSyncService* sync_service) { - sync_api::WriteTransaction trans(sync_service->GetUserShare()); + sync_api::UserShare* user_share) { + sync_api::WriteTransaction trans(user_share); sync_api::WriteNode write_node(&trans); if (write_node.InitByClientTagLookup(traits.model_type, id)) { write_node.Remove(); @@ -433,53 +342,4 @@ void RemoveServerData(const ExtensionSyncTraits& traits, } } -void UpdateClient(const ExtensionSyncTraits& traits, - const sync_pb::ExtensionSpecifics& server_data, - ExtensionService* extensions_service) { - DcheckIsExtensionSpecificsValid(server_data); - ExtensionData extension_data = - ExtensionData::FromData(ExtensionData::SERVER, server_data); - const Extension* extension = - extensions_service->GetExtensionById(server_data.id(), true); - if (extension) { - if (!traits.is_valid_and_syncable(*extension)) { - LOG(WARNING) << "Ignoring server data for invalid or " - << "non-syncable extension " << extension->id(); - return; - } - sync_pb::ExtensionSpecifics client_data; - GetExtensionSpecifics(*extension, extensions_service->extension_prefs(), - &client_data); - DcheckIsExtensionSpecificsValid(client_data); - extension_data = - ExtensionData::FromData(ExtensionData::CLIENT, client_data); - extension_data.SetData(ExtensionData::SERVER, true, server_data); - } - DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER)); - if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) { - TryUpdateClient(traits.is_valid_and_syncable, - extensions_service, &extension_data); - if (extension_data.NeedsUpdate(ExtensionData::CLIENT)) { - NudgeExtensionUpdater(extensions_service); - } - } - DCHECK(!extension_data.NeedsUpdate(ExtensionData::SERVER)); -} - -void RemoveFromClient(const ExtensionSyncTraits& traits, - const std::string& id, - ExtensionService* extensions_service) { - const Extension* extension = extensions_service->GetExtensionById(id, true); - if (extension) { - if (traits.is_valid_and_syncable(*extension)) { - extensions_service->UninstallExtension(id, false); - } else { - LOG(WARNING) << "Ignoring server data for invalid or " - << "non-syncable extension " << extension->id(); - } - } else { - LOG(ERROR) << "Trying to uninstall nonexistent extension " << id; - } -} - } // namespace browser_sync diff --git a/chrome/browser/sync/glue/extension_sync.h b/chrome/browser/sync/glue/extension_sync.h index af7f9a5..e1f3f15 100644 --- a/chrome/browser/sync/glue/extension_sync.h +++ b/chrome/browser/sync/glue/extension_sync.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -13,10 +13,14 @@ #include <string> class Extension; -class ExtensionService; +class ExtensionServiceInterface; class Profile; class ProfileSyncService; +namespace sync_api { +struct UserShare; +} // namespace sync_api + namespace sync_pb { class ExtensionSpecifics; } // namespace sync_pb @@ -35,17 +39,16 @@ typedef std::map<std::string, ExtensionData> ExtensionDataMap; // TODO(akalin): Move this somewhere where it can be used by // other data types. bool RootNodeHasChildren(const char* tag, - ProfileSyncService* sync_service, + sync_api::UserShare* user_share, bool* has_children); -ExtensionService* GetExtensionServiceFromProfile(Profile* profile); - // Fills |extension_data_map| with both client-side information about // installed extensions and the server-side information about // extensions to be synced. Returns true iff this was successful; if // unsuccessful, the contents of |extension_data_map| are undefined. bool SlurpExtensionData(const ExtensionSyncTraits& traits, - ProfileSyncService* sync_service, + const ExtensionServiceInterface& extensions_service, + sync_api::UserShare* user_share, ExtensionDataMap* extension_data_map); // Updates the server and client as necessary from @@ -56,30 +59,22 @@ bool SlurpExtensionData(const ExtensionSyncTraits& traits, // function is returned is that the updates were successfully started. bool FlushExtensionData(const ExtensionSyncTraits& traits, const ExtensionDataMap& extension_data_map, - ProfileSyncService* sync_service); + ExtensionServiceInterface* extensions_service, + sync_api::UserShare* user_share); // Updates the server data for the given extension. Returns true iff // this was successful; if unsuccessful, an error string is put into // |error|. bool UpdateServerData(const ExtensionSyncTraits& traits, const Extension& extension, - ProfileSyncService* sync_service, + const ExtensionServiceInterface& extensions_service, + sync_api::UserShare* user_share, std::string* error); // Removes the server data for the given extension ID. void RemoveServerData(const ExtensionSyncTraits& traits, const std::string& id, - ProfileSyncService* sync_service); - -// Starts updating the client data from the given server data. -void UpdateClient(const ExtensionSyncTraits& traits, - const sync_pb::ExtensionSpecifics& server_data, - ExtensionService* extensions_service); - -// Removes existing client data for the given extension. -void RemoveFromClient(const ExtensionSyncTraits& traits, - const std::string& id, - ExtensionService* extensions_service); + sync_api::UserShare* user_share); } // namespace browser_sync diff --git a/chrome/browser/sync/glue/extension_util.cc b/chrome/browser/sync/glue/extension_util.cc index 4713c92..5578f89 100644 --- a/chrome/browser/sync/glue/extension_util.cc +++ b/chrome/browser/sync/glue/extension_util.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -7,15 +7,15 @@ #include <sstream> #include "base/logging.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/stl_util-inl.h" #include "base/version.h" #include "chrome/browser/extensions/extension_prefs.h" #include "chrome/browser/extensions/extension_service.h" +#include "chrome/browser/extensions/extension_sync_data.h" #include "chrome/browser/sync/protocol/extension_specifics.pb.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" -#include "googleurl/src/gurl.h" namespace browser_sync { @@ -143,21 +143,12 @@ bool AreExtensionSpecificsNonUserPropertiesEqual( } void GetExtensionSpecifics(const Extension& extension, - ExtensionPrefs* extension_prefs, + const ExtensionServiceInterface& extension_service, sync_pb::ExtensionSpecifics* specifics) { - const std::string& id = extension.id(); - bool enabled = - extension_prefs->GetExtensionState(id) == Extension::ENABLED; - bool incognito_enabled = extension_prefs->IsIncognitoEnabled(id); - GetExtensionSpecificsHelper(extension, enabled, incognito_enabled, - specifics); -} - -void GetExtensionSpecificsHelper(const Extension& extension, - bool enabled, bool incognito_enabled, - sync_pb::ExtensionSpecifics* specifics) { DCHECK(IsExtensionValid(extension)); const std::string& id = extension.id(); + bool enabled = extension_service.IsExtensionEnabled(id); + bool incognito_enabled = extension_service.IsIncognitoEnabled(id); specifics->set_id(id); specifics->set_version(extension.VersionString()); specifics->set_update_url(extension.update_url().spec()); @@ -167,52 +158,6 @@ void GetExtensionSpecificsHelper(const Extension& extension, DcheckIsExtensionSpecificsValid(*specifics); } -bool IsExtensionOutdated(const Extension& extension, - const sync_pb::ExtensionSpecifics& specifics) { - DCHECK(IsExtensionValid(extension)); - DcheckIsExtensionSpecificsValid(specifics); - scoped_ptr<Version> specifics_version( - Version::GetVersionFromString(specifics.version())); - if (!specifics_version.get()) { - // If version is invalid, assume we're up-to-date. - return false; - } - return extension.version()->CompareTo(*specifics_version) < 0; -} - -void SetExtensionProperties( - const sync_pb::ExtensionSpecifics& specifics, - ExtensionService* extensions_service, const Extension* extension) { - DcheckIsExtensionSpecificsValid(specifics); - CHECK(extensions_service); - CHECK(extension); - DCHECK(IsExtensionValid(*extension)); - const std::string& id = extension->id(); - GURL update_url(specifics.update_url()); - if (update_url != extension->update_url()) { - LOG(WARNING) << "specifics for extension " << id - << "has a different update URL than the extension: " - << update_url.spec() << " vs. " << extension->update_url(); - } - ExtensionPrefs* extension_prefs = extensions_service->extension_prefs(); - bool enabled = extension_prefs->GetExtensionState(id) == Extension::ENABLED; - if (enabled && !specifics.enabled()) { - extensions_service->DisableExtension(id); - } else if (!enabled && specifics.enabled()) { - extensions_service->EnableExtension(id); - } - bool incognito_enabled = extension_prefs->IsIncognitoEnabled(id); - if (incognito_enabled != specifics.incognito_enabled()) { - extensions_service->SetIsIncognitoEnabled( - extension, specifics.incognito_enabled()); - } - if (specifics.name() != extension->name()) { - LOG(WARNING) << "specifics for extension " << id - << "has a different name than the extension: " - << specifics.name() << " vs. " << extension->name(); - } -} - void MergeExtensionSpecifics( const sync_pb::ExtensionSpecifics& specifics, bool merge_user_properties, @@ -237,4 +182,31 @@ void MergeExtensionSpecifics( } } +bool GetExtensionSyncData( + const sync_pb::ExtensionSpecifics& specifics, + ExtensionSyncData* sync_data) { + if (!Extension::IdIsValid(specifics.id())) { + return false; + } + + scoped_ptr<Version> version( + Version::GetVersionFromString(specifics.version())); + if (!version.get()) { + return false; + } + + // The update URL must be either empty or valid. + GURL update_url(specifics.update_url()); + if (!update_url.is_empty() && !update_url.is_valid()) { + return false; + } + + sync_data->id = specifics.id(); + sync_data->update_url = update_url; + sync_data->version = *version; + sync_data->enabled = specifics.enabled(); + sync_data->incognito_enabled = specifics.incognito_enabled(); + return true; +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/extension_util.h b/chrome/browser/sync/glue/extension_util.h index e908650..d914a75 100644 --- a/chrome/browser/sync/glue/extension_util.h +++ b/chrome/browser/sync/glue/extension_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -13,7 +13,8 @@ class Extension; class ExtensionPrefs; -class ExtensionService; +class ExtensionServiceInterface; +struct ExtensionSyncData; struct UninstalledExtensionInfo; namespace sync_pb { @@ -81,28 +82,9 @@ bool AreExtensionSpecificsNonUserPropertiesEqual( // must be a syncable extension. |specifics| will be valid after this // function is called. void GetExtensionSpecifics(const Extension& extension, - ExtensionPrefs* extension_prefs, + const ExtensionServiceInterface& extension_service, sync_pb::ExtensionSpecifics* specifics); -// Exposed only for testing. Pre- and post-conditions are the same as -// GetExtensionSpecifics(). -void GetExtensionSpecificsHelper(const Extension& extension, - bool enabled, bool incognito_enabled, - sync_pb::ExtensionSpecifics* specifics); - -// Returns whether or not the extension should be updated according to -// the specifics. |extension| must be syncable and |specifics| must -// be valid. -bool IsExtensionOutdated(const Extension& extension, - const sync_pb::ExtensionSpecifics& specifics); - -// Sets properties of |extension| according to the information in -// specifics. |extension| must be syncable and |specifics| must be -// valid. -void SetExtensionProperties( - const sync_pb::ExtensionSpecifics& specifics, - ExtensionService* extensions_service, const Extension* extension); - // Merge |specifics| into |merged_specifics|. Both must be valid and // have the same ID. The merge policy is currently to copy the // non-user properties of |specifics| into |merged_specifics| (and the @@ -113,6 +95,12 @@ void MergeExtensionSpecifics( bool merge_user_properties, sync_pb::ExtensionSpecifics* merged_specifics); +// Fills |sync_data| with the data from |specifics|. Returns true iff +// succesful. +bool GetExtensionSyncData( + const sync_pb::ExtensionSpecifics& specifics, + ExtensionSyncData* sync_data); + } // namespace browser_sync #endif // CHROME_BROWSER_SYNC_GLUE_EXTENSION_UTIL_H_ diff --git a/chrome/browser/sync/glue/extension_util_unittest.cc b/chrome/browser/sync/glue/extension_util_unittest.cc index 2ca92dd..5f8227c 100644 --- a/chrome/browser/sync/glue/extension_util_unittest.cc +++ b/chrome/browser/sync/glue/extension_util_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,15 +6,21 @@ #include "base/file_path.h" #include "base/values.h" +#include "chrome/browser/extensions/mock_extension_service.h" #include "chrome/browser/sync/protocol/extension_specifics.pb.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" +#include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" namespace browser_sync { namespace { +using ::testing::_; +using ::testing::Return; +using ::testing::StrictMock; + #if defined(OS_WIN) const FilePath::CharType kExtensionFilePath[] = FILE_PATH_LITERAL("c:\\foo"); #elif defined(OS_POSIX) @@ -71,13 +77,7 @@ scoped_refptr<Extension> MakeExtension( std::string error; scoped_refptr<Extension> extension = Extension::Create( - extension_path, location, source, false, true, &error); -#if defined(OS_CHROMEOS) - if (num_plugins > 0) { // plugins are illegal in extensions on chrome os. - EXPECT_FALSE(extension); - return NULL; - } -#endif + extension_path, location, source, Extension::STRICT_ERROR_CHECKS, &error); EXPECT_TRUE(extension); EXPECT_EQ("", error); return extension; @@ -135,6 +135,9 @@ TEST_F(ExtensionUtilTest, IsExtensionValid) { Extension::INTERNAL, 0, file_path)); EXPECT_FALSE(IsExtensionValid(*extension)); } + // These last 2 tests don't make sense on Chrome OS, where extension plugins + // are not allowed. +#if !defined(OS_CHROMEOS) { FilePath file_path(kExtensionFilePath); scoped_refptr<Extension> extension( @@ -149,6 +152,7 @@ TEST_F(ExtensionUtilTest, IsExtensionValid) { Extension::INTERNAL, 2, file_path)); EXPECT_FALSE(extension && IsExtensionValid(*extension)); } +#endif } TEST_F(ExtensionUtilTest, IsExtensionSpecificsUnset) { @@ -386,18 +390,26 @@ scoped_refptr<Extension> MakeSyncableExtension( source.SetString(extension_manifest_keys::kName, name); std::string error; scoped_refptr<Extension> extension = Extension::Create( - extension_path, Extension::INTERNAL, source, false, true, &error); + extension_path, Extension::INTERNAL, source, + Extension::STRICT_ERROR_CHECKS, &error); EXPECT_TRUE(extension); EXPECT_EQ("", error); return extension; } -TEST_F(ExtensionUtilTest, GetExtensionSpecificsHelper) { +TEST_F(ExtensionUtilTest, GetExtensionSpecifics) { FilePath file_path(kExtensionFilePath); + StrictMock<MockExtensionService> mock_extension_service; + EXPECT_CALL(mock_extension_service, IsExtensionEnabled(_)) + .WillOnce(Return(true)); + EXPECT_CALL(mock_extension_service, IsIncognitoEnabled(_)) + .WillOnce(Return(false)); + scoped_refptr<Extension> extension( - MakeSyncableExtension(kValidVersion, kValidUpdateUrl1, kName, file_path)); + MakeSyncableExtension( + kValidVersion, kValidUpdateUrl1, kName, file_path)); sync_pb::ExtensionSpecifics specifics; - GetExtensionSpecificsHelper(*extension, true, false, &specifics); + GetExtensionSpecifics(*extension, mock_extension_service, &specifics); EXPECT_EQ(extension->id(), specifics.id()); EXPECT_EQ(extension->VersionString(), kValidVersion); EXPECT_EQ(extension->update_url().spec(), kValidUpdateUrl1); @@ -406,22 +418,6 @@ TEST_F(ExtensionUtilTest, GetExtensionSpecificsHelper) { EXPECT_EQ(kName, specifics.name()); } -TEST_F(ExtensionUtilTest, IsExtensionOutdated) { - FilePath file_path(kExtensionFilePath); - scoped_refptr<Extension> extension( - MakeSyncableExtension(kVersion2, kValidUpdateUrl1, kName, file_path)); - sync_pb::ExtensionSpecifics specifics; - specifics.set_id(kValidId); - specifics.set_update_url(kValidUpdateUrl1); - - specifics.set_version(kVersion1); - EXPECT_FALSE(IsExtensionOutdated(*extension, specifics)); - specifics.set_version(kVersion2); - EXPECT_FALSE(IsExtensionOutdated(*extension, specifics)); - specifics.set_version(kVersion3); - EXPECT_TRUE(IsExtensionOutdated(*extension, specifics)); -} - // TODO(akalin): Make ExtensionService/ExtensionUpdater testable // enough to be able to write a unittest for SetExtensionProperties(). diff --git a/chrome/browser/sync/glue/foreign_session_tracker.h b/chrome/browser/sync/glue/foreign_session_tracker.h index dd99455..5076d8f 100644 --- a/chrome/browser/sync/glue/foreign_session_tracker.h +++ b/chrome/browser/sync/glue/foreign_session_tracker.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -12,7 +12,7 @@ #include <vector> #include "base/basictypes.h" -#include "base/scoped_vector.h" +#include "base/memory/scoped_vector.h" #include "chrome/browser/sessions/session_id.h" #include "chrome/browser/sessions/session_types.h" diff --git a/chrome/browser/sync/glue/frontend_data_type_controller.cc b/chrome/browser/sync/glue/frontend_data_type_controller.cc new file mode 100644 index 0000000..5e324f9 --- /dev/null +++ b/chrome/browser/sync/glue/frontend_data_type_controller.cc @@ -0,0 +1,172 @@ +// Copyright (c) 2011 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/sync/glue/frontend_data_type_controller.h" + +#include "base/logging.h" +#include "chrome/browser/profiles/profile.h" +#include "chrome/browser/sync/glue/change_processor.h" +#include "chrome/browser/sync/glue/model_associator.h" +#include "chrome/browser/sync/profile_sync_factory.h" +#include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "content/browser/browser_thread.h" + +namespace browser_sync { + +FrontendDataTypeController::FrontendDataTypeController() + : profile_sync_factory_(NULL), + profile_(NULL), + sync_service_(NULL) {} + +FrontendDataTypeController::FrontendDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service) + : profile_sync_factory_(profile_sync_factory), + profile_(profile), + sync_service_(sync_service), + state_(NOT_RUNNING) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(profile_sync_factory); + DCHECK(profile); + DCHECK(sync_service); +} + +FrontendDataTypeController::~FrontendDataTypeController() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); +} + +void FrontendDataTypeController::Start(StartCallback* start_callback) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + DCHECK(start_callback); + if (state_ != NOT_RUNNING) { + start_callback->Run(BUSY, FROM_HERE); + delete start_callback; + return; + } + + start_callback_.reset(start_callback); + + state_ = MODEL_STARTING; + if (!StartModels()) { + // If we are waiting for some external service to load before associating + // or we failed to start the models, we exit early. state_ will control + // what we perform next. + DCHECK(state_ == NOT_RUNNING || state_ == MODEL_STARTING); + return; + } + + state_ = ASSOCIATING; + if (!Associate()) { + // We failed to associate and are aborting. + DCHECK_EQ(state_, NOT_RUNNING); + return; + } + DCHECK_EQ(state_, RUNNING); +} + +bool FrontendDataTypeController::StartModels() { + DCHECK_EQ(state_, MODEL_STARTING); + // By default, no additional services need to be started before we can proceed + // with model association. + return true; +} + +bool FrontendDataTypeController::Associate() { + DCHECK_EQ(state_, ASSOCIATING); + CreateSyncComponents(); + + if (!model_associator_->CryptoReadyIfNecessary()) { + StartFailed(NEEDS_CRYPTO, FROM_HERE); + return false; + } + + bool sync_has_nodes = false; + if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { + StartFailed(UNRECOVERABLE_ERROR, FROM_HERE); + return false; + } + + base::TimeTicks start_time = base::TimeTicks::Now(); + bool merge_success = model_associator_->AssociateModels(); + RecordAssociationTime(base::TimeTicks::Now() - start_time); + if (!merge_success) { + StartFailed(ASSOCIATION_FAILED, FROM_HERE); + return false; + } + + sync_service_->ActivateDataType(this, change_processor_.get()); + state_ = RUNNING; + FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK, FROM_HERE); + return true; +} + +void FrontendDataTypeController::Stop() { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + // If Stop() is called while Start() is waiting for the datatype model to + // load, abort the start. + if (state_ == MODEL_STARTING) + FinishStart(ABORTED, FROM_HERE); + DCHECK(!start_callback_.get()); + + CleanupState(); + + if (change_processor_ != NULL) + sync_service_->DeactivateDataType(this, change_processor_.get()); + + if (model_associator_ != NULL) + model_associator_->DisassociateModels(); + + change_processor_.reset(); + model_associator_.reset(); + + state_ = NOT_RUNNING; +} + +void FrontendDataTypeController::CleanupState() { + // Do nothing by default. +} + +browser_sync::ModelSafeGroup FrontendDataTypeController::model_safe_group() + const { + return browser_sync::GROUP_UI; +} + +std::string FrontendDataTypeController::name() const { + // For logging only. + return syncable::ModelTypeToString(type()); +} + +DataTypeController::State FrontendDataTypeController::state() const { + return state_; +} + +void FrontendDataTypeController::OnUnrecoverableError( + const tracked_objects::Location& from_here, const std::string& message) { + // The ProfileSyncService will invoke our Stop() method in response to this. + RecordUnrecoverableError(from_here, message); + sync_service_->OnUnrecoverableError(from_here, message); +} + +void FrontendDataTypeController::FinishStart(StartResult result, + const tracked_objects::Location& location) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + start_callback_->Run(result, location); + start_callback_.reset(); +} + +void FrontendDataTypeController::StartFailed(StartResult result, + const tracked_objects::Location& location) { + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + CleanupState(); + model_associator_.reset(); + change_processor_.reset(); + state_ = NOT_RUNNING; + start_callback_->Run(result, location); + start_callback_.reset(); + RecordStartFailure(result); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/frontend_data_type_controller.h b/chrome/browser/sync/glue/frontend_data_type_controller.h new file mode 100644 index 0000000..d228bb9 --- /dev/null +++ b/chrome/browser/sync/glue/frontend_data_type_controller.h @@ -0,0 +1,113 @@ +// Copyright (c) 2011 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_GLUE_FRONTEND_DATA_TYPE_CONTROLLER_H__ +#define CHROME_BROWSER_SYNC_GLUE_FRONTEND_DATA_TYPE_CONTROLLER_H__ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/scoped_ptr.h" +#include "chrome/browser/sync/glue/data_type_controller.h" + +class Profile; +class ProfileSyncService; +class ProfileSyncFactory; + +namespace base { class TimeDelta; } +namespace browser_sync { + +class AssociatorInterface; +class ChangeProcessor; + +// Implementation for datatypes that reside on the frontend thread +// (UI thread). This is the same thread we perform initialization on, so we +// don't have to worry about thread safety. The main start/stop funtionality is +// implemented by default. +// Derived classes must implement (at least): +// syncable::ModelType type() const +// void CreateSyncComponents(); +// void RecordUnrecoverableError( +// const tracked_objects::Location& from_here, +// const std::string& message); +// void RecordAssociationTime(base::TimeDelta time); +// void RecordStartFailure(StartResult result); +class FrontendDataTypeController : public DataTypeController { + public: + FrontendDataTypeController( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service); + virtual ~FrontendDataTypeController(); + + // DataTypeController interface. + virtual void Start(StartCallback* start_callback); + + virtual void Stop(); + + virtual syncable::ModelType type() const = 0; + + virtual browser_sync::ModelSafeGroup model_safe_group() const; + + virtual std::string name() const; + + virtual State state() const; + + // UnrecoverableErrorHandler interface. + virtual void OnUnrecoverableError(const tracked_objects::Location& from_here, + const std::string& message); + protected: + // For testing only. + FrontendDataTypeController(); + + // Kick off any dependent services that need to be running before we can + // associate models. The default implementation is a no-op. + virtual bool StartModels(); + + // Build sync components and associate models. + virtual bool Associate(); + + // Perform any DataType controller specific state cleanup before stopping + // the datatype controller. The default implementation is a no-op. + virtual void CleanupState(); + + // Helper method to run the stashed start callback with a given result. + virtual void FinishStart(StartResult result, + const tracked_objects::Location& from_here); + + // Cleans up state and calls callback when start fails. + virtual void StartFailed(StartResult result, + const tracked_objects::Location& from_here); + + // Datatype specific creation of sync components. + virtual void CreateSyncComponents() = 0; + + // DataType specific histogram methods. Because histograms use static's, the + // specific datatype controllers must implement this themselves. + // Record unrecoverable errors. + virtual void RecordUnrecoverableError( + const tracked_objects::Location& from_here, + const std::string& message) = 0; + // Record association time. + virtual void RecordAssociationTime(base::TimeDelta time) = 0; + // Record causes of start failure. + virtual void RecordStartFailure(StartResult result) = 0; + + ProfileSyncFactory* const profile_sync_factory_; + Profile* const profile_; + ProfileSyncService* const sync_service_; + + State state_; + + scoped_ptr<StartCallback> start_callback_; + scoped_ptr<AssociatorInterface> model_associator_; + scoped_ptr<ChangeProcessor> change_processor_; + + DISALLOW_COPY_AND_ASSIGN(FrontendDataTypeController); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_FRONTEND_DATA_TYPE_CONTROLLER_H__ diff --git a/chrome/browser/sync/glue/frontend_data_type_controller_mock.cc b/chrome/browser/sync/glue/frontend_data_type_controller_mock.cc new file mode 100644 index 0000000..405d328 --- /dev/null +++ b/chrome/browser/sync/glue/frontend_data_type_controller_mock.cc @@ -0,0 +1,13 @@ +// Copyright (c) 2011 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/sync/glue/frontend_data_type_controller_mock.h" + +namespace browser_sync { + +FrontendDataTypeControllerMock::FrontendDataTypeControllerMock() {} + +FrontendDataTypeControllerMock::~FrontendDataTypeControllerMock() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/frontend_data_type_controller_mock.h b/chrome/browser/sync/glue/frontend_data_type_controller_mock.h new file mode 100644 index 0000000..0485b1f --- /dev/null +++ b/chrome/browser/sync/glue/frontend_data_type_controller_mock.h @@ -0,0 +1,47 @@ +// Copyright (c) 2011 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_GLUE_FRONTEND_DATA_TYPE_CONTROLLER_MOCK_H__ +#define CHROME_BROWSER_SYNC_GLUE_FRONTEND_DATA_TYPE_CONTROLLER_MOCK_H__ +#pragma once + +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace browser_sync { + +class FrontendDataTypeControllerMock : public FrontendDataTypeController { + public: + FrontendDataTypeControllerMock(); + virtual ~FrontendDataTypeControllerMock(); + + // DataTypeController mocks. + MOCK_METHOD1(Start, void(StartCallback* start_callback)); + MOCK_METHOD0(Stop, void()); + MOCK_METHOD0(enabled, bool()); + MOCK_CONST_METHOD0(type, syncable::ModelType()); + MOCK_CONST_METHOD0(name, std::string()); + MOCK_CONST_METHOD0(model_safe_group, browser_sync::ModelSafeGroup()); + MOCK_CONST_METHOD0(state, State()); + MOCK_METHOD2(OnUnrecoverableError, void(const tracked_objects::Location&, + const std::string&)); + + // FrontendDataTypeController mocks. + MOCK_METHOD0(StartModels, bool()); + MOCK_METHOD0(Associate, bool()); + MOCK_METHOD0(CleanupState, void()); + MOCK_METHOD2(FinishStart, void(StartResult result, + const tracked_objects::Location& from_here)); + MOCK_METHOD2(StartFailed, void(StartResult result, + const tracked_objects::Location& from_here)); + MOCK_METHOD0(CreateSyncComponents, void()); + MOCK_METHOD2(RecordUnrecoverableError, void(const tracked_objects::Location&, + const std::string&)); + MOCK_METHOD1(RecordAssociationTime, void(base::TimeDelta time)); + MOCK_METHOD1(RecordStartFailure, void(StartResult result)); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_FRONTEND_DATA_TYPE_CONTROLLER_MOCK_H__ diff --git a/chrome/browser/sync/glue/frontend_data_type_controller_unittest.cc b/chrome/browser/sync/glue/frontend_data_type_controller_unittest.cc new file mode 100644 index 0000000..c0c2290 --- /dev/null +++ b/chrome/browser/sync/glue/frontend_data_type_controller_unittest.cc @@ -0,0 +1,242 @@ +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" + +#include "base/callback.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/task.h" +#include "base/tracked_objects.h" +#include "chrome/browser/sync/glue/change_processor_mock.h" +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" +#include "chrome/browser/sync/glue/frontend_data_type_controller_mock.h" +#include "chrome/browser/sync/glue/model_associator_mock.h" +#include "chrome/browser/sync/profile_sync_factory_mock.h" +#include "chrome/browser/sync/profile_sync_service_mock.h" +#include "chrome/test/profile_mock.h" +#include "content/browser/browser_thread.h" + +using browser_sync::ChangeProcessorMock; +using browser_sync::DataTypeController; +using browser_sync::FrontendDataTypeController; +using browser_sync::FrontendDataTypeControllerMock; +using browser_sync::ModelAssociatorMock; +using testing::_; +using testing::DoAll; +using testing::InvokeWithoutArgs; +using testing::Return; +using testing::SetArgumentPointee; +using testing::StrictMock; + +class StartCallback { + public: + MOCK_METHOD2(Run, void(DataTypeController::StartResult result, + const tracked_objects::Location& from_here)); +}; + +class FrontendDataTypeControllerFake : public FrontendDataTypeController { + public: + FrontendDataTypeControllerFake( + ProfileSyncFactory* profile_sync_factory, + Profile* profile, + ProfileSyncService* sync_service, + FrontendDataTypeControllerMock* mock) + : FrontendDataTypeController(profile_sync_factory, + profile, + sync_service), + mock_(mock) {} + virtual syncable::ModelType type() const { return syncable::BOOKMARKS; } + + private: + virtual void CreateSyncComponents() { + ProfileSyncFactory::SyncComponents sync_components = + profile_sync_factory_-> + CreateBookmarkSyncComponents(sync_service_, this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); + } + + // We mock the following methods because their default implementations do + // nothing, but we still want to make sure they're called appropriately. + virtual bool StartModels() { + return mock_->StartModels(); + } + virtual void CleanupState() { + mock_->CleanupState(); + } + virtual void RecordUnrecoverableError( + const tracked_objects::Location& from_here, + const std::string& message) { + mock_->RecordUnrecoverableError(from_here, message); + } + virtual void RecordAssociationTime(base::TimeDelta time) { + mock_->RecordAssociationTime(time); + } + virtual void RecordStartFailure(DataTypeController::StartResult result) { + mock_->RecordStartFailure(result); + } + private: + FrontendDataTypeControllerMock* mock_; +}; + +class FrontendDataTypeControllerTest : public testing::Test { + public: + FrontendDataTypeControllerTest() + : ui_thread_(BrowserThread::UI, &message_loop_) {} + + virtual void SetUp() { + profile_sync_factory_.reset(new ProfileSyncFactoryMock()); + dtc_mock_ = new StrictMock<FrontendDataTypeControllerMock>(); + frontend_dtc_ = + new FrontendDataTypeControllerFake(profile_sync_factory_.get(), + &profile_, + &service_, + dtc_mock_.get()); + } + + protected: + void SetStartExpectations() { + EXPECT_CALL(*dtc_mock_, StartModels()).WillOnce(Return(true)); + model_associator_ = new ModelAssociatorMock(); + change_processor_ = new ChangeProcessorMock(); + EXPECT_CALL(*profile_sync_factory_, CreateBookmarkSyncComponents(_, _)). + WillOnce(Return(ProfileSyncFactory::SyncComponents(model_associator_, + change_processor_))); + } + + void SetAssociateExpectations() { + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillOnce(Return(true)); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillOnce(DoAll(SetArgumentPointee<0>(true), Return(true))); + EXPECT_CALL(*model_associator_, AssociateModels()). + WillOnce(Return(true)); + EXPECT_CALL(*dtc_mock_, RecordAssociationTime(_)); + } + + void SetActivateExpectations(DataTypeController::StartResult result) { + EXPECT_CALL(service_, ActivateDataType(_, _)); + EXPECT_CALL(start_callback_, Run(result,_)); + } + + void SetStopExpectations() { + EXPECT_CALL(*dtc_mock_, CleanupState()); + EXPECT_CALL(service_, DeactivateDataType(_, _)); + EXPECT_CALL(*model_associator_, DisassociateModels()); + } + + void SetStartFailExpectations(DataTypeController::StartResult result) { + EXPECT_CALL(*dtc_mock_, CleanupState()); + EXPECT_CALL(*dtc_mock_, RecordStartFailure(result)); + EXPECT_CALL(start_callback_, Run(result,_)); + } + + MessageLoopForUI message_loop_; + BrowserThread ui_thread_; + scoped_refptr<FrontendDataTypeControllerFake> frontend_dtc_; + scoped_ptr<ProfileSyncFactoryMock> profile_sync_factory_; + scoped_refptr<FrontendDataTypeControllerMock> dtc_mock_; + ProfileMock profile_; + ProfileSyncServiceMock service_; + ModelAssociatorMock* model_associator_; + ChangeProcessorMock* change_processor_; + StartCallback start_callback_; +}; + +TEST_F(FrontendDataTypeControllerTest, StartOk) { + SetStartExpectations(); + SetAssociateExpectations(); + SetActivateExpectations(DataTypeController::OK); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); + frontend_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::RUNNING, frontend_dtc_->state()); +} + +TEST_F(FrontendDataTypeControllerTest, StartFirstRun) { + SetStartExpectations(); + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillOnce(Return(true)); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillOnce(DoAll(SetArgumentPointee<0>(false), Return(true))); + EXPECT_CALL(*model_associator_, AssociateModels()). + WillOnce(Return(true)); + EXPECT_CALL(*dtc_mock_, RecordAssociationTime(_)); + SetActivateExpectations(DataTypeController::OK_FIRST_RUN); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); + frontend_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::RUNNING, frontend_dtc_->state()); +} + +TEST_F(FrontendDataTypeControllerTest, StartAssociationFailed) { + SetStartExpectations(); + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillOnce(Return(true)); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillOnce(DoAll(SetArgumentPointee<0>(true), Return(true))); + EXPECT_CALL(*model_associator_, AssociateModels()). + WillOnce(Return(false)); + EXPECT_CALL(*dtc_mock_, RecordAssociationTime(_)); + SetStartFailExpectations(DataTypeController::ASSOCIATION_FAILED); + // Set up association to fail with an association failed error. + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); + frontend_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); +} + +TEST_F(FrontendDataTypeControllerTest, + StartAssociationTriggersUnrecoverableError) { + SetStartExpectations(); + SetStartFailExpectations(DataTypeController::UNRECOVERABLE_ERROR); + // Set up association to fail with an unrecoverable error. + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); + EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). + WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); + frontend_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); +} + +TEST_F(FrontendDataTypeControllerTest, StartAssociationCryptoNotReady) { + SetStartExpectations(); + SetStartFailExpectations(DataTypeController::NEEDS_CRYPTO); + // Set up association to fail with a NEEDS_CRYPTO error. + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(false)); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); + frontend_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); +} + +TEST_F(FrontendDataTypeControllerTest, Stop) { + SetStartExpectations(); + SetAssociateExpectations(); + SetActivateExpectations(DataTypeController::OK); + SetStopExpectations(); + + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); + frontend_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::RUNNING, frontend_dtc_->state()); + frontend_dtc_->Stop(); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); +} + +TEST_F(FrontendDataTypeControllerTest, OnUnrecoverableError) { + SetStartExpectations(); + SetAssociateExpectations(); + SetActivateExpectations(DataTypeController::OK); + EXPECT_CALL(*dtc_mock_, RecordUnrecoverableError(_, "Test")); + EXPECT_CALL(service_, OnUnrecoverableError(_,_)). + WillOnce(InvokeWithoutArgs(frontend_dtc_.get(), + &FrontendDataTypeController::Stop)); + SetStopExpectations(); + + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); + frontend_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); + EXPECT_EQ(DataTypeController::RUNNING, frontend_dtc_->state()); + // This should cause frontend_dtc_->Stop() to be called. + frontend_dtc_->OnUnrecoverableError(FROM_HERE, "Test"); + EXPECT_EQ(DataTypeController::NOT_RUNNING, frontend_dtc_->state()); +} diff --git a/chrome/browser/sync/glue/history_model_worker.cc b/chrome/browser/sync/glue/history_model_worker.cc index e880476..a1abf13 100644 --- a/chrome/browser/sync/glue/history_model_worker.cc +++ b/chrome/browser/sync/glue/history_model_worker.cc @@ -1,11 +1,11 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/history_model_worker.h" +#include "base/memory/ref_counted.h" #include "base/message_loop.h" -#include "base/ref_counted.h" #include "base/task.h" #include "base/synchronization/waitable_event.h" #include "chrome/browser/history/history.h" diff --git a/chrome/browser/sync/glue/history_model_worker.h b/chrome/browser/sync/glue/history_model_worker.h index e449a93..f533acf 100644 --- a/chrome/browser/sync/glue/history_model_worker.h +++ b/chrome/browser/sync/glue/history_model_worker.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,7 +10,7 @@ #include "base/basictypes.h" #include "base/callback.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "content/browser/cancelable_request.h" class HistoryService; diff --git a/chrome/browser/sync/glue/http_bridge.cc b/chrome/browser/sync/glue/http_bridge.cc index 610de89..70498f7 100644 --- a/chrome/browser/sync/glue/http_bridge.cc +++ b/chrome/browser/sync/glue/http_bridge.cc @@ -11,6 +11,7 @@ #include "net/base/cookie_monster.h" #include "net/base/host_resolver.h" #include "net/base/load_flags.h" +#include "net/base/net_errors.h" #include "net/http/http_cache.h" #include "net/http/http_network_layer.h" #include "net/http/http_response_headers.h" @@ -22,7 +23,7 @@ namespace browser_sync { HttpBridge::RequestContextGetter::RequestContextGetter( - URLRequestContextGetter* baseline_context_getter) + net::URLRequestContextGetter* baseline_context_getter) : baseline_context_getter_(baseline_context_getter) { } @@ -49,7 +50,7 @@ HttpBridge::RequestContextGetter::GetIOMessageLoopProxy() const { } HttpBridgeFactory::HttpBridgeFactory( - URLRequestContextGetter* baseline_context_getter) { + net::URLRequestContextGetter* baseline_context_getter) { DCHECK(baseline_context_getter != NULL); request_context_getter_ = new HttpBridge::RequestContextGetter(baseline_context_getter); @@ -111,14 +112,17 @@ HttpBridge::RequestContext::~RequestContext() { delete http_transaction_factory(); } +HttpBridge::URLFetchState::URLFetchState() : url_poster(NULL), + aborted(false), + request_completed(false), + request_succeeded(false), + http_response_code(-1), + os_error_code(-1) {} +HttpBridge::URLFetchState::~URLFetchState() {} + HttpBridge::HttpBridge(HttpBridge::RequestContextGetter* context_getter) : context_getter_for_request_(context_getter), - url_poster_(NULL), created_on_loop_(MessageLoop::current()), - request_completed_(false), - request_succeeded_(false), - http_response_code_(-1), - os_error_code_(-1), http_post_completed_(false, false) { } @@ -127,7 +131,10 @@ HttpBridge::~HttpBridge() { void HttpBridge::SetUserAgent(const char* user_agent) { DCHECK_EQ(MessageLoop::current(), created_on_loop_); - DCHECK(!request_completed_); + if (DCHECK_IS_ON()) { + base::AutoLock lock(fetch_state_lock_); + DCHECK(!fetch_state_.request_completed); + } context_getter_for_request_->set_user_agent(user_agent); } @@ -139,7 +146,10 @@ void HttpBridge::SetExtraRequestHeaders(const char * headers) { void HttpBridge::SetURL(const char* url, int port) { DCHECK_EQ(MessageLoop::current(), created_on_loop_); - DCHECK(!request_completed_); + if (DCHECK_IS_ON()) { + base::AutoLock lock(fetch_state_lock_); + DCHECK(!fetch_state_.request_completed); + } DCHECK(url_for_request_.is_empty()) << "HttpBridge::SetURL called more than once?!"; GURL temp(url); @@ -154,7 +164,10 @@ void HttpBridge::SetPostPayload(const char* content_type, int content_length, const char* content) { DCHECK_EQ(MessageLoop::current(), created_on_loop_); - DCHECK(!request_completed_); + if (DCHECK_IS_ON()) { + base::AutoLock lock(fetch_state_lock_); + DCHECK(!fetch_state_.request_completed); + } DCHECK(content_type_.empty()) << "Bridge payload already set."; DCHECK_GE(content_length, 0) << "Content length < 0"; content_type_ = content_type; @@ -170,57 +183,88 @@ void HttpBridge::SetPostPayload(const char* content_type, bool HttpBridge::MakeSynchronousPost(int* os_error_code, int* response_code) { DCHECK_EQ(MessageLoop::current(), created_on_loop_); - DCHECK(!request_completed_); + if (DCHECK_IS_ON()) { + base::AutoLock lock(fetch_state_lock_); + DCHECK(!fetch_state_.request_completed); + } DCHECK(url_for_request_.is_valid()) << "Invalid URL for request"; DCHECK(!content_type_.empty()) << "Payload not set"; - BrowserThread::PostTask( - BrowserThread::IO, FROM_HERE, - NewRunnableMethod(this, &HttpBridge::CallMakeAsynchronousPost)); + if (!BrowserThread::PostTask( + BrowserThread::IO, FROM_HERE, + NewRunnableMethod(this, &HttpBridge::CallMakeAsynchronousPost))) { + // This usually happens when we're in a unit test. + LOG(WARNING) << "Could not post CallMakeAsynchronousPost task"; + return false; + } - if (!http_post_completed_.Wait()) // Block until network request completes. - NOTREACHED(); // See OnURLFetchComplete. + if (!http_post_completed_.Wait()) // Block until network request completes + NOTREACHED(); // or is aborted. See OnURLFetchComplete + // and Abort. - DCHECK(request_completed_); - *os_error_code = os_error_code_; - *response_code = http_response_code_; - return request_succeeded_; + base::AutoLock lock(fetch_state_lock_); + DCHECK(fetch_state_.request_completed || fetch_state_.aborted); + *os_error_code = fetch_state_.os_error_code; + *response_code = fetch_state_.http_response_code; + return fetch_state_.request_succeeded; } void HttpBridge::MakeAsynchronousPost() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); - DCHECK(!request_completed_); - - url_poster_ = new URLFetcher(url_for_request_, URLFetcher::POST, this); - url_poster_->set_request_context(context_getter_for_request_); - url_poster_->set_upload_data(content_type_, request_content_); - url_poster_->set_extra_request_headers(extra_headers_); - url_poster_->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES); - url_poster_->Start(); + base::AutoLock lock(fetch_state_lock_); + DCHECK(!fetch_state_.request_completed); + if (fetch_state_.aborted) + return; + + fetch_state_.url_poster = new URLFetcher(url_for_request_, + URLFetcher::POST, this); + fetch_state_.url_poster->set_request_context(context_getter_for_request_); + fetch_state_.url_poster->set_upload_data(content_type_, request_content_); + fetch_state_.url_poster->set_extra_request_headers(extra_headers_); + fetch_state_.url_poster->set_load_flags(net::LOAD_DO_NOT_SEND_COOKIES); + fetch_state_.url_poster->Start(); } int HttpBridge::GetResponseContentLength() const { DCHECK_EQ(MessageLoop::current(), created_on_loop_); - DCHECK(request_completed_); - return response_content_.size(); + base::AutoLock lock(fetch_state_lock_); + DCHECK(fetch_state_.request_completed); + return fetch_state_.response_content.size(); } const char* HttpBridge::GetResponseContent() const { DCHECK_EQ(MessageLoop::current(), created_on_loop_); - DCHECK(request_completed_); - return response_content_.data(); + base::AutoLock lock(fetch_state_lock_); + DCHECK(fetch_state_.request_completed); + return fetch_state_.response_content.data(); } const std::string HttpBridge::GetResponseHeaderValue( const std::string& name) const { DCHECK_EQ(MessageLoop::current(), created_on_loop_); - DCHECK(request_completed_); + base::AutoLock lock(fetch_state_lock_); + DCHECK(fetch_state_.request_completed); + std::string value; - response_headers_->EnumerateHeader(NULL, name, &value); + fetch_state_.response_headers->EnumerateHeader(NULL, name, &value); return value; } +void HttpBridge::Abort() { + base::AutoLock lock(fetch_state_lock_); + DCHECK(!fetch_state_.aborted); + if (fetch_state_.aborted || fetch_state_.request_completed) + return; + + fetch_state_.aborted = true; + BrowserThread::DeleteSoon(BrowserThread::IO, FROM_HERE, + fetch_state_.url_poster); + fetch_state_.url_poster = NULL; + fetch_state_.os_error_code = net::ERR_ABORTED; + http_post_completed_.Signal(); +} + void HttpBridge::OnURLFetchComplete(const URLFetcher *source, const GURL &url, const net::URLRequestStatus &status, @@ -228,20 +272,24 @@ void HttpBridge::OnURLFetchComplete(const URLFetcher *source, const ResponseCookies &cookies, const std::string &data) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO)); + base::AutoLock lock(fetch_state_lock_); + if (fetch_state_.aborted) + return; - request_completed_ = true; - request_succeeded_ = (net::URLRequestStatus::SUCCESS == status.status()); - http_response_code_ = response_code; - os_error_code_ = status.os_error(); + fetch_state_.request_completed = true; + fetch_state_.request_succeeded = + (net::URLRequestStatus::SUCCESS == status.status()); + fetch_state_.http_response_code = response_code; + fetch_state_.os_error_code = status.os_error(); - response_content_ = data; - response_headers_ = source->response_headers(); + fetch_state_.response_content = data; + fetch_state_.response_headers = source->response_headers(); // End of the line for url_poster_. It lives only on the IO loop. // We defer deletion because we're inside a callback from a component of the // URLFetcher, so it seems most natural / "polite" to let the stack unwind. - MessageLoop::current()->DeleteSoon(FROM_HERE, url_poster_); - url_poster_ = NULL; + MessageLoop::current()->DeleteSoon(FROM_HERE, fetch_state_.url_poster); + fetch_state_.url_poster = NULL; // Wake the blocked syncer thread in MakeSynchronousPost. // WARNING: DONT DO ANYTHING AFTER THIS CALL! |this| may be deleted! diff --git a/chrome/browser/sync/glue/http_bridge.h b/chrome/browser/sync/glue/http_bridge.h index 57c9f94..2ff3588 100644 --- a/chrome/browser/sync/glue/http_bridge.h +++ b/chrome/browser/sync/glue/http_bridge.h @@ -8,13 +8,18 @@ #include <string> -#include "base/ref_counted.h" +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/ref_counted.h" +#include "base/synchronization/lock.h" #include "base/synchronization/waitable_event.h" +#include "chrome/browser/sync/engine/http_post_provider_factory.h" +#include "chrome/browser/sync/engine/http_post_provider_interface.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/common/net/url_fetcher.h" -#include "chrome/common/net/url_request_context_getter.h" #include "googleurl/src/gurl.h" #include "net/url_request/url_request_context.h" +#include "net/url_request/url_request_context_getter.h" #include "testing/gtest/include/gtest/gtest_prod.h" class MessageLoop; @@ -35,12 +40,6 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, // A request context used for HTTP requests bridged from the sync backend. // A bridged RequestContext has a dedicated in-memory cookie store and does // not use a cache. Thus the same type can be used for incognito mode. - // TODO(timsteele): We subclass here instead of add a factory method on - // ChromeURLRequestContext because: - // 1) we want the ability to set_user_agent - // 2) avoids ifdefs for now - // 3) not sure we want to strictly follow settings for cookie policy, - // accept lang/charset, since changing these could break syncing. class RequestContext : public net::URLRequestContext { public: // |baseline_context| is used to obtain the accept-language, @@ -71,15 +70,15 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, }; // Lazy-getter for RequestContext objects. - class RequestContextGetter : public URLRequestContextGetter { + class RequestContextGetter : public net::URLRequestContextGetter { public: explicit RequestContextGetter( - URLRequestContextGetter* baseline_context_getter); + net::URLRequestContextGetter* baseline_context_getter); void set_user_agent(const std::string& ua) { user_agent_ = ua; } bool is_user_agent_set() const { return !user_agent_.empty(); } - // URLRequestContextGetter implementation. + // net::URLRequestContextGetter implementation. virtual net::URLRequestContext* GetURLRequestContext(); virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const; @@ -89,7 +88,7 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, // User agent to apply to the net::URLRequestContext. std::string user_agent_; - scoped_refptr<URLRequestContextGetter> baseline_context_getter_; + scoped_refptr<net::URLRequestContextGetter> baseline_context_getter_; // Lazily initialized by GetURLRequestContext(). scoped_refptr<RequestContext> context_; @@ -106,6 +105,7 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, virtual void SetPostPayload(const char* content_type, int content_length, const char* content); virtual bool MakeSynchronousPost(int* os_error_code, int* response_code); + virtual void Abort(); // WARNING: these response content methods are used to extract plain old data // and not null terminated strings, so you should make sure you have read @@ -125,7 +125,7 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, const std::string& data); #if defined(UNIT_TEST) - URLRequestContextGetter* GetRequestContextGetter() const { + net::URLRequestContextGetter* GetRequestContextGetter() const { return context_getter_for_request_; } #endif @@ -150,13 +150,6 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, // RequestContext definition for details. scoped_refptr<RequestContextGetter> context_getter_for_request_; - // Our hook into the network layer is a URLFetcher. USED ONLY ON THE IO LOOP, - // so we can block created_on_loop_ while the fetch is in progress. - // NOTE: This is not a scoped_ptr for a reason. It must be deleted on the same - // thread that created it, which isn't the same thread |this| gets deleted on. - // We must manually delete url_poster_ on the IO loop. - URLFetcher* url_poster_; - // The message loop of the thread we were created on. This is the thread that // will block on MakeSynchronousPost while the IO thread fetches data from // the network. @@ -171,39 +164,64 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, std::string content_type_; std::string request_content_; std::string extra_headers_; - scoped_refptr<net::HttpResponseHeaders> response_headers_; - - // Cached response data. - bool request_completed_; - bool request_succeeded_; - int http_response_code_; - int os_error_code_; - std::string response_content_; // A waitable event we use to provide blocking semantics to // MakeSynchronousPost. We block created_on_loop_ while the IO loop fetches // network request. base::WaitableEvent http_post_completed_; + struct URLFetchState { + URLFetchState(); + ~URLFetchState(); + // Our hook into the network layer is a URLFetcher. USED ONLY ON THE IO + // LOOP, so we can block created_on_loop_ while the fetch is in progress. + // NOTE: This is not a scoped_ptr for a reason. It must be deleted on the + // same thread that created it, which isn't the same thread |this| gets + // deleted on. We must manually delete url_poster_ on the IO loop. + URLFetcher* url_poster; + + // Used to support 'Abort' functionality. + bool aborted; + + // Cached response data. + bool request_completed; + bool request_succeeded; + int http_response_code; + int os_error_code; + std::string response_content; + scoped_refptr<net::HttpResponseHeaders> response_headers; + }; + + // This lock synchronizes use of state involved in the flow to fetch a URL + // using URLFetcher. Because we can Abort() from any thread, for example, + // this flow needs to be synchronized to gracefully clean up URLFetcher and + // return appropriate values in os_error_code. + mutable base::Lock fetch_state_lock_; + URLFetchState fetch_state_; + DISALLOW_COPY_AND_ASSIGN(HttpBridge); }; -class HttpBridgeFactory - : public sync_api::HttpPostProviderFactory { +class HttpBridgeFactory : public sync_api::HttpPostProviderFactory { public: - explicit HttpBridgeFactory(URLRequestContextGetter* baseline_context_getter); + explicit HttpBridgeFactory( + net::URLRequestContextGetter* baseline_context_getter); virtual ~HttpBridgeFactory(); - virtual sync_api::HttpPostProviderInterface* Create(); - virtual void Destroy(sync_api::HttpPostProviderInterface* http); + + // sync_api::HttpPostProviderFactory: + virtual sync_api::HttpPostProviderInterface* Create() OVERRIDE; + virtual void Destroy(sync_api::HttpPostProviderInterface* http) OVERRIDE; + private: // This request context is built on top of the baseline context and shares // common components. HttpBridge::RequestContextGetter* GetRequestContextGetter(); + scoped_refptr<HttpBridge::RequestContextGetter> request_context_getter_; + DISALLOW_COPY_AND_ASSIGN(HttpBridgeFactory); }; } // namespace browser_sync #endif // CHROME_BROWSER_SYNC_GLUE_HTTP_BRIDGE_H_ - diff --git a/chrome/browser/sync/glue/http_bridge_unittest.cc b/chrome/browser/sync/glue/http_bridge_unittest.cc index 4104d2f..995c53b 100644 --- a/chrome/browser/sync/glue/http_bridge_unittest.cc +++ b/chrome/browser/sync/glue/http_bridge_unittest.cc @@ -51,6 +51,10 @@ class HttpBridgeTest : public testing::Test { return bridge; } + static void Abort(HttpBridge* bridge) { + bridge->Abort(); + } + static void TestSameHttpNetworkSession(MessageLoop* main_message_loop, HttpBridgeTest* test) { scoped_refptr<HttpBridge> http_bridge(test->BuildBridge()); @@ -97,14 +101,19 @@ class DummyURLFetcher : public TestURLFetcher { // back with dummy response info. class ShuntedHttpBridge : public HttpBridge { public: - ShuntedHttpBridge(URLRequestContextGetter* baseline_context_getter, - HttpBridgeTest* test) + // If |never_finishes| is true, the simulated request never actually + // returns. + ShuntedHttpBridge(net::URLRequestContextGetter* baseline_context_getter, + HttpBridgeTest* test, bool never_finishes) : HttpBridge(new HttpBridge::RequestContextGetter( baseline_context_getter)), - test_(test) { } + test_(test), never_finishes_(never_finishes) { } protected: virtual void MakeAsynchronousPost() { ASSERT_TRUE(MessageLoop::current() == test_->io_thread_loop()); + if (never_finishes_) + return; + // We don't actually want to make a request for this test, so just callback // as if it completed. test_->io_thread_loop()->PostTask(FROM_HERE, @@ -125,6 +134,7 @@ class ShuntedHttpBridge : public HttpBridge { 200, cookies, response_content); } HttpBridgeTest* test_; + bool never_finishes_; }; TEST_F(HttpBridgeTest, TestUsesSameHttpNetworkSession) { @@ -139,10 +149,10 @@ TEST_F(HttpBridgeTest, TestUsesSameHttpNetworkSession) { // Test the HttpBridge without actually making any network requests. TEST_F(HttpBridgeTest, TestMakeSynchronousPostShunted) { - scoped_refptr<URLRequestContextGetter> ctx_getter( + scoped_refptr<net::URLRequestContextGetter> ctx_getter( new TestURLRequestContextGetter()); scoped_refptr<HttpBridge> http_bridge(new ShuntedHttpBridge( - ctx_getter, this)); + ctx_getter, this, false)); http_bridge->SetUserAgent("bob"); http_bridge->SetURL("http://www.google.com", 9999); http_bridge->SetPostPayload("text/plain", 2, " "); @@ -261,3 +271,40 @@ TEST_F(HttpBridgeTest, TestResponseHeader) { EXPECT_EQ(http_bridge->GetResponseHeaderValue("Content-type"), "text/html"); EXPECT_TRUE(http_bridge->GetResponseHeaderValue("invalid-header").empty()); } + +TEST_F(HttpBridgeTest, Abort) { + scoped_refptr<net::URLRequestContextGetter> ctx_getter( + new TestURLRequestContextGetter()); + scoped_refptr<ShuntedHttpBridge> http_bridge(new ShuntedHttpBridge( + ctx_getter, this, true)); + http_bridge->SetUserAgent("bob"); + http_bridge->SetURL("http://www.google.com", 9999); + http_bridge->SetPostPayload("text/plain", 2, " "); + + int os_error = 0; + int response_code = 0; + + BrowserThread::PostTask(BrowserThread::IO, FROM_HERE, NewRunnableFunction( + &HttpBridgeTest::Abort, http_bridge)); + bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code); + EXPECT_FALSE(success); + EXPECT_EQ(net::ERR_ABORTED, os_error); +} + +TEST_F(HttpBridgeTest, AbortLate) { + scoped_refptr<net::URLRequestContextGetter> ctx_getter( + new TestURLRequestContextGetter()); + scoped_refptr<ShuntedHttpBridge> http_bridge(new ShuntedHttpBridge( + ctx_getter, this, false)); + http_bridge->SetUserAgent("bob"); + http_bridge->SetURL("http://www.google.com", 9999); + http_bridge->SetPostPayload("text/plain", 2, " "); + + int os_error = 0; + int response_code = 0; + + bool success = http_bridge->MakeSynchronousPost(&os_error, &response_code); + ASSERT_TRUE(success); + http_bridge->Abort(); + // Ensures no double-free of URLFetcher, etc. +} diff --git a/chrome/browser/sync/glue/model_associator.h b/chrome/browser/sync/glue/model_associator.h index 7d8f985..eaa2a45 100644 --- a/chrome/browser/sync/glue/model_associator.h +++ b/chrome/browser/sync/glue/model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -43,6 +43,12 @@ class AssociatorInterface { // method is only implemented for model associators that are invoked // off the main thread. virtual void AbortAssociation() = 0; + + // Returns whether the datatype is ready for encryption/decryption if the + // sync service requires it. + // TODO(zea): This should be implemented automatically for each datatype, see + // http://crbug.com/76232. + virtual bool CryptoReadyIfNecessary() = 0; }; // In addition to the generic methods, association can refer to operations diff --git a/chrome/browser/sync/glue/model_associator_mock.h b/chrome/browser/sync/glue/model_associator_mock.h index a949932..51cb7c4 100644 --- a/chrome/browser/sync/glue/model_associator_mock.h +++ b/chrome/browser/sync/glue/model_associator_mock.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -20,6 +20,7 @@ class ModelAssociatorMock : public AssociatorInterface { MOCK_METHOD0(DisassociateModels, bool()); MOCK_METHOD1(SyncModelHasUserCreatedNodes, bool(bool* has_nodes)); MOCK_METHOD0(AbortAssociation, void()); + MOCK_METHOD0(CryptoReadyIfNecessary, bool()); }; } // namespace browser_sync diff --git a/chrome/browser/sync/glue/password_change_processor.cc b/chrome/browser/sync/glue/password_change_processor.cc index 30d77fa..de47d0a 100644 --- a/chrome/browser/sync/glue/password_change_processor.cc +++ b/chrome/browser/sync/glue/password_change_processor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -14,9 +14,9 @@ #include "chrome/browser/sync/glue/password_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_source.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_details.h" +#include "content/common/notification_source.h" +#include "content/common/notification_type.h" #include "webkit/glue/password_form.h" namespace browser_sync { @@ -130,7 +130,6 @@ void PasswordChangeProcessor::ApplyChangesFromSyncModel( DCHECK(expected_loop_ == MessageLoop::current()); if (!running()) return; - StopObserving(); sync_api::ReadNode password_root(trans); if (!password_root.InitByTagLookup(kPasswordTag)) { @@ -139,9 +138,8 @@ void PasswordChangeProcessor::ApplyChangesFromSyncModel( return; } - PasswordModelAssociator::PasswordVector new_passwords; - PasswordModelAssociator::PasswordVector updated_passwords; - PasswordModelAssociator::PasswordVector deleted_passwords; + DCHECK(deleted_passwords_.empty() && new_passwords_.empty() && + updated_passwords_.empty()); for (int i = 0; i < change_count; ++i) { if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == @@ -154,7 +152,7 @@ void PasswordChangeProcessor::ApplyChangesFromSyncModel( const sync_pb::PasswordSpecificsData& password = extra->unencrypted(); webkit_glue::PasswordForm form; PasswordModelAssociator::CopyPassword(password, &form); - deleted_passwords.push_back(form); + deleted_passwords_.push_back(form); model_associator_->Disassociate(changes[i].id); continue; } @@ -178,21 +176,32 @@ void PasswordChangeProcessor::ApplyChangesFromSyncModel( if (sync_api::SyncManager::ChangeRecord::ACTION_ADD == changes[i].action) { std::string tag(PasswordModelAssociator::MakeTag(password)); model_associator_->Associate(&tag, sync_node.GetId()); - new_passwords.push_back(password); + new_passwords_.push_back(password); } else { DCHECK(sync_api::SyncManager::ChangeRecord::ACTION_UPDATE == changes[i].action); - updated_passwords.push_back(password); + updated_passwords_.push_back(password); } } +} + +void PasswordChangeProcessor::CommitChangesFromSyncModel() { + DCHECK(expected_loop_ == MessageLoop::current()); + if (!running()) + return; + StopObserving(); - if (!model_associator_->WriteToPasswordStore(&new_passwords, - &updated_passwords, - &deleted_passwords)) { + if (!model_associator_->WriteToPasswordStore(&new_passwords_, + &updated_passwords_, + &deleted_passwords_)) { error_handler()->OnUnrecoverableError(FROM_HERE, "Error writing passwords"); return; } + deleted_passwords_.clear(); + new_passwords_.clear(); + updated_passwords_.clear(); + StartObserving(); } diff --git a/chrome/browser/sync/glue/password_change_processor.h b/chrome/browser/sync/glue/password_change_processor.h index 99955bf..155a1b3 100644 --- a/chrome/browser/sync/glue/password_change_processor.h +++ b/chrome/browser/sync/glue/password_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,10 +9,12 @@ #include "chrome/browser/sync/glue/change_processor.h" #include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "chrome/browser/sync/glue/password_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_type.h" class PasswordStore; class MessageLoop; @@ -20,7 +22,6 @@ class NotificationService; namespace browser_sync { -class PasswordModelAssociator; class UnrecoverableErrorHandler; // This class is responsible for taking changes from the password backend and @@ -39,17 +40,23 @@ class PasswordChangeProcessor : public ChangeProcessor, // Passwords -> sync_api model change application. virtual void Observe(NotificationType type, const NotificationSource& source, - const NotificationDetails& details); + const NotificationDetails& details) OVERRIDE; // sync_api model -> WebDataService change application. virtual void ApplyChangesFromSyncModel( const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, - int change_count); + int change_count) OVERRIDE; + + // Commit changes buffered during ApplyChanges. We must commit them to the + // password store only after the sync_api transaction is released, else there + // is risk of deadlock due to the password store posting tasks to the UI + // thread (http://crbug.com/70658). + virtual void CommitChangesFromSyncModel() OVERRIDE; protected: - virtual void StartImpl(Profile* profile); - virtual void StopImpl(); + virtual void StartImpl(Profile* profile) OVERRIDE; + virtual void StopImpl() OVERRIDE; private: void StartObserving(); @@ -63,6 +70,12 @@ class PasswordChangeProcessor : public ChangeProcessor, // holding a reference. PasswordStore* password_store_; + // Buffers used between ApplyChangesFromSyncModel and + // CommitChangesFromSyncModel. + PasswordModelAssociator::PasswordVector new_passwords_; + PasswordModelAssociator::PasswordVector updated_passwords_; + PasswordModelAssociator::PasswordVector deleted_passwords_; + NotificationRegistrar notification_registrar_; bool observing_; diff --git a/chrome/browser/sync/glue/password_data_type_controller.cc b/chrome/browser/sync/glue/password_data_type_controller.cc index 77c09da..2589861 100644 --- a/chrome/browser/sync/glue/password_data_type_controller.cc +++ b/chrome/browser/sync/glue/password_data_type_controller.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -25,7 +25,10 @@ PasswordDataTypeController::PasswordDataTypeController( : profile_sync_factory_(profile_sync_factory), profile_(profile), sync_service_(sync_service), - state_(NOT_RUNNING) { + state_(NOT_RUNNING), + abort_association_(false), + abort_association_complete_(false, false), + datatype_stopped_(false, false) { DCHECK(profile_sync_factory); DCHECK(profile); DCHECK(sync_service); @@ -38,7 +41,7 @@ void PasswordDataTypeController::Start(StartCallback* start_callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(start_callback); if (state_ != NOT_RUNNING) { - start_callback->Run(BUSY); + start_callback->Run(BUSY, FROM_HERE); delete start_callback; return; } @@ -46,71 +49,104 @@ void PasswordDataTypeController::Start(StartCallback* start_callback) { password_store_ = profile_->GetPasswordStore(Profile::EXPLICIT_ACCESS); if (!password_store_.get()) { LOG(ERROR) << "PasswordStore not initialized, password datatype controller" - << " aborting."; + << " aborting."; state_ = NOT_RUNNING; - start_callback->Run(ABORTED); - delete start_callback; - return; - } - - if (!sync_service_->IsCryptographerReady()) { - start_callback->Run(NEEDS_CRYPTO); + start_callback->Run(ABORTED, FROM_HERE); delete start_callback; return; } start_callback_.reset(start_callback); + abort_association_ = false; set_state(ASSOCIATING); password_store_->ScheduleTask( NewRunnableMethod(this, &PasswordDataTypeController::StartImpl)); } +// TODO(sync): Blocking the UI thread at shutdown is bad. If we had a way of +// distinguishing chrome shutdown from sync shutdown, we should be able to avoid +// this (http://crbug.com/55662). Further, all this functionality should be +// abstracted to a higher layer, where we could ensure all datatypes are doing +// the same thing (http://crbug.com/76232). void PasswordDataTypeController::Stop() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + // If Stop() is called while Start() is waiting for association to + // complete, we need to abort the association and wait for the PASSWORD + // thread to finish the StartImpl() task. + if (state_ == ASSOCIATING) { + { + base::AutoLock lock(abort_association_lock_); + abort_association_ = true; + if (model_associator_.get()) + model_associator_->AbortAssociation(); + } + // Wait for the model association to abort. + abort_association_complete_.Wait(); + StartDoneImpl(ABORTED, STOPPING); + } + + // If Stop() is called while Start() is waiting for another service to load, + // abort the start. + if (state_ == MODEL_STARTING) + StartDoneImpl(ABORTED, STOPPING); + + DCHECK(!start_callback_.get()); + if (change_processor_ != NULL) sync_service_->DeactivateDataType(this, change_processor_.get()); - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - set_state(NOT_RUNNING); DCHECK(password_store_.get()); password_store_->ScheduleTask( NewRunnableMethod(this, &PasswordDataTypeController::StopImpl)); + datatype_stopped_.Wait(); } bool PasswordDataTypeController::enabled() { return true; } -syncable::ModelType PasswordDataTypeController::type() { +syncable::ModelType PasswordDataTypeController::type() const { return syncable::PASSWORDS; } -browser_sync::ModelSafeGroup PasswordDataTypeController::model_safe_group() { +browser_sync::ModelSafeGroup PasswordDataTypeController::model_safe_group() + const { return browser_sync::GROUP_PASSWORD; } -const char* PasswordDataTypeController::name() const { +std::string PasswordDataTypeController::name() const { // For logging only. return "password"; } -DataTypeController::State PasswordDataTypeController::state() { +DataTypeController::State PasswordDataTypeController::state() const { return state_; } void PasswordDataTypeController::StartImpl() { // No additional services need to be started before we can proceed // with model association. - ProfileSyncFactory::SyncComponents sync_components = - profile_sync_factory_->CreatePasswordSyncComponents( - sync_service_, - password_store_.get(), - this); - model_associator_.reset(sync_components.model_associator); - change_processor_.reset(sync_components.change_processor); + { + base::AutoLock lock(abort_association_lock_); + if (abort_association_) { + abort_association_complete_.Signal(); + return; + } + ProfileSyncFactory::SyncComponents sync_components = + profile_sync_factory_->CreatePasswordSyncComponents( + sync_service_, + password_store_.get(), + this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); + } + + if (!model_associator_->CryptoReadyIfNecessary()) { + StartFailed(NEEDS_CRYPTO); + return; + } bool sync_has_nodes = false; if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { @@ -134,12 +170,16 @@ void PasswordDataTypeController::StartImpl() { void PasswordDataTypeController::StartDone( DataTypeController::StartResult result, DataTypeController::State new_state) { - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - NewRunnableMethod( - this, - &PasswordDataTypeController::StartDoneImpl, - result, - new_state)); + abort_association_complete_.Signal(); + base::AutoLock lock(abort_association_lock_); + if (!abort_association_) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, + &PasswordDataTypeController::StartDoneImpl, + result, + new_state)); + } } void PasswordDataTypeController::StartDoneImpl( @@ -147,15 +187,18 @@ void PasswordDataTypeController::StartDoneImpl( DataTypeController::State new_state) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); set_state(new_state); - start_callback_->Run(result); + start_callback_->Run(result, FROM_HERE); start_callback_.reset(); } void PasswordDataTypeController::StopImpl() { + if (model_associator_ != NULL) + model_associator_->DisassociateModels(); + change_processor_.reset(); model_associator_.reset(); - state_ = NOT_RUNNING; + datatype_stopped_.Signal(); } void PasswordDataTypeController::StartFailed(StartResult result) { diff --git a/chrome/browser/sync/glue/password_data_type_controller.h b/chrome/browser/sync/glue/password_data_type_controller.h index 56fb55b..1a3a9e4 100644 --- a/chrome/browser/sync/glue/password_data_type_controller.h +++ b/chrome/browser/sync/glue/password_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,7 +9,8 @@ #include <string> #include "base/basictypes.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/glue/data_type_controller.h" @@ -40,13 +41,13 @@ class PasswordDataTypeController : public DataTypeController { virtual bool enabled(); - virtual syncable::ModelType type(); + virtual syncable::ModelType type() const; - virtual browser_sync::ModelSafeGroup model_safe_group(); + virtual browser_sync::ModelSafeGroup model_safe_group() const; - virtual const char* name() const; + virtual std::string name() const; - virtual State state(); + virtual State state() const; // UnrecoverableHandler implementation virtual void OnUnrecoverableError(const tracked_objects::Location& from_here, @@ -76,6 +77,14 @@ class PasswordDataTypeController : public DataTypeController { scoped_ptr<StartCallback> start_callback_; scoped_refptr<PasswordStore> password_store_; + base::Lock abort_association_lock_; + bool abort_association_; + base::WaitableEvent abort_association_complete_; + + // Barrier to ensure that the datatype has been stopped on the DB thread + // from the UI thread. + base::WaitableEvent datatype_stopped_; + DISALLOW_COPY_AND_ASSIGN(PasswordDataTypeController); }; diff --git a/chrome/browser/sync/glue/password_model_associator.cc b/chrome/browser/sync/glue/password_model_associator.cc index 3ed5946..baaffde 100644 --- a/chrome/browser/sync/glue/password_model_associator.cc +++ b/chrome/browser/sync/glue/password_model_associator.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -45,14 +45,9 @@ bool PasswordModelAssociator::AssociateModels() { abort_association_pending_ = false; } - sync_api::WriteTransaction trans(sync_service_->GetUserShare()); - sync_api::ReadNode password_root(&trans); - if (!password_root.InitByTagLookup(kPasswordTag)) { - LOG(ERROR) << "Server did not create the top-level password node. We " - << "might be running against an out-of-date server."; - return false; - } - + // We must not be holding a transaction when we interact with the password + // store, as it can post tasks to the UI thread which can itself be blocked + // on our transaction, resulting in deadlock. (http://crbug.com/70658) std::vector<webkit_glue::PasswordForm*> passwords; if (!password_store_->FillAutofillableLogins(&passwords) || !password_store_->FillBlacklistLogins(&passwords)) { @@ -64,76 +59,89 @@ bool PasswordModelAssociator::AssociateModels() { std::set<std::string> current_passwords; PasswordVector new_passwords; PasswordVector updated_passwords; - - for (std::vector<webkit_glue::PasswordForm*>::iterator ix = passwords.begin(); - ix != passwords.end(); ++ix) { - if (IsAbortPending()) + { + sync_api::WriteTransaction trans(sync_service_->GetUserShare()); + sync_api::ReadNode password_root(&trans); + if (!password_root.InitByTagLookup(kPasswordTag)) { + LOG(ERROR) << "Server did not create the top-level password node. We " + << "might be running against an out-of-date server."; return false; - std::string tag = MakeTag(**ix); - - sync_api::ReadNode node(&trans); - if (node.InitByClientTagLookup(syncable::PASSWORDS, tag)) { - const sync_pb::PasswordSpecificsData& password = - node.GetPasswordSpecifics(); - DCHECK_EQ(tag, MakeTag(password)); + } - webkit_glue::PasswordForm new_password; + for (std::vector<webkit_glue::PasswordForm*>::iterator ix = + passwords.begin(); + ix != passwords.end(); ++ix) { + if (IsAbortPending()) + return false; + std::string tag = MakeTag(**ix); + + sync_api::ReadNode node(&trans); + if (node.InitByClientTagLookup(syncable::PASSWORDS, tag)) { + const sync_pb::PasswordSpecificsData& password = + node.GetPasswordSpecifics(); + DCHECK_EQ(tag, MakeTag(password)); + + webkit_glue::PasswordForm new_password; + + if (MergePasswords(password, **ix, &new_password)) { + sync_api::WriteNode write_node(&trans); + if (!write_node.InitByClientTagLookup(syncable::PASSWORDS, tag)) { + STLDeleteElements(&passwords); + LOG(ERROR) << "Failed to edit password sync node."; + return false; + } + WriteToSyncNode(new_password, &write_node); + updated_passwords.push_back(new_password); + } - if (MergePasswords(password, **ix, &new_password)) { - sync_api::WriteNode write_node(&trans); - if (!write_node.InitByClientTagLookup(syncable::PASSWORDS, tag)) { + Associate(&tag, node.GetId()); + } else { + sync_api::WriteNode node(&trans); + if (!node.InitUniqueByCreation(syncable::PASSWORDS, + password_root, tag)) { STLDeleteElements(&passwords); - LOG(ERROR) << "Failed to edit password sync node."; + LOG(ERROR) << "Failed to create password sync node."; return false; } - WriteToSyncNode(new_password, &write_node); - updated_passwords.push_back(new_password); - } - Associate(&tag, node.GetId()); - } else { - sync_api::WriteNode node(&trans); - if (!node.InitUniqueByCreation(syncable::PASSWORDS, - password_root, tag)) { - STLDeleteElements(&passwords); - LOG(ERROR) << "Failed to create password sync node."; - return false; - } + WriteToSyncNode(**ix, &node); - WriteToSyncNode(**ix, &node); + Associate(&tag, node.GetId()); + } - Associate(&tag, node.GetId()); + current_passwords.insert(tag); } - current_passwords.insert(tag); - } + STLDeleteElements(&passwords); - STLDeleteElements(&passwords); + int64 sync_child_id = password_root.GetFirstChildId(); + while (sync_child_id != sync_api::kInvalidId) { + sync_api::ReadNode sync_child_node(&trans); + if (!sync_child_node.InitByIdLookup(sync_child_id)) { + LOG(ERROR) << "Failed to fetch child node."; + return false; + } + const sync_pb::PasswordSpecificsData& password = + sync_child_node.GetPasswordSpecifics(); + std::string tag = MakeTag(password); - int64 sync_child_id = password_root.GetFirstChildId(); - while (sync_child_id != sync_api::kInvalidId) { - sync_api::ReadNode sync_child_node(&trans); - if (!sync_child_node.InitByIdLookup(sync_child_id)) { - LOG(ERROR) << "Failed to fetch child node."; - return false; - } - const sync_pb::PasswordSpecificsData& password = - sync_child_node.GetPasswordSpecifics(); - std::string tag = MakeTag(password); - - // The password only exists on the server. Add it to the local - // model. - if (current_passwords.find(tag) == current_passwords.end()) { - webkit_glue::PasswordForm new_password; - - CopyPassword(password, &new_password); - Associate(&tag, sync_child_node.GetId()); - new_passwords.push_back(new_password); - } + // The password only exists on the server. Add it to the local + // model. + if (current_passwords.find(tag) == current_passwords.end()) { + webkit_glue::PasswordForm new_password; - sync_child_id = sync_child_node.GetSuccessorId(); + CopyPassword(password, &new_password); + Associate(&tag, sync_child_node.GetId()); + new_passwords.push_back(new_password); + } + + sync_child_id = sync_child_node.GetSuccessorId(); + } } + // We must not be holding a transaction when we interact with the password + // store, as it can post tasks to the UI thread which can itself be blocked + // on our transaction, resulting in deadlock. (http://crbug.com/70658) if (!WriteToPasswordStore(&new_passwords, &updated_passwords, NULL)) { LOG(ERROR) << "Failed to write passwords."; return false; @@ -196,6 +204,13 @@ void PasswordModelAssociator::AbortAssociation() { abort_association_pending_ = true; } +bool PasswordModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + // We always encrypt passwords, so no need to check if encryption is enabled. + return sync_service_->IsCryptographerReady(&trans); +} + const std::string* PasswordModelAssociator::GetChromeNodeFromSyncId( int64 sync_id) { return NULL; @@ -271,6 +286,12 @@ bool PasswordModelAssociator::WriteToPasswordStore( password_store_->RemoveLoginImpl(*password); } } + + if (new_passwords || updated_passwords || deleted_passwords) { + // We have to notify password store observers of the change by hand since + // we use internal password store interfaces to make changes synchronously. + password_store_->PostNotifyLoginsChanged(); + } return true; } diff --git a/chrome/browser/sync/glue/password_model_associator.h b/chrome/browser/sync/glue/password_model_associator.h index 449bde7..0b3bac5 100644 --- a/chrome/browser/sync/glue/password_model_associator.h +++ b/chrome/browser/sync/glue/password_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -71,6 +71,9 @@ class PasswordModelAssociator // See ModelAssociator interface. virtual void AbortAssociation(); + // See ModelAssociator interface. + virtual bool CryptoReadyIfNecessary(); + // Not implemented. virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id); diff --git a/chrome/browser/sync/glue/password_model_worker.cc b/chrome/browser/sync/glue/password_model_worker.cc index 6132a03..1c208f5 100644 --- a/chrome/browser/sync/glue/password_model_worker.cc +++ b/chrome/browser/sync/glue/password_model_worker.cc @@ -1,11 +1,11 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/password_model_worker.h" #include "base/callback.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "base/task.h" #include "base/synchronization/waitable_event.h" #include "chrome/browser/password_manager/password_store.h" diff --git a/chrome/browser/sync/glue/password_model_worker.h b/chrome/browser/sync/glue/password_model_worker.h index 7ef537a..6c3f4b0 100644 --- a/chrome/browser/sync/glue/password_model_worker.h +++ b/chrome/browser/sync/glue/password_model_worker.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,7 +10,7 @@ #include "base/basictypes.h" #include "base/callback.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" class PasswordStore; diff --git a/chrome/browser/sync/glue/preference_change_processor.cc b/chrome/browser/sync/glue/preference_change_processor.cc index b8f0d20..6a6840b 100644 --- a/chrome/browser/sync/glue/preference_change_processor.cc +++ b/chrome/browser/sync/glue/preference_change_processor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -14,11 +14,11 @@ #include "chrome/browser/sync/glue/preference_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" -#include "chrome/common/json_value_serializer.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_source.h" #include "chrome/common/pref_names.h" #include "content/browser/browser_thread.h" +#include "content/common/json_value_serializer.h" +#include "content/common/notification_details.h" +#include "content/common/notification_source.h" namespace browser_sync { @@ -143,7 +143,7 @@ void PreferenceChangeProcessor::ApplyChangesFromSyncModel( // It is possible that we may receive a change to a preference we // do not want to sync. For example if the user is syncing a Mac // client and a Windows client, the Windows client does not - // support kShowPageOptionsButtons. Ignore updates from these + // support kConfirmToQuitEnabled. Ignore updates from these // preferences. const char* pref_name = name.c_str(); if (model_associator_->synced_preferences().count(pref_name) == 0) diff --git a/chrome/browser/sync/glue/preference_change_processor.h b/chrome/browser/sync/glue/preference_change_processor.h index 1ef88da..8111775 100644 --- a/chrome/browser/sync/glue/preference_change_processor.h +++ b/chrome/browser/sync/glue/preference_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -13,7 +13,7 @@ #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/change_processor.h" #include "chrome/browser/sync/glue/sync_backend_host.h" -#include "chrome/common/notification_observer.h" +#include "content/common/notification_observer.h" namespace browser_sync { diff --git a/chrome/browser/sync/glue/preference_data_type_controller.cc b/chrome/browser/sync/glue/preference_data_type_controller.cc index f1ddd51..3331bd3 100644 --- a/chrome/browser/sync/glue/preference_data_type_controller.cc +++ b/chrome/browser/sync/glue/preference_data_type_controller.cc @@ -1,132 +1,47 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/preference_data_type_controller.h" -#include "base/logging.h" #include "base/metrics/histogram.h" -#include "base/time.h" -#include "chrome/browser/sync/glue/preference_change_processor.h" -#include "chrome/browser/sync/glue/preference_model_associator.h" -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" -#include "chrome/browser/sync/unrecoverable_error_handler.h" -#include "content/browser/browser_thread.h" namespace browser_sync { PreferenceDataTypeController::PreferenceDataTypeController( ProfileSyncFactory* profile_sync_factory, + Profile* profile, ProfileSyncService* sync_service) - : profile_sync_factory_(profile_sync_factory), - sync_service_(sync_service), - state_(NOT_RUNNING) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(profile_sync_factory); - DCHECK(sync_service); + : FrontendDataTypeController(profile_sync_factory, + profile, + sync_service) { } -PreferenceDataTypeController::~PreferenceDataTypeController() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); -} - -void PreferenceDataTypeController::Start(StartCallback* start_callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(start_callback); - if (state_ != NOT_RUNNING) { - start_callback->Run(BUSY); - delete start_callback; - return; - } - - start_callback_.reset(start_callback); - - // No additional services need to be started before we can proceed - // with model association. - ProfileSyncFactory::SyncComponents sync_components = - profile_sync_factory_->CreatePreferenceSyncComponents(sync_service_, - this); - model_associator_.reset(sync_components.model_associator); - change_processor_.reset(sync_components.change_processor); - - bool sync_has_nodes = false; - if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { - StartFailed(UNRECOVERABLE_ERROR); - return; - } - - base::TimeTicks start_time = base::TimeTicks::Now(); - bool merge_success = model_associator_->AssociateModels(); - UMA_HISTOGRAM_TIMES("Sync.PreferenceAssociationTime", - base::TimeTicks::Now() - start_time); - if (!merge_success) { - StartFailed(ASSOCIATION_FAILED); - return; - } - - sync_service_->ActivateDataType(this, change_processor_.get()); - state_ = RUNNING; - FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK); -} - -void PreferenceDataTypeController::Stop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (change_processor_ != NULL) - sync_service_->DeactivateDataType(this, change_processor_.get()); +PreferenceDataTypeController::~PreferenceDataTypeController() {} - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - - change_processor_.reset(); - model_associator_.reset(); - start_callback_.reset(); - - state_ = NOT_RUNNING; -} - -bool PreferenceDataTypeController::enabled() { - return true; -} - -syncable::ModelType PreferenceDataTypeController::type() { +syncable::ModelType PreferenceDataTypeController::type() const { return syncable::PREFERENCES; } -browser_sync::ModelSafeGroup PreferenceDataTypeController::model_safe_group() { - return browser_sync::GROUP_UI; -} - -const char* PreferenceDataTypeController::name() const { - // For logging only. - return "preference"; -} - -DataTypeController::State PreferenceDataTypeController::state() { - return state_; +void PreferenceDataTypeController::CreateSyncComponents() { + ProfileSyncFactory::SyncComponents sync_components = profile_sync_factory_-> + CreatePreferenceSyncComponents(sync_service_, this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); } -void PreferenceDataTypeController::OnUnrecoverableError( +void PreferenceDataTypeController::RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); UMA_HISTOGRAM_COUNTS("Sync.PreferenceRunFailures", 1); - sync_service_->OnUnrecoverableError(from_here, message); } -void PreferenceDataTypeController::FinishStart(StartResult result) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - start_callback_->Run(result); - start_callback_.reset(); +void PreferenceDataTypeController::RecordAssociationTime(base::TimeDelta time) { + UMA_HISTOGRAM_TIMES("Sync.PreferenceAssociationTime", time); } -void PreferenceDataTypeController::StartFailed(StartResult result) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - model_associator_.reset(); - change_processor_.reset(); - start_callback_->Run(result); - start_callback_.reset(); +void PreferenceDataTypeController::RecordStartFailure(StartResult result) { UMA_HISTOGRAM_ENUMERATION("Sync.PreferenceStartFailures", result, MAX_START_RESULT); diff --git a/chrome/browser/sync/glue/preference_data_type_controller.h b/chrome/browser/sync/glue/preference_data_type_controller.h index 4fc177d..3f375b1 100644 --- a/chrome/browser/sync/glue/preference_data_type_controller.h +++ b/chrome/browser/sync/glue/preference_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,58 +6,31 @@ #define CHROME_BROWSER_SYNC_GLUE_PREFERENCE_DATA_TYPE_CONTROLLER_H__ #pragma once -#include "base/basictypes.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/glue/data_type_controller.h" +#include <string> -class ProfileSyncFactory; -class ProfileSyncService; +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" namespace browser_sync { -class AssociatorInterface; -class ChangeProcessor; - -class PreferenceDataTypeController : public DataTypeController { +class PreferenceDataTypeController : public FrontendDataTypeController { public: PreferenceDataTypeController( ProfileSyncFactory* profile_sync_factory, + Profile* profile, ProfileSyncService* sync_service); virtual ~PreferenceDataTypeController(); - virtual void Start(StartCallback* start_callback); - - virtual void Stop(); - - virtual bool enabled(); - - virtual syncable::ModelType type(); - - virtual browser_sync::ModelSafeGroup model_safe_group(); - - virtual const char* name() const; - - virtual State state(); - - // UnrecoverableErrorHandler interface. - virtual void OnUnrecoverableError(const tracked_objects::Location& from_here, - const std::string& message); + // FrontendDataTypeController implementation. + virtual syncable::ModelType type() const; private: - // Helper method to run the stashed start callback with a given result. - void FinishStart(StartResult result); - - // Cleans up state and calls callback when start fails. - void StartFailed(StartResult result); - - ProfileSyncFactory* profile_sync_factory_; - ProfileSyncService* sync_service_; - - State state_; - - scoped_ptr<StartCallback> start_callback_; - scoped_ptr<AssociatorInterface> model_associator_; - scoped_ptr<ChangeProcessor> change_processor_; + // FrontendDataTypeController implementations. + virtual void CreateSyncComponents(); + virtual void RecordUnrecoverableError( + const tracked_objects::Location& from_here, + const std::string& message); + virtual void RecordAssociationTime(base::TimeDelta time); + virtual void RecordStartFailure(StartResult result); DISALLOW_COPY_AND_ASSIGN(PreferenceDataTypeController); }; diff --git a/chrome/browser/sync/glue/preference_data_type_controller_unittest.cc b/chrome/browser/sync/glue/preference_data_type_controller_unittest.cc index 7103cbf..4df8b3f 100644 --- a/chrome/browser/sync/glue/preference_data_type_controller_unittest.cc +++ b/chrome/browser/sync/glue/preference_data_type_controller_unittest.cc @@ -1,12 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" #include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/task.h" #include "base/tracked_objects.h" #include "chrome/browser/sync/glue/preference_data_type_controller.h" @@ -14,6 +14,7 @@ #include "chrome/browser/sync/glue/model_associator_mock.h" #include "chrome/browser/sync/profile_sync_factory_mock.h" #include "chrome/browser/sync/profile_sync_service_mock.h" +#include "chrome/test/profile_mock.h" #include "content/browser/browser_thread.h" using browser_sync::PreferenceDataTypeController; @@ -28,7 +29,8 @@ using testing::SetArgumentPointee; class StartCallback { public: - MOCK_METHOD1(Run, void(DataTypeController::StartResult result)); + MOCK_METHOD2(Run, void(DataTypeController::StartResult result, + const tracked_objects::Location& location)); }; class PreferenceDataTypeControllerTest : public testing::Test { @@ -40,6 +42,7 @@ class PreferenceDataTypeControllerTest : public testing::Test { profile_sync_factory_.reset(new ProfileSyncFactoryMock()); preference_dtc_ = new PreferenceDataTypeController(profile_sync_factory_.get(), + &profile_, &service_); } @@ -53,6 +56,8 @@ class PreferenceDataTypeControllerTest : public testing::Test { } void SetAssociateExpectations() { + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels()). @@ -72,6 +77,7 @@ class PreferenceDataTypeControllerTest : public testing::Test { BrowserThread ui_thread_; scoped_refptr<PreferenceDataTypeController> preference_dtc_; scoped_ptr<ProfileSyncFactoryMock> profile_sync_factory_; + ProfileMock profile_; ProfileSyncServiceMock service_; ModelAssociatorMock* model_associator_; ChangeProcessorMock* change_processor_; @@ -83,7 +89,7 @@ TEST_F(PreferenceDataTypeControllerTest, Start) { SetAssociateExpectations(); SetActivateExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, preference_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); preference_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, preference_dtc_->state()); } @@ -94,7 +100,7 @@ TEST_F(PreferenceDataTypeControllerTest, StartFirstRun) { SetActivateExpectations(); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN, _)); preference_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -105,7 +111,7 @@ TEST_F(PreferenceDataTypeControllerTest, StartOk) { EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); preference_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -115,7 +121,7 @@ TEST_F(PreferenceDataTypeControllerTest, StartAssociationFailed) { EXPECT_CALL(*model_associator_, AssociateModels()). WillRepeatedly(Return(false)); - EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED, _)); preference_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, preference_dtc_->state()); } @@ -124,9 +130,11 @@ TEST_F(PreferenceDataTypeControllerTest, StartAssociationTriggersUnrecoverableError) { SetStartExpectations(); // Set up association to fail with an unrecoverable error. + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); - EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR)); + EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR, _)); preference_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, preference_dtc_->state()); } @@ -139,7 +147,7 @@ TEST_F(PreferenceDataTypeControllerTest, Stop) { EXPECT_EQ(DataTypeController::NOT_RUNNING, preference_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); preference_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, preference_dtc_->state()); preference_dtc_->Stop(); @@ -157,7 +165,7 @@ TEST_F(PreferenceDataTypeControllerTest, OnUnrecoverableError) { &PreferenceDataTypeController::Stop)); SetStopExpectations(); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); preference_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); // This should cause preference_dtc_->Stop() to be called. preference_dtc_->OnUnrecoverableError(FROM_HERE, "Test"); diff --git a/chrome/browser/sync/glue/preference_model_associator.cc b/chrome/browser/sync/glue/preference_model_associator.cc index 63a058c..7d75c38 100644 --- a/chrome/browser/sync/glue/preference_model_associator.cc +++ b/chrome/browser/sync/glue/preference_model_associator.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,18 +6,18 @@ #include "base/json/json_reader.h" #include "base/logging.h" -#include "base/values.h" #include "base/utf_string_conversions.h" +#include "base/values.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/synchronized_preferences.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/preference_specifics.pb.h" -#include "chrome/common/json_value_serializer.h" -#include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" #include "content/browser/browser_thread.h" +#include "content/common/json_value_serializer.h" +#include "content/common/notification_service.h" namespace browser_sync { @@ -134,6 +134,7 @@ bool PreferenceModelAssociator::AssociateModels() { it != synced_preferences_.end(); ++it) { const PrefService::Preference* pref = pref_service->FindPreference((*it).c_str()); + DCHECK(pref); InitPrefNodeAndAssociate(&trans, root, pref); } return true; @@ -271,8 +272,7 @@ Value* PreferenceModelAssociator::MergeListValues(const Value& from_value, for (ListValue::const_iterator i = from_list_value.begin(); i != from_list_value.end(); ++i) { Value* value = (*i)->DeepCopy(); - if (!result->AppendIfNotPresent(value)) - delete value; + result->AppendIfNotPresent(value); } return result; } @@ -326,4 +326,13 @@ void PreferenceModelAssociator::AfterUpdateOperations( } } +bool PreferenceModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + syncable::ModelTypeSet encrypted_types; + sync_service_->GetEncryptedDataTypes(&encrypted_types); + return encrypted_types.count(syncable::PREFERENCES) == 0 || + sync_service_->IsCryptographerReady(&trans); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/preference_model_associator.h b/chrome/browser/sync/glue/preference_model_associator.h index 5924e72..5c661b2 100644 --- a/chrome/browser/sync/glue/preference_model_associator.h +++ b/chrome/browser/sync/glue/preference_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -69,6 +69,9 @@ class PreferenceModelAssociator // thread. } + // See ModelAssociator interface. + virtual bool CryptoReadyIfNecessary(); + // Not implemented. virtual const PrefService::Preference* GetChromeNodeFromSyncId(int64 sync_id); diff --git a/chrome/browser/sync/glue/preference_model_associator_unittest.cc b/chrome/browser/sync/glue/preference_model_associator_unittest.cc index 394720a..7ed60b0 100644 --- a/chrome/browser/sync/glue/preference_model_associator_unittest.cc +++ b/chrome/browser/sync/glue/preference_model_associator_unittest.cc @@ -2,8 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/values.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/sync/glue/preference_model_associator.h" #include "chrome/common/pref_names.h" #include "chrome/test/testing_profile.h" @@ -95,35 +96,45 @@ TEST_F(ListPreferenceMergeTest, LocalEmpty) { TEST_F(ListPreferenceMergeTest, ServerNull) { scoped_ptr<Value> null_value(Value::CreateNullValue()); - ListValue* local_list_value = - pref_service_->GetMutableList(prefs::kURLsToRestoreOnStartup); - local_list_value->Append(Value::CreateStringValue(local_url0_)); + { + ListPrefUpdate update(pref_service_, prefs::kURLsToRestoreOnStartup); + ListValue* local_list_value = update.Get(); + local_list_value->Append(Value::CreateStringValue(local_url0_)); + } const PrefService::Preference* pref = pref_service_->FindPreference(prefs::kURLsToRestoreOnStartup); scoped_ptr<Value> merged_value( PreferenceModelAssociator::MergePreference(*pref, *null_value)); + const ListValue* local_list_value = + pref_service_->GetList(prefs::kURLsToRestoreOnStartup); EXPECT_TRUE(merged_value->Equals(local_list_value)); } TEST_F(ListPreferenceMergeTest, ServerEmpty) { scoped_ptr<Value> empty_value(new ListValue); - ListValue* local_list_value = - pref_service_->GetMutableList(prefs::kURLsToRestoreOnStartup); - local_list_value->Append(Value::CreateStringValue(local_url0_)); + { + ListPrefUpdate update(pref_service_, prefs::kURLsToRestoreOnStartup); + ListValue* local_list_value = update.Get(); + local_list_value->Append(Value::CreateStringValue(local_url0_)); + } const PrefService::Preference* pref = pref_service_->FindPreference(prefs::kURLsToRestoreOnStartup); scoped_ptr<Value> merged_value( PreferenceModelAssociator::MergePreference(*pref, *empty_value)); + const ListValue* local_list_value = + pref_service_->GetList(prefs::kURLsToRestoreOnStartup); EXPECT_TRUE(merged_value->Equals(local_list_value)); } TEST_F(ListPreferenceMergeTest, Merge) { - ListValue* local_list_value = - pref_service_->GetMutableList(prefs::kURLsToRestoreOnStartup); - local_list_value->Append(Value::CreateStringValue(local_url0_)); - local_list_value->Append(Value::CreateStringValue(local_url1_)); + { + ListPrefUpdate update(pref_service_, prefs::kURLsToRestoreOnStartup); + ListValue* local_list_value = update.Get(); + local_list_value->Append(Value::CreateStringValue(local_url0_)); + local_list_value->Append(Value::CreateStringValue(local_url1_)); + } const PrefService::Preference* pref = pref_service_->FindPreference(prefs::kURLsToRestoreOnStartup); @@ -139,11 +150,13 @@ TEST_F(ListPreferenceMergeTest, Merge) { } TEST_F(ListPreferenceMergeTest, Duplicates) { - ListValue* local_list_value = - pref_service_->GetMutableList(prefs::kURLsToRestoreOnStartup); - local_list_value->Append(Value::CreateStringValue(local_url0_)); - local_list_value->Append(Value::CreateStringValue(server_url0_)); - local_list_value->Append(Value::CreateStringValue(server_url1_)); + { + ListPrefUpdate update(pref_service_, prefs::kURLsToRestoreOnStartup); + ListValue* local_list_value = update.Get(); + local_list_value->Append(Value::CreateStringValue(local_url0_)); + local_list_value->Append(Value::CreateStringValue(server_url0_)); + local_list_value->Append(Value::CreateStringValue(server_url1_)); + } const PrefService::Preference* pref = pref_service_->FindPreference(prefs::kURLsToRestoreOnStartup); @@ -158,10 +171,12 @@ TEST_F(ListPreferenceMergeTest, Duplicates) { } TEST_F(ListPreferenceMergeTest, Equals) { - ListValue* local_list_value = - pref_service_->GetMutableList(prefs::kURLsToRestoreOnStartup); - local_list_value->Append(Value::CreateStringValue(server_url0_)); - local_list_value->Append(Value::CreateStringValue(server_url1_)); + { + ListPrefUpdate update(pref_service_, prefs::kURLsToRestoreOnStartup); + ListValue* local_list_value = update.Get(); + local_list_value->Append(Value::CreateStringValue(server_url0_)); + local_list_value->Append(Value::CreateStringValue(server_url1_)); + } scoped_ptr<Value> original(server_url_list_.DeepCopy()); const PrefService::Preference* pref = @@ -206,34 +221,44 @@ TEST_F(DictionaryPreferenceMergeTest, LocalEmpty) { TEST_F(DictionaryPreferenceMergeTest, ServerNull) { scoped_ptr<Value> null_value(Value::CreateNullValue()); - DictionaryValue* local_dict_value = - pref_service_->GetMutableDictionary(prefs::kContentSettingsPatterns); - SetContentPattern(local_dict_value, expression2_, content_type0_, 1); + { + DictionaryPrefUpdate update(pref_service_, prefs::kContentSettingsPatterns); + DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression2_, content_type0_, 1); + } const PrefService::Preference* pref = pref_service_->FindPreference(prefs::kContentSettingsPatterns); scoped_ptr<Value> merged_value( PreferenceModelAssociator::MergePreference(*pref, *null_value)); + const DictionaryValue* local_dict_value = + pref_service_->GetDictionary(prefs::kContentSettingsPatterns); EXPECT_TRUE(merged_value->Equals(local_dict_value)); } TEST_F(DictionaryPreferenceMergeTest, ServerEmpty) { scoped_ptr<Value> empty_value(new DictionaryValue); - DictionaryValue* local_dict_value = - pref_service_->GetMutableDictionary(prefs::kContentSettingsPatterns); - SetContentPattern(local_dict_value, expression2_, content_type0_, 1); + { + DictionaryPrefUpdate update(pref_service_, prefs::kContentSettingsPatterns); + DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression2_, content_type0_, 1); + } const PrefService::Preference* pref = pref_service_->FindPreference(prefs::kContentSettingsPatterns); scoped_ptr<Value> merged_value( PreferenceModelAssociator::MergePreference(*pref, *empty_value)); + const DictionaryValue* local_dict_value = + pref_service_->GetDictionary(prefs::kContentSettingsPatterns); EXPECT_TRUE(merged_value->Equals(local_dict_value)); } TEST_F(DictionaryPreferenceMergeTest, MergeNoConflicts) { - DictionaryValue* local_dict_value = - pref_service_->GetMutableDictionary(prefs::kContentSettingsPatterns); - SetContentPattern(local_dict_value, expression2_, content_type0_, 1); + { + DictionaryPrefUpdate update(pref_service_, prefs::kContentSettingsPatterns); + DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression2_, content_type0_, 1); + } scoped_ptr<Value> merged_value(PreferenceModelAssociator::MergePreference( *pref_service_->FindPreference(prefs::kContentSettingsPatterns), @@ -248,12 +273,14 @@ TEST_F(DictionaryPreferenceMergeTest, MergeNoConflicts) { } TEST_F(DictionaryPreferenceMergeTest, MergeConflicts) { - DictionaryValue* local_dict_value = - pref_service_->GetMutableDictionary(prefs::kContentSettingsPatterns); - SetContentPattern(local_dict_value, expression0_, content_type0_, 2); - SetContentPattern(local_dict_value, expression1_, content_type0_, 1); - SetContentPattern(local_dict_value, expression1_, content_type1_, 1); - SetContentPattern(local_dict_value, expression2_, content_type0_, 2); + { + DictionaryPrefUpdate update(pref_service_, prefs::kContentSettingsPatterns); + DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression0_, content_type0_, 2); + SetContentPattern(local_dict_value, expression1_, content_type0_, 1); + SetContentPattern(local_dict_value, expression1_, content_type1_, 1); + SetContentPattern(local_dict_value, expression2_, content_type0_, 2); + } scoped_ptr<Value> merged_value(PreferenceModelAssociator::MergePreference( *pref_service_->FindPreference(prefs::kContentSettingsPatterns), @@ -269,11 +296,13 @@ TEST_F(DictionaryPreferenceMergeTest, MergeConflicts) { } TEST_F(DictionaryPreferenceMergeTest, Equal) { - DictionaryValue* local_dict_value = - pref_service_->GetMutableDictionary(prefs::kContentSettingsPatterns); - SetContentPattern(local_dict_value, expression0_, content_type0_, 1); - SetContentPattern(local_dict_value, expression0_, content_type1_, 2); - SetContentPattern(local_dict_value, expression1_, content_type0_, 1); + { + DictionaryPrefUpdate update(pref_service_, prefs::kContentSettingsPatterns); + DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression0_, content_type0_, 1); + SetContentPattern(local_dict_value, expression0_, content_type1_, 2); + SetContentPattern(local_dict_value, expression1_, content_type0_, 1); + } scoped_ptr<Value> merged_value(PreferenceModelAssociator::MergePreference( *pref_service_->FindPreference(prefs::kContentSettingsPatterns), @@ -282,11 +311,13 @@ TEST_F(DictionaryPreferenceMergeTest, Equal) { } TEST_F(DictionaryPreferenceMergeTest, ConflictButServerWins) { - DictionaryValue* local_dict_value = - pref_service_->GetMutableDictionary(prefs::kContentSettingsPatterns); - SetContentPattern(local_dict_value, expression0_, content_type0_, 2); - SetContentPattern(local_dict_value, expression0_, content_type1_, 2); - SetContentPattern(local_dict_value, expression1_, content_type0_, 1); + { + DictionaryPrefUpdate update(pref_service_, prefs::kContentSettingsPatterns); + DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression0_, content_type0_, 2); + SetContentPattern(local_dict_value, expression0_, content_type1_, 2); + SetContentPattern(local_dict_value, expression1_, content_type0_, 1); + } scoped_ptr<Value> merged_value(PreferenceModelAssociator::MergePreference( *pref_service_->FindPreference(prefs::kContentSettingsPatterns), @@ -310,8 +341,11 @@ class IndividualPreferenceMergeTest : public AbstractPreferenceMergeTest { } bool MergeListPreference(const char* pref) { - ListValue* local_list_value = pref_service_->GetMutableList(pref); - local_list_value->Append(Value::CreateStringValue(url1_)); + { + ListPrefUpdate update(pref_service_, pref); + ListValue* local_list_value = update.Get(); + local_list_value->Append(Value::CreateStringValue(url1_)); + } scoped_ptr<Value> merged_value(PreferenceModelAssociator::MergePreference( *pref_service_->FindPreference(pref), @@ -324,9 +358,11 @@ class IndividualPreferenceMergeTest : public AbstractPreferenceMergeTest { } bool MergeDictionaryPreference(const char* pref) { - DictionaryValue* local_dict_value = - pref_service_->GetMutableDictionary(pref); - SetContentPattern(local_dict_value, expression1_, content_type0_, 1); + { + DictionaryPrefUpdate update(pref_service_, pref); + DictionaryValue* local_dict_value = update.Get(); + SetContentPattern(local_dict_value, expression1_, content_type0_, 1); + } scoped_ptr<Value> merged_value(PreferenceModelAssociator::MergePreference( *pref_service_->FindPreference(pref), diff --git a/chrome/browser/sync/glue/session_change_processor.cc b/chrome/browser/sync/glue/session_change_processor.cc index 7bc7a96..8c8a452 100644 --- a/chrome/browser/sync/glue/session_change_processor.cc +++ b/chrome/browser/sync/glue/session_change_processor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,16 +9,17 @@ #include <vector> #include "base/logging.h" -#include "base/scoped_vector.h" +#include "base/memory/scoped_vector.h" +#include "chrome/browser/extensions/extension_tab_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/session_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_source.h" #include "content/browser/tab_contents/navigation_controller.h" #include "content/browser/tab_contents/tab_contents.h" +#include "content/common/notification_details.h" +#include "content/common/notification_service.h" +#include "content/common/notification_source.h" namespace browser_sync { @@ -27,7 +28,8 @@ SessionChangeProcessor::SessionChangeProcessor( SessionModelAssociator* session_model_associator) : ChangeProcessor(error_handler), session_model_associator_(session_model_associator), - profile_(NULL) { + profile_(NULL), + setup_for_test_(false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(error_handler); DCHECK(session_model_associator_); @@ -124,13 +126,13 @@ void SessionChangeProcessor::Observe(NotificationType type, } case NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: { - TabContents* tab_contents = Source<TabContents>(source).ptr(); - DCHECK(tab_contents); - if (tab_contents->profile() != profile_) { + ExtensionTabHelper* extension_tab_helper = + Source<ExtensionTabHelper>(source).ptr(); + if (extension_tab_helper->tab_contents()->profile() != profile_) { return; } - if (tab_contents->extension_app()) { - modified_tabs.push_back(tab_contents); + if (extension_tab_helper->extension_app()) { + modified_tabs.push_back(extension_tab_helper->tab_contents()); } break; } diff --git a/chrome/browser/sync/glue/session_change_processor.h b/chrome/browser/sync/glue/session_change_processor.h index b8bcc8f..6213443 100644 --- a/chrome/browser/sync/glue/session_change_processor.h +++ b/chrome/browser/sync/glue/session_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -11,9 +11,9 @@ #include "chrome/browser/sessions/session_service.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/change_processor.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_type.h" -#include "chrome/common/notification_registrar.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_type.h" class NotificationDetails; class NotificationSource; diff --git a/chrome/browser/sync/glue/session_data_type_controller.cc b/chrome/browser/sync/glue/session_data_type_controller.cc index c4ca5b8..68186cb 100644 --- a/chrome/browser/sync/glue/session_data_type_controller.cc +++ b/chrome/browser/sync/glue/session_data_type_controller.cc @@ -1,131 +1,51 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/session_data_type_controller.h" #include "base/metrics/histogram.h" -#include "base/logging.h" -#include "base/time.h" -#include "chrome/browser/sync/glue/session_change_processor.h" -#include "chrome/browser/sync/glue/session_model_associator.h" -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" -#include "chrome/browser/sync/unrecoverable_error_handler.h" -#include "content/browser/browser_thread.h" namespace browser_sync { SessionDataTypeController::SessionDataTypeController( ProfileSyncFactory* profile_sync_factory, + Profile* profile, ProfileSyncService* sync_service) - : profile_sync_factory_(profile_sync_factory), - sync_service_(sync_service), - state_(NOT_RUNNING) { - DCHECK(profile_sync_factory); - DCHECK(sync_service); + : FrontendDataTypeController(profile_sync_factory, + profile, + sync_service) { } -SessionDataTypeController::~SessionDataTypeController() { -} - -void SessionDataTypeController::Start(StartCallback* start_callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(start_callback); - if (state_ != NOT_RUNNING) { - start_callback->Run(BUSY); - delete start_callback; - return; - } - - start_callback_.reset(start_callback); - - ProfileSyncFactory::SyncComponents sync_components = - profile_sync_factory_->CreateSessionSyncComponents(sync_service_, - this); - model_associator_.reset(sync_components.model_associator); - change_processor_.reset(sync_components.change_processor); - - bool sync_has_nodes = false; - if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { - StartFailed(UNRECOVERABLE_ERROR); - return; - } - - base::TimeTicks start_time = base::TimeTicks::Now(); - bool success = model_associator_->AssociateModels(); - UMA_HISTOGRAM_TIMES("Sync.SessionAssociationTime", - base::TimeTicks::Now() - start_time); - if (!success) { - StartFailed(ASSOCIATION_FAILED); - return; - } - - sync_service_->ActivateDataType(this, change_processor_.get()); - state_ = RUNNING; - FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK); -} - -void SessionDataTypeController::Stop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (change_processor_ != NULL) - sync_service_->DeactivateDataType(this, change_processor_.get()); +SessionDataTypeController::~SessionDataTypeController() {} - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - - change_processor_.reset(); - model_associator_.reset(); - start_callback_.reset(); - - state_ = NOT_RUNNING; -} - -bool SessionDataTypeController::enabled() { - return true; +SessionModelAssociator* SessionDataTypeController::GetModelAssociator() { + return reinterpret_cast<SessionModelAssociator*>(model_associator_.get()); } -syncable::ModelType SessionDataTypeController::type() { +syncable::ModelType SessionDataTypeController::type() const { return syncable::SESSIONS; } -browser_sync::ModelSafeGroup SessionDataTypeController::model_safe_group() { - return browser_sync::GROUP_UI; -} - -const char* SessionDataTypeController::name() const { - // For logging only. - return "session"; -} - -DataTypeController::State SessionDataTypeController::state() { - return state_; +void SessionDataTypeController::CreateSyncComponents() { + ProfileSyncFactory::SyncComponents sync_components = profile_sync_factory_-> + CreateSessionSyncComponents(sync_service_, this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); } -void SessionDataTypeController::OnUnrecoverableError( +void SessionDataTypeController::RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); UMA_HISTOGRAM_COUNTS("Sync.SessionRunFailures", 1); - sync_service_->OnUnrecoverableError(from_here, message); -} - -browser_sync::SessionModelAssociator* - SessionDataTypeController::GetModelAssociator() { - return static_cast<SessionModelAssociator*>(model_associator_.get()); } -void SessionDataTypeController::FinishStart(StartResult result) { - start_callback_->Run(result); - start_callback_.reset(); +void SessionDataTypeController::RecordAssociationTime(base::TimeDelta time) { + UMA_HISTOGRAM_TIMES("Sync.SessionAssociationTime", time); } -void SessionDataTypeController::StartFailed(StartResult result) { - model_associator_.reset(); - change_processor_.reset(); - start_callback_->Run(result); - start_callback_.reset(); +void SessionDataTypeController::RecordStartFailure(StartResult result) { UMA_HISTOGRAM_ENUMERATION("Sync.SessionStartFailures", result, MAX_START_RESULT); diff --git a/chrome/browser/sync/glue/session_data_type_controller.h b/chrome/browser/sync/glue/session_data_type_controller.h index 8736789..4ee284a 100644 --- a/chrome/browser/sync/glue/session_data_type_controller.h +++ b/chrome/browser/sync/glue/session_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,63 +8,37 @@ #include <string> -#include "base/basictypes.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/browser/sync/glue/session_model_associator.h" - -class ProfileSyncFactory; -class ProfileSyncService; +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" namespace browser_sync { -class AssociatorInterface; -class ChangeProcessor; +class SessionModelAssociator; -class SessionDataTypeController : public DataTypeController { +class SessionDataTypeController : public FrontendDataTypeController { public: SessionDataTypeController( ProfileSyncFactory* profile_sync_factory, + Profile* profile, ProfileSyncService* sync_service); virtual ~SessionDataTypeController(); - // DataTypeController implementation. - virtual void Start(StartCallback* start_callback); - - virtual void Stop(); - - virtual bool enabled(); - - virtual syncable::ModelType type(); - - virtual browser_sync::ModelSafeGroup model_safe_group(); - - virtual const char* name() const; + SessionModelAssociator* GetModelAssociator(); - virtual State state(); + // FrontendDataTypeController implementation. + virtual syncable::ModelType type() const; - // UnrecoverableErrorHandler interface. - virtual void OnUnrecoverableError( + private: + // FrontendDataTypeController implementations. + // Datatype specific creation of sync components. + virtual void CreateSyncComponents(); + // Record unrecoverable errors. + virtual void RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message); - - SessionModelAssociator* GetModelAssociator(); - - private: - // Helper method to run the stashed start callback with a given result. - void FinishStart(StartResult result); - - // Cleans up state and calls callback when start fails. - void StartFailed(StartResult result); - - ProfileSyncFactory* profile_sync_factory_; - ProfileSyncService* sync_service_; - - State state_; - - scoped_ptr<StartCallback> start_callback_; - scoped_ptr<AssociatorInterface> model_associator_; - scoped_ptr<ChangeProcessor> change_processor_; + // Record association time. + virtual void RecordAssociationTime(base::TimeDelta time); + // Record causes of start failure. + virtual void RecordStartFailure(StartResult result); DISALLOW_COPY_AND_ASSIGN(SessionDataTypeController); }; diff --git a/chrome/browser/sync/glue/session_model_associator.cc b/chrome/browser/sync/glue/session_model_associator.cc index 55caa37..d737d99 100644 --- a/chrome/browser/sync/glue/session_model_associator.cc +++ b/chrome/browser/sync/glue/session_model_associator.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,18 +8,20 @@ #include <utility> #include "base/logging.h" -#include "chrome/browser/browser_list.h" -#include "chrome/browser/browser_window.h" +#include "chrome/browser/extensions/extension_tab_helper.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/syncable/syncable.h" #include "chrome/browser/tabs/tab_strip_model.h" +#include "chrome/browser/ui/browser_list.h" +#include "chrome/browser/ui/browser_window.h" +#include "chrome/browser/ui/tab_contents/tab_contents_wrapper.h" #include "chrome/common/extensions/extension.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_service.h" #include "chrome/common/url_constants.h" #include "content/browser/tab_contents/navigation_controller.h" #include "content/browser/tab_contents/navigation_entry.h" +#include "content/common/notification_details.h" +#include "content/common/notification_service.h" namespace browser_sync { @@ -35,7 +37,8 @@ static const int max_sync_navigation_count = 6; SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service) : tab_pool_(sync_service), local_session_syncid_(sync_api::kInvalidId), - sync_service_(sync_service) { + sync_service_(sync_service), + setup_for_test_(false) { DCHECK(CalledOnValidThread()); DCHECK(sync_service_); } @@ -125,7 +128,7 @@ void SessionModelAssociator::ReassociateWindows(bool reload_tabs) { VLOG(1) << "Reassociating window " << window_id << " with " << (*i)->tab_count() << " tabs."; window_s.set_window_id(window_id); - window_s.set_selected_tab_index((*i)->selected_index()); + window_s.set_selected_tab_index((*i)->active_index()); if ((*i)->type() == Browser::TYPE_NORMAL) { window_s.set_browser_type( @@ -267,8 +270,13 @@ bool SessionModelAssociator::WriteTabContentsToSyncModel( int index_in_window = browser.tabstrip_model()->GetWrapperIndex(&tab); DCHECK(index_in_window != TabStripModel::kNoTab); tab_s->set_pinned(browser.tabstrip_model()->IsTabPinned(index_in_window)); - if (tab.extension_app()) - tab_s->set_extension_app_id(tab.extension_app()->id()); + TabContentsWrapper* wrapper = + TabContentsWrapper::GetCurrentWrapperForContents( + const_cast<TabContents*>(&tab)); + if (wrapper->extension_tab_helper()->extension_app()) { + tab_s->set_extension_app_id( + wrapper->extension_tab_helper()->extension_app()->id()); + } for (int i = min_index; i < max_index; ++i) { const NavigationEntry* entry = (i == pending_index) ? tab.controller().pending_entry() : tab.controller().GetEntryAtIndex(i); @@ -562,7 +570,7 @@ void SessionModelAssociator::DisassociateForeignSession( // Static void SessionModelAssociator::PopulateSessionWindowFromSpecifics( - std::string foreign_session_tag, + const std::string& foreign_session_tag, const sync_pb::SessionWindow& specifics, int64 mtime, SessionWindow* session_window, @@ -984,4 +992,13 @@ void SessionModelAssociator::PopulateSessionSpecificsTab( } } +bool SessionModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + syncable::ModelTypeSet encrypted_types; + sync_service_->GetEncryptedDataTypes(&encrypted_types); + return encrypted_types.count(syncable::SESSIONS) == 0 || + sync_service_->IsCryptographerReady(&trans); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/session_model_associator.h b/chrome/browser/sync/glue/session_model_associator.h index d8b16a4..28582e0 100644 --- a/chrome/browser/sync/glue/session_model_associator.h +++ b/chrome/browser/sync/glue/session_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -13,12 +13,11 @@ #include "base/basictypes.h" #include "base/format_macros.h" #include "base/gtest_prod_util.h" +#include "base/memory/scoped_vector.h" #include "base/observer_list.h" -#include "base/scoped_vector.h" #include "base/string_util.h" #include "base/threading/non_thread_safe.h" #include "base/utf_string_conversions.h" -#include "chrome/browser/browser_window.h" #include "chrome/browser/sessions/session_id.h" #include "chrome/browser/sessions/session_service.h" #include "chrome/browser/sessions/session_types.h" @@ -27,6 +26,7 @@ #include "chrome/browser/sync/glue/model_associator.h" #include "chrome/browser/sync/protocol/session_specifics.pb.h" #include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/ui/browser_window.h" #include "content/browser/tab_contents/tab_contents.h" class Profile; @@ -68,6 +68,9 @@ class SessionModelAssociator // No implementation needed, this associator runs on the main thread. } + // See ModelAssociator interface. + virtual bool CryptoReadyIfNecessary(); + // Returns sync id for the given chrome model id. // Returns sync_api::kInvalidId if the sync node is not found for the given // chrome id. @@ -331,7 +334,7 @@ class SessionModelAssociator // Used to populate a session window from the session specifics window // provided. Tracks any foreign session data created through |tracker|. static void PopulateSessionWindowFromSpecifics( - std::string foreign_session_tag, + const std::string& foreign_session_tag, const sync_pb::SessionWindow& window, const int64 mtime, SessionWindow* session_window, diff --git a/chrome/browser/sync/glue/session_model_associator_unittest.cc b/chrome/browser/sync/glue/session_model_associator_unittest.cc index 9f0ce3c..693e65f 100644 --- a/chrome/browser/sync/glue/session_model_associator_unittest.cc +++ b/chrome/browser/sync/glue/session_model_associator_unittest.cc @@ -1,16 +1,16 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 <string> #include <vector> -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/values.h" #include "chrome/browser/sessions/session_types.h" #include "chrome/browser/sync/glue/session_model_associator.h" -#include "chrome/common/page_transition_types.h" #include "chrome/common/url_constants.h" +#include "content/common/page_transition_types.h" #include "testing/gtest/include/gtest/gtest.h" using browser_sync::SessionModelAssociator; diff --git a/chrome/browser/sync/glue/sync_backend_host.cc b/chrome/browser/sync/glue/sync_backend_host.cc index e079f8e..59e4f7e 100644 --- a/chrome/browser/sync/glue/sync_backend_host.cc +++ b/chrome/browser/sync/glue/sync_backend_host.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,8 +6,8 @@ #include <algorithm> -#include "base/compiler_specific.h" #include "base/command_line.h" +#include "base/compiler_specific.h" #include "base/file_util.h" #include "base/task.h" #include "base/threading/thread_restrictions.h" @@ -21,10 +21,12 @@ #include "chrome/browser/sync/glue/change_processor.h" #include "chrome/browser/sync/glue/database_model_worker.h" #include "chrome/browser/sync/glue/history_model_worker.h" -#include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/glue/http_bridge.h" #include "chrome/browser/sync/glue/password_model_worker.h" +#include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/js_arg_list.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "chrome/browser/sync/notifier/sync_notifier_factory.h" #include "chrome/browser/sync/sessions/session_state.h" // TODO(tim): Remove this! We should have a syncapi pass-thru instead. #include "chrome/browser/sync/syncable/directory_manager.h" // Cryptographer. @@ -33,10 +35,11 @@ #include "chrome/common/chrome_switches.h" #include "chrome/common/chrome_version_info.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_type.h" #include "chrome/common/pref_names.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_service.h" +#include "content/common/notification_type.h" +#include "googleurl/src/gurl.h" #include "webkit/glue/webkit_glue.h" static const int kSaveChangesIntervalSeconds = 10; @@ -44,6 +47,7 @@ static const FilePath::CharType kSyncDataFolderName[] = FILE_PATH_LITERAL("Sync Data"); using browser_sync::DataTypeController; +using sync_notifier::SyncNotifierFactory; typedef TokenService::TokenAvailableDetails TokenAvailableDetails; typedef GoogleServiceAuthError AuthError; @@ -62,9 +66,6 @@ SyncBackendHost::SyncBackendHost(Profile* profile) sync_data_folder_path_( profile_->GetPath().Append(kSyncDataFolderName)), last_auth_error_(AuthError::None()), - using_new_syncer_thread_( - CommandLine::ForCurrentProcess()->HasSwitch( - switches::kNewSyncerThread)), syncapi_initialized_(false) { } @@ -74,9 +75,6 @@ SyncBackendHost::SyncBackendHost() profile_(NULL), frontend_(NULL), last_auth_error_(AuthError::None()), - using_new_syncer_thread_( - CommandLine::ForCurrentProcess()->HasSwitch( - switches::kNewSyncerThread)), syncapi_initialized_(false) { } @@ -89,10 +87,9 @@ void SyncBackendHost::Initialize( SyncFrontend* frontend, const GURL& sync_service_url, const syncable::ModelTypeSet& types, - URLRequestContextGetter* baseline_context_getter, + net::URLRequestContextGetter* baseline_context_getter, const SyncCredentials& credentials, - bool delete_sync_data_folder, - const notifier::NotifierOptions& notifier_options) { + bool delete_sync_data_folder) { if (!core_thread_.Start()) return; @@ -106,7 +103,7 @@ void SyncBackendHost::Initialize( // when a new type is synced as the worker may already exist and you just // need to update routing_info_. registrar_.workers[GROUP_DB] = new DatabaseModelWorker(); - registrar_.workers[GROUP_UI] = new UIModelWorker(frontend_loop_); + registrar_.workers[GROUP_UI] = new UIModelWorker(); registrar_.workers[GROUP_PASSIVE] = new ModelSafeWorker(); if (CommandLine::ForCurrentProcess()->HasSwitch( @@ -140,12 +137,15 @@ void SyncBackendHost::Initialize( // Nigori is populated by default now. registrar_.routing_info[syncable::NIGORI] = GROUP_PASSIVE; + // TODO(akalin): Create SyncNotifier here and pass it in as part of + // DoInitializeOptions. + core_->CreateSyncNotifier(baseline_context_getter); + InitCore(Core::DoInitializeOptions( sync_service_url, MakeHttpBridgeFactory(baseline_context_getter), credentials, delete_sync_data_folder, - notifier_options, RestoreEncryptionBootstrapToken(), false)); } @@ -179,9 +179,9 @@ bool SyncBackendHost::IsUsingExplicitPassphrase() { core_->syncapi()->IsUsingExplicitPassphrase(); } -bool SyncBackendHost::IsCryptographerReady() const { - return syncapi_initialized_ && - GetUserShare()->dir_manager->cryptographer()->is_ready(); +bool SyncBackendHost::IsCryptographerReady( + const sync_api::BaseTransaction* trans) const { + return syncapi_initialized_ && trans->GetCryptographer()->is_ready(); } JsBackend* SyncBackendHost::GetJsBackend() { @@ -194,7 +194,7 @@ JsBackend* SyncBackendHost::GetJsBackend() { } sync_api::HttpPostProviderFactory* SyncBackendHost::MakeHttpBridgeFactory( - URLRequestContextGetter* getter) { + net::URLRequestContextGetter* getter) { return new HttpBridgeFactory(getter); } @@ -211,14 +211,6 @@ void SyncBackendHost::UpdateCredentials(const SyncCredentials& credentials) { credentials)); } -void SyncBackendHost::UpdateEnabledTypes( - const syncable::ModelTypeSet& types) { - core_thread_.message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(core_.get(), - &SyncBackendHost::Core::DoUpdateEnabledTypes, - types)); -} - void SyncBackendHost::StartSyncingWithServer() { core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoStartSyncing)); @@ -353,57 +345,77 @@ void SyncBackendHost::ConfigureAutofillMigration() { } } +SyncBackendHost::PendingConfigureDataTypesState:: +PendingConfigureDataTypesState() : deleted_type(false) {} + +SyncBackendHost::PendingConfigureDataTypesState:: +~PendingConfigureDataTypesState() {} + +// static +SyncBackendHost::PendingConfigureDataTypesState* + SyncBackendHost::MakePendingConfigModeState( + const DataTypeController::TypeMap& data_type_controllers, + const syncable::ModelTypeSet& types, + CancelableTask* ready_task, + ModelSafeRoutingInfo* routing_info) { + PendingConfigureDataTypesState* state = new PendingConfigureDataTypesState(); + for (DataTypeController::TypeMap::const_iterator it = + data_type_controllers.begin(); + it != data_type_controllers.end(); ++it) { + syncable::ModelType type = it->first; + // If a type is not specified, remove it from the routing_info. + if (types.count(type) == 0) { + state->deleted_type = true; + routing_info->erase(type); + } else { + // Add a newly specified data type as GROUP_PASSIVE into the + // routing_info, if it does not already exist. + if (routing_info->count(type) == 0) { + (*routing_info)[type] = GROUP_PASSIVE; + state->added_types.set(type); + } + } + } + + state->ready_task.reset(ready_task); + state->initial_types = types; + return state; +} + void SyncBackendHost::ConfigureDataTypes( const DataTypeController::TypeMap& data_type_controllers, const syncable::ModelTypeSet& types, CancelableTask* ready_task) { // Only one configure is allowed at a time. - DCHECK(!configure_ready_task_.get()); + DCHECK(!pending_config_mode_state_.get()); + DCHECK(!pending_download_state_.get()); DCHECK(syncapi_initialized_); if (types.count(syncable::AUTOFILL_PROFILE) != 0) { ConfigureAutofillMigration(); } - bool deleted_type = false; - syncable::ModelTypeBitSet added_types; - { base::AutoLock lock(registrar_lock_); - for (DataTypeController::TypeMap::const_iterator it = - data_type_controllers.begin(); - it != data_type_controllers.end(); ++it) { - syncable::ModelType type = (*it).first; - - // If a type is not specified, remove it from the routing_info. - if (types.count(type) == 0) { - registrar_.routing_info.erase(type); - deleted_type = true; - } else { - // Add a newly specified data type as GROUP_PASSIVE into the - // routing_info, if it does not already exist. - if (registrar_.routing_info.count(type) == 0) { - registrar_.routing_info[type] = GROUP_PASSIVE; - added_types.set(type); - } - } - } + pending_config_mode_state_.reset( + MakePendingConfigModeState(data_type_controllers, types, ready_task, + ®istrar_.routing_info)); } - // If no new data types were added to the passive group, no need to - // wait for the syncer. - if (core_->syncapi()->InitialSyncEndedForAllEnabledTypes()) { - ready_task->Run(); - delete ready_task; - } else { - // Save the task here so we can run it when the syncer finishes - // initializing the new data types. It will be run only when the - // set of initially synced data types matches the types requested in - // this configure. - configure_ready_task_.reset(ready_task); - configure_initial_sync_types_ = types; - } + StartConfiguration(NewCallback(core_.get(), + &SyncBackendHost::Core::FinishConfigureDataTypes)); +} +void SyncBackendHost::StartConfiguration(Callback0::Type* callback) { + // Put syncer in the config mode. DTM will put us in normal mode once it is. + // done. This is to ensure we dont do a normal sync when we are doing model + // association. + core_thread_.message_loop()->PostTask(FROM_HERE, NewRunnableMethod( + core_.get(),&SyncBackendHost::Core::DoStartConfiguration, callback)); +} + +void SyncBackendHost::FinishConfigureDataTypesOnFrontendLoop() { + DCHECK_EQ(MessageLoop::current(), frontend_loop_); // Nudge the syncer. This is necessary for both datatype addition/deletion. // // Deletions need a nudge in order to ensure the deletion occurs in a timely @@ -412,30 +424,51 @@ void SyncBackendHost::ConfigureDataTypes( // In the case of additions, on the next sync cycle, the syncer should // notice that the routing info has changed and start the process of // downloading updates for newly added data types. Once this is - // complete, the configure_ready_task_ is run via an + // complete, the configure_state_.ready_task_ is run via an // OnInitializationComplete notification. - ScheduleSyncEventForConfigChange(deleted_type, added_types); -} -void SyncBackendHost::ScheduleSyncEventForConfigChange(bool deleted_type, - const syncable::ModelTypeBitSet& added_types) { - // We can only nudge when we've either deleted a dataype or added one, else - // we can cause unnecessary syncs. Unit tests cover this. - if (using_new_syncer_thread_) { - if (added_types.size() > 0) { - RequestConfig(added_types); - // TODO(tim): If we've added and deleted types, because we don't want to - // nudge until association finishes, circumstances of bug 56416 exist. We - // may need a way to nudge only for data type cleanup. Alternatively, we - // can chain configure_ready_task_ by appending a task to nudge in this - // case. - } else if (deleted_type) { - RequestNudge(); - } - } else if (deleted_type || - !core_->syncapi()->InitialSyncEndedForAllEnabledTypes()) { - RequestNudge(); + if (pending_config_mode_state_->deleted_type) { + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(core_.get(), + &SyncBackendHost::Core::DeferNudgeForCleanup)); + } + + if (pending_config_mode_state_->added_types.none() && + !core_->syncapi()->InitialSyncEndedForAllEnabledTypes()) { + LOG(WARNING) << "No new types, but initial sync not finished." + << "Possible sync db corruption / removal."; + // TODO(tim): Log / UMA / count this somehow? + // TODO(tim): If no added types, we could (should?) config only for + // types that are needed... but this is a rare corruption edge case or + // implies the user mucked around with their syncdb, so for now do all. + pending_config_mode_state_->added_types = + syncable::ModelTypeBitSetFromSet( + pending_config_mode_state_->initial_types); } + + // If we've added types, we always want to request a nudge/config (even if + // the initial sync is ended), in case we could not decrypt the data. + if (pending_config_mode_state_->added_types.none()) { + // No new types - just notify the caller that the types are available. + pending_config_mode_state_->ready_task->Run(); + } else { + pending_download_state_.reset(pending_config_mode_state_.release()); + + syncable::ModelTypeBitSet types_copy(pending_download_state_->added_types); + if (IsNigoriEnabled()) + types_copy.set(syncable::NIGORI); + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(core_.get(), + &SyncBackendHost::Core::DoRequestConfig, + types_copy)); + } + + pending_config_mode_state_.reset(); + + // Notify the SyncManager about the new types. + core_thread_.message_loop()->PostTask(FROM_HERE, + NewRunnableMethod(core_.get(), + &SyncBackendHost::Core::DoUpdateEnabledTypes)); } void SyncBackendHost::EncryptDataTypes( @@ -446,15 +479,10 @@ void SyncBackendHost::EncryptDataTypes( encrypted_types)); } -void SyncBackendHost::RequestNudge() { +void SyncBackendHost::RequestNudge(const tracked_objects::Location& location) { core_thread_.message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestNudge)); -} - -void SyncBackendHost::RequestConfig( - const syncable::ModelTypeBitSet& added_types) { - DCHECK(core_->syncapi()); - core_->syncapi()->RequestConfig(added_types); + NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestNudge, + location)); } void SyncBackendHost::ActivateDataType( @@ -486,23 +514,6 @@ void SyncBackendHost::DeactivateDataType( std::map<syncable::ModelType, ChangeProcessor*>::size_type erased = processors_.erase(data_type_controller->type()); DCHECK_EQ(erased, 1U); - - // TODO(sync): At this point we need to purge the data associated - // with this data type from the sync db. -} - -bool SyncBackendHost::RequestPause() { - DCHECK(!using_new_syncer_thread_); - core_thread_.message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestPause)); - return true; -} - -bool SyncBackendHost::RequestResume() { - DCHECK(!using_new_syncer_thread_); - core_thread_.message_loop()->PostTask(FROM_HERE, - NewRunnableMethod(core_.get(), &SyncBackendHost::Core::DoRequestResume)); - return true; } bool SyncBackendHost::RequestClearServerData() { @@ -515,20 +526,6 @@ bool SyncBackendHost::RequestClearServerData() { SyncBackendHost::Core::~Core() { } -void SyncBackendHost::Core::NotifyPaused() { - DCHECK(!host_->using_new_syncer_thread_); - NotificationService::current()->Notify(NotificationType::SYNC_PAUSED, - NotificationService::AllSources(), - NotificationService::NoDetails()); -} - -void SyncBackendHost::Core::NotifyResumed() { - DCHECK(!host_->using_new_syncer_thread_); - NotificationService::current()->Notify(NotificationType::SYNC_RESUMED, - NotificationService::AllSources(), - NotificationService::NoDetails()); -} - void SyncBackendHost::Core::NotifyPassphraseRequired(bool for_decryption) { if (!host_ || !host_->frontend_) return; @@ -586,19 +583,36 @@ void SyncBackendHost::Core::NotifyEncryptionComplete( host_->frontend_->OnEncryptionComplete(encrypted_types); } +void SyncBackendHost::Core::FinishConfigureDataTypes() { + host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, + &SyncBackendHost::Core::FinishConfigureDataTypesOnFrontendLoop)); +} + +void SyncBackendHost::Core::FinishConfigureDataTypesOnFrontendLoop() { + host_->FinishConfigureDataTypesOnFrontendLoop(); +} + + +void SyncBackendHost::Core::CreateSyncNotifier( + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter) { + const std::string& client_info = webkit_glue::GetUserAgent(GURL()); + SyncNotifierFactory sync_notifier_factory(client_info); + sync_notifier_.reset(sync_notifier_factory.CreateSyncNotifier( + *CommandLine::ForCurrentProcess(), + request_context_getter)); +} + SyncBackendHost::Core::DoInitializeOptions::DoInitializeOptions( const GURL& service_url, sync_api::HttpPostProviderFactory* http_bridge_factory, const sync_api::SyncCredentials& credentials, bool delete_sync_data_folder, - const notifier::NotifierOptions& notifier_options, - std::string restored_key_for_bootstrapping, + const std::string& restored_key_for_bootstrapping, bool setup_for_test_mode) : service_url(service_url), http_bridge_factory(http_bridge_factory), credentials(credentials), delete_sync_data_folder(delete_sync_data_folder), - notifier_options(notifier_options), restored_key_for_bootstrapping(restored_key_for_bootstrapping), setup_for_test_mode(setup_for_test_mode) { } @@ -657,7 +671,9 @@ SyncBackendHost::Core::Core(SyncBackendHost* backend) : host_(backend), syncapi_(new sync_api::SyncManager()), sync_manager_observer_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), - parent_router_(NULL) { + parent_router_(NULL), + processing_passphrase_(false), + deferred_nudge_for_cleanup_requested_(false) { } // Helper to construct a user agent string (ASCII) suitable for use by @@ -716,7 +732,7 @@ void SyncBackendHost::Core::DoInitialize(const DoInitializeOptions& options) { host_, // ModelSafeWorkerRegistrar. MakeUserAgentForSyncapi().c_str(), options.credentials, - options.notifier_options, + sync_notifier_.get(), options.restored_key_for_bootstrapping, options.setup_for_test_mode); DCHECK(success) << "Syncapi initialization failed!"; @@ -728,15 +744,17 @@ void SyncBackendHost::Core::DoUpdateCredentials( syncapi_->UpdateCredentials(credentials); } -void SyncBackendHost::Core::DoUpdateEnabledTypes( - const syncable::ModelTypeSet& types) { +void SyncBackendHost::Core::DoUpdateEnabledTypes() { DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); - syncapi_->UpdateEnabledTypes(types); + syncapi_->UpdateEnabledTypes(); } void SyncBackendHost::Core::DoStartSyncing() { DCHECK(MessageLoop::current() == host_->core_thread_.message_loop()); syncapi_->StartSyncing(); + if (deferred_nudge_for_cleanup_requested_) + syncapi_->RequestNudge(FROM_HERE); + deferred_nudge_for_cleanup_requested_ = false; } void SyncBackendHost::Core::DoSetPassphrase(const std::string& passphrase, @@ -761,6 +779,15 @@ void SyncBackendHost::Core::DoEncryptDataTypes( syncapi_->EncryptDataTypes(encrypted_types); } +void SyncBackendHost::Core::DoRequestConfig( + const syncable::ModelTypeBitSet& added_types) { + syncapi_->RequestConfig(added_types); +} + +void SyncBackendHost::Core::DoStartConfiguration(Callback0::Type* callback) { + syncapi_->StartConfigurationMode(callback); +} + UIModelWorker* SyncBackendHost::ui_worker() { ModelSafeWorker* w = registrar_.workers[GROUP_UI]; if (w == NULL) @@ -864,22 +891,29 @@ void SyncBackendHost::Core::HandleSyncCycleCompletedOnFrontendLoop( host_->last_snapshot_.reset(snapshot); + const syncable::ModelTypeSet& to_migrate = + snapshot->syncer_status.types_needing_local_migration; + if (!to_migrate.empty()) + host_->frontend_->OnMigrationNeededForTypes(to_migrate); + // If we are waiting for a configuration change, check here to see // if this sync cycle has initialized all of the types we've been // waiting for. - if (host_->configure_ready_task_.get()) { - bool found_all = true; + if (host_->pending_download_state_.get()) { + bool found_all_added = true; for (syncable::ModelTypeSet::const_iterator it = - host_->configure_initial_sync_types_.begin(); - it != host_->configure_initial_sync_types_.end(); ++it) { - found_all &= snapshot->initial_sync_ended.test(*it); + host_->pending_download_state_->initial_types.begin(); + it != host_->pending_download_state_->initial_types.end(); + ++it) { + if (host_->pending_download_state_->added_types.test(*it)) + found_all_added &= snapshot->initial_sync_ended.test(*it); } - - if (found_all) { - host_->configure_ready_task_->Run(); - host_->configure_ready_task_.reset(); - host_->configure_initial_sync_types_.clear(); + if (!found_all_added) { + NOTREACHED() << "Update didn't return updates for all types requested."; + } else { + host_->pending_download_state_->ready_task->Run(); } + host_->pending_download_state_.reset(); } host_->frontend_->OnSyncCycleCompleted(); } @@ -953,18 +987,6 @@ void SyncBackendHost::Core::OnPassphraseAccepted( bootstrap_token)); } -void SyncBackendHost::Core::OnPaused() { - host_->frontend_loop_->PostTask( - FROM_HERE, - NewRunnableMethod(this, &Core::NotifyPaused)); -} - -void SyncBackendHost::Core::OnResumed() { - host_->frontend_loop_->PostTask( - FROM_HERE, - NewRunnableMethod(this, &Core::NotifyResumed)); -} - void SyncBackendHost::Core::OnStopSyncingPermanently() { host_->frontend_loop_->PostTask(FROM_HERE, NewRunnableMethod(this, &Core::HandleStopSyncingPermanentlyOnFrontendLoop)); @@ -1047,22 +1069,15 @@ void SyncBackendHost::Core::StartSavingChanges() { this, &Core::SaveChanges); } -void SyncBackendHost::Core::DoRequestNudge() { - syncapi_->RequestNudge(); +void SyncBackendHost::Core::DoRequestNudge( + const tracked_objects::Location& nudge_location) { + syncapi_->RequestNudge(nudge_location); } void SyncBackendHost::Core::DoRequestClearServerData() { syncapi_->RequestClearServerData(); } -void SyncBackendHost::Core::DoRequestResume() { - syncapi_->RequestResume(); -} - -void SyncBackendHost::Core::DoRequestPause() { - syncapi()->RequestPause(); -} - void SyncBackendHost::Core::SaveChanges() { syncapi_->SaveChanges(); } @@ -1138,4 +1153,9 @@ void SyncBackendHost::Core::DoProcessMessage( syncapi_->GetJsBackend()->ProcessMessage(name, args, sender); } +void SyncBackendHost::Core::DeferNudgeForCleanup() { + DCHECK_EQ(MessageLoop::current(), host_->core_thread_.message_loop()); + deferred_nudge_for_cleanup_requested_ = true; +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index a822994..b5c2b70 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -11,8 +11,9 @@ #include <vector> #include "base/file_path.h" +#include "base/gtest_prod_util.h" +#include "base/memory/ref_counted.h" #include "base/message_loop.h" -#include "base/ref_counted.h" #include "base/synchronization/lock.h" #include "base/threading/thread.h" #include "base/timer.h" @@ -26,17 +27,20 @@ #include "chrome/browser/sync/js_event_router.h" #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/common/net/gaia/google_service_auth_error.h" -#include "chrome/common/net/url_request_context_getter.h" #include "googleurl/src/gurl.h" -#include "jingle/notifier/base/notifier_options.h" +#include "net/url_request/url_request_context_getter.h" class CancelableTask; class Profile; -namespace notifier { -struct NotifierOptions; +namespace net { +class URLRequestContextGetter; } +namespace sync_notifier { +class SyncNotifier; +} // namespace sync_notifier + namespace browser_sync { namespace sessions { @@ -91,6 +95,10 @@ class SyncFrontend { virtual void OnEncryptionComplete( const syncable::ModelTypeSet& encrypted_types) = 0; + // Called to perform migration of |types|. + virtual void OnMigrationNeededForTypes( + const syncable::ModelTypeSet& types) = 0; + protected: // Don't delete through SyncFrontend interface. virtual ~SyncFrontend() { @@ -126,16 +134,13 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { void Initialize(SyncFrontend* frontend, const GURL& service_url, const syncable::ModelTypeSet& types, - URLRequestContextGetter* baseline_context_getter, + net::URLRequestContextGetter* baseline_context_getter, const sync_api::SyncCredentials& credentials, - bool delete_sync_data_folder, - const notifier::NotifierOptions& notifier_options); + bool delete_sync_data_folder); // Called from |frontend_loop| to update SyncCredentials. void UpdateCredentials(const sync_api::SyncCredentials& credentials); - virtual void UpdateEnabledTypes(const syncable::ModelTypeSet& types); - // This starts the SyncerThread running a Syncer object to communicate with // sync servers. Until this is called, no changes will leave or enter this // browser from the cloud / sync servers. @@ -163,6 +168,10 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { const syncable::ModelTypeSet& types, CancelableTask* ready_task); + // Makes an asynchronous call to syncer to switch to config mode. When done + // syncer will call us back on FinishConfigureDataTypes. + virtual void StartConfiguration(Callback0::Type* callback); + // Encrypts the specified datatypes and marks them as needing encryption on // other machines. This affects all machines synced to this account and all // data belonging to the specified types. @@ -194,16 +203,6 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { void DeactivateDataType(DataTypeController* data_type_controller, ChangeProcessor* change_processor); - // Requests the backend to pause. Returns true if the request is - // sent sucessfully. When the backend does pause, a SYNC_PAUSED - // notification is sent to the notification service. - virtual bool RequestPause(); - - // Requests the backend to resume. Returns true if the request is - // sent sucessfully. When the backend does resume, a SYNC_RESUMED - // notification is sent to the notification service. - virtual bool RequestResume(); - // Asks the server to clear all data associated with ChromeSync. virtual bool RequestClearServerData(); @@ -245,7 +244,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // True if the cryptographer has any keys available to attempt decryption. // Could mean we've downloaded and loaded Nigori objects, or we bootstrapped // using a token previously received. - bool IsCryptographerReady() const; + bool IsCryptographerReady(const sync_api::BaseTransaction* trans) const; // Returns a pointer to the JsBackend (which is owned by the // service). Must be called only after the sync backend has been @@ -281,8 +280,6 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { virtual void OnPassphraseRequired(bool for_decryption); virtual void OnPassphraseFailed(); virtual void OnPassphraseAccepted(const std::string& bootstrap_token); - virtual void OnPaused(); - virtual void OnResumed(); virtual void OnStopSyncingPermanently(); virtual void OnUpdatedToken(const std::string& token); virtual void OnClearServerDataFailed(); @@ -308,8 +305,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { sync_api::HttpPostProviderFactory* http_bridge_factory, const sync_api::SyncCredentials& credentials, bool delete_sync_data_folder, - const notifier::NotifierOptions& notifier_options, - std::string restored_key_for_bootstrapping, + const std::string& restored_key_for_bootstrapping, bool setup_for_test_mode); ~DoInitializeOptions(); @@ -318,11 +314,14 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { sync_api::SyncCredentials credentials; std::string lsid; bool delete_sync_data_folder; - notifier::NotifierOptions notifier_options; std::string restored_key_for_bootstrapping; bool setup_for_test_mode; }; + // Called on |frontend_loop_|. + void CreateSyncNotifier(const scoped_refptr<net::URLRequestContextGetter>& + request_context_getter); + // Note: // // The Do* methods are the various entry points from our SyncBackendHost. @@ -337,9 +336,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // update on behalf of SyncBackendHost::UpdateCredentials void DoUpdateCredentials(const sync_api::SyncCredentials& credentials); - // Update the set of enabled sync types. Usually called when the user disables - // or enables a sync type. - void DoUpdateEnabledTypes(const syncable::ModelTypeSet& types); + // Called when the user disables or enables a sync type. + void DoUpdateEnabledTypes(); // Called on the SyncBackendHost core_thread_ to tell the syncapi to start // syncing (generally after initialization and authentication). @@ -347,11 +345,12 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // Called on the SyncBackendHost core_thread_ to nudge/pause/resume the // syncer. - void DoRequestNudge(); - void DoRequestPause(); - void DoRequestResume(); + void DoRequestNudge(const tracked_objects::Location& location); void DoRequestClearServerData(); + // Sets |deferred_nudge_for_cleanup_requested_| to true. See comment below. + void DeferNudgeForCleanup(); + // Called on our SyncBackendHost's |core_thread_| to set the passphrase // on behalf of SyncBackendHost::SupplyPassphrase. void DoSetPassphrase(const std::string& passphrase, bool is_explicit); @@ -377,6 +376,12 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // because the thread that was using them has exited (in step 2). void DoShutdown(bool stopping_sync); + // Posts a config request on the core thread. + virtual void DoRequestConfig(const syncable::ModelTypeBitSet& added_types); + + // Start the configuration mode. + virtual void DoStartConfiguration(Callback0::Type* callback); + // Set the base request context to use when making HTTP calls. // This method will add a reference to the context to persist it // on the IO thread. Must be removed from IO thread. @@ -396,6 +401,9 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { const std::string& name, const JsArgList& args, const JsEventHandler* sender); + // A callback from the SyncerThread when it is safe to continue config. + void FinishConfigureDataTypes(); + #if defined(UNIT_TEST) // Special form of initialization that does not try and authenticate the // last known user (since it will fail in test mode) and does some extra @@ -409,7 +417,7 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { credentials.sync_token = "token"; DoInitialize(DoInitializeOptions(GURL(), factory, credentials, delete_sync_data_folder, - notifier::NotifierOptions(), "", true)); + "", true)); } #endif @@ -422,15 +430,6 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // Return change processor for a particular model (return NULL on failure). ChangeProcessor* GetProcessor(syncable::ModelType modeltype); - - // Sends a SYNC_PAUSED notification to the notification service on - // the UI thread. - void NotifyPaused(); - - // Sends a SYNC_RESUMED notification to the notification service - // on the UI thread. - void NotifyResumed(); - // Invoked when initialization of syncapi is complete and we can start // our timer. // This must be called from the thread on which SaveChanges is intended to @@ -490,6 +489,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { const std::string& name, const JsArgList& args, const JsEventHandler* dst); + void FinishConfigureDataTypesOnFrontendLoop(); + // Return true if a model lives on the current thread. bool IsCurrentThreadSafeForModel(syncable::ModelType model_type); @@ -502,6 +503,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // The top-level syncapi entry point. scoped_ptr<sync_api::SyncManager> syncapi_; + scoped_ptr<sync_notifier::SyncNotifier> sync_notifier_; + JsSyncManagerObserver sync_manager_observer_; JsEventRouter* parent_router_; @@ -512,6 +515,10 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // modified from within the frontend_loop_ (UI thread). bool processing_passphrase_; + // True when a datatype has been disabled so that we nudge once sync is + // resumed (after configuration is finished). + bool deferred_nudge_for_cleanup_requested_; + DISALLOW_COPY_AND_ASSIGN(Core); }; @@ -521,17 +528,19 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { virtual void HandleInitializationCompletedOnFrontendLoop(); // Posts a nudge request on the core thread. - virtual void RequestNudge(); + virtual void RequestNudge(const tracked_objects::Location& location); - // Posts a config request on the core thread. - virtual void RequestConfig(const syncable::ModelTypeBitSet& added_types); + // Called to finish the job of ConfigureDataTypes once the syncer is in + // configuration mode. + void FinishConfigureDataTypes(); + void FinishConfigureDataTypesOnFrontendLoop(); // Allows tests to perform alternate core initialization work. virtual void InitCore(const Core::DoInitializeOptions& options); // Factory method for HttpPostProviderFactories. virtual sync_api::HttpPostProviderFactory* MakeHttpBridgeFactory( - URLRequestContextGetter* getter); + net::URLRequestContextGetter* getter); MessageLoop* core_loop() { return core_thread_.message_loop(); } @@ -548,15 +557,36 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { scoped_refptr<Core> core_; private: + FRIEND_TEST_ALL_PREFIXES(SyncBackendHostTest, MakePendingConfigModeState); + + struct PendingConfigureDataTypesState { + PendingConfigureDataTypesState(); + ~PendingConfigureDataTypesState(); + + // A task that should be called once data type configuration is + // complete. + scoped_ptr<CancelableTask> ready_task; + + // The set of types that we are waiting to be initially synced in a + // configuration cycle. + syncable::ModelTypeSet initial_types; + + // Additional details about which types were added / removed. + bool deleted_type; + syncable::ModelTypeBitSet added_types; + }; + UIModelWorker* ui_worker(); void ConfigureAutofillMigration(); - // Depending on switches::kUseNewSyncerThread, kicks the syncapi to respond - // to a change in the set of enabled data types. - void ScheduleSyncEventForConfigChange( - bool deleted_type, - const syncable::ModelTypeBitSet& added_types); + // Helper function for ConfigureDataTypes(). Caller owns return + // value. Takes ownership of |ready_task| (but not |routing_info|). + static PendingConfigureDataTypesState* MakePendingConfigModeState( + const DataTypeController::TypeMap& data_type_controllers, + const syncable::ModelTypeSet& types, + CancelableTask* ready_task, + ModelSafeRoutingInfo* routing_info); // A thread we dedicate for use by our Core to perform initialization, // authentication, handle messages from the syncapi, and periodically tell @@ -604,13 +634,8 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // Path of the folder that stores the sync data files. FilePath sync_data_folder_path_; - // A task that should be called once data type configuration is - // complete. - scoped_ptr<CancelableTask> configure_ready_task_; - - // The set of types that we are waiting to be initially synced in a - // configuration cycle. - syncable::ModelTypeSet configure_initial_sync_types_; + scoped_ptr<PendingConfigureDataTypesState> pending_download_state_; + scoped_ptr<PendingConfigureDataTypesState> pending_config_mode_state_; // UI-thread cache of the last AuthErrorState received from syncapi. GoogleServiceAuthError last_auth_error_; @@ -618,9 +643,6 @@ class SyncBackendHost : public browser_sync::ModelSafeWorkerRegistrar { // UI-thread cache of the last SyncSessionSnapshot received from syncapi. scoped_ptr<sessions::SyncSessionSnapshot> last_snapshot_; - // While two impls are in flight, using this for sanity checking. Bug 26339. - const bool using_new_syncer_thread_; - // Whether we've processed the initialization complete callback. bool syncapi_initialized_; diff --git a/chrome/browser/sync/glue/sync_backend_host_mock.cc b/chrome/browser/sync/glue/sync_backend_host_mock.cc index 2d618f7..8e16905 100644 --- a/chrome/browser/sync/glue/sync_backend_host_mock.cc +++ b/chrome/browser/sync/glue/sync_backend_host_mock.cc @@ -12,15 +12,6 @@ ACTION(InvokeTask) { } SyncBackendHostMock::SyncBackendHostMock() { - // By default, the RequestPause and RequestResume methods will - // send the confirmation notification and return true. - ON_CALL(*this, RequestPause()). - WillByDefault(testing::DoAll(Notify(NotificationType::SYNC_PAUSED), - testing::Return(true))); - ON_CALL(*this, RequestResume()). - WillByDefault(testing::DoAll(Notify(NotificationType::SYNC_RESUMED), - testing::Return(true))); - // By default, invoke the ready callback. ON_CALL(*this, ConfigureDataTypes(testing::_, testing::_, testing::_)). WillByDefault(InvokeTask()); diff --git a/chrome/browser/sync/glue/sync_backend_host_mock.h b/chrome/browser/sync/glue/sync_backend_host_mock.h index 12c6df1..a594674 100644 --- a/chrome/browser/sync/glue/sync_backend_host_mock.h +++ b/chrome/browser/sync/glue/sync_backend_host_mock.h @@ -8,10 +8,10 @@ #include <set> -#include "chrome/browser/sync/glue/sync_backend_host.h" #include "base/task.h" +#include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/profile_sync_test_util.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_type.h" #include "testing/gmock/include/gmock/gmock.h" namespace browser_sync { @@ -24,10 +24,7 @@ class SyncBackendHostMock : public SyncBackendHost { MOCK_METHOD3(ConfigureDataTypes, void(const DataTypeController::TypeMap&, const std::set<syncable::ModelType>&, CancelableTask*)); - MOCK_METHOD0(RequestPause, bool()); - MOCK_METHOD0(RequestResume, bool()); MOCK_METHOD0(StartSyncingWithServer, void()); - MOCK_METHOD1(UpdateEnabledTypes, void(const syncable::ModelTypeSet&)); }; } // namespace browser_sync diff --git a/chrome/browser/sync/glue/sync_backend_host_unittest.cc b/chrome/browser/sync/glue/sync_backend_host_unittest.cc new file mode 100644 index 0000000..c1b58ae --- /dev/null +++ b/chrome/browser/sync/glue/sync_backend_host_unittest.cc @@ -0,0 +1,194 @@ +// Copyright (c) 2011 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/sync/glue/sync_backend_host.h" + +#include <cstddef> + +#include "base/message_loop.h" +#include "base/scoped_ptr.h" +#include "chrome/browser/sync/engine/model_safe_worker.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/data_type_controller.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/test/testing_profile.h" +#include "content/browser/browser_thread.h" +#include "googleurl/src/gurl.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +// TODO(akalin): Remove this once we fix the TODO below. +#include "chrome/browser/prefs/pref_service.h" +#include "chrome/common/pref_names.h" + +namespace browser_sync { + +namespace { + +class MockSyncFrontend : public SyncFrontend { + public: + virtual ~MockSyncFrontend() {} + + MOCK_METHOD0(OnBackendInitialized, void()); + MOCK_METHOD0(OnSyncCycleCompleted, void()); + MOCK_METHOD0(OnAuthError, void()); + MOCK_METHOD0(OnStopSyncingPermanently, void()); + MOCK_METHOD0(OnClearServerDataSucceeded, void()); + MOCK_METHOD0(OnClearServerDataFailed, void()); + MOCK_METHOD1(OnPassphraseRequired, void(bool)); + MOCK_METHOD0(OnPassphraseAccepted, void()); + MOCK_METHOD1(OnEncryptionComplete, void(const syncable::ModelTypeSet&)); + MOCK_METHOD1(OnMigrationNeededForTypes, void(const syncable::ModelTypeSet&)); +}; + +} // namespace + +class SyncBackendHostTest : public testing::Test { + protected: + SyncBackendHostTest() + : ui_thread_(BrowserThread::UI, &ui_loop_) {} + + private: + MessageLoop ui_loop_; + BrowserThread ui_thread_; +}; + +TEST_F(SyncBackendHostTest, InitShutdown) { + TestingProfile profile; + profile.CreateRequestContext(); + + SyncBackendHost backend(&profile); + + // TODO(akalin): Handle this in SyncBackendHost instead of in + // ProfileSyncService, or maybe figure out a way to share the + // "register sync prefs" code. + PrefService* pref_service = profile.GetPrefs(); + pref_service->RegisterStringPref(prefs::kEncryptionBootstrapToken, ""); + + MockSyncFrontend mock_frontend; + sync_api::SyncCredentials credentials; + credentials.email = "user@example.com"; + credentials.sync_token = "sync_token"; + backend.Initialize(&mock_frontend, + GURL("http://www.example.com"), + syncable::ModelTypeSet(), + profile.GetRequestContext(), + credentials, + true); + backend.Shutdown(false); + // Scoping for io_thread to get destroyed before other locals. + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + // TODO(sanjeevr): Investigate whether we can do this within + // ResetRequestContext + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile.ResetRequestContext(); + } + MessageLoop::current()->RunAllPending(); +} + +TEST_F(SyncBackendHostTest, MakePendingConfigModeState) { + // Empty. + { + DataTypeController::TypeMap data_type_controllers; + syncable::ModelTypeSet types; + ModelSafeRoutingInfo routing_info; + + scoped_ptr<SyncBackendHost::PendingConfigureDataTypesState> + state(SyncBackendHost::MakePendingConfigModeState( + data_type_controllers, types, NULL, &routing_info)); + EXPECT_TRUE(routing_info.empty()); + EXPECT_FALSE(state->ready_task.get()); + EXPECT_EQ(types, state->initial_types); + EXPECT_FALSE(state->deleted_type); + EXPECT_TRUE(state->added_types.none()); + } + + // No enabled types. + { + DataTypeController::TypeMap data_type_controllers; + data_type_controllers[syncable::BOOKMARKS] = NULL; + syncable::ModelTypeSet types; + ModelSafeRoutingInfo routing_info; + + scoped_ptr<SyncBackendHost::PendingConfigureDataTypesState> + state(SyncBackendHost::MakePendingConfigModeState( + data_type_controllers, types, NULL, &routing_info)); + EXPECT_TRUE(routing_info.empty()); + EXPECT_FALSE(state->ready_task.get()); + EXPECT_EQ(types, state->initial_types); + EXPECT_TRUE(state->deleted_type); + EXPECT_TRUE(state->added_types.none()); + } + + // Add type. + { + DataTypeController::TypeMap data_type_controllers; + data_type_controllers[syncable::BOOKMARKS] = NULL; + syncable::ModelTypeSet types; + types.insert(syncable::BOOKMARKS); + ModelSafeRoutingInfo routing_info; + + scoped_ptr<SyncBackendHost::PendingConfigureDataTypesState> + state(SyncBackendHost::MakePendingConfigModeState( + data_type_controllers, types, NULL, &routing_info)); + + ModelSafeRoutingInfo expected_routing_info; + expected_routing_info[syncable::BOOKMARKS] = GROUP_PASSIVE; + EXPECT_EQ(expected_routing_info, routing_info); + EXPECT_FALSE(state->ready_task.get()); + EXPECT_EQ(types, state->initial_types); + EXPECT_FALSE(state->deleted_type); + + syncable::ModelTypeBitSet expected_added_types; + expected_added_types.set(syncable::BOOKMARKS); + EXPECT_EQ(expected_added_types, state->added_types); + } + + // Add existing type. + { + DataTypeController::TypeMap data_type_controllers; + data_type_controllers[syncable::BOOKMARKS] = NULL; + syncable::ModelTypeSet types; + types.insert(syncable::BOOKMARKS); + ModelSafeRoutingInfo routing_info; + routing_info[syncable::BOOKMARKS] = GROUP_PASSIVE; + ModelSafeRoutingInfo expected_routing_info = routing_info; + + scoped_ptr<SyncBackendHost::PendingConfigureDataTypesState> + state(SyncBackendHost::MakePendingConfigModeState( + data_type_controllers, types, NULL, &routing_info)); + + EXPECT_EQ(expected_routing_info, routing_info); + EXPECT_FALSE(state->ready_task.get()); + EXPECT_EQ(types, state->initial_types); + EXPECT_FALSE(state->deleted_type); + EXPECT_TRUE(state->added_types.none()); + } + + // Delete type. + { + DataTypeController::TypeMap data_type_controllers; + data_type_controllers[syncable::BOOKMARKS] = NULL; + syncable::ModelTypeSet types; + ModelSafeRoutingInfo routing_info; + routing_info[syncable::BOOKMARKS] = GROUP_PASSIVE; + + scoped_ptr<SyncBackendHost::PendingConfigureDataTypesState> + state(SyncBackendHost::MakePendingConfigModeState( + data_type_controllers, types, NULL, &routing_info)); + + ModelSafeRoutingInfo expected_routing_info; + EXPECT_EQ(expected_routing_info, routing_info); + EXPECT_FALSE(state->ready_task.get()); + EXPECT_EQ(types, state->initial_types); + EXPECT_TRUE(state->deleted_type); + EXPECT_TRUE(state->added_types.none()); + } +} + +// TODO(akalin): Write more SyncBackendHost unit tests. + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/synchronized_preferences.h b/chrome/browser/sync/glue/synchronized_preferences.h index 25135fa..beb8166 100644 --- a/chrome/browser/sync/glue/synchronized_preferences.h +++ b/chrome/browser/sync/glue/synchronized_preferences.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -22,13 +22,12 @@ static const char* kSynchronizedPreferences[] = { prefs::kHomePageIsNewTabPage, prefs::kHomePage, prefs::kShowHomeButton, - prefs::kShowPageOptionsButtons, // Default Search is not synced, needs a new data type. See // http://crbug.com/40482 // Options dialog: Personal Stuff tab. prefs::kPasswordManagerEnabled, - prefs::kAutoFillEnabled, + prefs::kAutofillEnabled, prefs::kUseCustomChromeFrame, // Options dialog: Under the hood -> Content Settings -> Cookies. @@ -77,7 +76,7 @@ static const char* kSynchronizedPreferences[] = { // Options dialog: Under the hood. prefs::kAlternateErrorPagesEnabled, prefs::kSearchSuggestEnabled, - prefs::kDnsPrefetchingEnabled, + prefs::kNetworkPredictionEnabled, prefs::kSafeBrowsingEnabled, prefs::kEnableTranslate, // Download directory not synced. @@ -96,7 +95,7 @@ static const char* kSynchronizedPreferences[] = { // Autofill dialog. #if defined(OS_MACOSX) - prefs::kAutoFillAuxiliaryProfilesEnabled, + prefs::kAutofillAuxiliaryProfilesEnabled, #endif // Translate preferences. @@ -110,8 +109,8 @@ static const char* kSynchronizedPreferences[] = { prefs::kDesktopNotificationAllowedOrigins, prefs::kDesktopNotificationDeniedOrigins, - // Cookie prompt dialog. - prefs::kCookiePromptExpanded, + // (Mac) Application menu. + prefs::kConfirmToQuitEnabled, #if defined(OS_CHROMEOS) // IME prefs @@ -186,6 +185,18 @@ static const char* kSynchronizedPreferences[] = { // Whether to show mobile plan notifications. // Settings -> Internet -> Mobile plan details prefs::kShowPlanNotifications, + + // Whether to require password to wake up from sleep + // Settings -> Personal Stuff -> Account + prefs::kEnableScreenLock, + + // Whether to enable tap-to-click + // Settings -> System -> Touchpad + prefs::kTapToClickEnabled, + + // Whether to use the 24-hour clock format. + // Settings -> System -> Date and Time + prefs::kUse24HourClock, #endif }; diff --git a/chrome/browser/sync/glue/theme_change_processor.cc b/chrome/browser/sync/glue/theme_change_processor.cc index 58f25c2..a75b7eb 100644 --- a/chrome/browser/sync/glue/theme_change_processor.cc +++ b/chrome/browser/sync/glue/theme_change_processor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,22 +9,14 @@ #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/theme_util.h" #include "chrome/browser/sync/protocol/theme_specifics.pb.h" -#include "chrome/browser/themes/browser_theme_provider.h" +#include "chrome/browser/themes/theme_service.h" +#include "chrome/browser/themes/theme_service_factory.h" #include "chrome/common/extensions/extension.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_source.h" +#include "content/common/notification_details.h" +#include "content/common/notification_source.h" namespace browser_sync { -namespace { -std::string GetThemeId(const Extension* current_theme) { - if (current_theme) { - DCHECK(current_theme->is_theme()); - } - return current_theme ? current_theme->id() : "default/system"; -} -} // namespace - ThemeChangeProcessor::ThemeChangeProcessor( UnrecoverableErrorHandler* error_handler) : ChangeProcessor(error_handler), @@ -39,79 +31,7 @@ void ThemeChangeProcessor::Observe(NotificationType type, const NotificationDetails& details) { DCHECK(running()); DCHECK(profile_); - const Extension* extension = NULL; - if (type == NotificationType::EXTENSION_UNLOADED) { - extension = Details<UnloadedExtensionInfo>(details)->extension; - } else { - extension = Details<const Extension>(details).ptr(); - } - std::string current_or_future_theme_id = - profile_->GetThemeProvider()->GetThemeID(); - const Extension* current_theme = profile_->GetTheme(); - switch (type.value) { - case NotificationType::BROWSER_THEME_CHANGED: - // We pay attention to this notification only when it signifies - // that a user changed the theme to the default/system theme, or - // when it signifies that a user changed the theme to a custom - // one that was already installed. Otherwise, current_theme - // still points to the previous theme until it gets installed - // and loaded (and we get an EXTENSION_LOADED notification). - VLOG(1) << "Got BROWSER_THEME_CHANGED notification for theme " - << GetThemeId(extension); - DCHECK_EQ(Source<BrowserThemeProvider>(source).ptr(), - profile_->GetThemeProvider()); - if (extension != NULL) { - DCHECK(extension->is_theme()); - DCHECK_EQ(extension->id(), current_or_future_theme_id); - if (!current_theme || (current_theme->id() != extension->id())) { - return; - } - } - break; - case NotificationType::EXTENSION_LOADED: - // We pay attention to this notification only when it signifies - // that a theme extension has been loaded because that means - // that the user set the current theme to a custom theme that - // needed to be downloaded and installed and that it was - // installed successfully. - DCHECK_EQ(Source<Profile>(source).ptr(), profile_); - CHECK(extension); - if (!extension->is_theme()) { - return; - } - VLOG(1) << "Got EXTENSION_LOADED notification for theme " - << extension->id(); - DCHECK_EQ(extension->id(), current_or_future_theme_id); - DCHECK_EQ(extension, current_theme); - break; - case NotificationType::EXTENSION_UNLOADED: - // We pay attention to this notification only when it signifies - // that a theme extension has been unloaded because that means - // that the user set the current theme to a custom theme and then - // changed his mind and undid it (reverting to the previous - // theme). - DCHECK_EQ(Source<Profile>(source).ptr(), profile_); - CHECK(extension); - if (!extension->is_theme()) { - return; - } - VLOG(1) << "Got EXTENSION_UNLOADED notification for theme " - << extension->id(); - extension = current_theme; - break; - default: - LOG(DFATAL) << "Unexpected notification received: " << type.value; - break; - } - - DCHECK_EQ(extension, current_theme); - if (extension) { - DCHECK(extension->is_theme()); - } - VLOG(1) << "Theme changed to " << GetThemeId(extension); - - // Here, we know that a theme is being set; the theme is a custom - // theme iff extension is non-NULL. + DCHECK(type == NotificationType::BROWSER_THEME_CHANGED); sync_api::WriteTransaction trans(share_handle()); sync_api::WriteNode node(&trans); @@ -198,17 +118,11 @@ void ThemeChangeProcessor::StopImpl() { void ThemeChangeProcessor::StartObserving() { DCHECK(profile_); - VLOG(1) << "Observing BROWSER_THEME_CHANGED, EXTENSION_LOADED, and " - "EXTENSION_UNLOADED"; + VLOG(1) << "Observing BROWSER_THEME_CHANGED"; notification_registrar_.Add( this, NotificationType::BROWSER_THEME_CHANGED, - Source<BrowserThemeProvider>(profile_->GetThemeProvider())); - notification_registrar_.Add( - this, NotificationType::EXTENSION_LOADED, - Source<Profile>(profile_)); - notification_registrar_.Add( - this, NotificationType::EXTENSION_UNLOADED, - Source<Profile>(profile_)); + Source<ThemeService>( + ThemeServiceFactory::GetForProfile(profile_))); } void ThemeChangeProcessor::StopObserving() { diff --git a/chrome/browser/sync/glue/theme_change_processor.h b/chrome/browser/sync/glue/theme_change_processor.h index a694c0b..7a94c92 100644 --- a/chrome/browser/sync/glue/theme_change_processor.h +++ b/chrome/browser/sync/glue/theme_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,9 +9,9 @@ #include "base/basictypes.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/change_processor.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_type.h" -#include "chrome/common/notification_registrar.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_type.h" class NotificationDetails; class NotificationSource; @@ -22,7 +22,7 @@ namespace browser_sync { class UnrecoverableErrorHandler; // This class is responsible for taking changes from the -// BrowserThemeProvider and applying them to the sync_api 'syncable' +// ThemeService and applying them to the sync_api 'syncable' // model, and vice versa. All operations and use of this class are // from the UI thread. class ThemeChangeProcessor : public ChangeProcessor, @@ -32,13 +32,13 @@ class ThemeChangeProcessor : public ChangeProcessor, virtual ~ThemeChangeProcessor(); // NotificationObserver implementation. - // BrowserThemeProvider -> sync_api model change application. + // ThemeService -> sync_api model change application. virtual void Observe(NotificationType type, const NotificationSource& source, const NotificationDetails& details); // ChangeProcessor implementation. - // sync_api model -> BrowserThemeProvider change application. + // sync_api model -> ThemeService change application. virtual void ApplyChangesFromSyncModel( const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, @@ -54,7 +54,7 @@ class ThemeChangeProcessor : public ChangeProcessor, void StopObserving(); NotificationRegistrar notification_registrar_; - // Owner of the BrowserThemeProvider. Non-NULL iff |running()| is + // Profile associated with the ThemeService. Non-NULL iff |running()| is // true. Profile* profile_; diff --git a/chrome/browser/sync/glue/theme_data_type_controller.cc b/chrome/browser/sync/glue/theme_data_type_controller.cc index 6abe990..efcbe11 100644 --- a/chrome/browser/sync/glue/theme_data_type_controller.cc +++ b/chrome/browser/sync/glue/theme_data_type_controller.cc @@ -1,19 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/theme_data_type_controller.h" #include "base/metrics/histogram.h" -#include "base/logging.h" -#include "base/time.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sync/glue/theme_change_processor.h" -#include "chrome/browser/sync/glue/theme_model_associator.h" -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" -#include "chrome/browser/sync/unrecoverable_error_handler.h" -#include "content/browser/browser_thread.h" namespace browser_sync { @@ -21,111 +14,42 @@ ThemeDataTypeController::ThemeDataTypeController( ProfileSyncFactory* profile_sync_factory, Profile* profile, ProfileSyncService* sync_service) - : profile_sync_factory_(profile_sync_factory), - profile_(profile), - sync_service_(sync_service), - state_(NOT_RUNNING) { - DCHECK(profile_sync_factory); - DCHECK(sync_service); + : FrontendDataTypeController(profile_sync_factory, + profile, + sync_service) { } ThemeDataTypeController::~ThemeDataTypeController() { } -void ThemeDataTypeController::Start(StartCallback* start_callback) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - DCHECK(start_callback); - if (state_ != NOT_RUNNING) { - start_callback->Run(BUSY); - delete start_callback; - return; - } +syncable::ModelType ThemeDataTypeController::type() const { + return syncable::THEMES; +} - start_callback_.reset(start_callback); +bool ThemeDataTypeController::StartModels() { + profile_->InitExtensions(true); + return true; +} - profile_->InitExtensions(); +void ThemeDataTypeController::CreateSyncComponents() { ProfileSyncFactory::SyncComponents sync_components = profile_sync_factory_->CreateThemeSyncComponents(sync_service_, - this); + this); model_associator_.reset(sync_components.model_associator); change_processor_.reset(sync_components.change_processor); - - bool sync_has_nodes = false; - if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { - StartFailed(UNRECOVERABLE_ERROR); - return; - } - - base::TimeTicks start_time = base::TimeTicks::Now(); - bool merge_success = model_associator_->AssociateModels(); - UMA_HISTOGRAM_TIMES("Sync.ThemeAssociationTime", - base::TimeTicks::Now() - start_time); - if (!merge_success) { - StartFailed(ASSOCIATION_FAILED); - return; - } - - sync_service_->ActivateDataType(this, change_processor_.get()); - state_ = RUNNING; - FinishStart(!sync_has_nodes ? OK_FIRST_RUN : OK); -} - -void ThemeDataTypeController::Stop() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - - if (change_processor_ != NULL) - sync_service_->DeactivateDataType(this, change_processor_.get()); - - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - - change_processor_.reset(); - model_associator_.reset(); - start_callback_.reset(); - - state_ = NOT_RUNNING; } -bool ThemeDataTypeController::enabled() { - return true; - } - -syncable::ModelType ThemeDataTypeController::type() { - return syncable::THEMES; -} - -browser_sync::ModelSafeGroup ThemeDataTypeController::model_safe_group() { - return browser_sync::GROUP_UI; -} - -const char* ThemeDataTypeController::name() const { - // For logging only. - return "theme"; -} - -DataTypeController::State ThemeDataTypeController::state() { - return state_; -} - - -void ThemeDataTypeController::OnUnrecoverableError( +void ThemeDataTypeController::RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message) { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); UMA_HISTOGRAM_COUNTS("Sync.ThemeRunFailures", 1); - sync_service_->OnUnrecoverableError(from_here, message); } -void ThemeDataTypeController::FinishStart(StartResult result) { - start_callback_->Run(result); - start_callback_.reset(); +void ThemeDataTypeController::RecordAssociationTime(base::TimeDelta time) { + UMA_HISTOGRAM_TIMES("Sync.ThemeAssociationTime", time); } -void ThemeDataTypeController::StartFailed(StartResult result) { - model_associator_.reset(); - change_processor_.reset(); - start_callback_->Run(result); - start_callback_.reset(); +void ThemeDataTypeController::RecordStartFailure(StartResult result) { UMA_HISTOGRAM_ENUMERATION("Sync.ThemeStartFailures", result, MAX_START_RESULT); diff --git a/chrome/browser/sync/glue/theme_data_type_controller.h b/chrome/browser/sync/glue/theme_data_type_controller.h index 09bc572..37f5ceb 100644 --- a/chrome/browser/sync/glue/theme_data_type_controller.h +++ b/chrome/browser/sync/glue/theme_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,20 +8,11 @@ #include <string> -#include "base/basictypes.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/glue/data_type_controller.h" - -class Profile; -class ProfileSyncFactory; -class ProfileSyncService; +#include "chrome/browser/sync/glue/frontend_data_type_controller.h" namespace browser_sync { -class AssociatorInterface; -class ChangeProcessor; - -class ThemeDataTypeController : public DataTypeController { +class ThemeDataTypeController : public FrontendDataTypeController { public: ThemeDataTypeController( ProfileSyncFactory* profile_sync_factory, @@ -30,42 +21,17 @@ class ThemeDataTypeController : public DataTypeController { virtual ~ThemeDataTypeController(); // DataTypeController implementation. - virtual void Start(StartCallback* start_callback); - - virtual void Stop(); - - virtual bool enabled(); - - virtual syncable::ModelType type(); - - virtual browser_sync::ModelSafeGroup model_safe_group(); - - virtual const char* name() const; - - virtual State state(); + virtual syncable::ModelType type() const; - // UnrecoverableErrorHandler interface. - virtual void OnUnrecoverableError( + private: + // DataTypeController implementations. + virtual bool StartModels(); + virtual void CreateSyncComponents(); + virtual void RecordUnrecoverableError( const tracked_objects::Location& from_here, const std::string& message); - - private: - // Helper method to run the stashed start callback with a given result. - void FinishStart(StartResult result); - - // Cleans up state and calls callback when start fails. - void StartFailed(StartResult result); - - ProfileSyncFactory* profile_sync_factory_; - Profile* profile_; - ProfileSyncService* sync_service_; - - State state_; - - scoped_ptr<StartCallback> start_callback_; - scoped_ptr<AssociatorInterface> model_associator_; - scoped_ptr<ChangeProcessor> change_processor_; - + virtual void RecordAssociationTime(base::TimeDelta time); + virtual void RecordStartFailure(StartResult result); DISALLOW_COPY_AND_ASSIGN(ThemeDataTypeController); }; diff --git a/chrome/browser/sync/glue/theme_data_type_controller_unittest.cc b/chrome/browser/sync/glue/theme_data_type_controller_unittest.cc index 16a91b4..4d2a1ad 100644 --- a/chrome/browser/sync/glue/theme_data_type_controller_unittest.cc +++ b/chrome/browser/sync/glue/theme_data_type_controller_unittest.cc @@ -1,12 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" #include "base/callback.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/task.h" #include "chrome/browser/sync/glue/theme_data_type_controller.h" #include "chrome/browser/sync/glue/change_processor_mock.h" @@ -28,7 +28,8 @@ using testing::SetArgumentPointee; class StartCallback { public: - MOCK_METHOD1(Run, void(DataTypeController::StartResult result)); + MOCK_METHOD2(Run, void(DataTypeController::StartResult result, + const tracked_objects::Location& location)); }; class ThemeDataTypeControllerTest : public testing::Test { @@ -53,6 +54,8 @@ class ThemeDataTypeControllerTest : public testing::Test { } void SetAssociateExpectations() { + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); EXPECT_CALL(*model_associator_, AssociateModels()). @@ -84,7 +87,7 @@ TEST_F(ThemeDataTypeControllerTest, Start) { SetAssociateExpectations(); SetActivateExpectations(); EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); theme_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, theme_dtc_->state()); } @@ -95,7 +98,7 @@ TEST_F(ThemeDataTypeControllerTest, StartFirstRun) { SetActivateExpectations(); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK_FIRST_RUN, _)); theme_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -106,7 +109,7 @@ TEST_F(ThemeDataTypeControllerTest, StartOk) { EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(true), Return(true))); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); theme_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); } @@ -116,7 +119,7 @@ TEST_F(ThemeDataTypeControllerTest, StartAssociationFailed) { EXPECT_CALL(*model_associator_, AssociateModels()). WillRepeatedly(Return(false)); - EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED)); + EXPECT_CALL(start_callback_, Run(DataTypeController::ASSOCIATION_FAILED, _)); theme_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); } @@ -125,9 +128,11 @@ TEST_F(ThemeDataTypeControllerTest, StartAssociationTriggersUnrecoverableError) { SetStartExpectations(); // Set up association to fail with an unrecoverable error. + EXPECT_CALL(*model_associator_, CryptoReadyIfNecessary()). + WillRepeatedly(Return(true)); EXPECT_CALL(*model_associator_, SyncModelHasUserCreatedNodes(_)). WillRepeatedly(DoAll(SetArgumentPointee<0>(false), Return(false))); - EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR)); + EXPECT_CALL(start_callback_, Run(DataTypeController::UNRECOVERABLE_ERROR, _)); theme_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); } @@ -140,7 +145,7 @@ TEST_F(ThemeDataTypeControllerTest, Stop) { EXPECT_EQ(DataTypeController::NOT_RUNNING, theme_dtc_->state()); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); theme_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); EXPECT_EQ(DataTypeController::RUNNING, theme_dtc_->state()); theme_dtc_->Stop(); @@ -159,7 +164,7 @@ TEST_F(ThemeDataTypeControllerTest, OnUnrecoverableError) { &ThemeDataTypeController::Stop)); SetStopExpectations(); - EXPECT_CALL(start_callback_, Run(DataTypeController::OK)); + EXPECT_CALL(start_callback_, Run(DataTypeController::OK, _)); theme_dtc_->Start(NewCallback(&start_callback_, &StartCallback::Run)); // This should cause theme_dtc_->Stop() to be called. theme_dtc_->OnUnrecoverableError(FROM_HERE, "Test"); diff --git a/chrome/browser/sync/glue/theme_model_associator.cc b/chrome/browser/sync/glue/theme_model_associator.cc index 6b51af2..6fcbf00 100644 --- a/chrome/browser/sync/glue/theme_model_associator.cc +++ b/chrome/browser/sync/glue/theme_model_associator.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -12,7 +12,6 @@ #include "chrome/browser/sync/glue/theme_util.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/theme_specifics.pb.h" -#include "chrome/browser/themes/browser_theme_provider.h" namespace browser_sync { @@ -94,4 +93,13 @@ bool ThemeModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { return true; } +bool ThemeModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + syncable::ModelTypeSet encrypted_types; + sync_service_->GetEncryptedDataTypes(&encrypted_types); + return encrypted_types.count(syncable::THEMES) == 0 || + sync_service_->IsCryptographerReady(&trans); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/theme_model_associator.h b/chrome/browser/sync/glue/theme_model_associator.h index 8e17724..e76b5bd 100644 --- a/chrome/browser/sync/glue/theme_model_associator.h +++ b/chrome/browser/sync/glue/theme_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -34,6 +34,7 @@ class ThemeModelAssociator : public AssociatorInterface { // No implementation needed, this associator runs on the main // thread. } + virtual bool CryptoReadyIfNecessary(); private: ProfileSyncService* sync_service_; diff --git a/chrome/browser/sync/glue/theme_util.cc b/chrome/browser/sync/glue/theme_util.cc index 2e91f02..f43257d 100644 --- a/chrome/browser/sync/glue/theme_util.cc +++ b/chrome/browser/sync/glue/theme_util.cc @@ -7,15 +7,17 @@ #include <string> #include "base/logging.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "chrome/browser/extensions/extension_install_ui.h" #include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/extensions/extension_updater.h" #if defined(TOOLKIT_USES_GTK) -#include "chrome/browser/ui/gtk/gtk_theme_provider.h" +#include "chrome/browser/ui/gtk/gtk_theme_service.h" #endif #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/protocol/theme_specifics.pb.h" +#include "chrome/browser/themes/theme_service.h" +#include "chrome/browser/themes/theme_service_factory.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "googleurl/src/gurl.h" @@ -36,7 +38,7 @@ bool IsSystemThemeDistinctFromDefaultTheme() { bool UseSystemTheme(Profile* profile) { #if defined(TOOLKIT_USES_GTK) - return GtkThemeProvider::GetFrom(profile)->UseGtkTheme(); + return GtkThemeService::GetFrom(profile)->UseGtkTheme(); #else return false; #endif @@ -91,7 +93,8 @@ void SetCurrentThemeFromThemeSpecifics( std::string id(theme_specifics.custom_theme_id()); GURL update_url(theme_specifics.custom_theme_update_url()); VLOG(1) << "Applying theme " << id << " with update_url " << update_url; - ExtensionService* extensions_service = profile->GetExtensionService(); + ExtensionServiceInterface* extensions_service = + profile->GetExtensionService(); CHECK(extensions_service); const Extension* extension = extensions_service->GetExtensionById(id, true); if (extension) { @@ -99,21 +102,15 @@ void SetCurrentThemeFromThemeSpecifics( VLOG(1) << "Extension " << id << " is not a theme; aborting"; return; } - ExtensionPrefs* extension_prefs = extensions_service->extension_prefs(); - CHECK(extension_prefs); - // TODO(akalin): GetExtensionState() isn't very safe as it - // returns Extension::ENABLED by default; either change it to - // return something else by default or create a separate - // function that does so. - if (extension_prefs->GetExtensionState(extension->id()) != - Extension::ENABLED) { + if (!extensions_service->IsExtensionEnabled(id)) { VLOG(1) << "Theme " << id << " is not enabled; aborting"; return; } // Get previous theme info before we set the new theme. std::string previous_theme_id; { - const Extension* current_theme = profile->GetTheme(); + const Extension* current_theme = + ThemeServiceFactory::GetThemeForProfile(profile); if (current_theme) { DCHECK(current_theme->is_theme()); previous_theme_id = current_theme->id(); @@ -122,7 +119,7 @@ void SetCurrentThemeFromThemeSpecifics( bool previous_use_system_theme = UseSystemTheme(profile); // An enabled theme extension with the given id was found, so // just set the current theme to it. - profile->SetTheme(extension); + ThemeServiceFactory::GetForProfile(profile)->SetTheme(extension); // Pretend the theme was just installed. ExtensionInstallUI::ShowThemeInfoBar( previous_theme_id, previous_use_system_theme, @@ -137,31 +134,24 @@ void SetCurrentThemeFromThemeSpecifics( const bool kInstallSilently = false; const bool kEnableOnInstall = true; const bool kEnableIncognitoOnInstall = false; - extensions_service->AddPendingExtensionFromSync( + extensions_service->pending_extension_manager()->AddFromSync( id, update_url, &IsTheme, kInstallSilently, kEnableOnInstall, kEnableIncognitoOnInstall); - ExtensionUpdater* extension_updater = extensions_service->updater(); - // Auto-updates should now be on always (see the construction of - // the ExtensionService in ProfileImpl::InitExtensions()). - if (!extension_updater) { - LOG(DFATAL) << "Extension updater unexpectedly NULL; " - << "auto-updates may be turned off"; - return; - } - extension_updater->CheckNow(); + extensions_service->CheckForUpdatesSoon(); } } else if (theme_specifics.use_system_theme_by_default()) { - profile->SetNativeTheme(); + ThemeServiceFactory::GetForProfile(profile)->SetNativeTheme(); } else { - profile->ClearTheme(); + ThemeServiceFactory::GetForProfile(profile)->UseDefaultTheme(); } } bool UpdateThemeSpecificsOrSetCurrentThemeIfNecessary( Profile* profile, sync_pb::ThemeSpecifics* theme_specifics) { if (!theme_specifics->use_custom_theme() && - (profile->GetTheme() || (UseSystemTheme(profile) && - IsSystemThemeDistinctFromDefaultTheme()))) { + (ThemeServiceFactory::GetThemeForProfile(profile) || + (UseSystemTheme(profile) && + IsSystemThemeDistinctFromDefaultTheme()))) { GetThemeSpecificsFromCurrentTheme(profile, theme_specifics); return true; } else { @@ -174,7 +164,8 @@ void GetThemeSpecificsFromCurrentTheme( Profile* profile, sync_pb::ThemeSpecifics* theme_specifics) { DCHECK(profile); - const Extension* current_theme = profile->GetTheme(); + const Extension* current_theme = + ThemeServiceFactory::GetThemeForProfile(profile); if (current_theme) { DCHECK(current_theme->is_theme()); } diff --git a/chrome/browser/sync/glue/theme_util_unittest.cc b/chrome/browser/sync/glue/theme_util_unittest.cc index dfa3ea3..d72eb4a 100644 --- a/chrome/browser/sync/glue/theme_util_unittest.cc +++ b/chrome/browser/sync/glue/theme_util_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,6 +8,8 @@ #include "base/values.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/protocol/theme_specifics.pb.h" +#include "chrome/browser/themes/theme_service.h" +#include "chrome/browser/themes/theme_service_factory.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" #include "chrome/test/testing_profile.h" @@ -33,7 +35,8 @@ scoped_refptr<Extension> MakeThemeExtension(const FilePath& extension_path, source.SetString(extension_manifest_keys::kVersion, "0.0.0.0"); std::string error; scoped_refptr<Extension> extension = Extension::Create( - extension_path, Extension::INTERNAL, source, false, true, &error); + extension_path, Extension::INTERNAL, source, + Extension::STRICT_ERROR_CHECKS, &error); EXPECT_TRUE(extension); EXPECT_EQ("", error); return extension; @@ -88,30 +91,37 @@ TEST_F(ThemeUtilTest, AreThemeSpecificsEqualHelper) { EXPECT_TRUE(AreThemeSpecificsEqualHelper(a, b, true)); } -class MockProfile : public TestingProfile { +class MockThemeService : public ThemeService { public: MOCK_METHOD0(SetNativeTheme, void()); - MOCK_METHOD0(ClearTheme, void()); - MOCK_METHOD0(GetTheme, Extension*()); + MOCK_METHOD0(UseDefaultTheme, void()); + MOCK_CONST_METHOD0(GetThemeID, std::string()); }; TEST_F(ThemeUtilTest, SetCurrentThemeDefaultTheme) { sync_pb::ThemeSpecifics theme_specifics; + TestingProfile profile; + MockThemeService* mock_theme_service = new MockThemeService; + ThemeServiceFactory::GetInstance()->ForceAssociationBetween(&profile, + mock_theme_service); - MockProfile mock_profile; - EXPECT_CALL(mock_profile, ClearTheme()).Times(1); + EXPECT_CALL(*mock_theme_service, UseDefaultTheme()).Times(1); - SetCurrentThemeFromThemeSpecifics(theme_specifics, &mock_profile); + SetCurrentThemeFromThemeSpecifics(theme_specifics, &profile); } TEST_F(ThemeUtilTest, SetCurrentThemeSystemTheme) { sync_pb::ThemeSpecifics theme_specifics; theme_specifics.set_use_system_theme_by_default(true); - MockProfile mock_profile; - EXPECT_CALL(mock_profile, SetNativeTheme()).Times(1); + TestingProfile profile; + MockThemeService* mock_theme_service = new MockThemeService; + ThemeServiceFactory::GetInstance()->ForceAssociationBetween(&profile, + mock_theme_service); - SetCurrentThemeFromThemeSpecifics(theme_specifics, &mock_profile); + EXPECT_CALL(*mock_theme_service, SetNativeTheme()).Times(1); + + SetCurrentThemeFromThemeSpecifics(theme_specifics, &profile); } // TODO(akalin): Make ExtensionService/ExtensionUpdater testable @@ -205,15 +215,19 @@ TEST_F(ThemeUtilTest, GetThemeSpecificsHelperCustomThemeDistinct) { } TEST_F(ThemeUtilTest, SetCurrentThemeIfNecessaryDefaultThemeNotNecessary) { - MockProfile mock_profile; - Extension* extension = NULL; - EXPECT_CALL(mock_profile, GetTheme()).WillOnce(Return(extension)); + TestingProfile profile; + MockThemeService* mock_theme_service = new MockThemeService; + ThemeServiceFactory::GetInstance()->ForceAssociationBetween(&profile, + mock_theme_service); + + EXPECT_CALL(*mock_theme_service, GetThemeID()).WillRepeatedly(Return( + ThemeService::kDefaultThemeID)); // TODO(akalin): Mock out call to GetPrefs() under TOOLKIT_USES_GTK. sync_pb::ThemeSpecifics theme_specifics; SetCurrentThemeFromThemeSpecificsIfNecessary(theme_specifics, - &mock_profile); + &profile); } } // namespace diff --git a/chrome/browser/sync/glue/typed_url_change_processor.cc b/chrome/browser/sync/glue/typed_url_change_processor.cc index 4651d52..c30a43d 100644 --- a/chrome/browser/sync/glue/typed_url_change_processor.cc +++ b/chrome/browser/sync/glue/typed_url_change_processor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -12,8 +12,8 @@ #include "chrome/browser/sync/glue/typed_url_model_associator.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_service.h" +#include "content/common/notification_type.h" namespace browser_sync { diff --git a/chrome/browser/sync/glue/typed_url_change_processor.h b/chrome/browser/sync/glue/typed_url_change_processor.h index 7c81588..501ee0e 100644 --- a/chrome/browser/sync/glue/typed_url_change_processor.h +++ b/chrome/browser/sync/glue/typed_url_change_processor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,12 +9,12 @@ #include "chrome/browser/sync/glue/change_processor.h" #include "base/basictypes.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/time.h" #include "chrome/browser/sync/glue/sync_backend_host.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_type.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_type.h" class MessageLoop; class NotificationService; diff --git a/chrome/browser/sync/glue/typed_url_data_type_controller.cc b/chrome/browser/sync/glue/typed_url_data_type_controller.cc index 316c245..93be84d 100644 --- a/chrome/browser/sync/glue/typed_url_data_type_controller.cc +++ b/chrome/browser/sync/glue/typed_url_data_type_controller.cc @@ -1,21 +1,21 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/glue/typed_url_data_type_controller.h" -#include "base/metrics/histogram.h" #include "base/logging.h" +#include "base/metrics/histogram.h" #include "base/task.h" #include "base/time.h" #include "chrome/browser/history/history.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/glue/typed_url_change_processor.h" #include "chrome/browser/sync/glue/typed_url_model_associator.h" -#include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" -#include "chrome/common/notification_service.h" +#include "chrome/browser/sync/profile_sync_service.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_service.h" namespace browser_sync { @@ -52,7 +52,10 @@ TypedUrlDataTypeController::TypedUrlDataTypeController( : profile_sync_factory_(profile_sync_factory), profile_(profile), sync_service_(sync_service), - state_(NOT_RUNNING) { + state_(NOT_RUNNING), + abort_association_(false), + abort_association_complete_(false, false), + datatype_stopped_(false, false) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(profile_sync_factory); DCHECK(profile); @@ -68,12 +71,13 @@ void TypedUrlDataTypeController::Start(StartCallback* start_callback) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(start_callback); if (state_ != NOT_RUNNING || start_callback_.get()) { - start_callback->Run(BUSY); + start_callback->Run(BUSY, FROM_HERE); delete start_callback; return; } start_callback_.reset(start_callback); + abort_association_ = false; HistoryService* history = profile_->GetHistoryServiceWithoutCreating(); if (history) { @@ -100,39 +104,65 @@ void TypedUrlDataTypeController::Observe(NotificationType type, history_service_->ScheduleDBTask(new ControlTask(this, true), this); } +// TODO(sync): Blocking the UI thread at shutdown is bad. If we had a way of +// distinguishing chrome shutdown from sync shutdown, we should be able to avoid +// this (http://crbug.com/55662). Further, all this functionality should be +// abstracted to a higher layer, where we could ensure all datatypes are doing +// the same thing (http://crbug.com/76232). void TypedUrlDataTypeController::Stop() { VLOG(1) << "Stopping typed_url data type controller."; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); + // If Stop() is called while Start() is waiting for association to + // complete, we need to abort the association and wait for the DB + // thread to finish the StartImpl() task. + if (state_ == ASSOCIATING) { + { + base::AutoLock lock(abort_association_lock_); + abort_association_ = true; + if (model_associator_.get()) + model_associator_->AbortAssociation(); + } + // Wait for the model association to abort. + abort_association_complete_.Wait(); + StartDoneImpl(ABORTED, STOPPING); + } + + // If Stop() is called while Start() is waiting for the history service to + // load, abort the start. + if (state_ == MODEL_STARTING) + StartDoneImpl(ABORTED, STOPPING); + + DCHECK(!start_callback_.get()); + if (change_processor_ != NULL) sync_service_->DeactivateDataType(this, change_processor_.get()); - if (model_associator_ != NULL) - model_associator_->DisassociateModels(); - set_state(NOT_RUNNING); DCHECK(history_service_.get()); history_service_->ScheduleDBTask(new ControlTask(this, false), this); + datatype_stopped_.Wait(); } bool TypedUrlDataTypeController::enabled() { return true; } -syncable::ModelType TypedUrlDataTypeController::type() { +syncable::ModelType TypedUrlDataTypeController::type() const { return syncable::TYPED_URLS; } -browser_sync::ModelSafeGroup TypedUrlDataTypeController::model_safe_group() { +browser_sync::ModelSafeGroup TypedUrlDataTypeController::model_safe_group() + const { return browser_sync::GROUP_HISTORY; } -const char* TypedUrlDataTypeController::name() const { +std::string TypedUrlDataTypeController::name() const { // For logging only. return "typed_url"; } -DataTypeController::State TypedUrlDataTypeController::state() { +DataTypeController::State TypedUrlDataTypeController::state() const { return state_; } @@ -140,13 +170,25 @@ void TypedUrlDataTypeController::StartImpl(history::HistoryBackend* backend) { VLOG(1) << "TypedUrl data type controller StartImpl called."; // No additional services need to be started before we can proceed // with model association. - ProfileSyncFactory::SyncComponents sync_components = - profile_sync_factory_->CreateTypedUrlSyncComponents( - sync_service_, - backend, - this); - model_associator_.reset(sync_components.model_associator); - change_processor_.reset(sync_components.change_processor); + { + base::AutoLock lock(abort_association_lock_); + if (abort_association_) { + abort_association_complete_.Signal(); + return; + } + ProfileSyncFactory::SyncComponents sync_components = + profile_sync_factory_->CreateTypedUrlSyncComponents( + sync_service_, + backend, + this); + model_associator_.reset(sync_components.model_associator); + change_processor_.reset(sync_components.change_processor); + } + + if (!model_associator_->CryptoReadyIfNecessary()) { + StartFailed(NEEDS_CRYPTO); + return; + } bool sync_has_nodes = false; if (!model_associator_->SyncModelHasUserCreatedNodes(&sync_has_nodes)) { @@ -171,12 +213,17 @@ void TypedUrlDataTypeController::StartDone( DataTypeController::StartResult result, DataTypeController::State new_state) { VLOG(1) << "TypedUrl data type controller StartDone called."; - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - NewRunnableMethod( - this, - &TypedUrlDataTypeController::StartDoneImpl, - result, - new_state)); + + abort_association_complete_.Signal(); + base::AutoLock lock(abort_association_lock_); + if (!abort_association_) { + BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, + NewRunnableMethod( + this, + &TypedUrlDataTypeController::StartDoneImpl, + result, + new_state)); + } } void TypedUrlDataTypeController::StartDoneImpl( @@ -185,7 +232,7 @@ void TypedUrlDataTypeController::StartDoneImpl( VLOG(1) << "TypedUrl data type controller StartDoneImpl called."; DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); set_state(new_state); - start_callback_->Run(result); + start_callback_->Run(result, FROM_HERE); start_callback_.reset(); if (result == UNRECOVERABLE_ERROR || result == ASSOCIATION_FAILED) { @@ -198,10 +245,13 @@ void TypedUrlDataTypeController::StartDoneImpl( void TypedUrlDataTypeController::StopImpl() { VLOG(1) << "TypedUrl data type controller StopImpl called."; + if (model_associator_ != NULL) + model_associator_->DisassociateModels(); + change_processor_.reset(); model_associator_.reset(); - state_ = NOT_RUNNING; + datatype_stopped_.Signal(); } void TypedUrlDataTypeController::StartFailed(StartResult result) { diff --git a/chrome/browser/sync/glue/typed_url_data_type_controller.h b/chrome/browser/sync/glue/typed_url_data_type_controller.h index edffa3b..386a01d 100644 --- a/chrome/browser/sync/glue/typed_url_data_type_controller.h +++ b/chrome/browser/sync/glue/typed_url_data_type_controller.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,13 +9,14 @@ #include <string> #include "base/basictypes.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/profile_sync_service.h" +#include "base/memory/scoped_ptr.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_type.h" +#include "chrome/browser/sync/profile_sync_service.h" #include "content/browser/cancelable_request.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_type.h" class NotificationSource; class NotificationDetails; @@ -52,13 +53,13 @@ class TypedUrlDataTypeController : public DataTypeController, virtual bool enabled(); - virtual syncable::ModelType type(); + virtual syncable::ModelType type() const; - virtual browser_sync::ModelSafeGroup model_safe_group(); + virtual browser_sync::ModelSafeGroup model_safe_group() const; - virtual const char* name() const; + virtual std::string name() const; - virtual State state(); + virtual State state() const; // UnrecoverableHandler implementation virtual void OnUnrecoverableError(const tracked_objects::Location& from_here, @@ -109,6 +110,14 @@ class TypedUrlDataTypeController : public DataTypeController, NotificationRegistrar notification_registrar_; + base::Lock abort_association_lock_; + bool abort_association_; + base::WaitableEvent abort_association_complete_; + + // Barrier to ensure that the datatype has been stopped on the DB thread + // from the UI thread. + base::WaitableEvent datatype_stopped_; + DISALLOW_COPY_AND_ASSIGN(TypedUrlDataTypeController); }; diff --git a/chrome/browser/sync/glue/typed_url_model_associator.cc b/chrome/browser/sync/glue/typed_url_model_associator.cc index 02144fc..2c05599 100644 --- a/chrome/browser/sync/glue/typed_url_model_associator.cc +++ b/chrome/browser/sync/glue/typed_url_model_associator.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -177,7 +177,7 @@ bool TypedUrlModelAssociator::AssociateModels() { // the history database after closing the write transaction, since // this is the only thread that writes to the database. We also don't have // to worry about the sync model getting out of sync, because changes are - // propogated to the ChangeProcessor on this thread. + // propagated to the ChangeProcessor on this thread. return WriteToHistoryBackend(&titles, &new_urls, &updated_urls, &new_visits, NULL); } @@ -487,4 +487,13 @@ void TypedUrlModelAssociator::DiffVisits( } } +bool TypedUrlModelAssociator::CryptoReadyIfNecessary() { + // We only access the cryptographer while holding a transaction. + sync_api::ReadTransaction trans(sync_service_->GetUserShare()); + syncable::ModelTypeSet encrypted_types; + sync_service_->GetEncryptedDataTypes(&encrypted_types); + return encrypted_types.count(syncable::TYPED_URLS) == 0 || + sync_service_->IsCryptographerReady(&trans); +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/typed_url_model_associator.h b/chrome/browser/sync/glue/typed_url_model_associator.h index 0ab3533..e9705a6 100644 --- a/chrome/browser/sync/glue/typed_url_model_associator.h +++ b/chrome/browser/sync/glue/typed_url_model_associator.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -75,6 +75,8 @@ class TypedUrlModelAssociator virtual void AbortAssociation(); + virtual bool CryptoReadyIfNecessary(); + // Not implemented. virtual const std::string* GetChromeNodeFromSyncId(int64 sync_id); diff --git a/chrome/browser/sync/glue/ui_model_worker.cc b/chrome/browser/sync/glue/ui_model_worker.cc index 38f4a98..541a7d8 100644 --- a/chrome/browser/sync/glue/ui_model_worker.cc +++ b/chrome/browser/sync/glue/ui_model_worker.cc @@ -7,6 +7,7 @@ #include "base/message_loop.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" #include "base/synchronization/waitable_event.h" +#include "content/browser/browser_thread.h" namespace browser_sync { @@ -19,7 +20,7 @@ void UIModelWorker::DoWorkAndWaitUntilDone(Callback0::Type* work) { // with state_ = STOPPED, so it is safe to read / compare in this case. CHECK_NE(ANNOTATE_UNPROTECTED_READ(state_), STOPPED); - if (MessageLoop::current() == ui_loop_) { + if (BrowserThread::CurrentlyOn(BrowserThread::UI)) { DLOG(WARNING) << "DoWorkAndWaitUntilDone called from " << "ui_loop_. Probably a nested invocation?"; work->Run(); @@ -35,17 +36,21 @@ void UIModelWorker::DoWorkAndWaitUntilDone(Callback0::Type* work) { base::AutoLock lock(lock_); DCHECK(!pending_work_); pending_work_ = new CallDoWorkAndSignalTask(work, &work_done, this); - ui_loop_->PostTask(FROM_HERE, pending_work_); + if (!BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, pending_work_)) { + LOG(WARNING) << "Could not post work to UI loop."; + pending_work_ = NULL; + syncapi_event_.Signal(); + return; + } } syncapi_event_.Signal(); // Notify that the syncapi produced work for us. work_done.Wait(); } -UIModelWorker::UIModelWorker(MessageLoop* ui_loop) +UIModelWorker::UIModelWorker() : state_(WORKING), pending_work_(NULL), syncapi_has_shutdown_(false), - ui_loop_(ui_loop), syncapi_event_(&lock_) { } @@ -67,7 +72,7 @@ void UIModelWorker::OnSyncerShutdownComplete() { } void UIModelWorker::Stop() { - DCHECK_EQ(MessageLoop::current(), ui_loop_); + DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); base::AutoLock lock(lock_); DCHECK_EQ(state_, WORKING); @@ -94,7 +99,7 @@ ModelSafeGroup UIModelWorker::GetModelSafeGroup() { } bool UIModelWorker::CurrentThreadIsWorkThread() { - return MessageLoop::current() == ui_loop_; + return BrowserThread::CurrentlyOn(BrowserThread::UI); } void UIModelWorker::CallDoWorkAndSignalTask::Run() { diff --git a/chrome/browser/sync/glue/ui_model_worker.h b/chrome/browser/sync/glue/ui_model_worker.h index 503d8cb..6a338db 100644 --- a/chrome/browser/sync/glue/ui_model_worker.h +++ b/chrome/browser/sync/glue/ui_model_worker.h @@ -31,7 +31,7 @@ namespace browser_sync { // after the actual syncer pthread has exited. class UIModelWorker : public browser_sync::ModelSafeWorker { public: - explicit UIModelWorker(MessageLoop* ui_loop); + UIModelWorker(); virtual ~UIModelWorker(); // A simple task to signal a waitable event after Run()ning a Closure. @@ -77,7 +77,7 @@ class UIModelWorker : public browser_sync::ModelSafeWorker { void OnSyncerShutdownComplete(); // Callback from |pending_work_| to notify us that it has been run. - // Called on |ui_loop_|. + // Called on ui loop. void OnTaskCompleted() { pending_work_ = NULL; } private: @@ -110,9 +110,6 @@ class UIModelWorker : public browser_sync::ModelSafeWorker { // the UI thread in Stop(). bool syncapi_has_shutdown_; - // The UI model home-sweet-home MessageLoop. - MessageLoop* const ui_loop_; - // We use a Lock for all data members and a ConditionVariable to synchronize. // We do this instead of using a WaitableEvent and a bool condition in order // to guard against races that could arise due to the fact that the lack of a diff --git a/chrome/browser/sync/glue/ui_model_worker_unittest.cc b/chrome/browser/sync/glue/ui_model_worker_unittest.cc index a2a1dde..c44af67 100644 --- a/chrome/browser/sync/glue/ui_model_worker_unittest.cc +++ b/chrome/browser/sync/glue/ui_model_worker_unittest.cc @@ -1,12 +1,13 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/callback.h" +#include "base/memory/ref_counted.h" #include "base/message_loop.h" -#include "base/ref_counted.h" #include "base/threading/thread.h" #include "base/synchronization/waitable_event.h" +#include "content/browser/browser_thread.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/ui_model_worker.h" #include "testing/gtest/include/gtest/gtest.h" @@ -18,22 +19,20 @@ using namespace sync_api; class UIModelWorkerVisitor { public: - UIModelWorkerVisitor(MessageLoop* faux_ui_loop, - base::WaitableEvent* was_run, + UIModelWorkerVisitor(base::WaitableEvent* was_run, bool quit_loop) - : faux_ui_loop_(faux_ui_loop), quit_loop_when_run_(quit_loop), + : quit_loop_when_run_(quit_loop), was_run_(was_run) { } virtual ~UIModelWorkerVisitor() { } virtual void DoWork() { - EXPECT_EQ(MessageLoop::current(), faux_ui_loop_); + EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::UI)); was_run_->Signal(); if (quit_loop_when_run_) MessageLoop::current()->Quit(); } private: - MessageLoop* faux_ui_loop_; bool quit_loop_when_run_; base::WaitableEvent* was_run_; DISALLOW_COPY_AND_ASSIGN(UIModelWorkerVisitor); @@ -110,7 +109,8 @@ class UIModelWorkerTest : public testing::Test { virtual void SetUp() { faux_syncer_thread_.Start(); - bmw_ = new UIModelWorker(&faux_ui_loop_); + ui_thread_.reset(new BrowserThread(BrowserThread::UI, &faux_ui_loop_)); + bmw_ = new UIModelWorker(); syncer_.reset(new Syncer(bmw_.get())); } @@ -118,9 +118,9 @@ class UIModelWorkerTest : public testing::Test { UIModelWorker* bmw() { return bmw_.get(); } base::Thread* core_thread() { return &faux_core_thread_; } base::Thread* syncer_thread() { return &faux_syncer_thread_; } - MessageLoop* ui_loop() { return &faux_ui_loop_; } private: MessageLoop faux_ui_loop_; + scoped_ptr<BrowserThread> ui_thread_; base::Thread faux_syncer_thread_; base::Thread faux_core_thread_; scoped_refptr<UIModelWorker> bmw_; @@ -130,7 +130,7 @@ class UIModelWorkerTest : public testing::Test { TEST_F(UIModelWorkerTest, ScheduledWorkRunsOnUILoop) { base::WaitableEvent v_was_run(false, false); scoped_ptr<UIModelWorkerVisitor> v( - new UIModelWorkerVisitor(ui_loop(), &v_was_run, true)); + new UIModelWorkerVisitor(&v_was_run, true)); syncer_thread()->message_loop()->PostTask(FROM_HERE, new FakeSyncShareTask(syncer(), v.get())); @@ -163,7 +163,7 @@ TEST_F(UIModelWorkerTest, StopWithPendingWork) { core_thread()->Start(); base::WaitableEvent v_ran(false, false); scoped_ptr<UIModelWorkerVisitor> v(new UIModelWorkerVisitor( - ui_loop(), &v_ran, false)); + &v_ran, false)); base::WaitableEvent* jobs[] = { &v_ran }; // The current message loop is not running, so queue a task to cause @@ -195,13 +195,13 @@ TEST_F(UIModelWorkerTest, HypotheticalManualPumpFlooding) { // Our ammunition. base::WaitableEvent fox1_ran(false, false); scoped_ptr<UIModelWorkerVisitor> fox1(new UIModelWorkerVisitor( - ui_loop(), &fox1_ran, false)); + &fox1_ran, false)); base::WaitableEvent fox2_ran(false, false); scoped_ptr<UIModelWorkerVisitor> fox2(new UIModelWorkerVisitor( - ui_loop(), &fox2_ran, false)); + &fox2_ran, false)); base::WaitableEvent fox3_ran(false, false); scoped_ptr<UIModelWorkerVisitor> fox3(new UIModelWorkerVisitor( - ui_loop(), &fox3_ran, false)); + &fox3_ran, false)); base::WaitableEvent* jobs[] = { &fox1_ran, &fox2_ran, &fox3_ran }; // The current message loop is not running, so queue a task to cause diff --git a/chrome/browser/sync/js_arg_list.cc b/chrome/browser/sync/js_arg_list.cc index 23165a3..a3e4e33 100644 --- a/chrome/browser/sync/js_arg_list.cc +++ b/chrome/browser/sync/js_arg_list.cc @@ -5,7 +5,7 @@ #include "chrome/browser/sync/js_arg_list.h" #include "base/json/json_writer.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" namespace browser_sync { diff --git a/chrome/browser/sync/js_arg_list.h b/chrome/browser/sync/js_arg_list.h index 66d1b21..90c1f0f 100644 --- a/chrome/browser/sync/js_arg_list.h +++ b/chrome/browser/sync/js_arg_list.h @@ -11,7 +11,7 @@ #include <string> #include <vector> -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "base/values.h" namespace browser_sync { diff --git a/chrome/browser/sync/js_arg_list_unittest.cc b/chrome/browser/sync/js_arg_list_unittest.cc index 961a46b..beed519 100644 --- a/chrome/browser/sync/js_arg_list_unittest.cc +++ b/chrome/browser/sync/js_arg_list_unittest.cc @@ -4,7 +4,7 @@ #include "chrome/browser/sync/js_arg_list.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "testing/gtest/include/gtest/gtest.h" namespace browser_sync { diff --git a/chrome/browser/sync/js_sync_manager_observer.cc b/chrome/browser/sync/js_sync_manager_observer.cc index 3999fdd..ef9f34d 100644 --- a/chrome/browser/sync/js_sync_manager_observer.cc +++ b/chrome/browser/sync/js_sync_manager_observer.cc @@ -98,19 +98,20 @@ void JsSyncManagerObserver::OnEncryptionComplete( JsArgList(return_args), NULL); } +void JsSyncManagerObserver::OnMigrationNeededForTypes( + const syncable::ModelTypeSet& types) { + ListValue return_args; + return_args.Append(syncable::ModelTypeSetToValue(types)); + parent_router_->RouteJsEvent("onMigrationNeededForTypes", + JsArgList(return_args), NULL); + // TODO(akalin): Bug 79247. Hook up JS boiler plate! +} + void JsSyncManagerObserver::OnInitializationComplete() { parent_router_->RouteJsEvent("onInitializationComplete", JsArgList(), NULL); } -void JsSyncManagerObserver::OnPaused() { - parent_router_->RouteJsEvent("onPaused", JsArgList(), NULL); -} - -void JsSyncManagerObserver::OnResumed() { - parent_router_->RouteJsEvent("onResumed", JsArgList(), NULL); -} - void JsSyncManagerObserver::OnStopSyncingPermanently() { parent_router_->RouteJsEvent("onStopSyncingPermanently", JsArgList(), NULL); diff --git a/chrome/browser/sync/js_sync_manager_observer.h b/chrome/browser/sync/js_sync_manager_observer.h index 2fa8638..9436f52 100644 --- a/chrome/browser/sync/js_sync_manager_observer.h +++ b/chrome/browser/sync/js_sync_manager_observer.h @@ -40,11 +40,10 @@ class JsSyncManagerObserver : public sync_api::SyncManager::Observer { virtual void OnEncryptionComplete( const syncable::ModelTypeSet& encrypted_types); virtual void OnInitializationComplete(); - virtual void OnPaused(); - virtual void OnResumed(); virtual void OnStopSyncingPermanently(); virtual void OnClearServerDataSucceeded(); virtual void OnClearServerDataFailed(); + virtual void OnMigrationNeededForTypes(const syncable::ModelTypeSet& types); private: JsEventRouter* parent_router_; diff --git a/chrome/browser/sync/js_sync_manager_observer_unittest.cc b/chrome/browser/sync/js_sync_manager_observer_unittest.cc index f5ed961..3631963 100644 --- a/chrome/browser/sync/js_sync_manager_observer_unittest.cc +++ b/chrome/browser/sync/js_sync_manager_observer_unittest.cc @@ -13,7 +13,7 @@ #include "chrome/browser/sync/js_test_util.h" #include "chrome/browser/sync/sessions/session_state.h" #include "chrome/browser/sync/syncable/model_type.h" -#include "chrome/test/sync/engine/test_directory_setter_upper.h" +#include "chrome/test/sync/engine/test_user_share.h" #include "testing/gtest/include/gtest/gtest.h" namespace browser_sync { @@ -39,12 +39,7 @@ TEST_F(JsSyncManagerObserverTest, NoArgNotifiations) { EXPECT_CALL(mock_router_, RouteJsEvent("onPassphraseFailed", HasArgs(JsArgList()), NULL)); - EXPECT_CALL(mock_router_, - RouteJsEvent("onPaused", - HasArgs(JsArgList()), NULL)); - EXPECT_CALL(mock_router_, - RouteJsEvent("onResumed", - HasArgs(JsArgList()), NULL)); + EXPECT_CALL(mock_router_, RouteJsEvent("onStopSyncingPermanently", HasArgs(JsArgList()), NULL)); @@ -57,8 +52,6 @@ TEST_F(JsSyncManagerObserverTest, NoArgNotifiations) { sync_manager_observer_.OnInitializationComplete(); sync_manager_observer_.OnPassphraseFailed(); - sync_manager_observer_.OnPaused(); - sync_manager_observer_.OnResumed(); sync_manager_observer_.OnStopSyncingPermanently(); sync_manager_observer_.OnClearServerDataSucceeded(); sync_manager_observer_.OnClearServerDataFailed(); @@ -173,6 +166,28 @@ TEST_F(JsSyncManagerObserverTest, OnEncryptionComplete) { sync_manager_observer_.OnEncryptionComplete(encrypted_types); } + +TEST_F(JsSyncManagerObserverTest, OnMigrationNeededForTypes) { + ListValue expected_args; + ListValue* type_values = new ListValue(); + syncable::ModelTypeSet types; + + expected_args.Append(type_values); + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType type = syncable::ModelTypeFromInt(i); + types.insert(type); + type_values->Append(Value::CreateStringValue( + syncable::ModelTypeToString(type))); + } + + EXPECT_CALL(mock_router_, + RouteJsEvent("onMigrationNeededForTypes", + HasArgsAsList(expected_args), NULL)); + + sync_manager_observer_.OnMigrationNeededForTypes(types); +} + namespace { // Makes a node of the given model type. Returns the id of the @@ -194,11 +209,8 @@ int64 MakeNode(sync_api::UserShare* share, syncable::ModelType model_type) { TEST_F(JsSyncManagerObserverTest, OnChangesApplied) { InSequence dummy; - TestDirectorySetterUpper setter_upper; - sync_api::UserShare share; - setter_upper.SetUp(); - share.dir_manager.reset(setter_upper.manager()); - share.name = setter_upper.name(); + TestUserShare test_user_share; + test_user_share.SetUp(); // We don't test with passwords as that requires additional setup. @@ -206,7 +218,8 @@ TEST_F(JsSyncManagerObserverTest, OnChangesApplied) { sync_api::SyncManager::ChangeRecord changes[syncable::MODEL_TYPE_COUNT]; for (int i = syncable::AUTOFILL_PROFILE; i < syncable::MODEL_TYPE_COUNT; ++i) { - changes[i].id = MakeNode(&share, syncable::ModelTypeFromInt(i)); + changes[i].id = + MakeNode(test_user_share.user_share(), syncable::ModelTypeFromInt(i)); switch (i % 3) { case 0: changes[i].action = @@ -222,7 +235,7 @@ TEST_F(JsSyncManagerObserverTest, OnChangesApplied) { break; } { - sync_api::ReadTransaction trans(&share); + sync_api::ReadTransaction trans(test_user_share.user_share()); sync_api::ReadNode node(&trans); EXPECT_TRUE(node.InitByIdLookup(changes[i].id)); changes[i].specifics = node.GetEntry()->Get(syncable::SPECIFICS); @@ -243,7 +256,7 @@ TEST_F(JsSyncManagerObserverTest, OnChangesApplied) { ListValue* expected_changes = new ListValue(); expected_args.Append(expected_changes); for (int j = i; j < syncable::MODEL_TYPE_COUNT; ++j) { - sync_api::ReadTransaction trans(&share); + sync_api::ReadTransaction trans(test_user_share.user_share()); expected_changes->Append(changes[j].ToValue(&trans)); } EXPECT_CALL(mock_router_, @@ -254,15 +267,13 @@ TEST_F(JsSyncManagerObserverTest, OnChangesApplied) { // Fire OnChangesApplied() for each data type. for (int i = syncable::AUTOFILL_PROFILE; i < syncable::MODEL_TYPE_COUNT; ++i) { - sync_api::ReadTransaction trans(&share); + sync_api::ReadTransaction trans(test_user_share.user_share()); sync_manager_observer_.OnChangesApplied(syncable::ModelTypeFromInt(i), &trans, &changes[i], syncable::MODEL_TYPE_COUNT - i); } - // |share.dir_manager| does not actually own its value. - ignore_result(share.dir_manager.release()); - setter_upper.TearDown(); + test_user_share.TearDown(); } } // namespace diff --git a/chrome/browser/sync/notifier/cache_invalidation_packet_handler.cc b/chrome/browser/sync/notifier/cache_invalidation_packet_handler.cc index 7fec7f4..de3a6d7 100644 --- a/chrome/browser/sync/notifier/cache_invalidation_packet_handler.cc +++ b/chrome/browser/sync/notifier/cache_invalidation_packet_handler.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -12,7 +12,6 @@ #include "base/logging.h" #include "base/rand_util.h" #include "base/string_number_conversions.h" -#include "chrome/browser/sync/sync_constants.h" #include "google/cacheinvalidation/invalidation-client.h" #include "jingle/notifier/listener/xml_element_util.h" #include "talk/xmpp/constants.h" @@ -25,6 +24,7 @@ namespace sync_notifier { namespace { const char kBotJid[] = "tango@bot.talk.google.com"; +const char kServiceUrl[] = "http://www.google.com/chrome/sync"; const buzz::QName kQnData("google:notifier", "data"); const buzz::QName kQnSeq("", "seq"); @@ -167,8 +167,7 @@ class CacheInvalidationSendMessageTask : public buzz::XmppTask { iq->AddElement(cache_invalidation_iq_packet); cache_invalidation_iq_packet->SetAttr(kQnSeq, base::IntToString(seq)); cache_invalidation_iq_packet->SetAttr(kQnSid, sid); - cache_invalidation_iq_packet->SetAttr(kQnServiceUrl, - browser_sync::kSyncServiceUrl); + cache_invalidation_iq_packet->SetAttr(kQnServiceUrl, kServiceUrl); cache_invalidation_iq_packet->SetBodyText(msg); return iq; } diff --git a/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h b/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h index a879836..707dacb 100644 --- a/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h +++ b/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -13,9 +13,9 @@ #include "base/basictypes.h" #include "base/gtest_prod_util.h" -#include "base/scoped_callback_factory.h" +#include "base/memory/scoped_callback_factory.h" +#include "base/memory/weak_ptr.h" #include "base/threading/non_thread_safe.h" -#include "base/weak_ptr.h" namespace invalidation { class InvalidationClient; diff --git a/chrome/browser/sync/notifier/cache_invalidation_packet_handler_unittest.cc b/chrome/browser/sync/notifier/cache_invalidation_packet_handler_unittest.cc index 1d30bbc..52f9591 100644 --- a/chrome/browser/sync/notifier/cache_invalidation_packet_handler_unittest.cc +++ b/chrome/browser/sync/notifier/cache_invalidation_packet_handler_unittest.cc @@ -1,12 +1,12 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/notifier/cache_invalidation_packet_handler.h" #include "base/base64.h" +#include "base/memory/weak_ptr.h" #include "base/message_loop.h" -#include "base/weak_ptr.h" #include "google/cacheinvalidation/invalidation-client.h" #include "jingle/notifier/base/fake_base_task.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chrome/browser/sync/notifier/chrome_invalidation_client.cc b/chrome/browser/sync/notifier/chrome_invalidation_client.cc index 0c38936..074e481 100644 --- a/chrome/browser/sync/notifier/chrome_invalidation_client.cc +++ b/chrome/browser/sync/notifier/chrome_invalidation_client.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -75,6 +75,7 @@ void ChromeInvalidationClient::Start( ChangeBaseTask(base_task); registration_manager_.reset( new RegistrationManager(invalidation_client_.get())); + registration_manager_->SetRegisteredTypes(registered_types_); } void ChromeInvalidationClient::ChangeBaseTask( @@ -105,8 +106,11 @@ void ChromeInvalidationClient::Stop() { void ChromeInvalidationClient::RegisterTypes( const syncable::ModelTypeSet& types) { DCHECK(non_thread_safe_.CalledOnValidThread()); - - registration_manager_->SetRegisteredTypes(types); + registered_types_ = types; + if (registration_manager_.get()) { + registration_manager_->SetRegisteredTypes(registered_types_); + } + // TODO(akalin): Clear invalidation versions for unregistered types. } void ChromeInvalidationClient::Invalidate( @@ -116,30 +120,72 @@ void ChromeInvalidationClient::Invalidate( DCHECK(invalidation::IsCallbackRepeatable(callback)); VLOG(1) << "Invalidate: " << InvalidationToString(invalidation); syncable::ModelType model_type; - if (ObjectIdToRealModelType(invalidation.object_id(), &model_type)) { - std::string payload; - // payload() CHECK()'s has_payload(), so we must check it ourselves first. - if (invalidation.has_payload()) - payload = invalidation.payload(); - - listener_->OnInvalidate(model_type, payload); - } else { + if (!ObjectIdToRealModelType(invalidation.object_id(), &model_type)) { LOG(WARNING) << "Could not get invalidation model type; " << "invalidating everything"; - listener_->OnInvalidateAll(); + EmitInvalidation(registered_types_, std::string()); + RunAndDeleteClosure(callback); + return; } + // The invalidation API spec allows for the possibility of redundant + // invalidations, so keep track of the max versions and drop + // invalidations with old versions. + // + // TODO(akalin): Now that we keep track of registered types, we + // should drop invalidations for unregistered types. We may also + // have to filter it at a higher level, as invalidations for + // newly-unregistered types may already be in flight. + // + // TODO(akalin): Persist |max_invalidation_versions_| somehow. + if (invalidation.version() != UNKNOWN_OBJECT_VERSION) { + std::map<syncable::ModelType, int64>::const_iterator it = + max_invalidation_versions_.find(model_type); + if ((it != max_invalidation_versions_.end()) && + (invalidation.version() <= it->second)) { + // Drop redundant invalidations. + RunAndDeleteClosure(callback); + return; + } + max_invalidation_versions_[model_type] = invalidation.version(); + } + + std::string payload; + // payload() CHECK()'s has_payload(), so we must check it ourselves first. + if (invalidation.has_payload()) + payload = invalidation.payload(); + + syncable::ModelTypeSet types; + types.insert(model_type); + EmitInvalidation(types, payload); + // TODO(akalin): We should really |callback| only after we get the + // updates from the sync server. (see http://crbug.com/78462). RunAndDeleteClosure(callback); } +// This should behave as if we got an invalidation with version +// UNKNOWN_OBJECT_VERSION for all known data types. void ChromeInvalidationClient::InvalidateAll( invalidation::Closure* callback) { DCHECK(non_thread_safe_.CalledOnValidThread()); DCHECK(invalidation::IsCallbackRepeatable(callback)); VLOG(1) << "InvalidateAll"; - listener_->OnInvalidateAll(); + EmitInvalidation(registered_types_, std::string()); + // TODO(akalin): We should really |callback| only after we get the + // updates from the sync server. (see http://crbug.com/76482). RunAndDeleteClosure(callback); } +void ChromeInvalidationClient::EmitInvalidation( + const syncable::ModelTypeSet& types, const std::string& payload) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + // TODO(akalin): Move all uses of ModelTypeBitSet for invalidations + // to ModelTypeSet. + syncable::ModelTypePayloadMap type_payloads = + syncable::ModelTypePayloadMapFromBitSet( + syncable::ModelTypeBitSetFromSet(types), payload); + listener_->OnInvalidate(type_payloads); +} + void ChromeInvalidationClient::RegistrationStateChanged( const invalidation::ObjectId& object_id, invalidation::RegistrationState new_state, @@ -159,9 +205,8 @@ void ChromeInvalidationClient::RegistrationStateChanged( } if (new_state != invalidation::RegistrationState_REGISTERED) { - // TODO(akalin): Figure out something else to do if the failure - // isn't transient. Even if it is transient, we may still want to - // add exponential back-off or limit the number of attempts. + // We don't care about |unknown_hint|; we let + // |registration_manager_| handle the registration backoff policy. registration_manager_->MarkRegistrationLost(model_type); } } @@ -175,19 +220,9 @@ void ChromeInvalidationClient::AllRegistrationsLost( RunAndDeleteClosure(callback); } -void ChromeInvalidationClient::RegistrationLost( - const invalidation::ObjectId& object_id, - invalidation::Closure* callback) { - DCHECK(non_thread_safe_.CalledOnValidThread()); - DCHECK(invalidation::IsCallbackRepeatable(callback)); - VLOG(1) << "RegistrationLost: " << ObjectIdToString(object_id); - syncable::ModelType model_type; - if (ObjectIdToRealModelType(object_id, &model_type)) { - registration_manager_->MarkRegistrationLost(model_type); - } else { - LOG(WARNING) << "Could not get object id model type; ignoring"; - } - RunAndDeleteClosure(callback); +void ChromeInvalidationClient::SessionStatusChanged(bool has_session) { + VLOG(1) << "SessionStatusChanged: " << has_session; + listener_->OnSessionStatusChanged(has_session); } void ChromeInvalidationClient::WriteState(const std::string& state) { diff --git a/chrome/browser/sync/notifier/chrome_invalidation_client.h b/chrome/browser/sync/notifier/chrome_invalidation_client.h index 904b91d..7ab0f3a 100644 --- a/chrome/browser/sync/notifier/chrome_invalidation_client.h +++ b/chrome/browser/sync/notifier/chrome_invalidation_client.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -9,18 +9,25 @@ #define CHROME_BROWSER_SYNC_NOTIFIER_CHROME_INVALIDATION_CLIENT_H_ #pragma once +#include <map> #include <string> #include "base/basictypes.h" -#include "base/scoped_callback_factory.h" -#include "base/scoped_ptr.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_callback_factory.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/weak_ptr.h" #include "base/threading/non_thread_safe.h" -#include "base/weak_ptr.h" #include "chrome/browser/sync/notifier/chrome_system_resources.h" #include "chrome/browser/sync/notifier/state_writer.h" #include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" #include "google/cacheinvalidation/invalidation-client.h" +// TODO(akalin): Move invalidation::InvalidationListener into its own +// file and include that instead of invalidation-client.h (which +// includes generated protobuf header files). + namespace talk_base { class Task; } // namespace @@ -30,9 +37,6 @@ namespace sync_notifier { class CacheInvalidationPacketHandler; class RegistrationManager; -// TODO(akalin): Hook this up to a NetworkChangeNotifier so we can -// properly notify invalidation_client_. - class ChromeInvalidationClient : public invalidation::InvalidationListener, public StateWriter { @@ -41,10 +45,10 @@ class ChromeInvalidationClient public: virtual ~Listener(); - virtual void OnInvalidate(syncable::ModelType model_type, - const std::string& payload) = 0; + virtual void OnInvalidate( + const syncable::ModelTypePayloadMap& type_payloads) = 0; - virtual void OnInvalidateAll() = 0; + virtual void OnSessionStatusChanged(bool has_session) = 0; }; ChromeInvalidationClient(); @@ -67,31 +71,22 @@ class ChromeInvalidationClient void ChangeBaseTask(base::WeakPtr<talk_base::Task> base_task); // Register the sync types that we're interested in getting - // notifications for. Must only be called between calls to Start() - // and Stop(). + // notifications for. May be called at any time. void RegisterTypes(const syncable::ModelTypeSet& types); // invalidation::InvalidationListener implementation. - // - // TODO(akalin): Move these into a private inner class. - virtual void Invalidate(const invalidation::Invalidation& invalidation, - invalidation::Closure* callback); - - virtual void InvalidateAll(invalidation::Closure* callback); - + invalidation::Closure* callback) OVERRIDE; + virtual void InvalidateAll(invalidation::Closure* callback) OVERRIDE; virtual void RegistrationStateChanged( const invalidation::ObjectId& object_id, invalidation::RegistrationState new_state, - const invalidation::UnknownHint& unknown_hint); - - virtual void AllRegistrationsLost(invalidation::Closure* callback); - - virtual void RegistrationLost(const invalidation::ObjectId& object_id, - invalidation::Closure* callback); + const invalidation::UnknownHint& unknown_hint) OVERRIDE; + virtual void AllRegistrationsLost(invalidation::Closure* callback) OVERRIDE; + virtual void SessionStatusChanged(bool has_session) OVERRIDE; // StateWriter implementation. - virtual void WriteState(const std::string& state); + virtual void WriteState(const std::string& state) OVERRIDE; private: friend class ChromeInvalidationClientTest; @@ -99,6 +94,8 @@ class ChromeInvalidationClient // Should only be called between calls to Start() and Stop(). void HandleOutboundPacket( invalidation::NetworkEndpoint* const& network_endpoint); + void EmitInvalidation( + const syncable::ModelTypeSet& types, const std::string& payload); base::NonThreadSafe non_thread_safe_; ChromeSystemResources chrome_system_resources_; @@ -111,6 +108,9 @@ class ChromeInvalidationClient scoped_ptr<CacheInvalidationPacketHandler> cache_invalidation_packet_handler_; scoped_ptr<RegistrationManager> registration_manager_; + std::map<syncable::ModelType, int64> max_invalidation_versions_; + // Stored to pass to |registration_manager_| on start. + syncable::ModelTypeSet registered_types_; DISALLOW_COPY_AND_ASSIGN(ChromeInvalidationClient); }; diff --git a/chrome/browser/sync/notifier/chrome_invalidation_client_unittest.cc b/chrome/browser/sync/notifier/chrome_invalidation_client_unittest.cc index 13438b9..5a99e77 100644 --- a/chrome/browser/sync/notifier/chrome_invalidation_client_unittest.cc +++ b/chrome/browser/sync/notifier/chrome_invalidation_client_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -7,6 +7,8 @@ #include "base/message_loop.h" #include "chrome/browser/sync/notifier/chrome_invalidation_client.h" #include "chrome/browser/sync/notifier/state_writer.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" #include "jingle/notifier/base/fake_base_task.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -15,12 +17,21 @@ namespace sync_notifier { using ::testing::_; using ::testing::Return; +using ::testing::StrictMock; + +namespace { + +const char kClientId[] = "client_id"; +const char kClientInfo[] = "client_info"; +const char kState[] = "state"; + +static const int64 kUnknownVersion = + invalidation::InvalidationListener::UNKNOWN_OBJECT_VERSION; class MockListener : public ChromeInvalidationClient::Listener { public: - MOCK_METHOD2(OnInvalidate, void(syncable::ModelType, - const std::string& payload)); - MOCK_METHOD0(OnInvalidateAll, void()); + MOCK_METHOD1(OnInvalidate, void(const syncable::ModelTypePayloadMap&)); + MOCK_METHOD1(OnSessionStatusChanged, void(bool)); }; class MockStateWriter : public StateWriter { @@ -28,18 +39,15 @@ class MockStateWriter : public StateWriter { MOCK_METHOD1(WriteState, void(const std::string&)); }; -class MockInvalidationClient : public invalidation::InvalidationClient { +class MockCallback { public: - MOCK_METHOD1(Start, void(const std::string& str)); - MOCK_METHOD1(Register, void(const invalidation::ObjectId&)); - MOCK_METHOD1(Unregister, void(const invalidation::ObjectId&)); - MOCK_METHOD0(network_endpoint, invalidation::NetworkEndpoint*()); + MOCK_METHOD0(Run, void()); + + invalidation::Closure* MakeClosure() { + return invalidation::NewPermanentCallback(this, &MockCallback::Run); + } }; -namespace { -const char kClientId[] = "client_id"; -const char kClientInfo[] = "client_info"; -const char kState[] = "state"; } // namespace class ChromeInvalidationClientTest : public testing::Test { @@ -56,7 +64,7 @@ class ChromeInvalidationClientTest : public testing::Test { } // Simulates DoInformOutboundListener() from network-manager.cc. - virtual void SimulateInformOutboundListener() { + void SimulateInformOutboundListener() { // Explicitness hack here to work around broken callback // implementations. void (invalidation::NetworkCallback::*run_function)( @@ -69,13 +77,164 @@ class ChromeInvalidationClientTest : public testing::Test { client_.invalidation_client_->network_endpoint())); } + // |payload| can be NULL, but not |type_name|. + void FireInvalidate(const char* type_name, + int64 version, const char* payload) { + const invalidation::ObjectId object_id( + invalidation::ObjectSource::CHROME_SYNC, type_name); + std::string payload_tmp = payload ? payload : ""; + const invalidation::Invalidation invalidation( + object_id, version, payload ? &payload_tmp : NULL, NULL); + MockCallback mock_callback; + EXPECT_CALL(mock_callback, Run()); + client_.Invalidate(invalidation, mock_callback.MakeClosure()); + } + + void FireInvalidateAll() { + MockCallback mock_callback; + EXPECT_CALL(mock_callback, Run()); + client_.InvalidateAll(mock_callback.MakeClosure()); + } + MessageLoop message_loop_; - MockListener mock_listener_; - MockStateWriter mock_state_writer_; + StrictMock<MockListener> mock_listener_; + StrictMock<MockStateWriter> mock_state_writer_; notifier::FakeBaseTask fake_base_task_; ChromeInvalidationClient client_; }; +namespace { + +syncable::ModelTypePayloadMap MakeMap(syncable::ModelType model_type, + const std::string& payload) { + syncable::ModelTypePayloadMap type_payloads; + type_payloads[model_type] = payload; + return type_payloads; +} + +syncable::ModelTypePayloadMap MakeMapFromSet(syncable::ModelTypeSet types, + const std::string& payload) { + syncable::ModelTypePayloadMap type_payloads; + for (syncable::ModelTypeSet::const_iterator it = types.begin(); + it != types.end(); ++it) { + type_payloads[*it] = payload; + } + return type_payloads; +} + +} // namespace + +TEST_F(ChromeInvalidationClientTest, InvalidateBadObjectId) { + syncable::ModelTypeSet types; + types.insert(syncable::BOOKMARKS); + types.insert(syncable::APPS); + client_.RegisterTypes(types); + EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); + FireInvalidate("bad", 1, NULL); +} + +TEST_F(ChromeInvalidationClientTest, InvalidateNoPayload) { + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::BOOKMARKS, ""))); + FireInvalidate("BOOKMARK", 1, NULL); +} + +TEST_F(ChromeInvalidationClientTest, InvalidateWithPayload) { + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::PREFERENCES, "payload"))); + FireInvalidate("PREFERENCE", 1, "payload"); +} + +TEST_F(ChromeInvalidationClientTest, InvalidateVersion) { + using ::testing::Mock; + + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::APPS, ""))); + + // Should trigger. + FireInvalidate("APP", 1, NULL); + + Mock::VerifyAndClearExpectations(&mock_listener_); + + // Should be dropped. + FireInvalidate("APP", 1, NULL); +} + +TEST_F(ChromeInvalidationClientTest, InvalidateUnknownVersion) { + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))) + .Times(2); + + // Should trigger twice. + FireInvalidate("EXTENSION", kUnknownVersion, NULL); + FireInvalidate("EXTENSION", kUnknownVersion, NULL); +} + +TEST_F(ChromeInvalidationClientTest, InvalidateVersionMultipleTypes) { + using ::testing::Mock; + + syncable::ModelTypeSet types; + types.insert(syncable::BOOKMARKS); + types.insert(syncable::APPS); + client_.RegisterTypes(types); + + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::APPS, ""))); + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); + + // Should trigger both. + FireInvalidate("APP", 3, NULL); + FireInvalidate("EXTENSION", 2, NULL); + + Mock::VerifyAndClearExpectations(&mock_listener_); + + // Should both be dropped. + FireInvalidate("APP", 1, NULL); + FireInvalidate("EXTENSION", 1, NULL); + + Mock::VerifyAndClearExpectations(&mock_listener_); + + // InvalidateAll shouldn't change any version state. + EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); + FireInvalidateAll(); + + Mock::VerifyAndClearExpectations(&mock_listener_); + + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::PREFERENCES, ""))); + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::EXTENSIONS, ""))); + EXPECT_CALL(mock_listener_, + OnInvalidate(MakeMap(syncable::APPS, ""))); + + // Should trigger all three. + FireInvalidate("PREFERENCE", 5, NULL); + FireInvalidate("EXTENSION", 3, NULL); + FireInvalidate("APP", 4, NULL); +} + +TEST_F(ChromeInvalidationClientTest, InvalidateAll) { + syncable::ModelTypeSet types; + types.insert(syncable::PREFERENCES); + types.insert(syncable::EXTENSIONS); + client_.RegisterTypes(types); + EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); + FireInvalidateAll(); +} + +TEST_F(ChromeInvalidationClientTest, RegisterTypes) { + syncable::ModelTypeSet types; + types.insert(syncable::PREFERENCES); + types.insert(syncable::EXTENSIONS); + client_.RegisterTypes(types); + // Registered types should be preserved across Stop/Start. + TearDown(); + SetUp(); + EXPECT_CALL(mock_listener_, OnInvalidate(MakeMapFromSet(types, ""))); + FireInvalidateAll(); +} + // Outbound packet sending should be resilient to // changing/disappearing base tasks. TEST_F(ChromeInvalidationClientTest, OutboundPackets) { diff --git a/chrome/browser/sync/notifier/chrome_system_resources.h b/chrome/browser/sync/notifier/chrome_system_resources.h index 06b8001..a3f9875 100644 --- a/chrome/browser/sync/notifier/chrome_system_resources.h +++ b/chrome/browser/sync/notifier/chrome_system_resources.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -13,8 +13,8 @@ #include <set> #include <string> +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "base/task.h" #include "base/threading/non_thread_safe.h" #include "chrome/browser/sync/notifier/state_writer.h" diff --git a/chrome/browser/sync/notifier/invalidation_notifier.cc b/chrome/browser/sync/notifier/invalidation_notifier.cc new file mode 100644 index 0000000..03bf2b7 --- /dev/null +++ b/chrome/browser/sync/notifier/invalidation_notifier.cc @@ -0,0 +1,133 @@ +// Copyright (c) 2011 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/sync/notifier/invalidation_notifier.h" + +#include "base/logging.h" +#include "base/message_loop_proxy.h" +#include "chrome/browser/sync/notifier/sync_notifier_observer.h" +#include "chrome/browser/sync/protocol/service_constants.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" +#include "jingle/notifier/base/const_communicator.h" +#include "jingle/notifier/base/notifier_options_util.h" +#include "jingle/notifier/communicator/connection_options.h" +#include "net/base/host_port_pair.h" +#include "net/url_request/url_request_context.h" +#include "talk/xmpp/jid.h" +#include "talk/xmpp/xmppclientsettings.h" + +namespace sync_notifier { + +InvalidationNotifier::InvalidationNotifier( + const notifier::NotifierOptions& notifier_options, + const std::string& client_info) + : state_(STOPPED), + notifier_options_(notifier_options), + client_info_(client_info) { + DCHECK_EQ(notifier::NOTIFICATION_SERVER, + notifier_options.notification_method); + DCHECK(notifier_options_.request_context_getter); + // TODO(akalin): Replace NonThreadSafe checks with IO thread checks. + DCHECK(notifier_options_.request_context_getter->GetIOMessageLoopProxy()-> + BelongsToCurrentThread()); +} + +InvalidationNotifier::~InvalidationNotifier() { + DCHECK(non_thread_safe_.CalledOnValidThread()); +} + +void InvalidationNotifier::AddObserver(SyncNotifierObserver* observer) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + observers_.AddObserver(observer); +} + +void InvalidationNotifier::RemoveObserver(SyncNotifierObserver* observer) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + observers_.RemoveObserver(observer); +} + +void InvalidationNotifier::SetState(const std::string& state) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + invalidation_state_ = state; +} + +void InvalidationNotifier::UpdateCredentials( + const std::string& email, const std::string& token) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + VLOG(1) << "Updating credentials for " << email; + buzz::XmppClientSettings xmpp_client_settings = + notifier::MakeXmppClientSettings(notifier_options_, + email, token, SYNC_SERVICE_NAME); + if (state_ >= CONNECTING) { + login_->UpdateXmppSettings(xmpp_client_settings); + } else { + notifier::ConnectionOptions options; + VLOG(1) << "First time updating credentials: connecting"; + login_.reset( + new notifier::Login(this, + xmpp_client_settings, + notifier::ConnectionOptions(), + notifier_options_.request_context_getter, + notifier::GetServerList(notifier_options_), + notifier_options_.try_ssltcp_first, + notifier_options_.auth_mechanism)); + login_->StartConnection(); + state_ = CONNECTING; + } +} + +void InvalidationNotifier::UpdateEnabledTypes( + const syncable::ModelTypeSet& types) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + invalidation_client_.RegisterTypes(types); +} + +void InvalidationNotifier::SendNotification() { + DCHECK(non_thread_safe_.CalledOnValidThread()); +} + +void InvalidationNotifier::OnConnect( + base::WeakPtr<talk_base::Task> base_task) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + VLOG(1) << "OnConnect"; + if (state_ >= STARTED) { + invalidation_client_.ChangeBaseTask(base_task); + } else { + VLOG(1) << "First time connecting: starting invalidation client"; + // TODO(akalin): Make cache_guid() part of the client ID. If we + // do so and we somehow propagate it up to the server somehow, we + // can make it so that we won't receive any notifications that + // were generated from our own changes. + const std::string kClientId = "invalidation_notifier"; + invalidation_client_.Start( + kClientId, client_info_, invalidation_state_, this, this, base_task); + invalidation_state_.clear(); + state_ = STARTED; + } +} + +void InvalidationNotifier::OnDisconnect() { + DCHECK(non_thread_safe_.CalledOnValidThread()); + VLOG(1) << "OnDisconnect"; +} + +void InvalidationNotifier::OnInvalidate( + const syncable::ModelTypePayloadMap& type_payloads) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + FOR_EACH_OBSERVER(SyncNotifierObserver, observers_, + OnIncomingNotification(type_payloads)); +} + +void InvalidationNotifier::OnSessionStatusChanged(bool has_session) { + FOR_EACH_OBSERVER(SyncNotifierObserver, observers_, + OnNotificationStateChange(has_session)); +} + +void InvalidationNotifier::WriteState(const std::string& state) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + VLOG(1) << "WriteState"; + FOR_EACH_OBSERVER(SyncNotifierObserver, observers_, StoreState(state)); +} + +} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/invalidation_notifier.h b/chrome/browser/sync/notifier/invalidation_notifier.h new file mode 100644 index 0000000..762aa93 --- /dev/null +++ b/chrome/browser/sync/notifier/invalidation_notifier.h @@ -0,0 +1,104 @@ +// Copyright (c) 2011 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. +// +// An implementation of SyncNotifier that wraps an invalidation +// client. Handles the details of connecting to XMPP and hooking it +// up to the invalidation client. +// +// You probably don't want to use this directly; use +// NonBlockingInvalidationNotifier. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_INVALIDATION_NOTIFIER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_INVALIDATION_NOTIFIER_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/compiler_specific.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "base/threading/non_thread_safe.h" +#include "chrome/browser/sync/notifier/chrome_invalidation_client.h" +#include "chrome/browser/sync/notifier/state_writer.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "jingle/notifier/base/notifier_options.h" +#include "jingle/notifier/communicator/login.h" + +namespace sync_notifier { + +// This class must live on the IO thread. +class InvalidationNotifier + : public SyncNotifier, + public notifier::LoginDelegate, + public ChromeInvalidationClient::Listener, + public StateWriter { + public: + InvalidationNotifier( + const notifier::NotifierOptions& notifier_options, + const std::string& client_info); + + virtual ~InvalidationNotifier(); + + // SyncNotifier implementation. + virtual void AddObserver(SyncNotifierObserver* observer) OVERRIDE; + virtual void RemoveObserver(SyncNotifierObserver* observer) OVERRIDE; + virtual void SetState(const std::string& state) OVERRIDE; + virtual void UpdateCredentials( + const std::string& email, const std::string& token) OVERRIDE; + virtual void UpdateEnabledTypes( + const syncable::ModelTypeSet& types) OVERRIDE; + virtual void SendNotification() OVERRIDE; + + // notifier::LoginDelegate implementation. + virtual void OnConnect(base::WeakPtr<talk_base::Task> base_task) OVERRIDE; + virtual void OnDisconnect() OVERRIDE; + + // ChromeInvalidationClient::Listener implementation. + virtual void OnInvalidate( + const syncable::ModelTypePayloadMap& type_payloads) OVERRIDE; + virtual void OnSessionStatusChanged(bool has_session) OVERRIDE; + + // StateWriter implementation. + virtual void WriteState(const std::string& state) OVERRIDE; + + private: + base::NonThreadSafe non_thread_safe_; + + // We start off in the STOPPED state. When we get our initial + // credentials, we connect and move to the CONNECTING state. When + // we're connected we start the invalidation client and move to the + // STARTED state. We never go back to a previous state. + enum State { + STOPPED, + CONNECTING, + STARTED + }; + State state_; + + // Used to build parameters for |login_|. + const notifier::NotifierOptions notifier_options_; + + // Passed to |invalidation_client_|. + const std::string client_info_; + + // Our observers (which must live on the same thread). + ObserverList<SyncNotifierObserver> observers_; + + // The state to pass to |chrome_invalidation_client_|. + std::string invalidation_state_; + + // The XMPP connection manager. + scoped_ptr<notifier::Login> login_; + + // The invalidation client. + ChromeInvalidationClient invalidation_client_; + + DISALLOW_COPY_AND_ASSIGN(InvalidationNotifier); +}; + +} // namespace sync_notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_INVALIDATION_NOTIFIER_H_ diff --git a/chrome/browser/sync/notifier/invalidation_notifier_unittest.cc b/chrome/browser/sync/notifier/invalidation_notifier_unittest.cc new file mode 100644 index 0000000..bcfa9a4 --- /dev/null +++ b/chrome/browser/sync/notifier/invalidation_notifier_unittest.cc @@ -0,0 +1,88 @@ +// Copyright (c) 2011 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/sync/notifier/invalidation_notifier.h" + +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "chrome/browser/sync/notifier/mock_sync_notifier_observer.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" +#include "chrome/test/test_url_request_context_getter.h" +#include "content/browser/browser_thread.h" +#include "jingle/notifier/base/fake_base_task.h" +#include "net/base/cert_verifier.h" +#include "net/base/host_resolver.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sync_notifier { + +namespace { + +using ::testing::InSequence; +using ::testing::StrictMock; + +class InvalidationNotifierTest : public testing::Test { + public: + InvalidationNotifierTest() : io_thread_(BrowserThread::IO, &message_loop_) {} + + protected: + virtual void SetUp() { + request_context_getter_ = new TestURLRequestContextGetter; + notifier::NotifierOptions notifier_options; + notifier_options.request_context_getter = request_context_getter_; + invalidation_notifier_.reset(new InvalidationNotifier(notifier_options, + "fake_client_info")); + invalidation_notifier_->AddObserver(&mock_observer_); + } + + virtual void TearDown() { + invalidation_notifier_->RemoveObserver(&mock_observer_); + invalidation_notifier_.reset(); + request_context_getter_ = NULL; + } + + MessageLoop message_loop_; + scoped_refptr<net::URLRequestContextGetter> request_context_getter_; + scoped_ptr<InvalidationNotifier> invalidation_notifier_; + StrictMock<MockSyncNotifierObserver> mock_observer_; + notifier::FakeBaseTask fake_base_task_; + // Since this test calls HostResolver code, we need an IO thread. + BrowserThread io_thread_; +}; + +TEST_F(InvalidationNotifierTest, Basic) { + InSequence dummy; + + syncable::ModelTypePayloadMap type_payloads; + type_payloads[syncable::PREFERENCES] = "payload"; + type_payloads[syncable::BOOKMARKS] = ""; + type_payloads[syncable::AUTOFILL] = ""; + + EXPECT_CALL(mock_observer_, OnNotificationStateChange(true)); + EXPECT_CALL(mock_observer_, StoreState("new_fake_state")); + EXPECT_CALL(mock_observer_, OnIncomingNotification(type_payloads)); + EXPECT_CALL(mock_observer_, OnNotificationStateChange(false)); + + invalidation_notifier_->SetState("fake_state"); + invalidation_notifier_->UpdateCredentials("foo@bar.com", "fake_token"); + + invalidation_notifier_->OnConnect(fake_base_task_.AsWeakPtr()); + invalidation_notifier_->OnSessionStatusChanged(true); + + invalidation_notifier_->WriteState("new_fake_state"); + + invalidation_notifier_->OnInvalidate(type_payloads); + + // Shouldn't trigger notification state change. + invalidation_notifier_->OnDisconnect(); + invalidation_notifier_->OnConnect(fake_base_task_.AsWeakPtr()); + + invalidation_notifier_->OnSessionStatusChanged(false); +} + +} // namespace + +} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/mock_sync_notifier_observer.cc b/chrome/browser/sync/notifier/mock_sync_notifier_observer.cc new file mode 100644 index 0000000..b46ac68 --- /dev/null +++ b/chrome/browser/sync/notifier/mock_sync_notifier_observer.cc @@ -0,0 +1,12 @@ +// Copyright (c) 2011 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/sync/notifier/mock_sync_notifier_observer.h" + +namespace sync_notifier { + +MockSyncNotifierObserver::MockSyncNotifierObserver() {} +MockSyncNotifierObserver::~MockSyncNotifierObserver() {} + +} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/mock_sync_notifier_observer.h b/chrome/browser/sync/notifier/mock_sync_notifier_observer.h new file mode 100644 index 0000000..a9e141f --- /dev/null +++ b/chrome/browser/sync/notifier/mock_sync_notifier_observer.h @@ -0,0 +1,29 @@ +// Copyright (c) 2011 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_NOTIFIER_MOCK_SYNC_NOTIFIER_OBSERVER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_MOCK_SYNC_NOTIFIER_OBSERVER_H_ +#pragma once + +#include <string> + +#include "chrome/browser/sync/notifier/sync_notifier_observer.h" +#include "testing/gmock/include/gmock/gmock.h" + +namespace sync_notifier { + +class MockSyncNotifierObserver : public SyncNotifierObserver { + public: + MockSyncNotifierObserver(); + virtual ~MockSyncNotifierObserver(); + + MOCK_METHOD1(OnIncomingNotification, + void(const syncable::ModelTypePayloadMap&)); + MOCK_METHOD1(OnNotificationStateChange, void(bool)); + MOCK_METHOD1(StoreState, void(const std::string&)); +}; + +} // namespace sync_notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_MOCK_SYNC_NOTIFIER_OBSERVER_H_ diff --git a/chrome/browser/sync/notifier/non_blocking_invalidation_notifier.cc b/chrome/browser/sync/notifier/non_blocking_invalidation_notifier.cc new file mode 100644 index 0000000..d5e1848 --- /dev/null +++ b/chrome/browser/sync/notifier/non_blocking_invalidation_notifier.cc @@ -0,0 +1,216 @@ +// Copyright (c) 2011 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/sync/notifier/non_blocking_invalidation_notifier.h" + +#include "base/logging.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/observer_list_threadsafe.h" +#include "base/threading/thread.h" +#include "chrome/browser/sync/notifier/invalidation_notifier.h" +#include "chrome/browser/sync/notifier/sync_notifier_observer.h" + +namespace sync_notifier { + +class NonBlockingInvalidationNotifier::Core + : public base::RefCountedThreadSafe<NonBlockingInvalidationNotifier::Core>, + public SyncNotifierObserver { + public: + // Called on parent thread. + Core(); + + // Called on parent thread. + void AddObserver(SyncNotifierObserver* observer); + void RemoveObserver(SyncNotifierObserver* observer); + + // Helpers called on I/O thread. + void Initialize(const notifier::NotifierOptions& notifier_options, + const std::string& client_info); + void Teardown(); + void SetState(const std::string& state); + void UpdateCredentials(const std::string& email, const std::string& token); + void UpdateEnabledTypes(const syncable::ModelTypeSet& types); + void SendNotification(); + + // SyncNotifierObserver implementation (all called on I/O thread). + virtual void OnIncomingNotification( + const syncable::ModelTypePayloadMap& type_payloads); + virtual void OnNotificationStateChange(bool notifications_enabled); + virtual void StoreState(const std::string& state); + + private: + friend class + base::RefCountedThreadSafe<NonBlockingInvalidationNotifier::Core>; + // Called on parent or I/O thread. + ~Core(); + + scoped_ptr<InvalidationNotifier> invalidation_notifier_; + scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy_; + scoped_refptr<ObserverListThreadSafe<SyncNotifierObserver> > observers_; + DISALLOW_COPY_AND_ASSIGN(Core); +}; + +NonBlockingInvalidationNotifier::Core::Core() + : observers_(new ObserverListThreadSafe<SyncNotifierObserver>()) { +} + +NonBlockingInvalidationNotifier::Core::~Core() { +} + +void NonBlockingInvalidationNotifier::Core::Initialize( + const notifier::NotifierOptions& notifier_options, + const std::string& client_info) { + DCHECK(notifier_options.request_context_getter); + DCHECK_EQ(notifier::NOTIFICATION_SERVER, + notifier_options.notification_method); + io_message_loop_proxy_ = notifier_options.request_context_getter-> + GetIOMessageLoopProxy(); + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + invalidation_notifier_.reset( + new InvalidationNotifier(notifier_options, client_info)); + invalidation_notifier_->AddObserver(this); +} + + +void NonBlockingInvalidationNotifier::Core::Teardown() { + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + invalidation_notifier_->RemoveObserver(this); + invalidation_notifier_.reset(); + io_message_loop_proxy_ = NULL; +} + +void NonBlockingInvalidationNotifier::Core::AddObserver( + SyncNotifierObserver* observer) { + observers_->AddObserver(observer); +} + +void NonBlockingInvalidationNotifier::Core::RemoveObserver( + SyncNotifierObserver* observer) { + observers_->RemoveObserver(observer); +} + +void NonBlockingInvalidationNotifier::Core::SetState( + const std::string& state) { + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + invalidation_notifier_->SetState(state); +} + +void NonBlockingInvalidationNotifier::Core::UpdateCredentials( + const std::string& email, const std::string& token) { + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + invalidation_notifier_->UpdateCredentials(email, token); +} + +void NonBlockingInvalidationNotifier::Core::UpdateEnabledTypes( + const syncable::ModelTypeSet& types) { + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + invalidation_notifier_->UpdateEnabledTypes(types); +} + +void NonBlockingInvalidationNotifier::Core::OnIncomingNotification( + const syncable::ModelTypePayloadMap& type_payloads) { + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + observers_->Notify(&SyncNotifierObserver::OnIncomingNotification, + type_payloads); +} + +void NonBlockingInvalidationNotifier::Core::OnNotificationStateChange( + bool notifications_enabled) { + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + observers_->Notify(&SyncNotifierObserver::OnNotificationStateChange, + notifications_enabled); +} + +void NonBlockingInvalidationNotifier::Core::StoreState( + const std::string& state) { + DCHECK(io_message_loop_proxy_->BelongsToCurrentThread()); + observers_->Notify(&SyncNotifierObserver::StoreState, state); +} + +NonBlockingInvalidationNotifier::NonBlockingInvalidationNotifier( + const notifier::NotifierOptions& notifier_options, + const std::string& client_info) + : core_(new Core), + construction_message_loop_proxy_( + base::MessageLoopProxy::CreateForCurrentThread()), + io_message_loop_proxy_(notifier_options.request_context_getter-> + GetIOMessageLoopProxy()) { + io_message_loop_proxy_->PostTask( + FROM_HERE, + NewRunnableMethod( + core_.get(), + &NonBlockingInvalidationNotifier::Core::Initialize, + notifier_options, client_info)); +} + +NonBlockingInvalidationNotifier::~NonBlockingInvalidationNotifier() { + DCHECK(construction_message_loop_proxy_->BelongsToCurrentThread()); + io_message_loop_proxy_->PostTask( + FROM_HERE, + NewRunnableMethod( + core_.get(), + &NonBlockingInvalidationNotifier::Core::Teardown)); +} + +void NonBlockingInvalidationNotifier::AddObserver( + SyncNotifierObserver* observer) { + CheckOrSetValidThread(); + core_->AddObserver(observer); +} + +void NonBlockingInvalidationNotifier::RemoveObserver( + SyncNotifierObserver* observer) { + CheckOrSetValidThread(); + core_->RemoveObserver(observer); +} + +void NonBlockingInvalidationNotifier::SetState(const std::string& state) { + CheckOrSetValidThread(); + io_message_loop_proxy_->PostTask( + FROM_HERE, + NewRunnableMethod( + core_.get(), + &NonBlockingInvalidationNotifier::Core::SetState, + state)); +} + +void NonBlockingInvalidationNotifier::UpdateCredentials( + const std::string& email, const std::string& token) { + CheckOrSetValidThread(); + io_message_loop_proxy_->PostTask( + FROM_HERE, + NewRunnableMethod( + core_.get(), + &NonBlockingInvalidationNotifier::Core::UpdateCredentials, + email, token)); +} + +void NonBlockingInvalidationNotifier::UpdateEnabledTypes( + const syncable::ModelTypeSet& types) { + CheckOrSetValidThread(); + io_message_loop_proxy_->PostTask( + FROM_HERE, + NewRunnableMethod( + core_.get(), + &NonBlockingInvalidationNotifier::Core::UpdateEnabledTypes, + types)); +} + +void NonBlockingInvalidationNotifier::SendNotification() { + CheckOrSetValidThread(); + // InvalidationClient doesn't implement SendNotification(), so no + // need to forward on the call. +} + +void NonBlockingInvalidationNotifier::CheckOrSetValidThread() { + if (method_message_loop_proxy_) { + DCHECK(method_message_loop_proxy_->BelongsToCurrentThread()); + } else { + method_message_loop_proxy_ = + base::MessageLoopProxy::CreateForCurrentThread(); + } +} + +} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/non_blocking_invalidation_notifier.h b/chrome/browser/sync/notifier/non_blocking_invalidation_notifier.h new file mode 100644 index 0000000..362e4fb --- /dev/null +++ b/chrome/browser/sync/notifier/non_blocking_invalidation_notifier.h @@ -0,0 +1,56 @@ +// Copyright (c) 2011 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. +// +// An implementation of SyncNotifier that wraps InvalidationNotifier +// on its own thread. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_NON_BLOCKING_INVALIDATION_NOTIFIER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_NON_BLOCKING_INVALIDATION_NOTIFIER_H_ +#pragma once + +#include <string> + +#include "base/basictypes.h" +#include "base/memory/ref_counted.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "jingle/notifier/base/notifier_options.h" + +namespace base { +class MessageLoopProxy; +} + +namespace sync_notifier { + +class NonBlockingInvalidationNotifier : public SyncNotifier { + public: + NonBlockingInvalidationNotifier( + const notifier::NotifierOptions& notifier_options, + const std::string& client_info); + + virtual ~NonBlockingInvalidationNotifier(); + + // SyncNotifier implementation. + virtual void AddObserver(SyncNotifierObserver* observer); + virtual void RemoveObserver(SyncNotifierObserver* observer); + virtual void SetState(const std::string& state); + virtual void UpdateCredentials( + const std::string& email, const std::string& token); + virtual void UpdateEnabledTypes(const syncable::ModelTypeSet& types); + virtual void SendNotification(); + + private: + void CheckOrSetValidThread(); + // The real guts of NonBlockingInvalidationNotifier, which allows this class + // to not be refcounted. + class Core; + scoped_refptr<Core> core_; + scoped_refptr<base::MessageLoopProxy> construction_message_loop_proxy_; + scoped_refptr<base::MessageLoopProxy> method_message_loop_proxy_; + scoped_refptr<base::MessageLoopProxy> io_message_loop_proxy_; + DISALLOW_COPY_AND_ASSIGN(NonBlockingInvalidationNotifier); +}; + +} // namespace sync_notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_NON_BLOCKING_INVALIDATION_NOTIFIER_H_ diff --git a/chrome/browser/sync/notifier/non_blocking_invalidation_notifier_unittest.cc b/chrome/browser/sync/notifier/non_blocking_invalidation_notifier_unittest.cc new file mode 100644 index 0000000..5fec4ee --- /dev/null +++ b/chrome/browser/sync/notifier/non_blocking_invalidation_notifier_unittest.cc @@ -0,0 +1,75 @@ +// Copyright (c) 2011 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/sync/notifier/non_blocking_invalidation_notifier.h" + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "chrome/browser/sync/notifier/mock_sync_notifier_observer.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" +#include "chrome/test/test_url_request_context_getter.h" +#include "content/browser/browser_thread.h" +#include "jingle/notifier/base/fake_base_task.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace sync_notifier { + +namespace { + +using ::testing::InSequence; +using ::testing::StrictMock; + +class NonBlockingInvalidationNotifierTest : public testing::Test { + public: + NonBlockingInvalidationNotifierTest() {} + + protected: + virtual void SetUp() { + request_context_getter_ = new TestURLRequestContextGetter; + notifier::NotifierOptions notifier_options; + notifier_options.request_context_getter = request_context_getter_; + invalidation_notifier_.reset( + new NonBlockingInvalidationNotifier(notifier_options, + "fake_client_info")); + invalidation_notifier_->AddObserver(&mock_observer_); + } + + virtual void TearDown() { + invalidation_notifier_->RemoveObserver(&mock_observer_); + invalidation_notifier_.reset(); + { + // The request context getter gets deleted on the I/O thread. To prevent a + // leak supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + request_context_getter_ = NULL; + } + MessageLoop::current()->RunAllPending(); + } + + MessageLoop message_loop_; + scoped_refptr<net::URLRequestContextGetter> request_context_getter_; + scoped_ptr<NonBlockingInvalidationNotifier> invalidation_notifier_; + StrictMock<MockSyncNotifierObserver> mock_observer_; + notifier::FakeBaseTask fake_base_task_; +}; + +TEST_F(NonBlockingInvalidationNotifierTest, Basic) { + syncable::ModelTypeSet types; + types.insert(syncable::BOOKMARKS); + types.insert(syncable::AUTOFILL); + + invalidation_notifier_->SetState("fake_state"); + invalidation_notifier_->UpdateCredentials("foo@bar.com", "fake_token"); + invalidation_notifier_->UpdateEnabledTypes(types); +} + +// TODO(akalin): Add synchronous operations for testing to +// NonBlockingInvalidationNotifierTest and use that to test it. + +} // namespace + +} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/p2p_notifier.cc b/chrome/browser/sync/notifier/p2p_notifier.cc new file mode 100644 index 0000000..46d9a9e --- /dev/null +++ b/chrome/browser/sync/notifier/p2p_notifier.cc @@ -0,0 +1,150 @@ +// Copyright (c) 2011 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/sync/notifier/p2p_notifier.h" + +#include "base/message_loop_proxy.h" +#include "chrome/browser/sync/notifier/sync_notifier_observer.h" +#include "chrome/browser/sync/protocol/service_constants.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" +#include "jingle/notifier/listener/mediator_thread_impl.h" +#include "jingle/notifier/listener/talk_mediator_impl.h" + +namespace sync_notifier { + +namespace { +const char kSyncNotificationChannel[] = "http://www.google.com/chrome/sync"; +const char kSyncNotificationData[] = "sync-ping-p2p"; +} // namespace + +P2PNotifier::P2PNotifier( + const notifier::NotifierOptions& notifier_options) + : talk_mediator_( + new notifier::TalkMediatorImpl( + new notifier::MediatorThreadImpl(notifier_options), + notifier_options)), + logged_in_(false), + notifications_enabled_(false), + construction_message_loop_proxy_( + base::MessageLoopProxy::CreateForCurrentThread()) { + talk_mediator_->SetDelegate(this); +} + +P2PNotifier::~P2PNotifier() { + DCHECK(construction_message_loop_proxy_->BelongsToCurrentThread()); +} + +void P2PNotifier::AddObserver(SyncNotifierObserver* observer) { + CheckOrSetValidThread(); + observer_list_.AddObserver(observer); +} + +// Note: Since we need to shutdown TalkMediator on the method_thread, we are +// calling Logout on TalkMediator when the last observer is removed. +// Users will need to call UpdateCredentials again to use the same object. +// TODO(akalin): Think of a better solution to fix this. +void P2PNotifier::RemoveObserver(SyncNotifierObserver* observer) { + CheckOrSetValidThread(); + observer_list_.RemoveObserver(observer); + + // Logout after the last observer is removed. + if (observer_list_.size() == 0) { + talk_mediator_->Logout(); + } +} + +void P2PNotifier::SetState(const std::string& state) { + CheckOrSetValidThread(); +} + +void P2PNotifier::UpdateCredentials( + const std::string& email, const std::string& token) { + CheckOrSetValidThread(); + // If already logged in, the new credentials will take effect on the + // next reconnection. + talk_mediator_->SetAuthToken(email, token, SYNC_SERVICE_NAME); + if (!logged_in_) { + if (!talk_mediator_->Login()) { + LOG(DFATAL) << "Could not login for " << email; + return; + } + + notifier::Subscription subscription; + subscription.channel = kSyncNotificationChannel; + // There may be some subtle issues around case sensitivity of the + // from field, but it doesn't matter too much since this is only + // used in p2p mode (which is only used in testing). + subscription.from = email; + talk_mediator_->AddSubscription(subscription); + + logged_in_ = true; + } +} + +void P2PNotifier::UpdateEnabledTypes(const syncable::ModelTypeSet& types) { + CheckOrSetValidThread(); + enabled_types_ = types; + MaybeEmitNotification(); +} + +void P2PNotifier::SendNotification() { + CheckOrSetValidThread(); + VLOG(1) << "Sending XMPP notification..."; + notifier::Notification notification; + notification.channel = kSyncNotificationChannel; + notification.data = kSyncNotificationData; + talk_mediator_->SendNotification(notification); +} + +void P2PNotifier::OnNotificationStateChange(bool notifications_enabled) { + CheckOrSetValidThread(); + notifications_enabled_ = notifications_enabled; + FOR_EACH_OBSERVER(SyncNotifierObserver, observer_list_, + OnNotificationStateChange(notifications_enabled_)); + MaybeEmitNotification(); +} + +void P2PNotifier::OnIncomingNotification( + const notifier::Notification& notification) { + CheckOrSetValidThread(); + VLOG(1) << "Sync received P2P notification."; + if (notification.channel != kSyncNotificationChannel) { + LOG(WARNING) << "Notification from unexpected source: " + << notification.channel; + } + MaybeEmitNotification(); +} + +void P2PNotifier::OnOutgoingNotification() {} + +void P2PNotifier::MaybeEmitNotification() { + if (!logged_in_) { + VLOG(1) << "Not logged in yet -- not emitting notification"; + return; + } + if (!notifications_enabled_) { + VLOG(1) << "Notifications not enabled -- not emitting notification"; + return; + } + if (enabled_types_.empty()) { + VLOG(1) << "No enabled types -- not emitting notification"; + return; + } + syncable::ModelTypePayloadMap type_payloads = + syncable::ModelTypePayloadMapFromBitSet( + syncable::ModelTypeBitSetFromSet(enabled_types_), std::string()); + FOR_EACH_OBSERVER(SyncNotifierObserver, observer_list_, + OnIncomingNotification(type_payloads)); +} + +void P2PNotifier::CheckOrSetValidThread() { + if (method_message_loop_proxy_) { + DCHECK(method_message_loop_proxy_->BelongsToCurrentThread()); + } else { + method_message_loop_proxy_ = + base::MessageLoopProxy::CreateForCurrentThread(); + } +} + +} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/p2p_notifier.h b/chrome/browser/sync/notifier/p2p_notifier.h new file mode 100644 index 0000000..e5b2743 --- /dev/null +++ b/chrome/browser/sync/notifier/p2p_notifier.h @@ -0,0 +1,76 @@ +// Copyright (c) 2011 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. +// +// A notifier that uses p2p notifications based on XMPP push +// notifications. Used only for sync integration tests. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_P2P_NOTIFIER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_P2P_NOTIFIER_H_ + +#include <string> + +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" +#include "base/observer_list.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "jingle/notifier/listener/talk_mediator.h" + +namespace base { +class MessageLoopProxy; +} + + +namespace notifier { +struct NotifierOptions; +} // namespace + +namespace sync_notifier { + +class P2PNotifier + : public SyncNotifier, + public notifier::TalkMediator::Delegate { + public: + explicit P2PNotifier(const notifier::NotifierOptions& notifier_options); + + virtual ~P2PNotifier(); + + // SyncNotifier implementation + virtual void AddObserver(SyncNotifierObserver* observer); + virtual void RemoveObserver(SyncNotifierObserver* observer); + virtual void SetState(const std::string& state); + virtual void UpdateCredentials( + const std::string& email, const std::string& token); + virtual void UpdateEnabledTypes(const syncable::ModelTypeSet& types); + virtual void SendNotification(); + + // TalkMediator::Delegate implementation. + virtual void OnNotificationStateChange(bool notifications_enabled); + virtual void OnIncomingNotification( + const notifier::Notification& notification); + virtual void OnOutgoingNotification(); + + private: + // Call OnIncomingNotification() on observers if we have a non-empty + // set of enabled types. + void MaybeEmitNotification(); + void CheckOrSetValidThread(); + + ObserverList<SyncNotifierObserver> observer_list_; + + // The actual notification listener. + scoped_ptr<notifier::TalkMediator> talk_mediator_; + // Whether we called Login() on |talk_mediator_| yet. + bool logged_in_; + // Whether |talk_mediator_| has notified us that notifications are + // enabled. + bool notifications_enabled_; + + syncable::ModelTypeSet enabled_types_; + scoped_refptr<base::MessageLoopProxy> construction_message_loop_proxy_; + scoped_refptr<base::MessageLoopProxy> method_message_loop_proxy_; +}; + +} // namespace sync_notifier +#endif // CHROME_BROWSER_SYNC_NOTIFIER_P2P_NOTIFIER_H_ diff --git a/chrome/browser/sync/notifier/registration_manager.cc b/chrome/browser/sync/notifier/registration_manager.cc index 69181a4..4dbc095 100644 --- a/chrome/browser/sync/notifier/registration_manager.cc +++ b/chrome/browser/sync/notifier/registration_manager.cc @@ -4,17 +4,55 @@ #include "chrome/browser/sync/notifier/registration_manager.h" +#include <algorithm> +#include <cstddef> #include <string> +#include "base/rand_util.h" #include "chrome/browser/sync/notifier/invalidation_util.h" #include "chrome/browser/sync/syncable/model_type.h" namespace sync_notifier { +RegistrationManager::PendingRegistrationInfo::PendingRegistrationInfo() {} + +RegistrationManager::RegistrationStatus::RegistrationStatus() + : model_type(syncable::UNSPECIFIED), + registration_manager(NULL), + state(invalidation::RegistrationState_UNREGISTERED) {} + +RegistrationManager::RegistrationStatus::~RegistrationStatus() {} + +void RegistrationManager::RegistrationStatus::DoRegister() { + DCHECK_NE(model_type, syncable::UNSPECIFIED); + DCHECK(registration_manager); + // We might be called explicitly, so stop the timer manually and + // reset the delay. + registration_timer.Stop(); + delay = base::TimeDelta(); + registration_manager->DoRegisterType(model_type); + DCHECK(!last_registration_request.is_null()); +} + +const int RegistrationManager::kInitialRegistrationDelaySeconds = 5; +const int RegistrationManager::kRegistrationDelayExponent = 2; +const double RegistrationManager::kRegistrationDelayMaxJitter = 0.5; +const int RegistrationManager::kMinRegistrationDelaySeconds = 1; +// 1 hour. +const int RegistrationManager::kMaxRegistrationDelaySeconds = 60 * 60; + RegistrationManager::RegistrationManager( invalidation::InvalidationClient* invalidation_client) : invalidation_client_(invalidation_client) { DCHECK(invalidation_client_); + // Initialize statuses. + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType model_type = syncable::ModelTypeFromInt(i); + RegistrationStatus* status = ®istration_statuses_[model_type]; + status->model_type = model_type; + status->registration_manager = this; + } } RegistrationManager::~RegistrationManager() { @@ -25,76 +63,168 @@ void RegistrationManager::SetRegisteredTypes( const syncable::ModelTypeSet& types) { DCHECK(non_thread_safe_.CalledOnValidThread()); - for (int i = syncable::FIRST_REAL_MODEL_TYPE; i < syncable::MODEL_TYPE_COUNT; - ++i) { - syncable::ModelType type = syncable::ModelTypeFromInt(i); - if (types.count(type) > 0) { - RegisterType(type); + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType model_type = syncable::ModelTypeFromInt(i); + if (types.count(model_type) > 0) { + if (!IsTypeRegistered(model_type)) { + TryRegisterType(model_type, false /* is_retry */); + } } else { - UnregisterType(type); + if (IsTypeRegistered(model_type)) { + UnregisterType(model_type); + } } } } -bool RegistrationManager::IsRegistered( - syncable::ModelType model_type) const { +void RegistrationManager::MarkRegistrationLost( + syncable::ModelType model_type) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + registration_statuses_[model_type].state = + invalidation::RegistrationState_UNREGISTERED; + TryRegisterType(model_type, true /* is_retry */); +} + +void RegistrationManager::MarkAllRegistrationsLost() { DCHECK(non_thread_safe_.CalledOnValidThread()); - RegistrationStatusMap::const_iterator it = - registration_status_.find(model_type); - if (it == registration_status_.end()) { - return false; + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType model_type = syncable::ModelTypeFromInt(i); + if (IsTypeRegistered(model_type)) { + MarkRegistrationLost(model_type); + } } - return it->second == invalidation::RegistrationState_REGISTERED; } -void RegistrationManager::MarkRegistrationLost( - syncable::ModelType model_type) { +syncable::ModelTypeSet RegistrationManager::GetRegisteredTypes() const { DCHECK(non_thread_safe_.CalledOnValidThread()); - invalidation::ObjectId object_id; - if (!RealModelTypeToObjectId(model_type, &object_id)) { - LOG(ERROR) << "Invalid model type: " << model_type; - return; + syncable::ModelTypeSet registered_types; + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType model_type = syncable::ModelTypeFromInt(i); + if (IsTypeRegistered(model_type)) { + registered_types.insert(model_type); + } } - RegistrationStatusMap::iterator it = - registration_status_.find(model_type); - if (it == registration_status_.end()) { - LOG(ERROR) << "Unknown model type: " << model_type; - return; + return registered_types; +} + +RegistrationManager::PendingRegistrationMap + RegistrationManager::GetPendingRegistrations() const { + DCHECK(non_thread_safe_.CalledOnValidThread()); + PendingRegistrationMap pending_registrations; + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType model_type = syncable::ModelTypeFromInt(i); + const RegistrationStatus& status = registration_statuses_[model_type]; + if (status.registration_timer.IsRunning()) { + pending_registrations[model_type].last_registration_request = + status.last_registration_request; + pending_registrations[model_type].registration_attempt = + status.last_registration_attempt; + pending_registrations[model_type].delay = status.delay; + pending_registrations[model_type].actual_delay = + status.registration_timer.GetCurrentDelay(); + } } - it->second = invalidation::RegistrationState_UNREGISTERED; - RegisterObject(object_id, it); + return pending_registrations; } -void RegistrationManager::MarkAllRegistrationsLost() { +void RegistrationManager::FirePendingRegistrationsForTest() { DCHECK(non_thread_safe_.CalledOnValidThread()); - for (RegistrationStatusMap::iterator it = - registration_status_.begin(); - it != registration_status_.end(); ++it) { - invalidation::ObjectId object_id; - if (!RealModelTypeToObjectId(it->first, &object_id)) { - LOG(DFATAL) << "Invalid model type: " << it->first; - continue; + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + syncable::ModelType model_type = syncable::ModelTypeFromInt(i); + RegistrationStatus* status = ®istration_statuses_[model_type]; + if (status->registration_timer.IsRunning()) { + status->DoRegister(); } - it->second = invalidation::RegistrationState_UNREGISTERED; - RegisterObject(object_id, it); } } -void RegistrationManager::RegisterType(syncable::ModelType model_type) { +// static +double RegistrationManager::CalculateBackoff( + double retry_interval, + double initial_retry_interval, + double min_retry_interval, + double max_retry_interval, + double backoff_exponent, + double jitter, + double max_jitter) { + // scaled_jitter lies in [-max_jitter, max_jitter]. + double scaled_jitter = jitter * max_jitter; + double new_retry_interval = + (retry_interval == 0.0) ? + (initial_retry_interval * (1.0 + scaled_jitter)) : + (retry_interval * (backoff_exponent + scaled_jitter)); + return std::max(min_retry_interval, + std::min(max_retry_interval, new_retry_interval)); +} + +double RegistrationManager::GetJitter() { + // |jitter| lies in [-1.0, 1.0), which is low-biased, but only + // barely. + // + // TODO(akalin): Fix the bias. + return 2.0 * base::RandDouble() - 1.0; +} + +void RegistrationManager::TryRegisterType(syncable::ModelType model_type, + bool is_retry) { + DCHECK(non_thread_safe_.CalledOnValidThread()); + RegistrationStatus* status = ®istration_statuses_[model_type]; + status->last_registration_attempt = base::Time::Now(); + if (is_retry) { + // If we're a retry, we must have tried at least once before. + DCHECK(!status->last_registration_request.is_null()); + // delay = max(0, (now - last request) + next_delay) + status->delay = + (status->last_registration_request - + status->last_registration_attempt) + + status->next_delay; + base::TimeDelta delay = + (status->delay <= base::TimeDelta()) ? + base::TimeDelta() : status->delay; + VLOG(2) << "Registering " + << syncable::ModelTypeToString(model_type) << " in " + << delay.InMilliseconds() << " ms"; + status->registration_timer.Stop(); + status->registration_timer.Start( + delay, status, &RegistrationManager::RegistrationStatus::DoRegister); + double next_delay_seconds = + CalculateBackoff(static_cast<double>(status->next_delay.InSeconds()), + kInitialRegistrationDelaySeconds, + kMinRegistrationDelaySeconds, + kMaxRegistrationDelaySeconds, + kRegistrationDelayExponent, + GetJitter(), + kRegistrationDelayMaxJitter); + status->next_delay = + base::TimeDelta::FromSeconds(static_cast<int64>(next_delay_seconds)); + VLOG(2) << "New next delay for " + << syncable::ModelTypeToString(model_type) << " is " + << status->next_delay.InSeconds() << " seconds"; + } else { + VLOG(2) << "Not a retry -- registering " + << syncable::ModelTypeToString(model_type) << " immediately"; + status->delay = base::TimeDelta(); + status->next_delay = base::TimeDelta(); + status->DoRegister(); + } +} + +void RegistrationManager::DoRegisterType(syncable::ModelType model_type) { DCHECK(non_thread_safe_.CalledOnValidThread()); invalidation::ObjectId object_id; if (!RealModelTypeToObjectId(model_type, &object_id)) { LOG(DFATAL) << "Invalid model type: " << model_type; return; } - RegistrationStatusMap::iterator it = - registration_status_.insert( - std::make_pair( - model_type, - invalidation::RegistrationState_UNREGISTERED)).first; - if (it->second == invalidation::RegistrationState_UNREGISTERED) { - RegisterObject(object_id, it); - } + invalidation_client_->Register(object_id); + RegistrationStatus* status = ®istration_statuses_[model_type]; + status->state = invalidation::RegistrationState_REGISTERED; + status->last_registration_request = base::Time::Now(); } void RegistrationManager::UnregisterType(syncable::ModelType model_type) { @@ -104,21 +234,16 @@ void RegistrationManager::UnregisterType(syncable::ModelType model_type) { LOG(DFATAL) << "Invalid model type: " << model_type; return; } - RegistrationStatusMap::iterator it = registration_status_.find(model_type); - - if (it != registration_status_.end()) { - if (it->second == invalidation::RegistrationState_REGISTERED) { - invalidation_client_->Unregister(object_id); - } - registration_status_.erase(it); - } + invalidation_client_->Unregister(object_id); + RegistrationStatus* status = ®istration_statuses_[model_type]; + status->state = invalidation::RegistrationState_UNREGISTERED; } -void RegistrationManager::RegisterObject( - const invalidation::ObjectId& object_id, - RegistrationStatusMap::iterator it) { - DCHECK_EQ(it->second, invalidation::RegistrationState_UNREGISTERED); - invalidation_client_->Register(object_id); - it->second = invalidation::RegistrationState_REGISTERED; +bool RegistrationManager::IsTypeRegistered( + syncable::ModelType model_type) const { + DCHECK(non_thread_safe_.CalledOnValidThread()); + return registration_statuses_[model_type].state == + invalidation::RegistrationState_REGISTERED; } + } // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/registration_manager.h b/chrome/browser/sync/notifier/registration_manager.h index d7edb04..8111585 100644 --- a/chrome/browser/sync/notifier/registration_manager.h +++ b/chrome/browser/sync/notifier/registration_manager.h @@ -12,35 +12,61 @@ #include <map> #include "base/basictypes.h" -#include "base/gtest_prod_util.h" +#include "base/time.h" +#include "base/timer.h" #include "base/threading/non_thread_safe.h" #include "chrome/browser/sync/syncable/model_type.h" +// For invalidation::RegistrationState. #include "google/cacheinvalidation/invalidation-client.h" namespace sync_notifier { +// Manages the details of registering types for invalidation. +// Implements exponential backoff for repeated registration attempts +// to the invalidation client. +// +// TODO(akalin): Consolidate exponential backoff code. Other +// implementations include the syncer thread (both versions) and XMPP +// retries. The most sophisticated one is URLRequestThrottler; making +// that generic should work for everyone. class RegistrationManager { - - friend class RegistrationManagerTest; - - FRIEND_TEST_ALL_PREFIXES(RegistrationManagerTest, RegisterType); - FRIEND_TEST_ALL_PREFIXES(RegistrationManagerTest, UnregisterType); - FRIEND_TEST_ALL_PREFIXES(RegistrationManagerTest, MarkRegistrationLost); - FRIEND_TEST_ALL_PREFIXES(RegistrationManagerTest, MarkAllRegistrationsLost); public: + // Constants for exponential backoff (used by tests). + static const int kInitialRegistrationDelaySeconds; + static const int kRegistrationDelayExponent; + static const double kRegistrationDelayMaxJitter; + static const int kMinRegistrationDelaySeconds; + static const int kMaxRegistrationDelaySeconds; + + // Types used by testing functions. + struct PendingRegistrationInfo { + PendingRegistrationInfo(); + + // Last time a registration request was actually sent. + base::Time last_registration_request; + // Time the registration was attempted. + base::Time registration_attempt; + // The calculated delay of the pending registration (which may be + // negative). + base::TimeDelta delay; + // The delay of the timer, which should be max(delay, 0). + base::TimeDelta actual_delay; + }; + // Map from types with pending registrations to info about the + // pending registration. + typedef std::map<syncable::ModelType, PendingRegistrationInfo> + PendingRegistrationMap; + // Does not take ownership of |invalidation_client_|. explicit RegistrationManager( invalidation::InvalidationClient* invalidation_client); - ~RegistrationManager(); + virtual ~RegistrationManager(); + // Registers all types included in the given set and sets all other + // types to be unregistered. void SetRegisteredTypes(const syncable::ModelTypeSet& types); - // Returns true iff |model_type| is currently registered. - // - // Currently only used by unit tests. - bool IsRegistered(syncable::ModelType model_type) const; - // Marks the registration for the |model_type| lost and re-registers // it. void MarkRegistrationLost(syncable::ModelType model_type); @@ -48,26 +74,84 @@ class RegistrationManager { // Marks all registrations lost and re-registers them. void MarkAllRegistrationsLost(); - private: - typedef std::map<syncable::ModelType, invalidation::RegistrationState> - RegistrationStatusMap; + // The functions below should only be used in tests. - // Registers the given |model_type|, which must be valid. - void RegisterType(syncable::ModelType model_type); + // Gets all currently-registered types. + syncable::ModelTypeSet GetRegisteredTypes() const; - void UnregisterType(syncable::ModelType model_type); + // Gets all pending registrations and their next min delays. + PendingRegistrationMap GetPendingRegistrations() const; + + // Run pending registrations immediately. + void FirePendingRegistrationsForTest(); + + // Calculate exponential backoff. |jitter| must be Uniform[-1.0, + // 1.0]. + static double CalculateBackoff(double retry_interval, + double initial_retry_interval, + double min_retry_interval, + double max_retry_interval, + double backoff_exponent, + double jitter, + double max_jitter); - // Calls invalidation_client_->Register() on |object_id|. sets - // it->second to UNREGISTERED -> PENDING. - void RegisterObject(const invalidation::ObjectId& object_id, - RegistrationStatusMap::iterator it); + protected: + // Overrideable for testing purposes. + virtual double GetJitter(); + + private: + struct RegistrationStatus { + RegistrationStatus(); + ~RegistrationStatus(); + + // Calls registration_manager->DoRegister(model_type). (needed by + // |registration_timer|). + void DoRegister(); + + // The model type for which this is the status. + syncable::ModelType model_type; + // The parent registration manager. + RegistrationManager* registration_manager; + + // The current registration state. + invalidation::RegistrationState state; + // When we last sent a registration request. + base::Time last_registration_request; + // When we last tried to register. + base::Time last_registration_attempt; + // The calculated delay of any pending registration (which may be + // negative). + base::TimeDelta delay; + // The minimum time to wait until any next registration attempt. + // Increased after each consecutive failure. + base::TimeDelta next_delay; + // The actual timer for registration. + base::OneShotTimer<RegistrationStatus> registration_timer; + }; + + // If |is_retry| is not set, registers the given type immediately + // and resets all backoff parameters. If |is_retry| is set, + // registers the given type at some point in the future and + // increases the delay until the next retry. + void TryRegisterType(syncable::ModelType model_type, + bool is_retry); + + // Registers the given type, which must be valid, immediately. + // Updates |last_registration| in the appropriate + // RegistrationStatus. Should only be called by + // RegistrationStatus::DoRegister(). + void DoRegisterType(syncable::ModelType model_type); + + // Unregisters the given type, which must be valid. + void UnregisterType(syncable::ModelType model_type); - void OnRegister(const invalidation::RegistrationUpdateResult& result); + // Returns true iff the given type, which must be valid, is registered. + bool IsTypeRegistered(syncable::ModelType model_type) const; base::NonThreadSafe non_thread_safe_; + RegistrationStatus registration_statuses_[syncable::MODEL_TYPE_COUNT]; // Weak pointer. invalidation::InvalidationClient* invalidation_client_; - RegistrationStatusMap registration_status_; DISALLOW_COPY_AND_ASSIGN(RegistrationManager); }; diff --git a/chrome/browser/sync/notifier/registration_manager_unittest.cc b/chrome/browser/sync/notifier/registration_manager_unittest.cc index ac5b236..a452d75 100644 --- a/chrome/browser/sync/notifier/registration_manager_unittest.cc +++ b/chrome/browser/sync/notifier/registration_manager_unittest.cc @@ -5,17 +5,20 @@ #include "chrome/browser/sync/notifier/registration_manager.h" #include <algorithm> +#include <cmath> #include <cstddef> #include <deque> #include <vector> #include "base/basictypes.h" +#include "base/message_loop.h" #include "chrome/browser/sync/notifier/invalidation_util.h" #include "chrome/browser/sync/syncable/model_type.h" #include "google/cacheinvalidation/invalidation-client.h" #include "testing/gtest/include/gtest/gtest.h" namespace sync_notifier { +namespace { syncable::ModelType ObjectIdToModelType( const invalidation::ObjectId& object_id) { @@ -24,33 +27,62 @@ syncable::ModelType ObjectIdToModelType( return model_type; } -// Fake invalidation client that just stores the args of calls to -// Register(). +// Fake registration manager that lets you override jitter. +class FakeRegistrationManager : public RegistrationManager { + public: + explicit FakeRegistrationManager( + invalidation::InvalidationClient* invalidation_client) + : RegistrationManager(invalidation_client), + jitter_(0.0) {} + + virtual ~FakeRegistrationManager() {} + + void SetJitter(double jitter) { + jitter_ = jitter; + } + + protected: + virtual double GetJitter() { + return jitter_; + } + + private: + double jitter_; + + DISALLOW_COPY_AND_ASSIGN(FakeRegistrationManager); +}; + +// Fake invalidation client that just stores the currently-registered +// model types. class FakeInvalidationClient : public invalidation::InvalidationClient { public: FakeInvalidationClient() {} virtual ~FakeInvalidationClient() {} + void LoseRegistration(syncable::ModelType model_type) { + EXPECT_GT(registered_types_.count(model_type), 0u); + registered_types_.erase(model_type); + } + + void LoseAllRegistrations() { + registered_types_.clear(); + } + + // invalidation::InvalidationClient implementation. + virtual void Start(const std::string& state) {} virtual void Register(const invalidation::ObjectId& oid) { - registered_types.push_back(ObjectIdToModelType(oid)); + syncable::ModelType model_type = ObjectIdToModelType(oid); + EXPECT_EQ(0u, registered_types_.count(model_type)); + registered_types_.insert(model_type); } virtual void Unregister(const invalidation::ObjectId& oid) { - syncable::ModelType type_to_unregister = ObjectIdToModelType(oid); - std::vector<syncable::ModelType>::iterator it = std::find( - registered_types.begin(), - registered_types.end(), - type_to_unregister); - - if(it == registered_types.end()) { - // We should not be unregistering a thing that is not yet registered. - ADD_FAILURE(); - } else { - registered_types.erase(it); - } + syncable::ModelType model_type = ObjectIdToModelType(oid); + EXPECT_GT(registered_types_.count(model_type), 0u); + registered_types_.erase(model_type); } virtual invalidation::NetworkEndpoint* network_endpoint() { @@ -58,177 +90,283 @@ class FakeInvalidationClient : public invalidation::InvalidationClient { return NULL; } - std::vector<syncable::ModelType> registered_types; + const syncable::ModelTypeSet GetRegisteredTypes() const { + return registered_types_; + } private: + syncable::ModelTypeSet registered_types_; + DISALLOW_COPY_AND_ASSIGN(FakeInvalidationClient); }; +const syncable::ModelType kModelTypes[] = { + syncable::BOOKMARKS, + syncable::PREFERENCES, + syncable::THEMES, + syncable::AUTOFILL, + syncable::EXTENSIONS, +}; +const size_t kModelTypeCount = arraysize(kModelTypes); + +void ExpectPendingRegistrations( + const syncable::ModelTypeSet& expected_pending_types, + double expected_delay_seconds, + const RegistrationManager::PendingRegistrationMap& pending_registrations) { + syncable::ModelTypeSet pending_types; + for (RegistrationManager::PendingRegistrationMap::const_iterator it = + pending_registrations.begin(); it != pending_registrations.end(); + ++it) { + SCOPED_TRACE(syncable::ModelTypeToString(it->first)); + pending_types.insert(it->first); + base::TimeDelta offset = + it->second.last_registration_request - + it->second.registration_attempt; + base::TimeDelta expected_delay = + base::TimeDelta::FromSeconds( + static_cast<int64>(expected_delay_seconds)) + offset; + // TODO(akalin): Add base::PrintTo() for base::Time and + // base::TimeDeltas. + EXPECT_EQ(it->second.delay, expected_delay) + << it->second.delay.InMicroseconds() + << ", " << expected_delay.InMicroseconds(); + if (it->second.delay <= base::TimeDelta()) { + EXPECT_EQ(it->second.actual_delay, base::TimeDelta()); + } else { + EXPECT_EQ(it->second.delay, it->second.actual_delay); + } + } + EXPECT_EQ(expected_pending_types, pending_types); +} + class RegistrationManagerTest : public testing::Test { protected: RegistrationManagerTest() - : registration_manager_(&fake_invalidation_client_) {} + : fake_registration_manager_(&fake_invalidation_client_) {} virtual ~RegistrationManagerTest() {} + void LoseRegistrations(const syncable::ModelTypeSet& types) { + for (syncable::ModelTypeSet::const_iterator it = types.begin(); + it != types.end(); ++it) { + fake_invalidation_client_.LoseRegistration(*it); + fake_registration_manager_.MarkRegistrationLost(*it); + } + } + + // Used by MarkRegistrationLostBackoff* tests. + void RunBackoffTest(double jitter) { + fake_registration_manager_.SetJitter(jitter); + syncable::ModelTypeSet types(kModelTypes, kModelTypes + kModelTypeCount); + fake_registration_manager_.SetRegisteredTypes(types); + + // Lose some types. + syncable::ModelTypeSet lost_types(kModelTypes, kModelTypes + 2); + LoseRegistrations(lost_types); + ExpectPendingRegistrations( + lost_types, 0.0, + fake_registration_manager_.GetPendingRegistrations()); + + // Trigger another failure to start delaying. + fake_registration_manager_.FirePendingRegistrationsForTest(); + LoseRegistrations(lost_types); + + double scaled_jitter = + jitter * RegistrationManager::kRegistrationDelayMaxJitter; + + double expected_delay = + RegistrationManager::kInitialRegistrationDelaySeconds * + (1.0 + scaled_jitter); + expected_delay = std::floor(expected_delay); + ExpectPendingRegistrations( + lost_types, expected_delay, + fake_registration_manager_.GetPendingRegistrations()); + + // Trigger another failure. + fake_registration_manager_.FirePendingRegistrationsForTest(); + LoseRegistrations(lost_types); + expected_delay *= + RegistrationManager::kRegistrationDelayExponent + scaled_jitter; + expected_delay = std::floor(expected_delay); + ExpectPendingRegistrations( + lost_types, expected_delay, + fake_registration_manager_.GetPendingRegistrations()); + + // Trigger enough failures to hit the ceiling. + while (expected_delay < RegistrationManager::kMaxRegistrationDelaySeconds) { + fake_registration_manager_.FirePendingRegistrationsForTest(); + LoseRegistrations(lost_types); + expected_delay *= + RegistrationManager::kRegistrationDelayExponent + scaled_jitter; + expected_delay = std::floor(expected_delay); + } + ExpectPendingRegistrations( + lost_types, + RegistrationManager::kMaxRegistrationDelaySeconds, + fake_registration_manager_.GetPendingRegistrations()); + } + FakeInvalidationClient fake_invalidation_client_; - RegistrationManager registration_manager_; + FakeRegistrationManager fake_registration_manager_; private: + // Needed by timers in RegistrationManager. + MessageLoop message_loop_; + DISALLOW_COPY_AND_ASSIGN(RegistrationManagerTest); }; -TEST_F(RegistrationManagerTest, RegisterType) { - const syncable::ModelType kModelTypes[] = { - syncable::BOOKMARKS, - syncable::PREFERENCES, - syncable::THEMES, - syncable::AUTOFILL, - syncable::EXTENSIONS, - }; - const size_t kModelTypeCount = arraysize(kModelTypes); - - // Register types. - for (size_t i = 0; i < kModelTypeCount; ++i) { - // Register twice; it shouldn't matter. - registration_manager_.RegisterType(kModelTypes[i]); - registration_manager_.RegisterType(kModelTypes[i]); - } +TEST_F(RegistrationManagerTest, SetRegisteredTypes) { + syncable::ModelTypeSet no_types; + syncable::ModelTypeSet types(kModelTypes, kModelTypes + kModelTypeCount); - ASSERT_EQ(kModelTypeCount, - fake_invalidation_client_.registered_types.size()); + EXPECT_EQ(no_types, fake_registration_manager_.GetRegisteredTypes()); + EXPECT_EQ(no_types, fake_invalidation_client_.GetRegisteredTypes()); - // Everything should be registered. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_TRUE(registration_manager_.IsRegistered(kModelTypes[i])); - } + fake_registration_manager_.SetRegisteredTypes(types); + EXPECT_EQ(types, fake_registration_manager_.GetRegisteredTypes()); + EXPECT_EQ(types, fake_invalidation_client_.GetRegisteredTypes()); - // Check object IDs. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_EQ(kModelTypes[i], - fake_invalidation_client_.registered_types[i]); - } + types.insert(syncable::APPS); + types.erase(syncable::BOOKMARKS); + fake_registration_manager_.SetRegisteredTypes(types); + EXPECT_EQ(types, fake_registration_manager_.GetRegisteredTypes()); + EXPECT_EQ(types, fake_invalidation_client_.GetRegisteredTypes()); } -TEST_F(RegistrationManagerTest, UnregisterType) { - const syncable::ModelType kModelTypes[] = { - syncable::BOOKMARKS, - syncable::PREFERENCES, - syncable::THEMES, - syncable::AUTOFILL, - syncable::EXTENSIONS, - }; - const size_t kModelTypeCount = arraysize(kModelTypes); - - // Register types. - for (size_t i = 0; i < kModelTypeCount; ++i) { - // Register twice; it shouldn't matter. - registration_manager_.RegisterType(kModelTypes[i]); - } - - ASSERT_EQ(kModelTypeCount, - fake_invalidation_client_.registered_types.size()); - - // Everything should be registered. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_TRUE(registration_manager_.IsRegistered(kModelTypes[i])); - } - - // Check object IDs. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_EQ(kModelTypes[i], - fake_invalidation_client_.registered_types[i]); - } - - // Now unregister the extension. - registration_manager_.UnregisterType(syncable::EXTENSIONS); - - // Check the count and the types currently registered to ensure extensions - // is unregistered. - ASSERT_EQ(kModelTypeCount - 1, - fake_invalidation_client_.registered_types.size()); +int GetRoundedBackoff(double retry_interval, double jitter) { + const double kInitialRetryInterval = 3.0; + const double kMinRetryInterval = 2.0; + const double kMaxRetryInterval = 20.0; + const double kBackoffExponent = 2.0; + const double kMaxJitter = 0.5; + + return static_cast<int>( + RegistrationManager::CalculateBackoff(retry_interval, + kInitialRetryInterval, + kMinRetryInterval, + kMaxRetryInterval, + kBackoffExponent, + jitter, + kMaxJitter)); +} - // Check object IDs. - for (size_t i = 0; i < kModelTypeCount - 1; ++i) { - EXPECT_EQ(kModelTypes[i], - fake_invalidation_client_.registered_types[i]); - } +TEST_F(RegistrationManagerTest, CalculateBackoff) { + // Test initial. + EXPECT_EQ(2, GetRoundedBackoff(0.0, -1.0)); + EXPECT_EQ(3, GetRoundedBackoff(0.0, 0.0)); + EXPECT_EQ(4, GetRoundedBackoff(0.0, +1.0)); + + // Test non-initial. + EXPECT_EQ(4, GetRoundedBackoff(3.0, -1.0)); + EXPECT_EQ(6, GetRoundedBackoff(3.0, 0.0)); + EXPECT_EQ(7, GetRoundedBackoff(3.0, +1.0)); + + EXPECT_EQ(7, GetRoundedBackoff(5.0, -1.0)); + EXPECT_EQ(10, GetRoundedBackoff(5.0, 0.0)); + EXPECT_EQ(12, GetRoundedBackoff(5.0, +1.0)); + + // Test ceiling. + EXPECT_EQ(19, GetRoundedBackoff(13.0, -1.0)); + EXPECT_EQ(20, GetRoundedBackoff(13.0, 0.0)); + EXPECT_EQ(20, GetRoundedBackoff(13.0, +1.0)); } TEST_F(RegistrationManagerTest, MarkRegistrationLost) { - const syncable::ModelType kModelTypes[] = { - syncable::BOOKMARKS, - syncable::PREFERENCES, - syncable::THEMES, - syncable::AUTOFILL, - syncable::EXTENSIONS, - }; - const size_t kModelTypeCount = arraysize(kModelTypes); - - // Register types. - for (size_t i = 0; i < kModelTypeCount; ++i) { - registration_manager_.RegisterType(kModelTypes[i]); - } - - ASSERT_EQ(kModelTypeCount, - fake_invalidation_client_.registered_types.size()); + syncable::ModelTypeSet types(kModelTypes, kModelTypes + kModelTypeCount); + + fake_registration_manager_.SetRegisteredTypes(types); + EXPECT_TRUE(fake_registration_manager_.GetPendingRegistrations().empty()); + + // Lose some types. + syncable::ModelTypeSet lost_types( + kModelTypes, kModelTypes + 3); + syncable::ModelTypeSet non_lost_types( + kModelTypes + 3, kModelTypes + kModelTypeCount); + LoseRegistrations(lost_types); + ExpectPendingRegistrations( + lost_types, 0.0, + fake_registration_manager_.GetPendingRegistrations()); + EXPECT_EQ(non_lost_types, fake_registration_manager_.GetRegisteredTypes()); + EXPECT_EQ(non_lost_types, fake_invalidation_client_.GetRegisteredTypes()); + + // Pretend we waited long enough to re-register. + fake_registration_manager_.FirePendingRegistrationsForTest(); + EXPECT_EQ(types, fake_registration_manager_.GetRegisteredTypes()); + EXPECT_EQ(types, fake_invalidation_client_.GetRegisteredTypes()); +} - // All should be registered. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_TRUE(registration_manager_.IsRegistered(kModelTypes[i])); - } +TEST_F(RegistrationManagerTest, MarkRegistrationLostBackoffLow) { + RunBackoffTest(-1.0); +} - // Mark the registrations of all but the first one lost. - for (size_t i = 1; i < kModelTypeCount; ++i) { - registration_manager_.MarkRegistrationLost(kModelTypes[i]); - } +TEST_F(RegistrationManagerTest, MarkRegistrationLostBackoffMid) { + RunBackoffTest(0.0); +} - ASSERT_EQ(2 * kModelTypeCount - 1, - fake_invalidation_client_.registered_types.size()); +TEST_F(RegistrationManagerTest, MarkRegistrationLostBackoffHigh) { + RunBackoffTest(+1.0); +} - // All should still be registered. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_TRUE(registration_manager_.IsRegistered(kModelTypes[i])); - } +TEST_F(RegistrationManagerTest, MarkRegistrationLostBackoffReset) { + syncable::ModelTypeSet types(kModelTypes, kModelTypes + kModelTypeCount); + + fake_registration_manager_.SetRegisteredTypes(types); + + // Lose some types. + syncable::ModelTypeSet lost_types(kModelTypes, kModelTypes + 2); + LoseRegistrations(lost_types); + ExpectPendingRegistrations( + lost_types, 0.0, + fake_registration_manager_.GetPendingRegistrations()); + + // Trigger another failure to start delaying. + fake_registration_manager_.FirePendingRegistrationsForTest(); + LoseRegistrations(lost_types); + double expected_delay = + RegistrationManager::kInitialRegistrationDelaySeconds; + ExpectPendingRegistrations( + lost_types, expected_delay, + fake_registration_manager_.GetPendingRegistrations()); + + // Set types again. + fake_registration_manager_.SetRegisteredTypes(types); + ExpectPendingRegistrations( + syncable::ModelTypeSet(), 0.0, + fake_registration_manager_.GetPendingRegistrations()); } TEST_F(RegistrationManagerTest, MarkAllRegistrationsLost) { - const syncable::ModelType kModelTypes[] = { - syncable::BOOKMARKS, - syncable::PREFERENCES, - syncable::THEMES, - syncable::AUTOFILL, - syncable::EXTENSIONS, - }; - const size_t kModelTypeCount = arraysize(kModelTypes); - - // Register types. - for (size_t i = 0; i < kModelTypeCount; ++i) { - registration_manager_.RegisterType(kModelTypes[i]); - } - - ASSERT_EQ(kModelTypeCount, - fake_invalidation_client_.registered_types.size()); - - // All should be registered. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_TRUE(registration_manager_.IsRegistered(kModelTypes[i])); - } - - // Mark the registrations of all but the first one lost. Then mark - // everything lost. - for (size_t i = 1; i < kModelTypeCount; ++i) { - registration_manager_.MarkRegistrationLost(kModelTypes[i]); - } - registration_manager_.MarkAllRegistrationsLost(); - - ASSERT_EQ(3 * kModelTypeCount - 1, - fake_invalidation_client_.registered_types.size()); - - // All should still be registered. - for (size_t i = 0; i < kModelTypeCount; ++i) { - EXPECT_TRUE(registration_manager_.IsRegistered(kModelTypes[i])); - } + syncable::ModelTypeSet types(kModelTypes, kModelTypes + kModelTypeCount); + + fake_registration_manager_.SetRegisteredTypes(types); + + fake_invalidation_client_.LoseAllRegistrations(); + fake_registration_manager_.MarkAllRegistrationsLost(); + + syncable::ModelTypeSet expected_types; + EXPECT_EQ(expected_types, fake_registration_manager_.GetRegisteredTypes()); + EXPECT_EQ(expected_types, fake_invalidation_client_.GetRegisteredTypes()); + + ExpectPendingRegistrations( + types, 0.0, + fake_registration_manager_.GetPendingRegistrations()); + + // Trigger another failure to start delaying. + fake_registration_manager_.FirePendingRegistrationsForTest(); + fake_invalidation_client_.LoseAllRegistrations(); + fake_registration_manager_.MarkAllRegistrationsLost(); + double expected_delay = + RegistrationManager::kInitialRegistrationDelaySeconds; + ExpectPendingRegistrations( + types, expected_delay, + fake_registration_manager_.GetPendingRegistrations()); + + // Pretend we waited long enough to re-register. + fake_registration_manager_.FirePendingRegistrationsForTest(); + EXPECT_EQ(types, fake_registration_manager_.GetRegisteredTypes()); + EXPECT_EQ(types, fake_invalidation_client_.GetRegisteredTypes()); } +} // namespace } // namespace notifier diff --git a/chrome/browser/sync/notifier/server_notifier_thread.cc b/chrome/browser/sync/notifier/server_notifier_thread.cc deleted file mode 100644 index 9dd62bf..0000000 --- a/chrome/browser/sync/notifier/server_notifier_thread.cc +++ /dev/null @@ -1,174 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/sync/notifier/server_notifier_thread.h" - -#include <string> -#include <vector> - -#include "base/logging.h" -#include "chrome/browser/sync/notifier/chrome_invalidation_client.h" -#include "googleurl/src/gurl.h" -#include "jingle/notifier/base/notifier_options.h" -#include "jingle/notifier/listener/notification_defines.h" -#include "talk/xmpp/xmppclient.h" -#include "webkit/glue/webkit_glue.h" - -namespace sync_notifier { - -ServerNotifierThread::ServerNotifierThread( - const notifier::NotifierOptions& notifier_options, - const std::string& state, StateWriter* state_writer) - : notifier::MediatorThreadImpl(notifier_options), - state_(state), - state_writers_(new ObserverListThreadSafe<StateWriter>()), - state_writer_(state_writer) { - DCHECK_EQ(notifier::NOTIFICATION_SERVER, - notifier_options.notification_method); - DCHECK(state_writer_); - state_writers_->AddObserver(state_writer_); -} - -ServerNotifierThread::~ServerNotifierThread() {} - -void ServerNotifierThread::ListenForUpdates() { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(this, &ServerNotifierThread::DoListenForUpdates)); -} - -void ServerNotifierThread::SubscribeForUpdates( - const std::vector<std::string>& subscribed_services_list) { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod( - this, &ServerNotifierThread::RegisterTypes)); - - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod( - this, &ServerNotifierThread::SignalSubscribed)); -} - -void ServerNotifierThread::UpdateEnabledTypes( - const syncable::ModelTypeSet& types) { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod( - this, - &ServerNotifierThread::SetRegisteredTypes, - types)); - - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod( - this, &ServerNotifierThread::RegisterTypes)); -} - -void ServerNotifierThread::Logout() { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - state_writers_->RemoveObserver(state_writer_); - state_writer_ = NULL; - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(this, - &ServerNotifierThread::StopInvalidationListener)); - MediatorThreadImpl::Logout(); -} - -void ServerNotifierThread::SendNotification( - const OutgoingNotificationData& data) { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - NOTREACHED() << "Shouldn't send notifications if ServerNotifierThread is " - "used"; -} - -void ServerNotifierThread::OnInvalidate( - syncable::ModelType model_type, - const std::string& payload) { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - DCHECK_GE(model_type, syncable::FIRST_REAL_MODEL_TYPE); - DCHECK_LT(model_type, syncable::MODEL_TYPE_COUNT); - VLOG(1) << "OnInvalidate: " << syncable::ModelTypeToString(model_type); - - syncable::ModelTypeBitSet model_types; - model_types[model_type] = true; - IncomingNotificationData notification_data; - notification_data.service_url = model_types.to_string(); - notification_data.service_specific_data = payload; - observers_->Notify(&Observer::OnIncomingNotification, notification_data); -} - -void ServerNotifierThread::OnInvalidateAll() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - VLOG(1) << "OnInvalidateAll"; - - syncable::ModelTypeBitSet model_types; - model_types.set(); // InvalidateAll, so set all datatypes to true. - IncomingNotificationData notification_data; - notification_data.service_url = model_types.to_string(); - notification_data.service_specific_data = std::string(); // No payload. - observers_->Notify(&Observer::OnIncomingNotification, notification_data); -} - -void ServerNotifierThread::WriteState(const std::string& state) { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - VLOG(1) << "WriteState"; - state_writers_->Notify(&StateWriter::WriteState, state); -} - -void ServerNotifierThread::DoListenForUpdates() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - if (!base_task_.get()) { - return; - } - - if (chrome_invalidation_client_.get()) { - // If we already have an invalidation client, simply change the - // base task. - chrome_invalidation_client_->ChangeBaseTask(base_task_); - } else { - // Otherwise, create the invalidation client. - chrome_invalidation_client_.reset(new ChromeInvalidationClient()); - - // TODO(akalin): Make cache_guid() part of the client ID. If we do - // so and we somehow propagate it up to the server somehow, we can - // make it so that we won't receive any notifications that were - // generated from our own changes. - const std::string kClientId = "server_notifier_thread"; - // Use user agent as |client_info| so we can use it for debugging - // server-side. - const std::string& client_info = webkit_glue::GetUserAgent(GURL()); - chrome_invalidation_client_->Start( - kClientId, client_info, state_, this, this, base_task_); - RegisterTypes(); - state_.clear(); - } -} - -void ServerNotifierThread::RegisterTypes() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - if (!chrome_invalidation_client_.get()) { - return; - } - chrome_invalidation_client_->RegisterTypes(registered_types_); -} - -void ServerNotifierThread::SignalSubscribed() { - observers_->Notify(&Observer::OnSubscriptionStateChange, true); -} - -void ServerNotifierThread::StopInvalidationListener() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - chrome_invalidation_client_.reset(); -} - -void ServerNotifierThread::SetRegisteredTypes(syncable::ModelTypeSet types) { - registered_types_ = types; -} - -} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/server_notifier_thread.h b/chrome/browser/sync/notifier/server_notifier_thread.h deleted file mode 100644 index 4800735..0000000 --- a/chrome/browser/sync/notifier/server_notifier_thread.h +++ /dev/null @@ -1,106 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. -// -// This class is the (hackish) way to use the XMPP parts of -// MediatorThread for server-issued notifications. -// -// TODO(akalin): Decomp MediatorThread into an XMPP service part and a -// notifications-specific part and use the XMPP service part for -// server-issued notifications. - -#ifndef CHROME_BROWSER_SYNC_NOTIFIER_SERVER_NOTIFIER_THREAD_H_ -#define CHROME_BROWSER_SYNC_NOTIFIER_SERVER_NOTIFIER_THREAD_H_ -#pragma once - -#include <string> -#include <vector> - -#include "base/observer_list_threadsafe.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" -#include "chrome/browser/sync/notifier/chrome_invalidation_client.h" -#include "chrome/browser/sync/notifier/state_writer.h" -#include "chrome/browser/sync/syncable/model_type.h" -#include "jingle/notifier/listener/mediator_thread_impl.h" - -namespace notifier { -struct NotifierOptions; -} - -namespace sync_notifier { - -class ServerNotifierThread - : public notifier::MediatorThreadImpl, - public ChromeInvalidationClient::Listener, - public StateWriter { - public: - // Does not take ownership of |state_writer| (which may not - // be NULL). - explicit ServerNotifierThread( - const notifier::NotifierOptions& notifier_options, - const std::string& state, StateWriter* state_writer); - - virtual ~ServerNotifierThread(); - - // Overridden to start listening to server notifications. - virtual void ListenForUpdates(); - - // Overridden to immediately notify the delegate that subscriptions - // (i.e., notifications) are on. Must be called only after a call - // to ListenForUpdates(). - virtual void SubscribeForUpdates( - const std::vector<std::string>& subscribed_services_list); - - // Overridden to stop listening to server notifications. - virtual void Logout(); - - // Must not be called. - virtual void SendNotification(const OutgoingNotificationData& data); - - // ChromeInvalidationClient::Listener implementation. - // We pass on two pieces of information to observers through the - // IncomingNotificationData. - // - the model type being invalidated, through IncomingNotificationData's - // service_url. - // - the invalidation payload for that model type, through - // IncomingNotificationData's service_specific_data. - virtual void OnInvalidate(syncable::ModelType model_type, - const std::string& payload); - virtual void OnInvalidateAll(); - - // StateWriter implementation. - virtual void WriteState(const std::string& state); - - virtual void UpdateEnabledTypes(const syncable::ModelTypeSet& types); - - private: - // Posted to the worker thread by ListenForUpdates(). - void DoListenForUpdates(); - - // Posted to the worker thread by SubscribeForUpdates(). - void RegisterTypes(); - - void SignalSubscribed(); - - // Posted to the worker thread by Logout(). - void StopInvalidationListener(); - - std::string state_; - // Hack to get the nice thread-safe behavior for |state_writer_|. - scoped_refptr<ObserverListThreadSafe<StateWriter> > state_writers_; - // We still need to keep |state_writer_| around to remove it from - // |state_writers_|. - StateWriter* state_writer_; - scoped_ptr<ChromeInvalidationClient> chrome_invalidation_client_; - - syncable::ModelTypeSet registered_types_; - - void SetRegisteredTypes(syncable::ModelTypeSet types); -}; - -} // namespace sync_notifier - -DISABLE_RUNNABLE_METHOD_REFCOUNT(sync_notifier::ServerNotifierThread); - -#endif // CHROME_BROWSER_SYNC_NOTIFIER_SERVER_NOTIFIER_THREAD_H_ diff --git a/chrome/browser/sync/notifier/server_notifier_thread_unittest.cc b/chrome/browser/sync/notifier/server_notifier_thread_unittest.cc deleted file mode 100644 index 3873c42..0000000 --- a/chrome/browser/sync/notifier/server_notifier_thread_unittest.cc +++ /dev/null @@ -1,160 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include <string> - -#include "base/compiler_specific.h" -#include "base/message_loop.h" -#include "base/scoped_ptr.h" -#include "base/task.h" -#include "chrome/browser/sync/notifier/server_notifier_thread.h" -#include "chrome/browser/sync/notifier/state_writer.h" -#include "jingle/notifier/base/fake_base_task.h" -#include "jingle/notifier/base/notifier_options.h" -#include "talk/xmpp/xmppclientsettings.h" -#include "testing/gmock/include/gmock/gmock.h" -#include "testing/gtest/include/gtest/gtest.h" - -namespace sync_notifier { - -class FakeServerNotifierThread : public ServerNotifierThread { - public: - FakeServerNotifierThread() - : ServerNotifierThread(notifier::NotifierOptions(), "fake state", - ALLOW_THIS_IN_INITIALIZER_LIST(this)) {} - - virtual ~FakeServerNotifierThread() {} - - virtual void Start() { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - ServerNotifierThread::Start(); - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(this, - &FakeServerNotifierThread::InitFakes)); - } - - virtual void Logout() { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(this, - &FakeServerNotifierThread::DestroyFakes)); - ServerNotifierThread::Logout(); - } - - // We prevent the real connection attempt from happening and use - // SimulateConnection()/SimulateDisconnection() instead. - virtual void Login(const buzz::XmppClientSettings& settings) { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - } - - // We pass ourselves as the StateWriter in the constructor, so shim - // out WriteState() to prevent an infinite loop. - virtual void WriteState(const std::string& state) { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - } - - void SimulateConnect() { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(this, - &FakeServerNotifierThread::DoSimulateConnect)); - } - - void SimulateDisconnect() { - DCHECK_EQ(MessageLoop::current(), parent_message_loop_); - worker_message_loop()->PostTask( - FROM_HERE, - NewRunnableMethod(this, - &FakeServerNotifierThread::DoSimulateDisconnect)); - } - - private: - void InitFakes() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - fake_base_task_.reset(new notifier::FakeBaseTask()); - } - - void DestroyFakes() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - fake_base_task_.reset(); - } - - void DoSimulateConnect() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - OnConnect(fake_base_task_->AsWeakPtr()); - } - - void DoSimulateDisconnect() { - DCHECK_EQ(MessageLoop::current(), worker_message_loop()); - OnDisconnect(); - } - - // Used only on the worker thread. - scoped_ptr<notifier::FakeBaseTask> fake_base_task_; -}; - -} // namespace sync_notifier - -DISABLE_RUNNABLE_METHOD_REFCOUNT(sync_notifier::FakeServerNotifierThread); - -namespace sync_notifier { - -namespace { - -class ServerNotifierThreadTest : public testing::Test { - protected: - MessageLoop message_loop_; -}; - -syncable::ModelTypeSet GetTypeSetWithAllTypes() { - syncable::ModelTypeSet all_types; - - for (int i = syncable::FIRST_REAL_MODEL_TYPE; - i < syncable::MODEL_TYPE_COUNT; ++i) { - syncable::ModelType model_type = syncable::ModelTypeFromInt(i); - all_types.insert(model_type); - } - - return all_types; -} - -TEST_F(ServerNotifierThreadTest, Basic) { - FakeServerNotifierThread server_notifier_thread; - - server_notifier_thread.Start(); - server_notifier_thread.Login(buzz::XmppClientSettings()); - server_notifier_thread.SimulateConnect(); - server_notifier_thread.ListenForUpdates(); - server_notifier_thread.SubscribeForUpdates(std::vector<std::string>()); - server_notifier_thread.Logout(); -} - -TEST_F(ServerNotifierThreadTest, DisconnectBeforeListen) { - FakeServerNotifierThread server_notifier_thread; - - server_notifier_thread.Start(); - server_notifier_thread.Login(buzz::XmppClientSettings()); - server_notifier_thread.ListenForUpdates(); - server_notifier_thread.SubscribeForUpdates(std::vector<std::string>()); - server_notifier_thread.Logout(); -} - -TEST_F(ServerNotifierThreadTest, Disconnected) { - FakeServerNotifierThread server_notifier_thread; - - server_notifier_thread.Start(); - server_notifier_thread.Login(buzz::XmppClientSettings()); - server_notifier_thread.SimulateConnect(); - server_notifier_thread.SimulateDisconnect(); - server_notifier_thread.ListenForUpdates(); - server_notifier_thread.SubscribeForUpdates(std::vector<std::string>()); - server_notifier_thread.Logout(); -} - -} // namespace - -} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/sync_notifier.h b/chrome/browser/sync/notifier/sync_notifier.h new file mode 100644 index 0000000..5bf6c8c --- /dev/null +++ b/chrome/browser/sync/notifier/sync_notifier.h @@ -0,0 +1,61 @@ +// Copyright (c) 2011 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. +// +// Interface to the sync notifier, which is an object that receives +// notifications when updates are available for a set of sync types. +// All the observers are notified when such an event happens. +// +// A SyncNotifier must be destroyed on the same thread it was created on, +// and all its methods must be called on the same thread (not necessarily +// the one it was created on). If the methods thread is different from the +// creation thread, then the methods thread must not exist when the SyncNotifier +// is created and destroyed. +// +// In particular, the SyncNotifier will be created on the UI thread, the syncer +// core thread will be created, the SyncNotifier will be used on that core +// thread, the syncer core thread will be destroyed, and then the SyncNotifier +// will be destroyed. +// +// TODO(akalin): Remove the code to deal with this situation once the syncer +// core thread goes away. + +#ifndef CHROME_BROWSER_SYNC_NOTIFIER_SYNC_NOTIFIER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_SYNC_NOTIFIER_H_ + +#include <string> + +#include "chrome/browser/sync/syncable/model_type.h" + +namespace sync_notifier { +class SyncNotifierObserver; + +class SyncNotifier { + public: + SyncNotifier() {} + virtual ~SyncNotifier() {} + + virtual void AddObserver(SyncNotifierObserver* observer) = 0; + virtual void RemoveObserver(SyncNotifierObserver* observer) = 0; + + // SetState must be called once, before any call to UpdateCredentials. + virtual void SetState(const std::string& state) = 0; + + // The observers won't be notified of any notifications until + // UpdateCredentials is called at least once. It can be called more than + // once. + virtual void UpdateCredentials( + const std::string& email, const std::string& token) = 0; + + virtual void UpdateEnabledTypes(const syncable::ModelTypeSet& types) = 0; + + // This is here only to support the old p2p notification implementation, + // which is still used by sync integration tests. + // TODO(akalin): Remove this once we move the integration tests off p2p + // notifications. + virtual void SendNotification() = 0; +}; +} // namespace sync_notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_SYNC_NOTIFIER_H_ + diff --git a/chrome/browser/sync/notifier/sync_notifier_factory.cc b/chrome/browser/sync/notifier/sync_notifier_factory.cc new file mode 100644 index 0000000..1a1a531 --- /dev/null +++ b/chrome/browser/sync/notifier/sync_notifier_factory.cc @@ -0,0 +1,111 @@ +// Copyright (c) 2011 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/sync/notifier/sync_notifier_factory.h" + +#include <string> + +#include "base/command_line.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "chrome/browser/sync/notifier/non_blocking_invalidation_notifier.h" +#include "chrome/browser/sync/notifier/p2p_notifier.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "chrome/common/chrome_switches.h" +#include "jingle/notifier/base/const_communicator.h" +#include "jingle/notifier/base/notifier_options.h" +#include "net/base/host_port_pair.h" + +namespace sync_notifier { +namespace { + +// TODO(akalin): Figure out whether this should be a method of +// HostPortPair. +net::HostPortPair StringToHostPortPair(const std::string& host_port_str, + uint16 default_port) { + std::string::size_type colon_index = host_port_str.find(':'); + if (colon_index == std::string::npos) { + return net::HostPortPair(host_port_str, default_port); + } + + std::string host = host_port_str.substr(0, colon_index); + std::string port_str = host_port_str.substr(colon_index + 1); + int port = default_port; + if (!base::StringToInt(port_str, &port) || + (port <= 0) || (port > kuint16max)) { + LOG(WARNING) << "Could not parse valid port from " << port_str + << "; using port " << default_port; + return net::HostPortPair(host, default_port); + } + + return net::HostPortPair(host, port); +} + +SyncNotifier* CreateDefaultSyncNotifier( + const CommandLine& command_line, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter, + const std::string& client_info) { + // Contains options specific to how sync clients send and listen to + // jingle notifications. + notifier::NotifierOptions notifier_options; + notifier_options.request_context_getter = request_context_getter; + + // Override the notification server host from the command-line, if provided. + if (command_line.HasSwitch(switches::kSyncNotificationHost)) { + std::string value(command_line.GetSwitchValueASCII( + switches::kSyncNotificationHost)); + if (!value.empty()) { + notifier_options.xmpp_host_port = + StringToHostPortPair(value, notifier::kDefaultXmppPort); + } + VLOG(1) << "Using " << notifier_options.xmpp_host_port.ToString() + << " for test sync notification server."; + } + + notifier_options.try_ssltcp_first = + command_line.HasSwitch(switches::kSyncTrySsltcpFirstForXmpp); + if (notifier_options.try_ssltcp_first) + VLOG(1) << "Trying SSL/TCP port before XMPP port for notifications."; + + notifier_options.invalidate_xmpp_login = + command_line.HasSwitch(switches::kSyncInvalidateXmppLogin); + if (notifier_options.invalidate_xmpp_login) { + VLOG(1) << "Invalidating sync XMPP login."; + } + + notifier_options.allow_insecure_connection = + command_line.HasSwitch(switches::kSyncAllowInsecureXmppConnection); + if (notifier_options.allow_insecure_connection) { + VLOG(1) << "Allowing insecure XMPP connections."; + } + + if (command_line.HasSwitch(switches::kSyncNotificationMethod)) { + const std::string notification_method_str( + command_line.GetSwitchValueASCII(switches::kSyncNotificationMethod)); + notifier_options.notification_method = + notifier::StringToNotificationMethod(notification_method_str); + } + + if (notifier_options.notification_method == notifier::NOTIFICATION_P2P) { + return new P2PNotifier(notifier_options); + } + + return new NonBlockingInvalidationNotifier(notifier_options, client_info); +} +} // namespace + +SyncNotifierFactory::SyncNotifierFactory(const std::string& client_info) + : client_info_(client_info) {} + +SyncNotifierFactory::~SyncNotifierFactory() { +} + +SyncNotifier* SyncNotifierFactory::CreateSyncNotifier( + const CommandLine& command_line, + const scoped_refptr<net::URLRequestContextGetter>& request_context_getter) { + return CreateDefaultSyncNotifier(command_line, + request_context_getter, + client_info_); +} +} // namespace sync_notifier diff --git a/chrome/browser/sync/notifier/sync_notifier_factory.h b/chrome/browser/sync/notifier/sync_notifier_factory.h new file mode 100644 index 0000000..5a0f618 --- /dev/null +++ b/chrome/browser/sync/notifier/sync_notifier_factory.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 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_NOTIFIER_SYNC_NOTIFIER_FACTORY_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_SYNC_NOTIFIER_FACTORY_H_ + +#include <string> + +#include "base/memory/ref_counted.h" + +class CommandLine; + +namespace net { +class URLRequestContextGetter; +} + +namespace sync_notifier { + +class SyncNotifier; + +// Class to instantiate various implementations of the SyncNotifier interface. +class SyncNotifierFactory { + public: + // |client_info| is a string identifying the client, e.g. a user + // agent string. + explicit SyncNotifierFactory(const std::string& client_info); + ~SyncNotifierFactory(); + + // Creates the appropriate sync notifier. The caller should take ownership + // of the object returned and delete it when no longer used. + SyncNotifier* CreateSyncNotifier( + const CommandLine& command_line, + const scoped_refptr<net::URLRequestContextGetter>& + request_context_getter); + + private: + const std::string client_info_; +}; + +} // namespace sync_notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_SYNC_NOTIFIER_FACTORY_H_ diff --git a/chrome/browser/sync/notifier/sync_notifier_observer.h b/chrome/browser/sync/notifier/sync_notifier_observer.h new file mode 100644 index 0000000..f2d7cda --- /dev/null +++ b/chrome/browser/sync/notifier/sync_notifier_observer.h @@ -0,0 +1,31 @@ +// Copyright (c) 2011 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_NOTIFIER_SYNC_NOTIFIER_OBSERVER_H_ +#define CHROME_BROWSER_SYNC_NOTIFIER_SYNC_NOTIFIER_OBSERVER_H_ +#pragma once + +#include <string> + +#include "chrome/browser/sync/syncable/model_type_payload_map.h" + +namespace sync_notifier { + +class SyncNotifierObserver { + public: + SyncNotifierObserver() {} + virtual ~SyncNotifierObserver() {} + + virtual void OnIncomingNotification( + const syncable::ModelTypePayloadMap& type_payloads) = 0; + virtual void OnNotificationStateChange(bool notifications_enabled) = 0; + + // TODO(nileshagrawal): Find a way to hide state handling inside the + // sync notifier implementation. + virtual void StoreState(const std::string& state) = 0; +}; + +} // namespace sync_notifier + +#endif // CHROME_BROWSER_SYNC_NOTIFIER_SYNC_NOTIFIER_OBSERVER_H_ diff --git a/chrome/browser/sync/profile_sync_factory_impl.cc b/chrome/browser/sync/profile_sync_factory_impl.cc index 6391aa5..9102e88 100644 --- a/chrome/browser/sync/profile_sync_factory_impl.cc +++ b/chrome/browser/sync/profile_sync_factory_impl.cc @@ -1,8 +1,9 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/command_line.h" +#include "chrome/browser/extensions/extension_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/glue/app_data_type_controller.h" #include "chrome/browser/sync/glue/autofill_change_processor.h" @@ -15,7 +16,6 @@ #include "chrome/browser/sync/glue/bookmark_data_type_controller.h" #include "chrome/browser/sync/glue/bookmark_model_associator.h" #include "chrome/browser/sync/glue/data_type_manager_impl.h" -#include "chrome/browser/sync/glue/data_type_manager_impl2.h" #include "chrome/browser/sync/glue/extension_change_processor.h" #include "chrome/browser/sync/glue/extension_data_type_controller.h" #include "chrome/browser/sync/glue/extension_model_associator.h" @@ -54,7 +54,6 @@ using browser_sync::BookmarkModelAssociator; using browser_sync::DataTypeController; using browser_sync::DataTypeManager; using browser_sync::DataTypeManagerImpl; -using browser_sync::DataTypeManagerImpl2; using browser_sync::ExtensionChangeProcessor; using browser_sync::ExtensionDataTypeController; using browser_sync::ExtensionModelAssociator; @@ -116,13 +115,9 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService( new ExtensionDataTypeController(this, profile_, pss)); } -#if defined(OS_POSIX) && !defined(OS_MACOSX) && !defined(OS_CHROMEOS) - if (command_line_->HasSwitch(switches::kEnableSyncPasswords)) { -#else // Password sync is enabled by default. Register unless explicitly // disabled. if (!command_line_->HasSwitch(switches::kDisableSyncPasswords)) { -#endif pss->RegisterDataTypeController( new PasswordDataTypeController(this, profile_, pss)); } @@ -131,7 +126,7 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService( // disabled. if (!command_line_->HasSwitch(switches::kDisableSyncPreferences)) { pss->RegisterDataTypeController( - new PreferenceDataTypeController(this, pss)); + new PreferenceDataTypeController(this, profile_, pss)); } // Theme sync is enabled by default. Register unless explicitly disabled. @@ -151,7 +146,7 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService( // enabled. if (command_line_->HasSwitch(switches::kEnableSyncSessions)) { pss->RegisterDataTypeController( - new SessionDataTypeController(this, pss)); + new SessionDataTypeController(this, profile_, pss)); } if (!command_line_->HasSwitch(switches::kDisableSyncAutofillProfile)) { @@ -164,10 +159,7 @@ ProfileSyncService* ProfileSyncFactoryImpl::CreateProfileSyncService( DataTypeManager* ProfileSyncFactoryImpl::CreateDataTypeManager( SyncBackendHost* backend, const DataTypeController::TypeMap& controllers) { - if (command_line_->HasSwitch(switches::kNewSyncerThread)) - return new DataTypeManagerImpl2(backend, controllers); - else - return new DataTypeManagerImpl(backend, controllers); + return new DataTypeManagerImpl(backend, controllers); } ProfileSyncFactory::SyncComponents @@ -178,8 +170,11 @@ ProfileSyncFactoryImpl::CreateAppSyncComponents( // For now we simply use extensions sync objects with the app sync // traits. If apps become more than simply extensions, we may have // to write our own apps model associator and/or change processor. + ExtensionServiceInterface* extension_service = + profile_sync_service->profile()->GetExtensionService(); + sync_api::UserShare* user_share = profile_sync_service->GetUserShare(); ExtensionModelAssociator* model_associator = - new ExtensionModelAssociator(traits, profile_sync_service); + new ExtensionModelAssociator(traits, extension_service, user_share); ExtensionChangeProcessor* change_processor = new ExtensionChangeProcessor(traits, error_handler); return SyncComponents(model_associator, change_processor); @@ -227,8 +222,12 @@ ProfileSyncFactory::SyncComponents ProfileSyncFactoryImpl::CreateBookmarkSyncComponents( ProfileSyncService* profile_sync_service, UnrecoverableErrorHandler* error_handler) { + BookmarkModel* bookmark_model = + profile_sync_service->profile()->GetBookmarkModel(); + sync_api::UserShare* user_share = profile_sync_service->GetUserShare(); BookmarkModelAssociator* model_associator = - new BookmarkModelAssociator(profile_sync_service, + new BookmarkModelAssociator(bookmark_model, + user_share, error_handler); BookmarkChangeProcessor* change_processor = new BookmarkChangeProcessor(model_associator, @@ -242,8 +241,11 @@ ProfileSyncFactoryImpl::CreateExtensionSyncComponents( UnrecoverableErrorHandler* error_handler) { browser_sync::ExtensionSyncTraits traits = browser_sync::GetExtensionSyncTraits(); + ExtensionServiceInterface* extension_service = + profile_sync_service->profile()->GetExtensionService(); + sync_api::UserShare* user_share = profile_sync_service->GetUserShare(); ExtensionModelAssociator* model_associator = - new ExtensionModelAssociator(traits, profile_sync_service); + new ExtensionModelAssociator(traits, extension_service, user_share); ExtensionChangeProcessor* change_processor = new ExtensionChangeProcessor(traits, error_handler); return SyncComponents(model_associator, change_processor); diff --git a/chrome/browser/sync/profile_sync_factory_impl_unittest.cc b/chrome/browser/sync/profile_sync_factory_impl_unittest.cc index 30e6df0..907b2f6 100644 --- a/chrome/browser/sync/profile_sync_factory_impl_unittest.cc +++ b/chrome/browser/sync/profile_sync_factory_impl_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -7,8 +7,8 @@ #include "testing/gtest/include/gtest/gtest.h" #include "base/command_line.h" #include "base/file_path.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/data_type_controller.h" #include "chrome/browser/sync/profile_sync_service.h" @@ -42,11 +42,7 @@ class ProfileSyncFactoryImplTest : public testing::Test { datatypes.push_back(syncable::EXTENSIONS); datatypes.push_back(syncable::APPS); datatypes.push_back(syncable::AUTOFILL_PROFILE); -// TODO(mdm): re-enable this test on Linux/BSD/etc. once we make password sync -// compatible with GNOME Keyring. -#if !defined(OS_POSIX) || defined(OS_MACOSX) || defined(OS_CHROMEOS) datatypes.push_back(syncable::PASSWORDS); -#endif return datatypes; } @@ -137,11 +133,7 @@ TEST_F(ProfileSyncFactoryImplTest, CreatePSSDisableAutofillProfile) { syncable::AUTOFILL_PROFILE); } -// TODO(mdm): re-enable this test on Linux/BSD/etc. once we make password sync -// compatible with GNOME Keyring. -#if !defined(OS_POSIX) || defined(OS_MACOSX) || defined(OS_CHROMEOS) TEST_F(ProfileSyncFactoryImplTest, CreatePSSDisablePasswords) { TestSwitchDisablesType(switches::kDisableSyncPasswords, syncable::PASSWORDS); } -#endif diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h index a1d1a15..2d8d8e2 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.h +++ b/chrome/browser/sync/profile_sync_factory_mock.h @@ -6,7 +6,7 @@ #define CHROME_BROWSER_SYNC_PROFILE_SYNC_FACTORY_MOCK_H__ #pragma once -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "testing/gmock/include/gmock/gmock.h" diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index 3815541..a75c1b8 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -4,28 +4,29 @@ #include "chrome/browser/sync/profile_sync_service.h" +#include <stddef.h> #include <map> +#include <ostream> #include <set> +#include <utility> #include "base/basictypes.h" -#include "base/callback.h" #include "base/command_line.h" +#include "base/compiler_specific.h" #include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/message_loop.h" #include "base/metrics/histogram.h" -#include "base/stl_util-inl.h" #include "base/string16.h" -#include "base/string_number_conversions.h" -#include "base/string_util.h" +#include "base/stringprintf.h" #include "base/task.h" #include "base/threading/thread_restrictions.h" -#include "base/utf_string_conversions.h" -#include "chrome/browser/browser_signin.h" -#include "chrome/browser/history/history_types.h" +#include "chrome/browser/net/gaia/token_service.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/net/gaia/token_service.h" -#include "chrome/browser/sync/glue/autofill_profile_data_type_controller.h" +#include "chrome/browser/sync/backend_migrator.h" +#include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/change_processor.h" #include "chrome/browser/sync/glue/data_type_controller.h" #include "chrome/browser/sync/glue/data_type_manager.h" @@ -33,20 +34,19 @@ #include "chrome/browser/sync/js_arg_list.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/signin_manager.h" -#include "chrome/browser/sync/token_migrator.h" +#include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_list.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/notification_details.h" -#include "chrome/common/notification_source.h" -#include "chrome/common/notification_type.h" #include "chrome/common/pref_names.h" #include "chrome/common/time_format.h" #include "chrome/common/url_constants.h" -#include "grit/chromium_strings.h" +#include "content/common/notification_details.h" +#include "content/common/notification_source.h" +#include "content/common/notification_type.h" #include "grit/generated_resources.h" -#include "jingle/notifier/communicator/const_communicator.h" -#include "net/base/cookie_monster.h" #include "ui/base/l10n/l10n_util.h" +#include "ui/gfx/native_widget_types.h" using browser_sync::ChangeProcessor; using browser_sync::DataTypeController; @@ -68,8 +68,6 @@ ProfileSyncService::ProfileSyncService(ProfileSyncFactory* factory, Profile* profile, const std::string& cros_user) : last_auth_error_(AuthError::None()), - tried_creating_explicit_passphrase_(false), - tried_setting_explicit_passphrase_(false), observed_passphrase_required_(false), passphrase_required_for_decryption_(false), passphrase_migration_in_progress_(false), @@ -82,10 +80,8 @@ ProfileSyncService::ProfileSyncService(ProfileSyncFactory* factory, wizard_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), unrecoverable_error_detected_(false), scoped_runnable_method_factory_(ALLOW_THIS_IN_INITIALIZER_LIST(this)), - token_migrator_(NULL), + expect_sync_configuration_aborted_(false), clear_server_data_state_(CLEAR_NOT_STARTED) { - DCHECK(factory); - DCHECK(profile); registrar_.Add(this, NotificationType::SYNC_DATA_TYPES_UPDATED, Source<Profile>(profile)); @@ -110,19 +106,6 @@ ProfileSyncService::ProfileSyncService(ProfileSyncFactory* factory, tried_implicit_gaia_remove_when_bug_62103_fixed_ = false; } -ProfileSyncService::ProfileSyncService() - : last_auth_error_(AuthError::None()), - factory_(NULL), - profile_(NULL), - sync_service_url_(kSyncServerUrl), - backend_initialized_(false), - is_auth_in_progress_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(wizard_(this)), - unrecoverable_error_detected_(false), - ALLOW_THIS_IN_INITIALIZER_LIST(scoped_runnable_method_factory_(this)), - expect_sync_configuration_aborted_(false) { -} - ProfileSyncService::~ProfileSyncService() { Shutdown(false); } @@ -144,16 +127,6 @@ bool ProfileSyncService::AreCredentialsAvailable() { return false; } -void ProfileSyncService::LoadMigratedCredentials(const std::string& username, - const std::string& token) { - signin_->SetUsername(username); - profile()->GetPrefs()->SetString(prefs::kGoogleServicesUsername, username); - profile()->GetTokenService()->OnIssueAuthTokenSuccess( - GaiaConstants::kSyncService, token); - profile()->GetPrefs()->SetBoolean(prefs::kSyncCredentialsMigrated, true); - token_migrator_.reset(); -} - void ProfileSyncService::Initialize() { InitSettings(); RegisterPreferences(); @@ -195,18 +168,6 @@ void ProfileSyncService::Initialize() { // Note that if we haven't finished setting up sync, backend bring up will // be done by the wizard. StartUp(); - } else { - if (!cros_user_.empty()) { - // We don't attempt migration on cros, as we should just get new - // credentials from the login manager on startup. - return; - } - - // Try to migrate the tokens (if that hasn't already succeeded). - if (!profile()->GetPrefs()->GetBoolean(prefs::kSyncCredentialsMigrated)) { - token_migrator_.reset(new TokenMigrator(this, profile_->GetPath())); - token_migrator_->TryMigration(); - } } } @@ -262,32 +223,6 @@ void ProfileSyncService::GetDataTypeControllerStates( (*state_map)[iter->first] = iter->second.get()->state(); } -namespace { - -// TODO(akalin): Figure out whether this should be a method of -// HostPortPair. -net::HostPortPair StringToHostPortPair(const std::string& host_port_str, - uint16 default_port) { - std::string::size_type colon_index = host_port_str.find(':'); - if (colon_index == std::string::npos) { - return net::HostPortPair(host_port_str, default_port); - } - - std::string host = host_port_str.substr(0, colon_index); - std::string port_str = host_port_str.substr(colon_index + 1); - int port = default_port; - if (!base::StringToInt(port_str, &port) || - (port <= 0) || (port > kuint16max)) { - LOG(WARNING) << "Could not parse valid port from " << port_str - << "; using port " << default_port; - return net::HostPortPair(host, default_port); - } - - return net::HostPortPair(host, port); -} - -} // namespace - void ProfileSyncService::InitSettings() { const CommandLine& command_line = *CommandLine::ForCurrentProcess(); @@ -306,42 +241,6 @@ void ProfileSyncService::InitSettings() { } } } - - // Override the notification server host from the command-line, if provided. - if (command_line.HasSwitch(switches::kSyncNotificationHost)) { - std::string value(command_line.GetSwitchValueASCII( - switches::kSyncNotificationHost)); - if (!value.empty()) { - notifier_options_.xmpp_host_port = - StringToHostPortPair(value, notifier::kDefaultXmppPort); - } - VLOG(1) << "Using " << notifier_options_.xmpp_host_port.ToString() - << " for test sync notification server."; - } - - notifier_options_.try_ssltcp_first = - command_line.HasSwitch(switches::kSyncTrySsltcpFirstForXmpp); - if (notifier_options_.try_ssltcp_first) - VLOG(1) << "Trying SSL/TCP port before XMPP port for notifications."; - - notifier_options_.invalidate_xmpp_login = - command_line.HasSwitch(switches::kSyncInvalidateXmppLogin); - if (notifier_options_.invalidate_xmpp_login) { - VLOG(1) << "Invalidating sync XMPP login."; - } - - notifier_options_.allow_insecure_connection = - command_line.HasSwitch(switches::kSyncAllowInsecureXmppConnection); - if (notifier_options_.allow_insecure_connection) { - VLOG(1) << "Allowing insecure XMPP connections."; - } - - if (command_line.HasSwitch(switches::kSyncNotificationMethod)) { - const std::string notification_method_str( - command_line.GetSwitchValueASCII(switches::kSyncNotificationMethod)); - notifier_options_.notification_method = - notifier::StringToNotificationMethod(notification_method_str); - } } void ProfileSyncService::RegisterPreferences() { @@ -351,7 +250,6 @@ void ProfileSyncService::RegisterPreferences() { pref_service->RegisterInt64Pref(prefs::kSyncLastSyncedTime, 0); pref_service->RegisterBooleanPref(prefs::kSyncHasSetupCompleted, false); pref_service->RegisterBooleanPref(prefs::kSyncSuppressStart, false); - pref_service->RegisterBooleanPref(prefs::kSyncCredentialsMigrated, false); // If you've never synced before, or if you're using Chrome OS, all datatypes // are on by default. @@ -424,8 +322,7 @@ void ProfileSyncService::InitializeBackend(bool delete_sync_data_folder) { types, profile_->GetRequestContext(), credentials, - delete_sync_data_folder, - notifier_options_); + delete_sync_data_folder); } void ProfileSyncService::CreateBackend() { @@ -521,10 +418,6 @@ void ProfileSyncService::SetSyncSetupCompleted() { prefs->SetBoolean(prefs::kSyncHasSetupCompleted, true); prefs->SetBoolean(prefs::kSyncSuppressStart, false); - // Indicate that setup has been completed on the new credentials store - // so that we don't try to migrate. - prefs->SetBoolean(prefs::kSyncCredentialsMigrated, true); - prefs->ScheduleSavePersistentPrefs(); } @@ -617,7 +510,7 @@ void ProfileSyncService::OnBackendInitialized() { if (!cros_user_.empty()) { if (profile_->GetPrefs()->GetBoolean(prefs::kSyncSuppressStart)) { - ShowConfigure(NULL); + ShowConfigure(NULL, true); } else { SetSyncSetupCompleted(); } @@ -630,6 +523,7 @@ void ProfileSyncService::OnBackendInitialized() { void ProfileSyncService::OnSyncCycleCompleted() { UpdateLastSyncedTime(); + VLOG(2) << "Notifying observers sync cycle completed"; NotifyObservers(); } @@ -705,9 +599,25 @@ void ProfileSyncService::OnClearServerDataSucceeded() { void ProfileSyncService::OnPassphraseRequired(bool for_decryption) { DCHECK(backend_.get()); DCHECK(backend_->IsNigoriEnabled()); + + // TODO(lipalani) : add this check to other locations as well. + if (unrecoverable_error_detected_) { + // When unrecoverable error is detected we post a task to shutdown the + // backend. The task might not have executed yet. + return; + } observed_passphrase_required_ = true; passphrase_required_for_decryption_ = for_decryption; + // First try supplying gaia password as the passphrase. + if (!gaia_password_.empty()) { + SetPassphrase(gaia_password_, false, true); + gaia_password_ = std::string(); + return; + } + + // If the above failed then try the custom passphrase the user might have + // entered in setup. if (!cached_passphrase_.value.empty()) { SetPassphrase(cached_passphrase_.value, cached_passphrase_.is_explicit, @@ -727,11 +637,6 @@ void ProfileSyncService::OnPassphraseRequired(bool for_decryption) { if (WizardIsVisible() && for_decryption) { wizard_.Step(SyncSetupWizard::ENTER_PASSPHRASE); - } else if (WizardIsVisible() && !for_decryption) { - // The user is enabling an encrypted data type for the first - // time, and we don't even have a default passphrase. We need - // to refresh credentials and show the passphrase migration. - SigninForPassphraseMigration(NULL); } NotifyObservers(); @@ -742,12 +647,13 @@ void ProfileSyncService::OnPassphraseAccepted() { // this time. syncable::ModelTypeSet types; GetPreferredDataTypes(&types); - data_type_manager_->Configure(types); + // Reset "passphrase_required" flag before configuring the DataTypeManager + // since we know we no longer require the passphrase. + observed_passphrase_required_ = false; + if (data_type_manager_.get()) + data_type_manager_->Configure(types); NotifyObservers(); - observed_passphrase_required_ = false; - tried_setting_explicit_passphrase_ = false; - tried_creating_explicit_passphrase_ = false; wizard_.Step(SyncSetupWizard::DONE); } @@ -760,15 +666,17 @@ void ProfileSyncService::OnEncryptionComplete( } } -void ProfileSyncService::ShowLoginDialog(gfx::NativeWindow parent_window) { - if (!cros_user_.empty()) { - // For ChromeOS, any login UI needs to be handled by the settings page. - Browser* browser = BrowserList::GetLastActiveWithProfile(profile()); - if (browser) - browser->ShowOptionsTab(chrome::kPersonalOptionsSubPage); - return; - } +void ProfileSyncService::OnMigrationNeededForTypes( + const syncable::ModelTypeSet& types) { + DCHECK(backend_initialized_); + DCHECK(data_type_manager_.get()); + + // Migrator must be valid, because we don't sync until it is created and this + // callback originates from a sync cycle. + migrator_->MigrateTypes(types); +} +void ProfileSyncService::ShowLoginDialog(gfx::NativeWindow parent_window) { if (WizardIsVisible()) { wizard_.Focus(); // Force the wizard to step to the login screen (which will only actually @@ -783,7 +691,6 @@ void ProfileSyncService::ShowLoginDialog(gfx::NativeWindow parent_window) { auth_error_time_ = base::TimeTicks(); // Reset auth_error_time_ to null. } - wizard_.SetParent(parent_window); wizard_.Step(SyncSetupWizard::GAIA_LOGIN); NotifyObservers(); @@ -808,13 +715,17 @@ void ProfileSyncService::ShowErrorUI(gfx::NativeWindow parent_window) { } -void ProfileSyncService::ShowConfigure(gfx::NativeWindow parent_window) { +void ProfileSyncService::ShowConfigure( + gfx::NativeWindow parent_window, bool sync_everything) { if (WizardIsVisible()) { wizard_.Focus(); return; } - wizard_.SetParent(parent_window); - wizard_.Step(SyncSetupWizard::CONFIGURE); + + if (sync_everything) + wizard_.Step(SyncSetupWizard::SYNC_EVERYTHING); + else + wizard_.Step(SyncSetupWizard::CONFIGURE); } void ProfileSyncService::PromptForExistingPassphrase( @@ -823,7 +734,7 @@ void ProfileSyncService::PromptForExistingPassphrase( wizard_.Focus(); return; } - wizard_.SetParent(parent_window); + wizard_.Step(SyncSetupWizard::ENTER_PASSPHRASE); } @@ -904,7 +815,7 @@ void ProfileSyncService::OnUserSubmittedAuth( if (!signin_.get()) { // In ChromeOS we sign in during login, so we do not instantiate signin_. // If this function gets called, we need to re-authenticate (e.g. for - // two factor signin), so instantiante signin_ here. + // two factor signin), so instantiate signin_ here. signin_.reset(new SigninManager()); signin_->Initialize(profile_); } @@ -936,6 +847,7 @@ void ProfileSyncService::OnUserChoseDatatypes(bool sync_everything, NOTREACHED(); return; } + profile_->GetPrefs()->SetBoolean(prefs::kKeepEverythingSynced, sync_everything); @@ -950,7 +862,6 @@ void ProfileSyncService::OnUserCancelledDialog() { expect_sync_configuration_aborted_ = true; DisableForUser(); } - wizard_.SetParent(NULL); // Though an auth could still be in progress, once the dialog is closed we // don't want the UI to stay stuck in the "waiting for authentication" state @@ -1045,8 +956,9 @@ bool ProfileSyncService::IsUsingSecondaryPassphrase() const { observed_passphrase_required_)); } -bool ProfileSyncService::IsCryptographerReady() const { - return backend_.get() && backend_->IsCryptographerReady(); +bool ProfileSyncService::IsCryptographerReady( + const sync_api::BaseTransaction* trans) const { + return backend_.get() && backend_->IsCryptographerReady(trans); } SyncBackendHost* ProfileSyncService::GetBackendForTest() { @@ -1066,6 +978,10 @@ void ProfileSyncService::ConfigureDataTypeManager() { registrar_.Add(this, NotificationType::SYNC_CONFIGURE_DONE, Source<DataTypeManager>(data_type_manager_.get())); + + // We create the migrator at the same time. + migrator_.reset( + new browser_sync::BackendMigrator(this, data_type_manager_.get())); } syncable::ModelTypeSet types; @@ -1078,6 +994,20 @@ void ProfileSyncService::ConfigureDataTypeManager() { encrypted_types_.clear(); if (types.count(syncable::PASSWORDS) > 0) encrypted_types_.insert(syncable::PASSWORDS); + if (observed_passphrase_required_ && passphrase_required_for_decryption_) { + if (IsEncryptedDatatypeEnabled()) { + // We need a passphrase still. Prompt the user for a passphrase, and + // DataTypeManager::Configure() will get called once the passphrase is + // accepted. + OnPassphraseRequired(true); + return; + } else { + // We've been informed that a passphrase is required for decryption, but + // now there are no encrypted data types enabled, so clear the flag + // (NotifyObservers() will be called when configuration completes). + observed_passphrase_required_ = false; + } + } data_type_manager_->Configure(types); } @@ -1179,15 +1109,14 @@ void ProfileSyncService::SetPassphrase(const std::string& passphrase, if (ShouldPushChanges() || observed_passphrase_required_) { backend_->SetPassphrase(passphrase, is_explicit); } else { - cached_passphrase_.value = passphrase; - cached_passphrase_.is_explicit = is_explicit; - cached_passphrase_.is_creation = is_creation; + if (is_explicit) { + cached_passphrase_.value = passphrase; + cached_passphrase_.is_explicit = is_explicit; + cached_passphrase_.is_creation = is_creation; + } else { + gaia_password_ = passphrase; + } } - - if (is_explicit && is_creation) - tried_creating_explicit_passphrase_ = true; - else if (is_explicit) - tried_setting_explicit_passphrase_ = true; } void ProfileSyncService::EncryptDataTypes( @@ -1210,18 +1139,27 @@ void ProfileSyncService::Observe(NotificationType type, break; } case NotificationType::SYNC_CONFIGURE_DONE: { - DataTypeManager::ConfigureResult result = - *(Details<DataTypeManager::ConfigureResult>(details).ptr()); + DataTypeManager::ConfigureResultWithErrorLocation* result_with_location = + Details<DataTypeManager::ConfigureResultWithErrorLocation>( + details).ptr(); + + DataTypeManager::ConfigureResult result = result_with_location->result; if (result == DataTypeManager::ABORTED && expect_sync_configuration_aborted_) { expect_sync_configuration_aborted_ = false; return; } + // Clear out the gaia password if it is already there. + gaia_password_ = std::string(); if (result != DataTypeManager::OK) { - OnUnrecoverableError(FROM_HERE, "Sync Configuration failed."); + std::string message = StringPrintf("Sync Configuration failed with %d", + result); + OnUnrecoverableError(*(result_with_location->location), message); + cached_passphrase_ = CachedPassphrase(); return; } + // If the user had entered a custom passphrase use it now. if (!cached_passphrase_.value.empty()) { // Don't hold on to the passphrase in raw form longer than needed. SetPassphrase(cached_passphrase_.value, @@ -1230,9 +1168,14 @@ void ProfileSyncService::Observe(NotificationType type, cached_passphrase_ = CachedPassphrase(); } + // We should never get in a state where we have no encrypted datatypes + // enabled, and yet we still think we require a passphrase. + DCHECK(!(observed_passphrase_required_ && + passphrase_required_for_decryption_ && + !IsEncryptedDatatypeEnabled())); + // TODO(sync): Less wizard, more toast. - if (!observed_passphrase_required_) - wizard_.Step(SyncSetupWizard::DONE); + wizard_.Step(SyncSetupWizard::DONE); NotifyObservers(); // In the old world, this would be a no-op. With new syncer thread, diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h index f0f057e..412d165 100644 --- a/chrome/browser/sync/profile_sync_service.h +++ b/chrome/browser/sync/profile_sync_service.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,40 +10,50 @@ #include "base/basictypes.h" #include "base/gtest_prod_util.h" +#include "base/memory/scoped_ptr.h" #include "base/observer_list.h" -#include "base/scoped_ptr.h" #include "base/string16.h" +#include "base/task.h" #include "base/time.h" #include "base/timer.h" +#include "base/tracked.h" #include "chrome/browser/prefs/pref_member.h" -#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/engine/model_safe_worker.h" #include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/browser/sync/glue/data_type_manager.h" -#include "chrome/browser/sync/glue/session_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/js_event_handler_list.h" #include "chrome/browser/sync/profile_sync_service_observer.h" -#include "chrome/browser/sync/signin_manager.h" #include "chrome/browser/sync/sync_setup_wizard.h" +#include "chrome/browser/sync/syncable/autofill_migration.h" #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/unrecoverable_error_handler.h" #include "chrome/common/net/gaia/google_service_auth_error.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_type.h" #include "googleurl/src/gurl.h" -#include "jingle/notifier/base/notifier_options.h" +#include "ui/gfx/native_widget_types.h" class NotificationDetails; class NotificationSource; -class NotificationType; class Profile; class ProfileSyncFactory; -class TabContents; -class TokenMigrator; +class SigninManager; namespace browser_sync { +class BackendMigrator; +class ChangeProcessor; +class DataTypeManager; class JsFrontend; -} // namespace browser_sync +class SessionModelAssociator; +namespace sessions { struct SyncSessionSnapshot; } +} + +namespace sync_api { +class BaseTransaction; +struct SyncCredentials; +struct UserShare; +} // ProfileSyncService is the layer between browser subsystems like bookmarks, // and the sync backend. Each subsystem is logically thought of as being @@ -145,10 +155,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // available for the backend to start up. bool AreCredentialsAvailable(); - // Loads credentials migrated from the old user settings db. - void LoadMigratedCredentials(const std::string& username, - const std::string& token); - // Registers a data type controller with the sync service. This // makes the data type controller available for use, it does not // enable or activate the synchronization of the data type (see @@ -192,6 +198,8 @@ class ProfileSyncService : public browser_sync::SyncFrontend, virtual void OnPassphraseAccepted(); virtual void OnEncryptionComplete( const syncable::ModelTypeSet& encrypted_types); + virtual void OnMigrationNeededForTypes( + const syncable::ModelTypeSet& types); // Called when a user enters credentials through UI. virtual void OnUserSubmittedAuth(const std::string& username, @@ -232,12 +240,18 @@ class ProfileSyncService : public browser_sync::SyncFrontend, return wizard_.IsVisible(); } virtual void ShowLoginDialog(gfx::NativeWindow parent_window); + SyncSetupWizard& get_wizard() { return wizard_; } // This method handles clicks on "sync error" UI, showing the appropriate // dialog for the error condition (relogin / enter passphrase). virtual void ShowErrorUI(gfx::NativeWindow parent_window); - void ShowConfigure(gfx::NativeWindow parent_window); + // Shows the configure screen of the Sync setup wizard. If |sync_everything| + // is true, shows the corresponding page in the customize screen; otherwise, + // displays the page that gives the user the ability to select which data + // types to sync. + void ShowConfigure(gfx::NativeWindow parent_window, bool sync_everything); + void PromptForExistingPassphrase(gfx::NativeWindow parent_window); void SigninForPassphraseMigration(gfx::NativeWindow parent_window); @@ -267,14 +281,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend, return is_auth_in_progress_; } - bool tried_creating_explicit_passphrase() const { - return tried_creating_explicit_passphrase_; - } - - bool tried_setting_explicit_passphrase() const { - return tried_setting_explicit_passphrase_; - } - bool observed_passphrase_required() const { return observed_passphrase_required_; } @@ -283,13 +289,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend, return passphrase_required_for_decryption_; } - // A timestamp marking the last time the service observed a transition from - // the SYNCING state to the READY state. Note that this does not reflect the - // last time we polled the server to see if there were any changes; the - // timestamp is only snapped when syncing takes place and we download or - // upload some bookmark entity. - const base::Time& last_synced_time() const { return last_synced_time_; } - // Returns a user-friendly string form of last synced time (in minutes). virtual string16 GetLastSyncedTimeString() const; @@ -348,7 +347,7 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // ProfileSyncServiceHarness. Figure out a different way to expose // this info to that class, and remove these functions. - const browser_sync::sessions::SyncSessionSnapshot* + virtual const browser_sync::sessions::SyncSessionSnapshot* GetLastSessionSnapshot() const; // Returns whether or not the underlying sync engine has made any @@ -415,8 +414,10 @@ class ProfileSyncService : public browser_sync::SyncFrontend, syncable::ModelTypeSet* registered_types) const; // Checks whether the Cryptographer is ready to encrypt and decrypt updates - // for sensitive data types. - virtual bool IsCryptographerReady() const; + // for sensitive data types. Caller must be holding a + // syncapi::BaseTransaction to ensure thread safety. + virtual bool IsCryptographerReady( + const sync_api::BaseTransaction* trans) const; // Returns true if a secondary passphrase is being used. virtual bool IsUsingSecondaryPassphrase() const; @@ -458,13 +459,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend, const std::string& cros_user() const { return cros_user_; } protected: - // Used by ProfileSyncServiceMock only. - // - // TODO(akalin): Separate this class out into an abstract - // ProfileSyncService interface and a ProfileSyncServiceImpl class - // so we don't need this hack anymore. - ProfileSyncService(); - // Used by test classes that derive from ProfileSyncService. virtual browser_sync::SyncBackendHost* GetBackendForTest(); @@ -510,14 +504,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // Cache of the last name the client attempted to authenticate. std::string last_attempted_user_email_; - // Whether the user has tried creating an explicit passphrase on this - // machine. - bool tried_creating_explicit_passphrase_; - - // Whether the user has tried setting an explicit passphrase on this - // machine. - bool tried_setting_explicit_passphrase_; - // Whether we have seen a SYNC_PASSPHRASE_REQUIRED since initializing the // backend, telling us that it is safe to send a passphrase down ASAP. bool observed_passphrase_required_; @@ -597,10 +583,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend, std::string unrecoverable_error_message_; scoped_ptr<tracked_objects::Location> unrecoverable_error_location_; - // Contains options specific to how sync clients send and listen to - // notifications. - notifier::NotifierOptions notifier_options_; - // Manages the start and stop of the various data types. scoped_ptr<browser_sync::DataTypeManager> data_type_manager_; @@ -622,8 +604,6 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // desist syncing immediately. bool expect_sync_configuration_aborted_; - scoped_ptr<TokenMigrator> token_migrator_; - // Sometimes we need to temporarily hold on to a passphrase because we don't // yet have a backend to send it to. This happens during initialization as // we don't StartUp until we have a valid token, which happens after valid @@ -636,6 +616,9 @@ class ProfileSyncService : public browser_sync::SyncFrontend, }; CachedPassphrase cached_passphrase_; + // TODO(lipalani): Bug 82221 unify this with the CachedPassphrase struct. + std::string gaia_password_; + // TODO(tim): Remove this once new 'explicit passphrase' code flushes through // dev channel. See bug 62103. // To "migrate" early adopters of password sync on dev channel to the new @@ -659,6 +642,8 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // encrypted through the OnEncryptionComplete callback of SyncFrontend. syncable::ModelTypeSet encrypted_types_; + scoped_ptr<browser_sync::BackendMigrator> migrator_; + DISALLOW_COPY_AND_ASSIGN(ProfileSyncService); }; diff --git a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc index 64753fb..2b13528 100644 --- a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,14 +9,14 @@ #include "testing/gtest/include/gtest/gtest.h" #include "base/callback.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" #include "base/string16.h" +#include "base/synchronization/waitable_event.h" #include "base/task.h" #include "base/time.h" #include "base/utf_string_conversions.h" -#include "base/synchronization/waitable_event.h" #include "chrome/browser/autofill/autofill_common_test.h" #include "chrome/browser/sync/abstract_profile_sync_service_test.h" #include "chrome/browser/sync/engine/model_changing_syncer_command.h" @@ -34,18 +34,19 @@ #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" #include "chrome/browser/sync/syncable/autofill_migration.h" #include "chrome/browser/sync/syncable/directory_manager.h" -#include "chrome/browser/sync/syncable/syncable.h" #include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/syncable.h" #include "chrome/browser/sync/test_profile_sync_service.h" #include "chrome/browser/webdata/autofill_change.h" #include "chrome/browser/webdata/autofill_entry.h" +#include "chrome/browser/webdata/autofill_table.h" #include "chrome/browser/webdata/web_database.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/notification_source.h" -#include "chrome/common/notification_type.h" #include "chrome/test/profile_mock.h" #include "chrome/test/sync/engine/test_id_factory.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_source.h" +#include "content/common/notification_type.h" #include "testing/gmock/include/gmock/gmock.h" using base::Time; @@ -65,7 +66,6 @@ using syncable::CREATE_NEW_UPDATE_ITEM; using syncable::AUTOFILL; using syncable::BASE_VERSION; using syncable::CREATE; -using syncable::DirectoryChangeEvent; using syncable::GET_BY_SERVER_TAG; using syncable::INVALID; using syncable::MutableEntry; @@ -91,8 +91,9 @@ namespace syncable { class Id; } -class WebDatabaseMock : public WebDatabase { +class AutofillTableMock : public AutofillTable { public: + AutofillTableMock() : AutofillTable(NULL, NULL) {} MOCK_METHOD2(RemoveFormElement, bool(const string16& name, const string16& value)); // NOLINT MOCK_METHOD1(GetAllAutofillEntries, @@ -103,19 +104,33 @@ class WebDatabaseMock : public WebDatabase { std::vector<base::Time>* timestamps)); MOCK_METHOD1(UpdateAutofillEntries, bool(const std::vector<AutofillEntry>&)); // NOLINT - MOCK_METHOD1(GetAutoFillProfiles, - bool(std::vector<AutoFillProfile*>*)); // NOLINT - MOCK_METHOD1(UpdateAutoFillProfile, - bool(const AutoFillProfile&)); // NOLINT - MOCK_METHOD1(AddAutoFillProfile, - bool(const AutoFillProfile&)); // NOLINT - MOCK_METHOD1(RemoveAutoFillProfile, + MOCK_METHOD1(GetAutofillProfiles, + bool(std::vector<AutofillProfile*>*)); // NOLINT + MOCK_METHOD1(UpdateAutofillProfile, + bool(const AutofillProfile&)); // NOLINT + MOCK_METHOD1(AddAutofillProfile, + bool(const AutofillProfile&)); // NOLINT + MOCK_METHOD1(RemoveAutofillProfile, bool(const std::string&)); // NOLINT }; +class WebDatabaseFake : public WebDatabase { + public: + explicit WebDatabaseFake(AutofillTable* autofill_table) + : autofill_table_(autofill_table) {} + + virtual AutofillTable* GetAutofillTable() { + return autofill_table_; + } + + private: + AutofillTable* autofill_table_; +}; + + class ProfileSyncServiceAutofillTest; -template<class AutoFillProfile> +template<class AutofillProfile> syncable::ModelType GetModelType() { return syncable::UNSPECIFIED; } @@ -126,7 +141,7 @@ syncable::ModelType GetModelType<AutofillEntry>() { } template<> -syncable::ModelType GetModelType<AutoFillProfile>() { +syncable::ModelType GetModelType<AutofillProfile>() { return syncable::AUTOFILL_PROFILE; } @@ -214,7 +229,7 @@ class AutofillProfileFactory : public AbstractAutofillFactory { return new AutofillProfileDataTypeController(factory, profile, service); - } + } void SetExpectation(ProfileSyncFactoryMock* factory, ProfileSyncService* service, @@ -237,8 +252,7 @@ template <class T> class AddAutofillTask; class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { protected: - ProfileSyncServiceAutofillTest() : db_thread_(BrowserThread::DB) { - } + ProfileSyncServiceAutofillTest() : db_thread_(BrowserThread::DB) {} AutofillProfileFactory profile_factory_; AutofillEntryFactory entry_factory_; @@ -254,7 +268,9 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { } } virtual void SetUp() { - web_data_service_ = new WebDataServiceFake(&web_database_); + profile_.CreateRequestContext(); + web_database_.reset(new WebDatabaseFake(&autofill_table_)); + web_data_service_ = new WebDataServiceFake(web_database_.get()); personal_data_manager_ = new PersonalDataManagerMock(); EXPECT_CALL(*personal_data_manager_, LoadProfiles()).Times(1); EXPECT_CALL(*personal_data_manager_, LoadCreditCards()).Times(1); @@ -269,6 +285,12 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { service_.reset(); notification_service_->TearDown(); db_thread_.Stop(); + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile_.ResetRequestContext(); + } MessageLoop::current()->RunAllPending(); } @@ -283,14 +305,14 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { factory->CreateDataTypeController(&factory_, &profile_, service_.get()); - SyncBackendHostForProfileSyncTest:: - SetDefaultExpectationsForWorkerCreation(&profile_); + SyncBackendHostForProfileSyncTest:: + SetDefaultExpectationsForWorkerCreation(&profile_); - factory->SetExpectation(&factory_, - service_.get(), - &web_database_, - personal_data_manager_.get(), - data_type_controller); + factory->SetExpectation(&factory_, + service_.get(), + web_database_.get(), + personal_data_manager_.get(), + data_type_controller); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillOnce(ReturnNewDataTypeManager()); @@ -311,7 +333,6 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { EXPECT_CALL(profile_, GetTokenService()). WillRepeatedly(Return(&token_service_)); - service_->set_num_expected_resumes(will_fail_association ? 0 : 1); service_->RegisterDataTypeController(data_type_controller); service_->Initialize(); MessageLoop::current()->Run(); @@ -333,7 +354,7 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { return true; } - bool AddAutofillSyncNode(const AutoFillProfile& profile) { + bool AddAutofillSyncNode(const AutofillProfile& profile) { sync_api::WriteTransaction trans(service_->GetUserShare()); sync_api::ReadNode autofill_root(&trans); if (!autofill_root.InitByTagLookup(browser_sync::kAutofillProfileTag)) @@ -348,7 +369,7 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { } bool GetAutofillEntriesFromSyncDB(std::vector<AutofillEntry>* entries, - std::vector<AutoFillProfile>* profiles) { + std::vector<AutofillProfile>* profiles) { sync_api::ReadTransaction trans(service_->GetUserShare()); sync_api::ReadNode autofill_root(&trans); if (!autofill_root.InitByTagLookup(browser_sync::kAutofillTag)) @@ -373,7 +394,7 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { } entries->push_back(AutofillEntry(key, timestamps)); } else if (autofill.has_profile()) { - AutoFillProfile p; + AutofillProfile p; p.set_guid(autofill.profile().guid()); AutofillProfileModelAssociator::OverwriteProfileWithServerData(&p, autofill.profile()); @@ -385,7 +406,7 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { } bool GetAutofillProfilesFromSyncDBUnderProfileNode( - std::vector<AutoFillProfile>* profiles) { + std::vector<AutofillProfile>* profiles) { sync_api::ReadTransaction trans(service_->GetUserShare()); sync_api::ReadNode autofill_root(&trans); if (!autofill_root.InitByTagLookup(browser_sync::kAutofillProfileTag)) @@ -399,7 +420,7 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { const sync_pb::AutofillProfileSpecifics& autofill( child_node.GetAutofillProfileSpecifics()); - AutoFillProfile p; + AutofillProfile p; p.set_guid(autofill.guid()); AutofillProfileModelAssociator::OverwriteProfileWithServerData(&p, autofill); @@ -410,9 +431,9 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { } void SetIdleChangeProcessorExpectations() { - EXPECT_CALL(web_database_, RemoveFormElement(_, _)).Times(0); - EXPECT_CALL(web_database_, GetAutofillTimestamps(_, _, _)).Times(0); - EXPECT_CALL(web_database_, UpdateAutofillEntries(_)).Times(0); + EXPECT_CALL(autofill_table_, RemoveFormElement(_, _)).Times(0); + EXPECT_CALL(autofill_table_, GetAutofillTimestamps(_, _, _)).Times(0); + EXPECT_CALL(autofill_table_, UpdateAutofillEntries(_)).Times(0); } static AutofillEntry MakeAutofillEntry(const char* name, @@ -435,14 +456,15 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { } friend class AddAutofillTask<AutofillEntry>; - friend class AddAutofillTask<AutoFillProfile>; + friend class AddAutofillTask<AutofillProfile>; friend class FakeServerUpdater; BrowserThread db_thread_; scoped_refptr<ThreadNotificationService> notification_service_; ProfileMock profile_; - WebDatabaseMock web_database_; + AutofillTableMock autofill_table_; + scoped_ptr<WebDatabaseFake> web_database_; scoped_refptr<WebDataService> web_data_service_; scoped_refptr<PersonalDataManagerMock> personal_data_manager_; }; @@ -483,12 +505,12 @@ class WriteTransactionTest: public WriteTransaction { : WriteTransaction(directory, writer, source_file, line), wait_for_syncapi_(wait_for_syncapi) { } - virtual void NotifyTransactionComplete() { + virtual void NotifyTransactionComplete(syncable::ModelTypeBitSet types) { // This is where we differ. Force a thread change here, giving another // thread a chance to create a WriteTransaction (*wait_for_syncapi_)->Wait(); - WriteTransaction::NotifyTransactionComplete(); + WriteTransaction::NotifyTransactionComplete(types); } private: @@ -613,15 +635,15 @@ TEST_F(ProfileSyncServiceAutofillTest, FailModelAssociation) { } TEST_F(ProfileSyncServiceAutofillTest, EmptyNativeEmptySync) { - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); EXPECT_CALL(*personal_data_manager_, Refresh()); StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> sync_entries; - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles)); EXPECT_EQ(0U, sync_entries.size()); EXPECT_EQ(0U, sync_profiles.size()); @@ -630,16 +652,16 @@ TEST_F(ProfileSyncServiceAutofillTest, EmptyNativeEmptySync) { TEST_F(ProfileSyncServiceAutofillTest, HasNativeEntriesEmptySync) { std::vector<AutofillEntry> entries; entries.push_back(MakeAutofillEntry("foo", "bar", 1)); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true))); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); EXPECT_CALL(*personal_data_manager_, Refresh()); StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> sync_entries; - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles)); ASSERT_EQ(1U, entries.size()); EXPECT_TRUE(entries[0] == sync_entries[0]); @@ -648,10 +670,10 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeEntriesEmptySync) { TEST_F(ProfileSyncServiceAutofillTest, HasProfileEmptySync) { - std::vector<AutoFillProfile*> profiles; - std::vector<AutoFillProfile> expected_profiles; - // Owned by GetAutoFillProfiles caller. - AutoFillProfile* profile0 = new AutoFillProfile; + std::vector<AutofillProfile*> profiles; + std::vector<AutofillProfile> expected_profiles; + // Owned by GetAutofillProfiles caller. + AutofillProfile* profile0 = new AutofillProfile; autofill_test::SetProfileInfoWithGuid(profile0, "54B3F9AA-335E-4F71-A27D-719C41564230", "Billing", "Mitchell", "Morrison", @@ -659,14 +681,14 @@ TEST_F(ProfileSyncServiceAutofillTest, HasProfileEmptySync) { "91601", "US", "12345678910", "01987654321"); profiles.push_back(profile0); expected_profiles.push_back(*profile0); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)). WillOnce(DoAll(SetArgumentPointee<0>(profiles), Return(true))); EXPECT_CALL(*personal_data_manager_, Refresh()); SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL_PROFILE); StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode(&sync_profiles)); EXPECT_EQ(1U, sync_profiles.size()); EXPECT_EQ(0, expected_profiles[0].Compare(sync_profiles[0])); @@ -679,16 +701,16 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeWithDuplicatesEmptySync) { entries.push_back(MakeAutofillEntry("foo", "bar", 1)); entries.push_back(MakeAutofillEntry("dup", "", 2)); entries.push_back(MakeAutofillEntry("dup", "", 3)); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true))); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); EXPECT_CALL(*personal_data_manager_, Refresh()); StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> sync_entries; - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles)); EXPECT_EQ(2U, sync_entries.size()); } @@ -700,17 +722,17 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncNoMerge) { std::vector<AutofillEntry> native_entries; native_entries.push_back(native_entry); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true))); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); std::vector<AutofillEntry> sync_entries; sync_entries.push_back(sync_entry); AddAutofillTask<AutofillEntry> task(this, sync_entries); - EXPECT_CALL(web_database_, UpdateAutofillEntries(ElementsAre(sync_entry))). + EXPECT_CALL(autofill_table_, UpdateAutofillEntries(ElementsAre(sync_entry))). WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); @@ -722,7 +744,7 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncNoMerge) { expected_entries.insert(sync_entry); std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles)); std::set<AutofillEntry> new_sync_entries_set(new_sync_entries.begin(), @@ -738,22 +760,22 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeEntry) { std::vector<AutofillEntry> native_entries; native_entries.push_back(native_entry); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true))); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); std::vector<AutofillEntry> sync_entries; sync_entries.push_back(sync_entry); AddAutofillTask<AutofillEntry> task(this, sync_entries); - EXPECT_CALL(web_database_, UpdateAutofillEntries(ElementsAre(merged_entry))). - WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, + UpdateAutofillEntries(ElementsAre(merged_entry))).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles)); ASSERT_EQ(1U, new_sync_entries.size()); @@ -761,35 +783,35 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeEntry) { } TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeProfile) { - AutoFillProfile sync_profile; + AutofillProfile sync_profile; autofill_test::SetProfileInfoWithGuid(&sync_profile, "23355099-1170-4B71-8ED4-144470CC9EBE", "Billing", "Mitchell", "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "12345678910", "01987654321"); - AutoFillProfile* native_profile = new AutoFillProfile; + AutofillProfile* native_profile = new AutofillProfile; autofill_test::SetProfileInfoWithGuid(native_profile, "23355099-1170-4B71-8ED4-144470CC9EBE", "Billing", "Alicia", "Saenz", "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", "32801", "US", "19482937549", "13502849239"); - std::vector<AutoFillProfile*> native_profiles; + std::vector<AutofillProfile*> native_profiles; native_profiles.push_back(native_profile); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; sync_profiles.push_back(sync_profile); - AddAutofillTask<AutoFillProfile> task(this, sync_profiles); + AddAutofillTask<AutofillProfile> task(this, sync_profiles); - EXPECT_CALL(web_database_, UpdateAutoFillProfile(_)). + EXPECT_CALL(autofill_table_, UpdateAutofillProfile(_)). WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( &new_sync_profiles)); ASSERT_EQ(1U, new_sync_profiles.size()); @@ -797,7 +819,7 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeProfile) { } TEST_F(ProfileSyncServiceAutofillTest, MergeProfileWithDifferentGuid) { - AutoFillProfile sync_profile; + AutofillProfile sync_profile; autofill_test::SetProfileInfoWithGuid(&sync_profile, "23355099-1170-4B71-8ED4-144470CC9EBE", "Billing", @@ -806,31 +828,31 @@ TEST_F(ProfileSyncServiceAutofillTest, MergeProfileWithDifferentGuid) { "91601", "US", "12345678910", "01987654321"); std::string native_guid = "EDC609ED-7EEE-4F27-B00C-423242A9C44B"; - AutoFillProfile* native_profile = new AutoFillProfile; + AutofillProfile* native_profile = new AutofillProfile; autofill_test::SetProfileInfoWithGuid(native_profile, native_guid.c_str(), "Billing", "Mitchell", "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "12345678910", "01987654321"); - std::vector<AutoFillProfile*> native_profiles; + std::vector<AutofillProfile*> native_profiles; native_profiles.push_back(native_profile); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; sync_profiles.push_back(sync_profile); - AddAutofillTask<AutoFillProfile> task(this, sync_profiles); + AddAutofillTask<AutofillProfile> task(this, sync_profiles); - EXPECT_CALL(web_database_, AddAutoFillProfile(_)). + EXPECT_CALL(autofill_table_, AddAutofillProfile(_)). WillOnce(Return(true)); - EXPECT_CALL(web_database_, RemoveAutoFillProfile(native_guid)). + EXPECT_CALL(autofill_table_, RemoveAutofillProfile(native_guid)). WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( &new_sync_profiles)); ASSERT_EQ(1U, new_sync_profiles.size()); @@ -839,8 +861,8 @@ TEST_F(ProfileSyncServiceAutofillTest, MergeProfileWithDifferentGuid) { } TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddEntry) { - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); @@ -850,7 +872,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddEntry) { AutofillEntry added_entry(MakeAutofillEntry("added", "entry", 1)); std::vector<base::Time> timestamps(added_entry.timestamps()); - EXPECT_CALL(web_database_, GetAutofillTimestamps(_, _, _)). + EXPECT_CALL(autofill_table_, GetAutofillTimestamps(_, _, _)). WillOnce(DoAll(SetArgumentPointee<2>(timestamps), Return(true))); AutofillChangeList changes; @@ -861,7 +883,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddEntry) { Details<AutofillChangeList>(&changes)); std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles)); ASSERT_EQ(1U, new_sync_entries.size()); @@ -869,14 +891,14 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddEntry) { } TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddProfile) { - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL_PROFILE); StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); - AutoFillProfile added_profile; + AutofillProfile added_profile; autofill_test::SetProfileInfoWithGuid(&added_profile, "D6ADA912-D374-4C0A-917D-F5C8EBE43011", "Josephine", "Alicia", "Saenz", "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", @@ -889,7 +911,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddProfile) { Source<WebDataService>(web_data_service_.get()), Details<AutofillProfileChange>(&change)); - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( &new_sync_profiles)); ASSERT_EQ(1U, new_sync_profiles.size()); @@ -901,9 +923,9 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateEntry) { std::vector<AutofillEntry> original_entries; original_entries.push_back(original_entry); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true))); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); CreateRootTask task(this, syncable::AUTOFILL); StartSyncService(&task, false, syncable::AUTOFILL); @@ -912,7 +934,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateEntry) { AutofillEntry updated_entry(MakeAutofillEntry("my", "entry", 1, 2)); std::vector<base::Time> timestamps(updated_entry.timestamps()); - EXPECT_CALL(web_database_, GetAutofillTimestamps(_, _, _)). + EXPECT_CALL(autofill_table_, GetAutofillTimestamps(_, _, _)). WillOnce(DoAll(SetArgumentPointee<2>(timestamps), Return(true))); AutofillChangeList changes; @@ -924,7 +946,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateEntry) { Details<AutofillChangeList>(&changes)); std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles)); ASSERT_EQ(1U, new_sync_entries.size()); @@ -937,9 +959,9 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveEntry) { std::vector<AutofillEntry> original_entries; original_entries.push_back(original_entry); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(original_entries), Return(true))); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); CreateRootTask task(this, syncable::AUTOFILL); StartSyncService(&task, false, syncable::AUTOFILL); @@ -954,32 +976,32 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveEntry) { Details<AutofillChangeList>(&changes)); std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, &new_sync_profiles)); ASSERT_EQ(0U, new_sync_entries.size()); } TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveProfile) { - AutoFillProfile sync_profile; + AutofillProfile sync_profile; autofill_test::SetProfileInfoWithGuid(&sync_profile, "3BA5FA1B-1EC4-4BB3-9B57-EC92BE3C1A09", "Josephine", "Alicia", "Saenz", "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", "32801", "US", "19482937549", "13502849239"); - AutoFillProfile* native_profile = new AutoFillProfile; + AutofillProfile* native_profile = new AutofillProfile; autofill_test::SetProfileInfoWithGuid(native_profile, "3BA5FA1B-1EC4-4BB3-9B57-EC92BE3C1A09", "Josephine", "Alicia", "Saenz", "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", "32801", "US", "19482937549", "13502849239"); - std::vector<AutoFillProfile*> native_profiles; + std::vector<AutofillProfile*> native_profiles; native_profiles.push_back(native_profile); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; sync_profiles.push_back(sync_profile); - AddAutofillTask<AutoFillProfile> task(this, sync_profiles); + AddAutofillTask<AutofillProfile> task(this, sync_profiles); EXPECT_CALL(*personal_data_manager_, Refresh()); StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); @@ -991,15 +1013,15 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveProfile) { Source<WebDataService>(web_data_service_.get()), Details<AutofillProfileChange>(&change)); - std::vector<AutoFillProfile> new_sync_profiles; + std::vector<AutofillProfile> new_sync_profiles; ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( &new_sync_profiles)); ASSERT_EQ(0U, new_sync_profiles.size()); } TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeError) { - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); CreateRootTask task(this, syncable::AUTOFILL); StartSyncService(&task, false, syncable::AUTOFILL); @@ -1033,9 +1055,9 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeError) { // Crashy, http://crbug.com/57884 TEST_F(ProfileSyncServiceAutofillTest, DISABLED_ServerChangeRace) { - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); - EXPECT_CALL(web_database_, UpdateAutofillEntries(_)). + EXPECT_CALL(autofill_table_, GetAllAutofillEntries(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, GetAutofillProfiles(_)).WillOnce(Return(true)); + EXPECT_CALL(autofill_table_, UpdateAutofillEntries(_)). WillRepeatedly(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()).Times(3); CreateRootTask task(this, syncable::AUTOFILL); @@ -1065,7 +1087,7 @@ TEST_F(ProfileSyncServiceAutofillTest, DISABLED_ServerChangeRace) { updater->CreateNewEntryAndWait(MakeAutofillEntry("server2", "entry2", 3)); std::vector<AutofillEntry> sync_entries; - std::vector<AutoFillProfile> sync_profiles; + std::vector<AutofillProfile> sync_profiles; ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles)); EXPECT_EQ(3U, sync_entries.size()); EXPECT_EQ(0U, sync_profiles.size()); diff --git a/chrome/browser/sync/profile_sync_service_bookmark_unittest.cc b/chrome/browser/sync/profile_sync_service_bookmark_unittest.cc new file mode 100644 index 0000000..ee82421 --- /dev/null +++ b/chrome/browser/sync/profile_sync_service_bookmark_unittest.cc @@ -0,0 +1,1362 @@ +// Copyright (c) 2011 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. + +// TODO(akalin): This file is basically just a unit test for +// BookmarkChangeProcessor. Write unit tests for +// BookmarkModelAssociator separately. + +#include <stack> +#include <vector> + +#include "base/file_path.h" +#include "base/file_util.h" +#include "base/memory/scoped_ptr.h" +#include "base/message_loop.h" +#include "base/string16.h" +#include "base/string_number_conversions.h" +#include "base/string_util.h" +#include "base/utf_string_conversions.h" +#include "chrome/browser/bookmarks/bookmark_model.h" +#include "chrome/browser/sync/abstract_profile_sync_service_test.h" +#include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/bookmark_change_processor.h" +#include "chrome/browser/sync/glue/bookmark_model_associator.h" +#include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/test/sync/engine/test_id_factory.h" +#include "chrome/test/sync/engine/test_user_share.h" +#include "chrome/test/testing_profile.h" +#include "content/browser/browser_thread.h" +#include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace browser_sync { + +namespace { + +using testing::_; +using testing::InvokeWithoutArgs; +using testing::Mock; +using testing::StrictMock; + +class TestBookmarkModelAssociator : public BookmarkModelAssociator { + public: + TestBookmarkModelAssociator( + BookmarkModel* bookmark_model, + sync_api::UserShare* user_share, + UnrecoverableErrorHandler* unrecoverable_error_handler) + : BookmarkModelAssociator(bookmark_model, user_share, + unrecoverable_error_handler), + user_share_(user_share) {} + + // TODO(akalin): This logic lazily creates any tagged node that is + // requested. A better way would be to have utility functions to + // create sync nodes from some bookmark structure and to use that. + virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) { + std::wstring tag_wide; + if (!UTF8ToWide(tag.c_str(), tag.length(), &tag_wide)) { + NOTREACHED() << "Unable to convert UTF8 to wide for string: " << tag; + return false; + } + + bool root_exists = false; + syncable::ModelType type = model_type(); + { + sync_api::WriteTransaction trans(user_share_); + sync_api::ReadNode uber_root(&trans); + uber_root.InitByRootLookup(); + + sync_api::ReadNode root(&trans); + root_exists = root.InitByTagLookup( + ProfileSyncServiceTestHelper::GetTagForType(type)); + } + + if (!root_exists) { + bool created = ProfileSyncServiceTestHelper::CreateRoot( + type, + user_share_, + &id_factory_); + if (!created) + return false; + } + + sync_api::WriteTransaction trans(user_share_); + sync_api::ReadNode root(&trans); + EXPECT_TRUE(root.InitByTagLookup( + ProfileSyncServiceTestHelper::GetTagForType(type))); + + // First, try to find a node with the title among the root's children. + // This will be the case if we are testing model persistence, and + // are reloading a sync repository created earlier in the test. + int64 last_child_id = sync_api::kInvalidId; + for (int64 id = root.GetFirstChildId(); id != sync_api::kInvalidId; /***/) { + sync_api::ReadNode child(&trans); + child.InitByIdLookup(id); + last_child_id = id; + if (tag_wide == child.GetTitle()) { + *sync_id = id; + return true; + } + id = child.GetSuccessorId(); + } + + sync_api::ReadNode predecessor_node(&trans); + sync_api::ReadNode* predecessor = NULL; + if (last_child_id != sync_api::kInvalidId) { + predecessor_node.InitByIdLookup(last_child_id); + predecessor = &predecessor_node; + } + sync_api::WriteNode node(&trans); + // Create new fake tagged nodes at the end of the ordering. + node.InitByCreation(type, root, predecessor); + node.SetIsFolder(true); + node.SetTitle(tag_wide); + node.SetExternalId(0); + *sync_id = node.GetId(); + return true; + } + + private: + sync_api::UserShare* user_share_; + browser_sync::TestIdFactory id_factory_; +}; + +// FakeServerChange constructs a list of sync_api::ChangeRecords while modifying +// the sync model, and can pass the ChangeRecord list to a +// sync_api::SyncObserver (i.e., the ProfileSyncService) to test the client +// change-application behavior. +// Tests using FakeServerChange should be careful to avoid back-references, +// since FakeServerChange will send the edits in the order specified. +class FakeServerChange { + public: + explicit FakeServerChange(sync_api::WriteTransaction* trans) : trans_(trans) { + } + + // Pretend that the server told the syncer to add a bookmark object. + int64 Add(const std::wstring& title, + const std::string& url, + bool is_folder, + int64 parent_id, + int64 predecessor_id) { + sync_api::ReadNode parent(trans_); + EXPECT_TRUE(parent.InitByIdLookup(parent_id)); + sync_api::WriteNode node(trans_); + if (predecessor_id == 0) { + EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent, NULL)); + } else { + sync_api::ReadNode predecessor(trans_); + EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id)); + EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); + EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent, + &predecessor)); + } + EXPECT_EQ(node.GetPredecessorId(), predecessor_id); + EXPECT_EQ(node.GetParentId(), parent_id); + node.SetIsFolder(is_folder); + node.SetTitle(title); + if (!is_folder) + node.SetURL(GURL(url)); + sync_api::SyncManager::ChangeRecord record; + record.action = sync_api::SyncManager::ChangeRecord::ACTION_ADD; + record.id = node.GetId(); + changes_.push_back(record); + return node.GetId(); + } + + // Add a bookmark folder. + int64 AddFolder(const std::wstring& title, + int64 parent_id, + int64 predecessor_id) { + return Add(title, std::string(), true, parent_id, predecessor_id); + } + + // Add a bookmark. + int64 AddURL(const std::wstring& title, + const std::string& url, + int64 parent_id, + int64 predecessor_id) { + return Add(title, url, false, parent_id, predecessor_id); + } + + // Pretend that the server told the syncer to delete an object. + void Delete(int64 id) { + { + // Delete the sync node. + sync_api::WriteNode node(trans_); + EXPECT_TRUE(node.InitByIdLookup(id)); + EXPECT_FALSE(node.GetFirstChildId()); + node.Remove(); + } + { + // Verify the deletion. + sync_api::ReadNode node(trans_); + EXPECT_FALSE(node.InitByIdLookup(id)); + } + + sync_api::SyncManager::ChangeRecord record; + record.action = sync_api::SyncManager::ChangeRecord::ACTION_DELETE; + record.id = id; + // Deletions are always first in the changelist, but we can't actually do + // WriteNode::Remove() on the node until its children are moved. So, as + // a practical matter, users of FakeServerChange must move or delete + // children before parents. Thus, we must insert the deletion record + // at the front of the vector. + changes_.insert(changes_.begin(), record); + } + + // Set a new title value, and return the old value. + std::wstring ModifyTitle(int64 id, const std::wstring& new_title) { + sync_api::WriteNode node(trans_); + EXPECT_TRUE(node.InitByIdLookup(id)); + std::wstring old_title = node.GetTitle(); + node.SetTitle(new_title); + SetModified(id); + return old_title; + } + + // Set a new parent and predecessor value. Return the old parent id. + // We could return the old predecessor id, but it turns out not to be + // very useful for assertions. + int64 ModifyPosition(int64 id, int64 parent_id, int64 predecessor_id) { + sync_api::ReadNode parent(trans_); + EXPECT_TRUE(parent.InitByIdLookup(parent_id)); + sync_api::WriteNode node(trans_); + EXPECT_TRUE(node.InitByIdLookup(id)); + int64 old_parent_id = node.GetParentId(); + if (predecessor_id == 0) { + EXPECT_TRUE(node.SetPosition(parent, NULL)); + } else { + sync_api::ReadNode predecessor(trans_); + EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id)); + EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); + EXPECT_TRUE(node.SetPosition(parent, &predecessor)); + } + SetModified(id); + return old_parent_id; + } + + // Pass the fake change list to |service|. + void ApplyPendingChanges(ChangeProcessor* processor) { + processor->ApplyChangesFromSyncModel(trans_, + changes_.size() ? &changes_[0] : NULL, changes_.size()); + } + + const std::vector<sync_api::SyncManager::ChangeRecord>& changes() { + return changes_; + } + + private: + // Helper function to push an ACTION_UPDATE record onto the back + // of the changelist. + void SetModified(int64 id) { + // Coalesce multi-property edits. + if (!changes_.empty() && changes_.back().id == id && + changes_.back().action == + sync_api::SyncManager::ChangeRecord::ACTION_UPDATE) + return; + sync_api::SyncManager::ChangeRecord record; + record.action = sync_api::SyncManager::ChangeRecord::ACTION_UPDATE; + record.id = id; + changes_.push_back(record); + } + + // The transaction on which everything happens. + sync_api::WriteTransaction *trans_; + + // The change list we construct. + std::vector<sync_api::SyncManager::ChangeRecord> changes_; +}; + +class MockUnrecoverableErrorHandler : public UnrecoverableErrorHandler { + public: + MOCK_METHOD2(OnUnrecoverableError, + void(const tracked_objects::Location&, const std::string&)); +}; + +class ProfileSyncServiceBookmarkTest : public testing::Test { + protected: + enum LoadOption { LOAD_FROM_STORAGE, DELETE_EXISTING_STORAGE }; + enum SaveOption { SAVE_TO_STORAGE, DONT_SAVE_TO_STORAGE }; + + ProfileSyncServiceBookmarkTest() + : ui_thread_(BrowserThread::UI, &message_loop_), + file_thread_(BrowserThread::FILE, &message_loop_), + model_(NULL) { + } + + virtual ~ProfileSyncServiceBookmarkTest() { + StopSync(); + UnloadBookmarkModel(); + } + + virtual void SetUp() { + test_user_share_.SetUp(); + } + + virtual void TearDown() { + test_user_share_.TearDown(); + } + + // Load (or re-load) the bookmark model. |load| controls use of the + // bookmarks file on disk. |save| controls whether the newly loaded + // bookmark model will write out a bookmark file as it goes. + void LoadBookmarkModel(LoadOption load, SaveOption save) { + bool delete_bookmarks = load == DELETE_EXISTING_STORAGE; + profile_.CreateBookmarkModel(delete_bookmarks); + model_ = profile_.GetBookmarkModel(); + // Wait for the bookmarks model to load. + profile_.BlockUntilBookmarkModelLoaded(); + // This noticeably speeds up the unit tests that request it. + if (save == DONT_SAVE_TO_STORAGE) + model_->ClearStore(); + message_loop_.RunAllPending(); + } + + void StartSync() { + // Set up model associator. + model_associator_.reset(new TestBookmarkModelAssociator( + profile_.GetBookmarkModel(), + test_user_share_.user_share(), + &mock_unrecoverable_error_handler_)); + EXPECT_TRUE(model_associator_->AssociateModels()); + MessageLoop::current()->RunAllPending(); + + // Set up change processor. + change_processor_.reset( + new BookmarkChangeProcessor(model_associator_.get(), + &mock_unrecoverable_error_handler_)); + change_processor_->Start(&profile_, test_user_share_.user_share()); + } + + void StopSync() { + change_processor_->Stop(); + change_processor_.reset(); + + EXPECT_TRUE(model_associator_->DisassociateModels()); + model_associator_.reset(); + + message_loop_.RunAllPending(); + + // TODO(akalin): Actually close the database and flush it to disk + // (and make StartSync reload from disk). This would require + // refactoring TestUserShare. + } + + void UnloadBookmarkModel() { + profile_.CreateBookmarkModel(false /* delete_bookmarks */); + model_ = NULL; + message_loop_.RunAllPending(); + } + + bool InitSyncNodeFromChromeNode(const BookmarkNode* bnode, + sync_api::BaseNode* sync_node) { + return model_associator_->InitSyncNodeFromChromeId(bnode->id(), + sync_node); + } + + void ExpectSyncerNodeMatching(sync_api::BaseTransaction* trans, + const BookmarkNode* bnode) { + sync_api::ReadNode gnode(trans); + ASSERT_TRUE(InitSyncNodeFromChromeNode(bnode, &gnode)); + // Non-root node titles and parents must match. + if (bnode != model_->GetBookmarkBarNode() && + bnode != model_->other_node()) { + EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(gnode.GetTitle())); + EXPECT_EQ( + model_associator_->GetChromeNodeFromSyncId(gnode.GetParentId()), + bnode->parent()); + } + EXPECT_EQ(bnode->is_folder(), gnode.GetIsFolder()); + if (bnode->is_url()) + EXPECT_EQ(bnode->GetURL(), gnode.GetURL()); + + // Check for position matches. + int browser_index = bnode->parent()->GetIndexOf(bnode); + if (browser_index == 0) { + EXPECT_EQ(gnode.GetPredecessorId(), 0); + } else { + const BookmarkNode* bprev = + bnode->parent()->GetChild(browser_index - 1); + sync_api::ReadNode gprev(trans); + ASSERT_TRUE(InitSyncNodeFromChromeNode(bprev, &gprev)); + EXPECT_EQ(gnode.GetPredecessorId(), gprev.GetId()); + EXPECT_EQ(gnode.GetParentId(), gprev.GetParentId()); + } + if (browser_index == bnode->parent()->child_count() - 1) { + EXPECT_EQ(gnode.GetSuccessorId(), 0); + } else { + const BookmarkNode* bnext = + bnode->parent()->GetChild(browser_index + 1); + sync_api::ReadNode gnext(trans); + ASSERT_TRUE(InitSyncNodeFromChromeNode(bnext, &gnext)); + EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId()); + EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId()); + } + if (bnode->child_count()) { + EXPECT_TRUE(gnode.GetFirstChildId()); + } + } + + void ExpectSyncerNodeMatching(const BookmarkNode* bnode) { + sync_api::ReadTransaction trans(test_user_share_.user_share()); + ExpectSyncerNodeMatching(&trans, bnode); + } + + void ExpectBrowserNodeMatching(sync_api::BaseTransaction* trans, + int64 sync_id) { + EXPECT_TRUE(sync_id); + const BookmarkNode* bnode = + model_associator_->GetChromeNodeFromSyncId(sync_id); + ASSERT_TRUE(bnode); + int64 id = model_associator_->GetSyncIdFromChromeId(bnode->id()); + EXPECT_EQ(id, sync_id); + ExpectSyncerNodeMatching(trans, bnode); + } + + void ExpectBrowserNodeUnknown(int64 sync_id) { + EXPECT_FALSE(model_associator_->GetChromeNodeFromSyncId(sync_id)); + } + + void ExpectBrowserNodeKnown(int64 sync_id) { + EXPECT_TRUE(model_associator_->GetChromeNodeFromSyncId(sync_id)); + } + + void ExpectSyncerNodeKnown(const BookmarkNode* node) { + int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id()); + EXPECT_NE(sync_id, sync_api::kInvalidId); + } + + void ExpectSyncerNodeUnknown(const BookmarkNode* node) { + int64 sync_id = model_associator_->GetSyncIdFromChromeId(node->id()); + EXPECT_EQ(sync_id, sync_api::kInvalidId); + } + + void ExpectBrowserNodeTitle(int64 sync_id, const std::wstring& title) { + const BookmarkNode* bnode = + model_associator_->GetChromeNodeFromSyncId(sync_id); + ASSERT_TRUE(bnode); + EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(title)); + } + + void ExpectBrowserNodeURL(int64 sync_id, const std::string& url) { + const BookmarkNode* bnode = + model_associator_->GetChromeNodeFromSyncId(sync_id); + ASSERT_TRUE(bnode); + EXPECT_EQ(GURL(url), bnode->GetURL()); + } + + void ExpectBrowserNodeParent(int64 sync_id, int64 parent_sync_id) { + const BookmarkNode* node = + model_associator_->GetChromeNodeFromSyncId(sync_id); + ASSERT_TRUE(node); + const BookmarkNode* parent = + model_associator_->GetChromeNodeFromSyncId(parent_sync_id); + EXPECT_TRUE(parent); + EXPECT_EQ(node->parent(), parent); + } + + void ExpectModelMatch(sync_api::BaseTransaction* trans) { + const BookmarkNode* root = model_->root_node(); + EXPECT_EQ(root->GetIndexOf(model_->GetBookmarkBarNode()), 0); + EXPECT_EQ(root->GetIndexOf(model_->other_node()), 1); + + std::stack<int64> stack; + stack.push(bookmark_bar_id()); + while (!stack.empty()) { + int64 id = stack.top(); + stack.pop(); + if (!id) continue; + + ExpectBrowserNodeMatching(trans, id); + + sync_api::ReadNode gnode(trans); + ASSERT_TRUE(gnode.InitByIdLookup(id)); + stack.push(gnode.GetFirstChildId()); + stack.push(gnode.GetSuccessorId()); + } + } + + void ExpectModelMatch() { + sync_api::ReadTransaction trans(test_user_share_.user_share()); + ExpectModelMatch(&trans); + } + + int64 other_bookmarks_id() { + return + model_associator_->GetSyncIdFromChromeId(model_->other_node()->id()); + } + + int64 bookmark_bar_id() { + return model_associator_->GetSyncIdFromChromeId( + model_->GetBookmarkBarNode()->id()); + } + + private: + // Used by both |ui_thread_| and |file_thread_|. + MessageLoop message_loop_; + BrowserThread ui_thread_; + // Needed by |model_|. + BrowserThread file_thread_; + + TestingProfile profile_; + scoped_ptr<TestBookmarkModelAssociator> model_associator_; + + protected: + BookmarkModel* model_; + TestUserShare test_user_share_; + scoped_ptr<BookmarkChangeProcessor> change_processor_; + StrictMock<MockUnrecoverableErrorHandler> mock_unrecoverable_error_handler_; +}; + +TEST_F(ProfileSyncServiceBookmarkTest, InitialState) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + EXPECT_TRUE(other_bookmarks_id()); + EXPECT_TRUE(bookmark_bar_id()); + + ExpectModelMatch(); +} + +TEST_F(ProfileSyncServiceBookmarkTest, BookmarkModelOperations) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + // Test addition. + const BookmarkNode* folder = + model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("foobar")); + ExpectSyncerNodeMatching(folder); + ExpectModelMatch(); + const BookmarkNode* folder2 = + model_->AddFolder(folder, 0, ASCIIToUTF16("nested")); + ExpectSyncerNodeMatching(folder2); + ExpectModelMatch(); + const BookmarkNode* url1 = model_->AddURL( + folder, 0, ASCIIToUTF16("Internets #1 Pies Site"), + GURL("http://www.easypie.com/")); + ExpectSyncerNodeMatching(url1); + ExpectModelMatch(); + const BookmarkNode* url2 = model_->AddURL( + folder, 1, ASCIIToUTF16("Airplanes"), GURL("http://www.easyjet.com/")); + ExpectSyncerNodeMatching(url2); + ExpectModelMatch(); + + // Test modification. + model_->SetTitle(url2, ASCIIToUTF16("EasyJet")); + ExpectModelMatch(); + model_->Move(url1, folder2, 0); + ExpectModelMatch(); + model_->Move(folder2, model_->GetBookmarkBarNode(), 0); + ExpectModelMatch(); + model_->SetTitle(folder2, ASCIIToUTF16("Not Nested")); + ExpectModelMatch(); + model_->Move(folder, folder2, 0); + ExpectModelMatch(); + model_->SetTitle(folder, ASCIIToUTF16("who's nested now?")); + ExpectModelMatch(); + model_->Copy(url2, model_->GetBookmarkBarNode(), 0); + ExpectModelMatch(); + + // Test deletion. + // Delete a single item. + model_->Remove(url2->parent(), url2->parent()->GetIndexOf(url2)); + ExpectModelMatch(); + // Delete an item with several children. + model_->Remove(folder2->parent(), + folder2->parent()->GetIndexOf(folder2)); + ExpectModelMatch(); +} + +TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeProcessing) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + sync_api::WriteTransaction trans(test_user_share_.user_share()); + + FakeServerChange adds(&trans); + int64 f1 = adds.AddFolder(L"Server Folder B", bookmark_bar_id(), 0); + int64 f2 = adds.AddFolder(L"Server Folder A", bookmark_bar_id(), f1); + int64 u1 = adds.AddURL(L"Some old site", "ftp://nifty.andrew.cmu.edu/", + bookmark_bar_id(), f2); + int64 u2 = adds.AddURL(L"Nifty", "ftp://nifty.andrew.cmu.edu/", f1, 0); + // u3 is a duplicate URL + int64 u3 = adds.AddURL(L"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1, u2); + // u4 is a duplicate title, different URL. + adds.AddURL(L"Some old site", "http://slog.thestranger.com/", + bookmark_bar_id(), u1); + // u5 tests an empty-string title. + std::string javascript_url( + "javascript:(function(){var w=window.open(" \ + "'about:blank','gnotesWin','location=0,menubar=0," \ + "scrollbars=0,status=0,toolbar=0,width=300," \ + "height=300,resizable');});"); + adds.AddURL(L"", javascript_url, other_bookmarks_id(), 0); + + std::vector<sync_api::SyncManager::ChangeRecord>::const_iterator it; + // The bookmark model shouldn't yet have seen any of the nodes of |adds|. + for (it = adds.changes().begin(); it != adds.changes().end(); ++it) + ExpectBrowserNodeUnknown(it->id); + + adds.ApplyPendingChanges(change_processor_.get()); + + // Make sure the bookmark model received all of the nodes in |adds|. + for (it = adds.changes().begin(); it != adds.changes().end(); ++it) + ExpectBrowserNodeMatching(&trans, it->id); + ExpectModelMatch(&trans); + + // Part two: test modifications. + FakeServerChange mods(&trans); + // Mess with u2, and move it into empty folder f2 + // TODO(ncarter): Determine if we allow ModifyURL ops or not. + /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */ + std::wstring u2_old_title = mods.ModifyTitle(u2, L"The Google"); + int64 u2_old_parent = mods.ModifyPosition(u2, f2, 0); + + // Now move f1 after u2. + std::wstring f1_old_title = mods.ModifyTitle(f1, L"Server Folder C"); + int64 f1_old_parent = mods.ModifyPosition(f1, f2, u2); + + // Then add u3 after f1. + int64 u3_old_parent = mods.ModifyPosition(u3, f2, f1); + + // Test that the property changes have not yet taken effect. + ExpectBrowserNodeTitle(u2, u2_old_title); + /* ExpectBrowserNodeURL(u2, u2_old_url); */ + ExpectBrowserNodeParent(u2, u2_old_parent); + + ExpectBrowserNodeTitle(f1, f1_old_title); + ExpectBrowserNodeParent(f1, f1_old_parent); + + ExpectBrowserNodeParent(u3, u3_old_parent); + + // Apply the changes. + mods.ApplyPendingChanges(change_processor_.get()); + + // Check for successful application. + for (it = mods.changes().begin(); it != mods.changes().end(); ++it) + ExpectBrowserNodeMatching(&trans, it->id); + ExpectModelMatch(&trans); + + // Part 3: Test URL deletion. + FakeServerChange dels(&trans); + dels.Delete(u2); + dels.Delete(u3); + + ExpectBrowserNodeKnown(u2); + ExpectBrowserNodeKnown(u3); + + dels.ApplyPendingChanges(change_processor_.get()); + + ExpectBrowserNodeUnknown(u2); + ExpectBrowserNodeUnknown(u3); + ExpectModelMatch(&trans); +} + +// Tests a specific case in ApplyModelChanges where we move the +// children out from under a parent, and then delete the parent +// in the same changelist. The delete shows up first in the changelist, +// requiring the children to be moved to a temporary location. +TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeRequiringFosterParent) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + sync_api::WriteTransaction trans(test_user_share_.user_share()); + + // Stress the immediate children of other_node because that's where + // ApplyModelChanges puts a temporary foster parent node. + std::string url("http://dev.chromium.org/"); + FakeServerChange adds(&trans); + int64 f0 = other_bookmarks_id(); // + other_node + int64 f1 = adds.AddFolder(L"f1", f0, 0); // + f1 + int64 f2 = adds.AddFolder(L"f2", f1, 0); // + f2 + int64 u3 = adds.AddURL( L"u3", url, f2, 0); // + u3 NOLINT + int64 u4 = adds.AddURL( L"u4", url, f2, u3); // + u4 NOLINT + int64 u5 = adds.AddURL( L"u5", url, f1, f2); // + u5 NOLINT + int64 f6 = adds.AddFolder(L"f6", f1, u5); // + f6 + int64 u7 = adds.AddURL( L"u7", url, f0, f1); // + u7 NOLINT + + std::vector<sync_api::SyncManager::ChangeRecord>::const_iterator it; + // The bookmark model shouldn't yet have seen any of the nodes of |adds|. + for (it = adds.changes().begin(); it != adds.changes().end(); ++it) + ExpectBrowserNodeUnknown(it->id); + + adds.ApplyPendingChanges(change_processor_.get()); + + // Make sure the bookmark model received all of the nodes in |adds|. + for (it = adds.changes().begin(); it != adds.changes().end(); ++it) + ExpectBrowserNodeMatching(&trans, it->id); + ExpectModelMatch(&trans); + + // We have to do the moves before the deletions, but FakeServerChange will + // put the deletion at the front of the changelist. + FakeServerChange ops(&trans); + ops.ModifyPosition(f6, other_bookmarks_id(), 0); + ops.ModifyPosition(u3, other_bookmarks_id(), f1); // Prev == f1 is OK here. + ops.ModifyPosition(f2, other_bookmarks_id(), u7); + ops.ModifyPosition(u7, f2, 0); + ops.ModifyPosition(u4, other_bookmarks_id(), f2); + ops.ModifyPosition(u5, f6, 0); + ops.Delete(f1); + + ops.ApplyPendingChanges(change_processor_.get()); + + ExpectModelMatch(&trans); +} + +// Simulate a server change record containing a valid but non-canonical URL. +TEST_F(ProfileSyncServiceBookmarkTest, ServerChangeWithNonCanonicalURL) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + { + sync_api::WriteTransaction trans(test_user_share_.user_share()); + + FakeServerChange adds(&trans); + std::string url("http://dev.chromium.org"); + EXPECT_NE(GURL(url).spec(), url); + adds.AddURL(L"u1", url, other_bookmarks_id(), 0); + + adds.ApplyPendingChanges(change_processor_.get()); + + EXPECT_TRUE(model_->other_node()->child_count() == 1); + ExpectModelMatch(&trans); + } + + // Now reboot the sync service, forcing a merge step. + StopSync(); + LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + // There should still be just the one bookmark. + EXPECT_TRUE(model_->other_node()->child_count() == 1); + ExpectModelMatch(); +} + +// Simulate a server change record containing an invalid URL (per GURL). +// TODO(ncarter): Disabled due to crashes. Fix bug 1677563. +TEST_F(ProfileSyncServiceBookmarkTest, DISABLED_ServerChangeWithInvalidURL) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + int child_count = 0; + { + sync_api::WriteTransaction trans(test_user_share_.user_share()); + + FakeServerChange adds(&trans); + std::string url("x"); + EXPECT_FALSE(GURL(url).is_valid()); + adds.AddURL(L"u1", url, other_bookmarks_id(), 0); + + adds.ApplyPendingChanges(change_processor_.get()); + + // We're lenient about what should happen -- the model could wind up with + // the node or without it; but things should be consistent, and we + // shouldn't crash. + child_count = model_->other_node()->child_count(); + EXPECT_TRUE(child_count == 0 || child_count == 1); + ExpectModelMatch(&trans); + } + + // Now reboot the sync service, forcing a merge step. + StopSync(); + LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + // Things ought not to have changed. + EXPECT_EQ(model_->other_node()->child_count(), child_count); + ExpectModelMatch(); +} + + +// Test strings that might pose a problem if the titles ever became used as +// file names in the sync backend. +TEST_F(ProfileSyncServiceBookmarkTest, CornerCaseNames) { + // TODO(ncarter): Bug 1570238 explains the failure of this test. + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + const char* names[] = { + // The empty string. + "", + // Illegal Windows filenames. + "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", + "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", + "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", + // Current/parent directory markers. + ".", "..", "...", + // Files created automatically by the Windows shell. + "Thumbs.db", ".DS_Store", + // Names including Win32-illegal characters, and path separators. + "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar", + "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar", + "foo[bar", + }; + // Create both folders and bookmarks using each name. + GURL url("http://www.doublemint.com"); + for (size_t i = 0; i < arraysize(names); ++i) { + model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16(names[i])); + model_->AddURL(model_->other_node(), 0, ASCIIToUTF16(names[i]), url); + } + + // Verify that the browser model matches the sync model. + EXPECT_TRUE(model_->other_node()->child_count() == 2*arraysize(names)); + ExpectModelMatch(); +} + +// Stress the internal representation of position by sparse numbers. We want +// to repeatedly bisect the range of available positions, to force the +// syncer code to renumber its ranges. Pick a number big enough so that it +// would exhaust 32bits of room between items a couple of times. +TEST_F(ProfileSyncServiceBookmarkTest, RepeatedMiddleInsertion) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + static const int kTimesToInsert = 256; + + // Create two book-end nodes to insert between. + model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("Alpha")); + model_->AddFolder(model_->other_node(), 1, ASCIIToUTF16("Omega")); + int count = 2; + + // Test insertion in first half of range by repeatedly inserting in second + // position. + for (int i = 0; i < kTimesToInsert; ++i) { + string16 title = ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i); + model_->AddFolder(model_->other_node(), 1, title); + count++; + } + + // Test insertion in second half of range by repeatedly inserting in + // second-to-last position. + for (int i = 0; i < kTimesToInsert; ++i) { + string16 title = ASCIIToUTF16("Post-insertion ") + base::IntToString16(i); + model_->AddFolder(model_->other_node(), count - 1, title); + count++; + } + + // Verify that the browser model matches the sync model. + EXPECT_EQ(model_->other_node()->child_count(), count); + ExpectModelMatch(); +} + +// Introduce a consistency violation into the model, and see that it +// puts itself into a lame, error state. +TEST_F(ProfileSyncServiceBookmarkTest, UnrecoverableErrorSuspendsService) { + EXPECT_CALL(mock_unrecoverable_error_handler_, + OnUnrecoverableError(_, _)); + + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + // Add a node which will be the target of the consistency violation. + const BookmarkNode* node = + model_->AddFolder(model_->other_node(), 0, ASCIIToUTF16("node")); + ExpectSyncerNodeMatching(node); + + // Now destroy the syncer node as if we were the ProfileSyncService without + // updating the ProfileSyncService state. This should introduce + // inconsistency between the two models. + { + sync_api::WriteTransaction trans(test_user_share_.user_share()); + sync_api::WriteNode sync_node(&trans); + ASSERT_TRUE(InitSyncNodeFromChromeNode(node, &sync_node)); + sync_node.Remove(); + } + // The models don't match at this point, but the ProfileSyncService + // doesn't know it yet. + ExpectSyncerNodeKnown(node); + + // Add a child to the inconsistent node. This should cause detection of the + // problem and the syncer should stop processing changes. + model_->AddFolder(node, 0, ASCIIToUTF16("nested")); +} + +// See what happens if we run model association when there are two exact URL +// duplicate bookmarks. The BookmarkModelAssociator should not fall over when +// this happens. +TEST_F(ProfileSyncServiceBookmarkTest, MergeDuplicates) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), + GURL("http://dup.com/")); + model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), + GURL("http://dup.com/")); + + EXPECT_EQ(2, model_->other_node()->child_count()); + + // Restart the sync service to trigger model association. + StopSync(); + StartSync(); + + EXPECT_EQ(2, model_->other_node()->child_count()); + ExpectModelMatch(); +} + +struct TestData { + const wchar_t* title; + const char* url; +}; + +// TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code +// in the bookmark model unittest, to make it simpler to set up test data +// here (and reduce the amount of duplication among tests), and to reduce the +// duplication. +class ProfileSyncServiceBookmarkTestWithData + : public ProfileSyncServiceBookmarkTest { + protected: + // Populates or compares children of the given bookmark node from/with the + // given test data array with the given size. + void PopulateFromTestData(const BookmarkNode* node, + const TestData* data, + int size); + void CompareWithTestData(const BookmarkNode* node, + const TestData* data, + int size); + + void ExpectBookmarkModelMatchesTestData(); + void WriteTestDataToBookmarkModel(); +}; + +namespace { + +// Constants for bookmark model that looks like: +// |-- Bookmark bar +// | |-- u2, http://www.u2.com/ +// | |-- f1 +// | | |-- f1u4, http://www.f1u4.com/ +// | | |-- f1u2, http://www.f1u2.com/ +// | | |-- f1u3, http://www.f1u3.com/ +// | | +-- f1u1, http://www.f1u1.com/ +// | |-- u1, http://www.u1.com/ +// | +-- f2 +// | |-- f2u2, http://www.f2u2.com/ +// | |-- f2u4, http://www.f2u4.com/ +// | |-- f2u3, http://www.f2u3.com/ +// | +-- f2u1, http://www.f2u1.com/ +// +-- Other bookmarks +// |-- f3 +// | |-- f3u4, http://www.f3u4.com/ +// | |-- f3u2, http://www.f3u2.com/ +// | |-- f3u3, http://www.f3u3.com/ +// | +-- f3u1, http://www.f3u1.com/ +// |-- u4, http://www.u4.com/ +// |-- u3, http://www.u3.com/ +// --- f4 +// | |-- f4u1, http://www.f4u1.com/ +// | |-- f4u2, http://www.f4u2.com/ +// | |-- f4u3, http://www.f4u3.com/ +// | +-- f4u4, http://www.f4u4.com/ +// |-- dup +// | +-- dupu1, http://www.dupu1.com/ +// +-- dup +// +-- dupu2, http://www.dupu1.com/ +// +static TestData kBookmarkBarChildren[] = { + { L"u2", "http://www.u2.com/" }, + { L"f1", NULL }, + { L"u1", "http://www.u1.com/" }, + { L"f2", NULL }, +}; +static TestData kF1Children[] = { + { L"f1u4", "http://www.f1u4.com/" }, + { L"f1u2", "http://www.f1u2.com/" }, + { L"f1u3", "http://www.f1u3.com/" }, + { L"f1u1", "http://www.f1u1.com/" }, +}; +static TestData kF2Children[] = { + { L"f2u2", "http://www.f2u2.com/" }, + { L"f2u4", "http://www.f2u4.com/" }, + { L"f2u3", "http://www.f2u3.com/" }, + { L"f2u1", "http://www.f2u1.com/" }, +}; + +static TestData kOtherBookmarkChildren[] = { + { L"f3", NULL }, + { L"u4", "http://www.u4.com/" }, + { L"u3", "http://www.u3.com/" }, + { L"f4", NULL }, + { L"dup", NULL }, + { L"dup", NULL }, +}; +static TestData kF3Children[] = { + { L"f3u4", "http://www.f3u4.com/" }, + { L"f3u2", "http://www.f3u2.com/" }, + { L"f3u3", "http://www.f3u3.com/" }, + { L"f3u1", "http://www.f3u1.com/" }, +}; +static TestData kF4Children[] = { + { L"f4u1", "http://www.f4u1.com/" }, + { L"f4u2", "http://www.f4u2.com/" }, + { L"f4u3", "http://www.f4u3.com/" }, + { L"f4u4", "http://www.f4u4.com/" }, +}; +static TestData kDup1Children[] = { + { L"dupu1", "http://www.dupu1.com/" }, +}; +static TestData kDup2Children[] = { + { L"dupu2", "http://www.dupu2.com/" }, +}; + +} // anonymous namespace. + +void ProfileSyncServiceBookmarkTestWithData::PopulateFromTestData( + const BookmarkNode* node, const TestData* data, int size) { + DCHECK(node); + DCHECK(data); + DCHECK(node->is_folder()); + for (int i = 0; i < size; ++i) { + const TestData& item = data[i]; + if (item.url) { + model_->AddURL(node, i, WideToUTF16Hack(item.title), GURL(item.url)); + } else { + model_->AddFolder(node, i, WideToUTF16Hack(item.title)); + } + } +} + +void ProfileSyncServiceBookmarkTestWithData::CompareWithTestData( + const BookmarkNode* node, const TestData* data, int size) { + DCHECK(node); + DCHECK(data); + DCHECK(node->is_folder()); + ASSERT_EQ(size, node->child_count()); + for (int i = 0; i < size; ++i) { + const BookmarkNode* child_node = node->GetChild(i); + const TestData& item = data[i]; + EXPECT_EQ(child_node->GetTitle(), WideToUTF16Hack(item.title)); + if (item.url) { + EXPECT_FALSE(child_node->is_folder()); + EXPECT_TRUE(child_node->is_url()); + EXPECT_EQ(child_node->GetURL(), GURL(item.url)); + } else { + EXPECT_TRUE(child_node->is_folder()); + EXPECT_FALSE(child_node->is_url()); + } + } +} + +// TODO(munjal): We should implement some way of generating random data and can +// use the same seed to generate the same sequence. +void ProfileSyncServiceBookmarkTestWithData::WriteTestDataToBookmarkModel() { + const BookmarkNode* bookmarks_bar_node = model_->GetBookmarkBarNode(); + PopulateFromTestData(bookmarks_bar_node, + kBookmarkBarChildren, + arraysize(kBookmarkBarChildren)); + + ASSERT_GE(bookmarks_bar_node->child_count(), 4); + const BookmarkNode* f1_node = bookmarks_bar_node->GetChild(1); + PopulateFromTestData(f1_node, kF1Children, arraysize(kF1Children)); + const BookmarkNode* f2_node = bookmarks_bar_node->GetChild(3); + PopulateFromTestData(f2_node, kF2Children, arraysize(kF2Children)); + + const BookmarkNode* other_bookmarks_node = model_->other_node(); + PopulateFromTestData(other_bookmarks_node, + kOtherBookmarkChildren, + arraysize(kOtherBookmarkChildren)); + + ASSERT_GE(other_bookmarks_node->child_count(), 6); + const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); + PopulateFromTestData(f3_node, kF3Children, arraysize(kF3Children)); + const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); + PopulateFromTestData(f4_node, kF4Children, arraysize(kF4Children)); + const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); + PopulateFromTestData(dup_node, kDup1Children, arraysize(kDup1Children)); + dup_node = other_bookmarks_node->GetChild(5); + PopulateFromTestData(dup_node, kDup2Children, arraysize(kDup2Children)); + + ExpectBookmarkModelMatchesTestData(); +} + +void ProfileSyncServiceBookmarkTestWithData:: + ExpectBookmarkModelMatchesTestData() { + const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); + CompareWithTestData(bookmark_bar_node, + kBookmarkBarChildren, + arraysize(kBookmarkBarChildren)); + + ASSERT_GE(bookmark_bar_node->child_count(), 4); + const BookmarkNode* f1_node = bookmark_bar_node->GetChild(1); + CompareWithTestData(f1_node, kF1Children, arraysize(kF1Children)); + const BookmarkNode* f2_node = bookmark_bar_node->GetChild(3); + CompareWithTestData(f2_node, kF2Children, arraysize(kF2Children)); + + const BookmarkNode* other_bookmarks_node = model_->other_node(); + CompareWithTestData(other_bookmarks_node, + kOtherBookmarkChildren, + arraysize(kOtherBookmarkChildren)); + + ASSERT_GE(other_bookmarks_node->child_count(), 6); + const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); + CompareWithTestData(f3_node, kF3Children, arraysize(kF3Children)); + const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); + CompareWithTestData(f4_node, kF4Children, arraysize(kF4Children)); + const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); + CompareWithTestData(dup_node, kDup1Children, arraysize(kDup1Children)); + dup_node = other_bookmarks_node->GetChild(5); + CompareWithTestData(dup_node, kDup2Children, arraysize(kDup2Children)); +} + +// Tests persistence of the profile sync service by unloading the +// database and then reloading it from disk. +TEST_F(ProfileSyncServiceBookmarkTestWithData, Persistence) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + WriteTestDataToBookmarkModel(); + + ExpectModelMatch(); + + // Force both models to discard their data and reload from disk. This + // simulates what would happen if the browser were to shutdown normally, + // and then relaunch. + StopSync(); + UnloadBookmarkModel(); + LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + ExpectBookmarkModelMatchesTestData(); + + // With the BookmarkModel contents verified, ExpectModelMatch will + // verify the contents of the sync model. + ExpectModelMatch(); +} + +// Tests the merge case when the BookmarkModel is non-empty but the +// sync model is empty. This corresponds to uploading browser +// bookmarks to an initially empty, new account. +TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptySyncModel) { + // Don't start the sync service until we've populated the bookmark model. + LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); + + WriteTestDataToBookmarkModel(); + + // Restart sync. This should trigger a merge step during + // initialization -- we expect the browser bookmarks to be written + // to the sync service during this call. + StartSync(); + + // Verify that the bookmark model hasn't changed, and that the sync model + // matches it exactly. + ExpectBookmarkModelMatchesTestData(); + ExpectModelMatch(); +} + +// Tests the merge case when the BookmarkModel is empty but the sync model is +// non-empty. This corresponds (somewhat) to a clean install of the browser, +// with no bookmarks, connecting to a sync account that has some bookmarks. +TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeWithEmptyBookmarkModel) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + WriteTestDataToBookmarkModel(); + + ExpectModelMatch(); + + // Force the databse to unload and write itself to disk. + StopSync(); + + // Blow away the bookmark model -- it should be empty afterwards. + UnloadBookmarkModel(); + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + EXPECT_EQ(model_->GetBookmarkBarNode()->child_count(), 0); + EXPECT_EQ(model_->other_node()->child_count(), 0); + + // Now restart the sync service. Starting it should populate the bookmark + // model -- test for consistency. + StartSync(); + ExpectBookmarkModelMatchesTestData(); + ExpectModelMatch(); +} + +// Tests the merge cases when both the models are expected to be identical +// after the merge. +TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeExpectedIdenticalModels) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); + StartSync(); + WriteTestDataToBookmarkModel(); + ExpectModelMatch(); + StopSync(); + UnloadBookmarkModel(); + + // At this point both the bookmark model and the server should have the + // exact same data and it should match the test data. + LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + ExpectBookmarkModelMatchesTestData(); + ExpectModelMatch(); + StopSync(); + UnloadBookmarkModel(); + + // Now reorder some bookmarks in the bookmark model and then merge. Make + // sure we get the order of the server after merge. + LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); + ExpectBookmarkModelMatchesTestData(); + const BookmarkNode* bookmark_bar = model_->GetBookmarkBarNode(); + ASSERT_TRUE(bookmark_bar); + ASSERT_GT(bookmark_bar->child_count(), 1); + model_->Move(bookmark_bar->GetChild(0), bookmark_bar, 1); + StartSync(); + ExpectModelMatch(); + ExpectBookmarkModelMatchesTestData(); +} + +// Tests the merge cases when both the models are expected to be identical +// after the merge. +TEST_F(ProfileSyncServiceBookmarkTestWithData, MergeModelsWithSomeExtras) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + WriteTestDataToBookmarkModel(); + ExpectBookmarkModelMatchesTestData(); + + // Remove some nodes and reorder some nodes. + const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); + int remove_index = 2; + ASSERT_GT(bookmark_bar_node->child_count(), remove_index); + const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index); + ASSERT_TRUE(child_node); + ASSERT_TRUE(child_node->is_url()); + model_->Remove(bookmark_bar_node, remove_index); + ASSERT_GT(bookmark_bar_node->child_count(), remove_index); + child_node = bookmark_bar_node->GetChild(remove_index); + ASSERT_TRUE(child_node); + ASSERT_TRUE(child_node->is_folder()); + model_->Remove(bookmark_bar_node, remove_index); + + const BookmarkNode* other_node = model_->other_node(); + ASSERT_GE(other_node->child_count(), 1); + const BookmarkNode* f3_node = other_node->GetChild(0); + ASSERT_TRUE(f3_node); + ASSERT_TRUE(f3_node->is_folder()); + remove_index = 2; + ASSERT_GT(f3_node->child_count(), remove_index); + model_->Remove(f3_node, remove_index); + ASSERT_GT(f3_node->child_count(), remove_index); + model_->Remove(f3_node, remove_index); + + StartSync(); + ExpectModelMatch(); + StopSync(); + + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + WriteTestDataToBookmarkModel(); + ExpectBookmarkModelMatchesTestData(); + + // Remove some nodes and reorder some nodes. + bookmark_bar_node = model_->GetBookmarkBarNode(); + remove_index = 0; + ASSERT_GT(bookmark_bar_node->child_count(), remove_index); + child_node = bookmark_bar_node->GetChild(remove_index); + ASSERT_TRUE(child_node); + ASSERT_TRUE(child_node->is_url()); + model_->Remove(bookmark_bar_node, remove_index); + ASSERT_GT(bookmark_bar_node->child_count(), remove_index); + child_node = bookmark_bar_node->GetChild(remove_index); + ASSERT_TRUE(child_node); + ASSERT_TRUE(child_node->is_folder()); + model_->Remove(bookmark_bar_node, remove_index); + + ASSERT_GE(bookmark_bar_node->child_count(), 2); + model_->Move(bookmark_bar_node->GetChild(0), bookmark_bar_node, 1); + + other_node = model_->other_node(); + ASSERT_GE(other_node->child_count(), 1); + f3_node = other_node->GetChild(0); + ASSERT_TRUE(f3_node); + ASSERT_TRUE(f3_node->is_folder()); + remove_index = 0; + ASSERT_GT(f3_node->child_count(), remove_index); + model_->Remove(f3_node, remove_index); + ASSERT_GT(f3_node->child_count(), remove_index); + model_->Remove(f3_node, remove_index); + + ASSERT_GE(other_node->child_count(), 4); + model_->Move(other_node->GetChild(0), other_node, 1); + model_->Move(other_node->GetChild(2), other_node, 3); + + StartSync(); + ExpectModelMatch(); + + // After the merge, the model should match the test data. + ExpectBookmarkModelMatchesTestData(); +} + +// Tests that when persisted model associations are used, things work fine. +TEST_F(ProfileSyncServiceBookmarkTestWithData, ModelAssociationPersistence) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + WriteTestDataToBookmarkModel(); + StartSync(); + ExpectModelMatch(); + // Force sync to shut down and write itself to disk. + StopSync(); + // Now restart sync. This time it should use the persistent + // associations. + StartSync(); + ExpectModelMatch(); +} + +// Tests that when persisted model associations are used, things work fine. +TEST_F(ProfileSyncServiceBookmarkTestWithData, + ModelAssociationInvalidPersistence) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + WriteTestDataToBookmarkModel(); + StartSync(); + ExpectModelMatch(); + // Force sync to shut down and write itself to disk. + StopSync(); + // Change the bookmark model before restarting sync service to simulate + // the situation where bookmark model is different from sync model and + // make sure model associator correctly rebuilds associations. + const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); + model_->AddURL(bookmark_bar_node, 0, ASCIIToUTF16("xtra"), + GURL("http://www.xtra.com")); + // Now restart sync. This time it will try to use the persistent + // associations and realize that they are invalid and hence will rebuild + // associations. + StartSync(); + ExpectModelMatch(); +} + +TEST_F(ProfileSyncServiceBookmarkTestWithData, SortChildren) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); + StartSync(); + + // Write test data to bookmark model and verify that the models match. + WriteTestDataToBookmarkModel(); + const BookmarkNode* folder_added = model_->other_node()->GetChild(0); + ASSERT_TRUE(folder_added); + ASSERT_TRUE(folder_added->is_folder()); + + ExpectModelMatch(); + + // Sort the other-bookmarks children and expect that hte models match. + model_->SortChildren(folder_added); + ExpectModelMatch(); +} + +// See what happens if we enable sync but then delete the "Sync Data" +// folder. +TEST_F(ProfileSyncServiceBookmarkTestWithData, + RecoverAfterDeletingSyncDataDirectory) { + LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); + StartSync(); + + WriteTestDataToBookmarkModel(); + + StopSync(); + + // Nuke the sync DB and reload. + test_user_share_.TearDown(); + test_user_share_.SetUp(); + + StartSync(); + + // Make sure we're back in sync. In real life, the user would need + // to reauthenticate before this happens, but in the test, authentication + // is sidestepped. + ExpectBookmarkModelMatchesTestData(); + ExpectModelMatch(); +} + +} // namespace + +} // namespace browser_sync diff --git a/chrome/browser/sync/profile_sync_service_harness.cc b/chrome/browser/sync/profile_sync_service_harness.cc index 72bb584..ae13c6d 100644 --- a/chrome/browser/sync/profile_sync_service_harness.cc +++ b/chrome/browser/sync/profile_sync_service_harness.cc @@ -1,20 +1,26 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/profile_sync_service_harness.h" +#include <stddef.h> #include <algorithm> +#include <iterator> +#include <ostream> +#include <set> #include <vector> +#include "base/logging.h" +#include "base/memory/ref_counted.h" #include "base/message_loop.h" +#include "base/task.h" +#include "base/tracked.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/net/gaia/token_service.h" -#include "chrome/browser/sync/glue/sync_backend_host.h" #include "chrome/browser/sync/sessions/session_state.h" -#include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/net/gaia/google_service_auth_error.h" -#include "chrome/common/notification_source.h" +#include "chrome/browser/sync/signin_manager.h" + +using browser_sync::sessions::SyncSessionSnapshot; // The amount of time for which we wait for a live sync operation to complete. static const int kLiveSyncOperationTimeoutMs = 45000; @@ -160,28 +166,8 @@ bool ProfileSyncServiceHarness::SetupSync( (syncable::MODEL_TYPE_COUNT - syncable::FIRST_REAL_MODEL_TYPE)); service()->OnUserChoseDatatypes(sync_everything, synced_datatypes); - // Wait for a passphrase to be required. - DCHECK_EQ(wait_state_, WAITING_FOR_PASSPHRASE_REQUIRED); - if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, - "Waiting for Passphrase required.")) { - LOG(ERROR) << "Passphrase required not seen after " - << kLiveSyncOperationTimeoutMs / 1000 - << " seconds."; - return false; - } - - // Wait for initial gaia passphrase to be accepted. - DCHECK_EQ(wait_state_, WAITING_FOR_PASSPHRASE_ACCEPTED); - if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, - "Waiting for Passphrase accept.")) { - LOG(ERROR) << "Passphrase accept not seen after " - << kLiveSyncOperationTimeoutMs / 1000 - << " seconds."; - return false; - } - - DCHECK_EQ(wait_state_, WAITING_FOR_INITIAL_SYNC); // Wait for initial sync cycle to be completed. + DCHECK_EQ(wait_state_, WAITING_FOR_INITIAL_SYNC); if (!AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, "Waiting for initial sync cycle to complete.")) { LOG(ERROR) << "Initial sync cycle did not complete after " @@ -212,27 +198,9 @@ bool ProfileSyncServiceHarness::RunStateChangeMachine() { case WAITING_FOR_ON_BACKEND_INITIALIZED: { LogClientInfo("WAITING_FOR_ON_BACKEND_INITIALIZED"); if (service()->sync_initialized()) { - // The sync backend is initialized. We now wait for passphrase events. - SignalStateCompleteWithNextState(WAITING_FOR_PASSPHRASE_REQUIRED); - } - break; - } - case WAITING_FOR_PASSPHRASE_REQUIRED: { - LogClientInfo("WAITING_FOR_PASSPHRASE_REQUIRED"); - if (service()->observed_passphrase_required()) { - // Special case when the first client signs in to sync. - if (id_ == 0) - DCHECK(!service()->passphrase_required_for_decryption()); - // The SYNC_PASSPHRASE_REQUIRED notification has been seen. - SignalStateCompleteWithNextState(WAITING_FOR_PASSPHRASE_ACCEPTED); - } - break; - } - case WAITING_FOR_PASSPHRASE_ACCEPTED: { - LogClientInfo("WAITING_FOR_PASSPHRASE_ACCEPTED"); - if (service()->ShouldPushChanges()) - // The SYNC_PASSPHRASE_ACCEPTED notification has been seen. + // The sync backend is initialized. SignalStateCompleteWithNextState(WAITING_FOR_INITIAL_SYNC); + } break; } case WAITING_FOR_INITIAL_SYNC: { @@ -270,6 +238,25 @@ bool ProfileSyncServiceHarness::RunStateChangeMachine() { SignalStateCompleteWithNextState(FULLY_SYNCED); break; } + case WAITING_FOR_PASSPHRASE_ACCEPTED: { + LogClientInfo("WAITING_FOR_PASSPHRASE_ACCEPTED"); + // TODO(atwilson): After ProfileSyncService::OnPassphraseAccepted() is + // fixed, add an extra check to make sure that the value of + // service()->observed_passphrase_required() is false. + if (service()->ShouldPushChanges()) { + // The passphrase has been accepted, and sync has been restarted. + SignalStateCompleteWithNextState(FULLY_SYNCED); + } + break; + } + case WAITING_FOR_ENCRYPTION: { + LogClientInfo("WAITING_FOR_ENCRYPTION"); + if (IsTypeEncrypted(waiting_for_encryption_type_)) { + // Encryption is complete for the type we are waiting on. + SignalStateCompleteWithNextState(FULLY_SYNCED); + } + break; + } case SERVER_UNREACHABLE: { LogClientInfo("SERVER_UNREACHABLE"); if (GetStatus().server_reachable) { @@ -289,14 +276,6 @@ bool ProfileSyncServiceHarness::RunStateChangeMachine() { LogClientInfo("SYNC_DISABLED"); break; } - case WAITING_FOR_ENCRYPTION: { - // If the type whose encryption we are waiting for is now complete, there - // is nothing to do. - LogClientInfo("WAITING_FOR_ENCRYPTION"); - if (IsTypeEncrypted(waiting_for_encryption_type_)) - SignalStateCompleteWithNextState(FULLY_SYNCED); - break; - } default: // Invalid state during observer callback which may be triggered by other // classes using the the UI message loop. Defer to their handling. @@ -315,6 +294,15 @@ bool ProfileSyncServiceHarness::AwaitPassphraseAccepted() { LOG(ERROR) << "Sync disabled for Client " << id_ << "."; return false; } + + // TODO(atwilson): After ProfileSyncService::OnPassphraseAccepted() is + // fixed, add an extra check to make sure that the value of + // service()->observed_passphrase_required() is false. + if (service()->ShouldPushChanges()) { + // Passphrase is already accepted; don't wait. + return true; + } + wait_state_ = WAITING_FOR_PASSPHRASE_ACCEPTED; return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, "Waiting for passphrase accepted."); @@ -327,31 +315,32 @@ bool ProfileSyncServiceHarness::AwaitSyncCycleCompletion( LOG(ERROR) << "Sync disabled for Client " << id_ << "."; return false; } - if (!IsSynced()) { - if (wait_state_ == SERVER_UNREACHABLE) { - // Client was offline; wait for it to go online, and then wait for sync. - AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); - DCHECK_EQ(wait_state_, WAITING_FOR_SYNC_TO_FINISH); - return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); - } else { - DCHECK(service()->sync_initialized()); - wait_state_ = WAITING_FOR_SYNC_TO_FINISH; - AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); - if (wait_state_ == FULLY_SYNCED) { - // Client is online; sync was successful. - return true; - } else if (wait_state_ == SERVER_UNREACHABLE) { - // Client is offline; sync was unsuccessful. - return false; - } else { - LOG(ERROR) << "Invalid wait state:" << wait_state_; - return false; - } - } - } else { + + if (IsSynced()) { // Client is already synced; don't wait. return true; } + + if (wait_state_ == SERVER_UNREACHABLE) { + // Client was offline; wait for it to go online, and then wait for sync. + AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); + DCHECK_EQ(wait_state_, WAITING_FOR_SYNC_TO_FINISH); + return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); + } else { + DCHECK(service()->sync_initialized()); + wait_state_ = WAITING_FOR_SYNC_TO_FINISH; + AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); + if (wait_state_ == FULLY_SYNCED) { + // Client is online; sync was successful. + return true; + } else if (wait_state_ == SERVER_UNREACHABLE) { + // Client is offline; sync was unsuccessful. + return false; + } else { + LOG(ERROR) << "Invalid wait state:" << wait_state_; + return false; + } + } } bool ProfileSyncServiceHarness::AwaitMutualSyncCycleCompletion( @@ -401,10 +390,13 @@ bool ProfileSyncServiceHarness::WaitUntilTimestampMatches( LOG(ERROR) << "Sync disabled for Client " << id_ << "."; return false; } - DCHECK(!timestamp_match_partner_); - if (MatchesOtherClient(partner)) + + if (MatchesOtherClient(partner)) { + // Timestamps already match; don't wait. return true; + } + DCHECK(!timestamp_match_partner_); timestamp_match_partner_ = partner; partner->service()->AddObserver(this); wait_state_ = WAITING_FOR_UPDATES; @@ -580,7 +572,7 @@ std::string ProfileSyncServiceHarness::GetUpdatedTimestamp( return snap->download_progress_markers[model_type]; } -void ProfileSyncServiceHarness::LogClientInfo(std::string message) { +void ProfileSyncServiceHarness::LogClientInfo(const std::string& message) { if (service()) { const SyncSessionSnapshot* snap = GetLastSessionSnapshot(); if (snap) { diff --git a/chrome/browser/sync/profile_sync_service_harness.h b/chrome/browser/sync/profile_sync_service_harness.h index d895e6a..c4470aa 100644 --- a/chrome/browser/sync/profile_sync_service_harness.h +++ b/chrome/browser/sync/profile_sync_service_harness.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,13 +9,19 @@ #include <string> #include <vector> -#include "base/time.h" +#include "base/basictypes.h" #include "chrome/browser/sync/profile_sync_service.h" - -using browser_sync::sessions::SyncSessionSnapshot; +#include "chrome/browser/sync/profile_sync_service_observer.h" +#include "chrome/browser/sync/syncable/model_type.h" class Profile; +namespace browser_sync { + namespace sessions { + struct SyncSessionSnapshot; + } +} + // An instance of this class is basically our notion of a "sync client" for // automation purposes. It harnesses the ProfileSyncService member of the // profile passed to it on construction and automates certain things like setup @@ -87,7 +93,7 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { std::vector<ProfileSyncServiceHarness*>& clients); // If a SetPassphrase call has been issued with a valid passphrase, this - // will wait until the Cryptographer broadcasts SYNC_PASSPHRASE_ACCEPTED. + // will wait until the passphrase has been accepted. bool AwaitPassphraseAccepted(); // Returns the ProfileSyncService member of the the sync client. @@ -112,7 +118,8 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { void DisableSyncForAllDatatypes(); // Returns a snapshot of the current sync session. - const SyncSessionSnapshot* GetLastSessionSnapshot() const; + const browser_sync::sessions::SyncSessionSnapshot* + GetLastSessionSnapshot() const; // Encrypt the datatype |type|. This method will block while the sync backend // host performs the encryption or a timeout is reached. Returns false if @@ -135,12 +142,6 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { // The sync client awaits the OnBackendInitialized() callback. WAITING_FOR_ON_BACKEND_INITIALIZED, - // Waiting for a passphrase to be required. - WAITING_FOR_PASSPHRASE_REQUIRED, - - // Waiting for a set passphrase to be accepted by the cryptographer. - WAITING_FOR_PASSPHRASE_ACCEPTED, - // The sync client is waiting for the first sync cycle to complete. WAITING_FOR_INITIAL_SYNC, @@ -150,6 +151,10 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { // The sync client anticipates incoming updates leading to a new sync cycle. WAITING_FOR_UPDATES, + // The sync client is waiting for its passphrase to be accepted by the + // cryptographer. + WAITING_FOR_PASSPHRASE_ACCEPTED, + // The sync client anticipates encryption of new datatypes. WAITING_FOR_ENCRYPTION, @@ -187,7 +192,7 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { bool MatchesOtherClient(ProfileSyncServiceHarness* partner); // Logs message with relevant info about client's sync state (if available). - void LogClientInfo(std::string message); + void LogClientInfo(const std::string& message); // Gets the current progress indicator of the current sync session // for a particular datatype. diff --git a/chrome/browser/sync/profile_sync_service_mock.cc b/chrome/browser/sync/profile_sync_service_mock.cc index bf46922..36b3186 100644 --- a/chrome/browser/sync/profile_sync_service_mock.cc +++ b/chrome/browser/sync/profile_sync_service_mock.cc @@ -6,7 +6,9 @@ #include "chrome/browser/ui/webui/chrome_url_data_manager.h" #include "chrome/common/url_constants.h" -ProfileSyncServiceMock::ProfileSyncServiceMock() {} +ProfileSyncServiceMock::ProfileSyncServiceMock() + : ProfileSyncService(NULL, NULL, "") { +} ProfileSyncServiceMock::~ProfileSyncServiceMock() { } diff --git a/chrome/browser/sync/profile_sync_service_mock.h b/chrome/browser/sync/profile_sync_service_mock.h index 0727ebe..5237897 100644 --- a/chrome/browser/sync/profile_sync_service_mock.h +++ b/chrome/browser/sync/profile_sync_service_mock.h @@ -53,6 +53,8 @@ class ProfileSyncServiceMock : public ProfileSyncService { void(syncable::ModelTypeSet* preferred_types)); MOCK_CONST_METHOD1(GetRegisteredDataTypes, void(syncable::ModelTypeSet* registered_types)); + MOCK_CONST_METHOD0(GetLastSessionSnapshot, + const browser_sync::sessions::SyncSessionSnapshot*()); MOCK_METHOD0(QueryDetailedSyncStatus, browser_sync::SyncBackendHost::Status()); diff --git a/chrome/browser/sync/profile_sync_service_password_unittest.cc b/chrome/browser/sync/profile_sync_service_password_unittest.cc index 0856f5a..16cb99e 100644 --- a/chrome/browser/sync/profile_sync_service_password_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_password_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,7 +6,9 @@ #include "testing/gtest/include/gtest/gtest.h" +#include "base/synchronization/waitable_event.h" #include "base/task.h" +#include "base/test/test_timeouts.h" #include "base/time.h" #include "base/utf_string_conversions.h" #include "chrome/browser/password_manager/password_store.h" @@ -28,6 +30,7 @@ #include "chrome/common/pref_names.h" #include "chrome/test/sync/engine/test_id_factory.h" #include "chrome/test/profile_mock.h" +#include "content/browser/browser_thread.h" #include "content/common/notification_observer_mock.h" #include "content/common/notification_source.h" #include "content/common/notification_type.h" @@ -58,11 +61,13 @@ using syncable::UNIQUE_SERVER_TAG; using syncable::UNITTEST; using syncable::WriteTransaction; using testing::_; +using testing::AtLeast; using testing::DoAll; using testing::DoDefault; using testing::ElementsAre; using testing::Eq; using testing::Invoke; +using testing::InvokeWithoutArgs; using testing::Return; using testing::SaveArg; using testing::SetArgumentPointee; @@ -78,6 +83,17 @@ ACTION_P3(MakePasswordSyncComponents, service, ps, dtc) { change_processor); } +ACTION_P(AcquireSyncTransaction, password_test_service) { + // Check to make sure we can aquire a transaction (will crash if a transaction + // is already held by this thread, deadlock if held by another thread). + sync_api::WriteTransaction trans(password_test_service->GetUserShare()); + VLOG(1) << "Sync transaction acquired."; +} + +static void QuitMessageLoop() { + MessageLoop::current()->Quit(); +} + class MockPasswordStore : public PasswordStore { public: MOCK_METHOD1(RemoveLogin, void(const PasswordForm&)); @@ -115,17 +131,12 @@ class PasswordTestProfileSyncService : public TestProfileSyncService { virtual ~PasswordTestProfileSyncService() {} - virtual void OnPassphraseRequired(bool for_decryption) { - TestProfileSyncService::OnPassphraseRequired(for_decryption); - ADD_FAILURE(); - } - virtual void OnPassphraseAccepted() { - TestProfileSyncService::OnPassphraseAccepted(); - if (passphrase_accept_task_) { passphrase_accept_task_->Run(); } + + TestProfileSyncService::OnPassphraseAccepted(); } private: @@ -133,12 +144,17 @@ class PasswordTestProfileSyncService : public TestProfileSyncService { }; class ProfileSyncServicePasswordTest : public AbstractProfileSyncServiceTest { + public: + sync_api::UserShare* GetUserShare() { + return service_->GetUserShare(); + } protected: ProfileSyncServicePasswordTest() : db_thread_(BrowserThread::DB) { } virtual void SetUp() { + profile_.CreateRequestContext(); password_store_ = new MockPasswordStore(); db_thread_.Start(); @@ -147,38 +163,53 @@ class ProfileSyncServicePasswordTest : public AbstractProfileSyncServiceTest { registrar_.Add(&observer_, NotificationType::SYNC_CONFIGURE_DONE, NotificationService::AllSources()); + registrar_.Add(&observer_, + NotificationType::SYNC_CONFIGURE_BLOCKED, + NotificationService::AllSources()); } virtual void TearDown() { service_.reset(); notification_service_->TearDown(); db_thread_.Stop(); + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile_.ResetRequestContext(); + } MessageLoop::current()->RunAllPending(); } - void StartSyncService(Task* root_task, Task* node_task) { - StartSyncService(root_task, node_task, 2, 2); + static void SignalEvent(base::WaitableEvent* done) { + done->Signal(); + } + + void FlushLastDBTask() { + base::WaitableEvent done(false, false); + BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, + NewRunnableFunction(&ProfileSyncServicePasswordTest::SignalEvent, + &done)); + done.TimedWait(base::TimeDelta::FromMilliseconds( + TestTimeouts::action_timeout_ms())); } - void StartSyncService(Task* root_task, Task* node_task, - int num_resume_expectations, - int num_pause_expectations) { + void StartSyncService(Task* root_task, Task* node_task) { if (!service_.get()) { service_.reset(new PasswordTestProfileSyncService( &factory_, &profile_, "test_user", false, root_task, node_task)); service_->RegisterPreferences(); profile_.GetPrefs()->SetBoolean(prefs::kSyncPasswords, true); - service_->set_num_expected_resumes(num_resume_expectations); - service_->set_num_expected_pauses(num_pause_expectations); PasswordDataTypeController* data_type_controller = new PasswordDataTypeController(&factory_, &profile_, service_.get()); EXPECT_CALL(factory_, CreatePasswordSyncComponents(_, _, _)). - WillOnce(MakePasswordSyncComponents(service_.get(), - password_store_.get(), - data_type_controller)); + Times(AtLeast(1)). // Can be more if we hit NEEDS_CRYPTO. + WillRepeatedly(MakePasswordSyncComponents(service_.get(), + password_store_.get(), + data_type_controller)); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillOnce(ReturnNewDataTypeManager()); @@ -190,23 +221,23 @@ class ProfileSyncServicePasswordTest : public AbstractProfileSyncServiceTest { WillRepeatedly(Return(&token_service_)); EXPECT_CALL(profile_, GetPasswordStore(_)). - Times(3). + Times(AtLeast(2)). // Can be more if we hit NEEDS_CRYPTO. WillRepeatedly(Return(password_store_.get())); EXPECT_CALL(observer_, Observe( NotificationType(NotificationType::SYNC_CONFIGURE_DONE),_,_)); + EXPECT_CALL(observer_, + Observe( + NotificationType( + NotificationType::SYNC_CONFIGURE_BLOCKED),_,_)) + .WillOnce(InvokeWithoutArgs(QuitMessageLoop)); service_->RegisterDataTypeController(data_type_controller); service_->Initialize(); MessageLoop::current()->Run(); + FlushLastDBTask(); - EXPECT_CALL( - observer_, - Observe( - NotificationType(NotificationType::SYNC_CONFIGURE_DONE), - _,_)). - WillOnce(QuitUIMessageLoop()); service_->SetPassphrase("foo", false, true); MessageLoop::current()->Run(); } @@ -276,7 +307,6 @@ class ProfileSyncServicePasswordTest : public AbstractProfileSyncServiceTest { ProfileMock profile_; scoped_refptr<MockPasswordStore> password_store_; NotificationRegistrar registrar_; - }; class AddPasswordEntriesTask : public Task { @@ -298,7 +328,7 @@ class AddPasswordEntriesTask : public Task { }; TEST_F(ProfileSyncServicePasswordTest, FailModelAssociation) { - StartSyncService(NULL, NULL, 1, 2); + StartSyncService(NULL, NULL); EXPECT_TRUE(service_->unrecoverable_error_detected()); } @@ -457,6 +487,71 @@ TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncNoMerge) { EXPECT_TRUE(ComparePasswords(expected_forms[1], new_sync_forms[1])); } +// Same as HasNativeHasEmptyNoMerge, but we attempt to aquire a sync transaction +// every time the password store is accessed. +TEST_F(ProfileSyncServicePasswordTest, EnsureNoTransactions) { + std::vector<PasswordForm*> native_forms; + std::vector<PasswordForm> sync_forms; + std::vector<PasswordForm> expected_forms; + { + PasswordForm* new_form = new PasswordForm; + new_form->scheme = PasswordForm::SCHEME_HTML; + new_form->signon_realm = "pie"; + new_form->origin = GURL("http://pie.com"); + new_form->action = GURL("http://pie.com/submit"); + new_form->username_element = UTF8ToUTF16("name"); + new_form->username_value = UTF8ToUTF16("tom"); + new_form->password_element = UTF8ToUTF16("cork"); + new_form->password_value = UTF8ToUTF16("password1"); + new_form->ssl_valid = true; + new_form->preferred = false; + new_form->date_created = base::Time::FromInternalValue(1234); + new_form->blacklisted_by_user = false; + + native_forms.push_back(new_form); + expected_forms.push_back(*new_form); + } + + { + PasswordForm new_form; + new_form.scheme = PasswordForm::SCHEME_HTML; + new_form.signon_realm = "pie2"; + new_form.origin = GURL("http://pie2.com"); + new_form.action = GURL("http://pie2.com/submit"); + new_form.username_element = UTF8ToUTF16("name2"); + new_form.username_value = UTF8ToUTF16("tom2"); + new_form.password_element = UTF8ToUTF16("cork2"); + new_form.password_value = UTF8ToUTF16("password12"); + new_form.ssl_valid = false; + new_form.preferred = true; + new_form.date_created = base::Time::FromInternalValue(12345); + new_form.blacklisted_by_user = false; + sync_forms.push_back(new_form); + expected_forms.push_back(new_form); + } + + EXPECT_CALL(*password_store_, FillAutofillableLogins(_)) + .WillOnce(DoAll(SetArgumentPointee<0>(native_forms), + AcquireSyncTransaction(this), + Return(true))); + EXPECT_CALL(*password_store_, FillBlacklistLogins(_)) + .WillOnce(DoAll(AcquireSyncTransaction(this), + Return(true))); + EXPECT_CALL(*password_store_, AddLoginImpl(_)) + .WillOnce(AcquireSyncTransaction(this)); + + CreateRootTask root_task(this, syncable::PASSWORDS); + AddPasswordEntriesTask node_task(this, sync_forms); + StartSyncService(&root_task, &node_task); + + std::vector<PasswordForm> new_sync_forms; + GetPasswordEntriesFromSyncDB(&new_sync_forms); + + EXPECT_EQ(2U, new_sync_forms.size()); + EXPECT_TRUE(ComparePasswords(expected_forms[0], new_sync_forms[0])); + EXPECT_TRUE(ComparePasswords(expected_forms[1], new_sync_forms[1])); +} + TEST_F(ProfileSyncServicePasswordTest, HasNativeHasSyncMergeEntry) { std::vector<PasswordForm*> native_forms; std::vector<PasswordForm> sync_forms; diff --git a/chrome/browser/sync/profile_sync_service_preference_unittest.cc b/chrome/browser/sync/profile_sync_service_preference_unittest.cc index 8c44e17..4a99ba6 100644 --- a/chrome/browser/sync/profile_sync_service_preference_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_preference_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,6 +9,7 @@ #include "base/stl_util-inl.h" #include "base/string_piece.h" #include "base/task.h" +#include "chrome/browser/prefs/scoped_user_pref_update.h" #include "chrome/browser/sync/abstract_profile_sync_service_test.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/preference_change_processor.h" @@ -20,10 +21,10 @@ #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/test_profile_sync_service.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/json_value_serializer.h" #include "chrome/common/pref_names.h" #include "chrome/test/testing_pref_service.h" #include "chrome/test/testing_profile.h" +#include "content/common/json_value_serializer.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -51,7 +52,7 @@ class ProfileSyncServicePreferenceTest virtual void SetUp() { profile_.reset(new TestingProfile()); - profile_->set_has_history_service(true); + profile_->CreateRequestContext(); prefs_ = profile_->GetTestingPrefService(); prefs_->RegisterStringPref(not_synced_preference_name_.c_str(), @@ -60,7 +61,12 @@ class ProfileSyncServicePreferenceTest virtual void TearDown() { service_.reset(); - profile_.reset(); + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile_.reset(); + } MessageLoop::current()->RunAllPending(); } @@ -83,10 +89,9 @@ class ProfileSyncServicePreferenceTest EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillOnce(ReturnNewDataTypeManager()); - service_->set_num_expected_resumes(will_fail_association ? 0 : 1); - service_->RegisterDataTypeController( new PreferenceDataTypeController(&factory_, + profile_.get(), service_.get())); profile_->GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "token"); @@ -119,12 +124,12 @@ class ProfileSyncServicePreferenceTest return reader.JsonToValue(specifics.value(), false, false); } - int64 WriteSyncedValue(sync_api::WriteNode& node, - const std::string& name, - const Value& value) { - if (!PreferenceModelAssociator::WritePreferenceToNode(name, value, &node)) + int64 WriteSyncedValue(const std::string& name, + const Value& value, + sync_api::WriteNode* node) { + if (!PreferenceModelAssociator::WritePreferenceToNode(name, value, node)) return sync_api::kInvalidId; - return node.GetId(); + return node->GetId(); } int64 SetSyncedValue(const std::string& name, const Value& value) { @@ -139,11 +144,11 @@ class ProfileSyncServicePreferenceTest int64 node_id = model_associator_->GetSyncIdFromChromeId(name); if (node_id == sync_api::kInvalidId) { if (tag_node.InitByClientTagLookup(syncable::PREFERENCES, name)) - return WriteSyncedValue(tag_node, name, value); + return WriteSyncedValue(name, value, &tag_node); if (node.InitUniqueByCreation(syncable::PREFERENCES, root, name)) - return WriteSyncedValue(node, name, value); + return WriteSyncedValue(name, value, &node); } else if (node.InitByIdLookup(node_id)) { - return WriteSyncedValue(node, name, value); + return WriteSyncedValue(name, value, &node); } return sync_api::kInvalidId; } @@ -248,9 +253,12 @@ TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationDoNotSyncDefaults) { TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationEmptyCloud) { prefs_->SetString(prefs::kHomePage, example_url0_); - ListValue* url_list = prefs_->GetMutableList(prefs::kURLsToRestoreOnStartup); - url_list->Append(Value::CreateStringValue(example_url0_)); - url_list->Append(Value::CreateStringValue(example_url1_)); + { + ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup); + ListValue* url_list = update.Get(); + url_list->Append(Value::CreateStringValue(example_url0_)); + url_list->Append(Value::CreateStringValue(example_url1_)); + } CreateRootTask task(this, syncable::PREFERENCES); ASSERT_TRUE(StartSyncService(&task, false)); ASSERT_TRUE(task.success()); @@ -266,9 +274,12 @@ TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationEmptyCloud) { TEST_F(ProfileSyncServicePreferenceTest, ModelAssociationCloudHasData) { prefs_->SetString(prefs::kHomePage, example_url0_); - ListValue* url_list = prefs_->GetMutableList(prefs::kURLsToRestoreOnStartup); - url_list->Append(Value::CreateStringValue(example_url0_)); - url_list->Append(Value::CreateStringValue(example_url1_)); + { + ListPrefUpdate update(prefs_, prefs::kURLsToRestoreOnStartup); + ListValue* url_list = update.Get(); + url_list->Append(Value::CreateStringValue(example_url0_)); + url_list->Append(Value::CreateStringValue(example_url1_)); + } PreferenceValues cloud_data; cloud_data[prefs::kHomePage] = Value::CreateStringValue(example_url1_); diff --git a/chrome/browser/sync/profile_sync_service_session_unittest.cc b/chrome/browser/sync/profile_sync_service_session_unittest.cc index 1570ff3..87aa5ad 100644 --- a/chrome/browser/sync/profile_sync_service_session_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_session_unittest.cc @@ -1,13 +1,13 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 <map> #include <string> +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_temp_dir.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" -#include "base/scoped_temp_dir.h" #include "base/stl_util-inl.h" #include "base/task.h" #include "chrome/browser/sessions/session_service.h" @@ -18,8 +18,8 @@ #include "chrome/browser/sync/glue/session_data_type_controller.h" #include "chrome/browser/sync/glue/session_model_associator.h" #include "chrome/browser/sync/glue/sync_backend_host.h" -#include "chrome/browser/sync/profile_sync_test_util.h" #include "chrome/browser/sync/profile_sync_factory_mock.h" +#include "chrome/browser/sync/profile_sync_test_util.h" #include "chrome/browser/sync/protocol/session_specifics.pb.h" #include "chrome/browser/sync/protocol/sync.pb.h" #include "chrome/browser/sync/syncable/directory_manager.h" @@ -27,14 +27,14 @@ #include "chrome/browser/sync/syncable/syncable.h" #include "chrome/browser/sync/test_profile_sync_service.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" -#include "chrome/common/notification_service.h" #include "chrome/test/browser_with_test_window_test.h" #include "chrome/test/file_test_utils.h" #include "chrome/test/profile_mock.h" -#include "chrome/test/testing_profile.h" #include "chrome/test/sync/engine/test_id_factory.h" +#include "chrome/test/testing_profile.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" +#include "content/common/notification_service.h" #include "testing/gmock/include/gmock/gmock.h" #include "testing/gtest/include/gtest/gtest.h" @@ -66,8 +66,7 @@ class ProfileSyncServiceSessionTest virtual void SetUp() { // BrowserWithTestWindowTest implementation. BrowserWithTestWindowTest::SetUp(); - - profile()->set_has_history_service(true); + profile()->CreateRequestContext(); ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); SessionService* session_service = new SessionService(temp_dir_.path()); helper_.set_service(session_service); @@ -94,6 +93,13 @@ class ProfileSyncServiceSessionTest helper_.set_service(NULL); profile()->set_session_service(NULL); sync_service_.reset(); + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile()->ResetRequestContext(); + } + MessageLoop::current()->RunAllPending(); } bool StartSyncService(Task* task, bool will_fail_association) { @@ -115,9 +121,10 @@ class ProfileSyncServiceSessionTest model_associator_, change_processor_))); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillOnce(ReturnNewDataTypeManager()); - sync_service_->set_num_expected_resumes(will_fail_association ? 0 : 1); sync_service_->RegisterDataTypeController( - new SessionDataTypeController(&factory_, sync_service_.get())); + new SessionDataTypeController(&factory_, + profile(), + sync_service_.get())); profile()->GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "token"); sync_service_->Initialize(); diff --git a/chrome/browser/sync/profile_sync_service_startup_unittest.cc b/chrome/browser/sync/profile_sync_service_startup_unittest.cc index 4bc486a..e1b732a 100644 --- a/chrome/browser/sync/profile_sync_service_startup_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_startup_unittest.cc @@ -1,11 +1,11 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 "testing/gtest/include/gtest/gtest.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" #include "chrome/browser/net/gaia/token_service.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/sync/glue/data_type_manager.h" @@ -15,15 +15,16 @@ #include "chrome/browser/sync/test_profile_sync_service.h" #include "chrome/common/net/gaia/gaia_auth_consumer.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/notification_type.h" #include "chrome/common/pref_names.h" #include "chrome/test/testing_profile.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_type.h" #include "testing/gmock/include/gmock/gmock.h" using browser_sync::DataTypeManager; using browser_sync::DataTypeManagerMock; using testing::_; +using testing::AnyNumber; using testing::DoAll; using testing::InvokeArgument; using testing::Mock; @@ -55,16 +56,22 @@ class ProfileSyncServiceStartupTest : public testing::Test { } virtual void SetUp() { + profile_.CreateRequestContext(); service_.reset(new TestProfileSyncService(&factory_, &profile_, "test", true, NULL)); service_->AddObserver(&observer_); - service_->set_num_expected_resumes(0); - service_->set_num_expected_pauses(0); service_->set_synchronous_sync_configuration(); } virtual void TearDown() { service_->RemoveObserver(&observer_); + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile_.ResetRequestContext(); + } + MessageLoop::current()->RunAllPending(); } protected: @@ -92,7 +99,7 @@ TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(StartFirstTime)) { // Should not actually start, rather just clean things up and wait // to be enabled. - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); service_->Initialize(); // Preferences should be back to defaults. @@ -101,11 +108,11 @@ TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(StartFirstTime)) { Mock::VerifyAndClearExpectations(data_type_manager); // Then start things up. - EXPECT_CALL(*data_type_manager, Configure(_)).Times(2); + EXPECT_CALL(*data_type_manager, Configure(_)).Times(3); EXPECT_CALL(*data_type_manager, state()). WillOnce(Return(DataTypeManager::CONFIGURED)); EXPECT_CALL(*data_type_manager, Stop()).Times(1); - EXPECT_CALL(observer_, OnStateChanged()).Times(5); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); // Create some tokens in the token service; the service will startup when // it is notified that tokens are available. @@ -119,12 +126,12 @@ TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(StartFirstTime)) { TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(StartNormal)) { DataTypeManagerMock* data_type_manager = SetUpDataTypeManager(); - EXPECT_CALL(*data_type_manager, Configure(_)).Times(1); + EXPECT_CALL(*data_type_manager, Configure(_)).Times(2); EXPECT_CALL(*data_type_manager, state()). WillOnce(Return(DataTypeManager::CONFIGURED)); EXPECT_CALL(*data_type_manager, Stop()).Times(1); - EXPECT_CALL(observer_, OnStateChanged()).Times(3); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); // Pre load the tokens profile_.GetTokenService()->IssueAuthTokenForTest( @@ -137,7 +144,7 @@ TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(ManagedStartup)) { profile_.GetPrefs()->SetBoolean(prefs::kSyncManaged, true); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).Times(0); - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); // Service should not be started by Initialize() since it's managed. profile_.GetTokenService()->IssueAuthTokenForTest( @@ -147,8 +154,8 @@ TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(ManagedStartup)) { TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(SwitchManaged)) { DataTypeManagerMock* data_type_manager = SetUpDataTypeManager(); - EXPECT_CALL(*data_type_manager, Configure(_)).Times(1); - EXPECT_CALL(observer_, OnStateChanged()).Times(3); + EXPECT_CALL(*data_type_manager, Configure(_)).Times(2); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); profile_.GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "sync_token"); @@ -159,21 +166,21 @@ TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(SwitchManaged)) { EXPECT_CALL(*data_type_manager, state()). WillOnce(Return(DataTypeManager::CONFIGURED)); EXPECT_CALL(*data_type_manager, Stop()).Times(1); - EXPECT_CALL(observer_, OnStateChanged()).Times(2); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); profile_.GetPrefs()->SetBoolean(prefs::kSyncManaged, true); // When switching back to unmanaged, the state should change, but the service // should not start up automatically (kSyncSetupCompleted will be false). Mock::VerifyAndClearExpectations(data_type_manager); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).Times(0); - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); profile_.GetPrefs()->ClearPref(prefs::kSyncManaged); } TEST_F(ProfileSyncServiceStartupTest, ClearServerData) { DataTypeManagerMock* data_type_manager = SetUpDataTypeManager(); - EXPECT_CALL(*data_type_manager, Configure(_)).Times(1); - EXPECT_CALL(observer_, OnStateChanged()).Times(3); + EXPECT_CALL(*data_type_manager, Configure(_)).Times(2); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); profile_.GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "sync_token"); @@ -187,12 +194,12 @@ TEST_F(ProfileSyncServiceStartupTest, ClearServerData) { EXPECT_TRUE(ProfileSyncService::CLEAR_NOT_STARTED == service_->GetClearServerDataState()); - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); service_->OnClearServerDataFailed(); EXPECT_TRUE(ProfileSyncService::CLEAR_FAILED == service_->GetClearServerDataState()); - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); service_->OnClearServerDataSucceeded(); EXPECT_TRUE(ProfileSyncService::CLEAR_SUCCEEDED == service_->GetClearServerDataState()); @@ -206,12 +213,12 @@ TEST_F(ProfileSyncServiceStartupTest, ClearServerData) { EXPECT_TRUE(ProfileSyncService::CLEAR_NOT_STARTED == service_->GetClearServerDataState()); - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); service_->OnClearServerDataTimeout(); EXPECT_TRUE(ProfileSyncService::CLEAR_FAILED == service_->GetClearServerDataState()); - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); service_->OnClearServerDataSucceeded(); EXPECT_TRUE(ProfileSyncService::CLEAR_SUCCEEDED == service_->GetClearServerDataState()); @@ -230,17 +237,19 @@ TEST_F(ProfileSyncServiceStartupTest, ClearServerData) { service_->GetClearServerDataState()); // Stop the timer and reset the state - EXPECT_CALL(observer_, OnStateChanged()).Times(1); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); service_->OnClearServerDataSucceeded(); service_->ResetClearServerDataState(); } TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(StartFailure)) { DataTypeManagerMock* data_type_manager = SetUpDataTypeManager(); - DataTypeManager::ConfigureResult result = + DataTypeManager::ConfigureResult configure_result = DataTypeManager::ASSOCIATION_FAILED; + browser_sync::DataTypeManager::ConfigureResultWithErrorLocation result( + configure_result, FROM_HERE, syncable::ModelTypeSet()); EXPECT_CALL(*data_type_manager, Configure(_)). - WillOnce(DoAll(NotifyFromDataTypeManager(data_type_manager, + WillRepeatedly(DoAll(NotifyFromDataTypeManager(data_type_manager, NotificationType::SYNC_CONFIGURE_START), NotifyFromDataTypeManagerWithResult(data_type_manager, NotificationType::SYNC_CONFIGURE_DONE, @@ -248,7 +257,7 @@ TEST_F(ProfileSyncServiceStartupTest, SKIP_MACOSX(StartFailure)) { EXPECT_CALL(*data_type_manager, state()). WillOnce(Return(DataTypeManager::STOPPED)); - EXPECT_CALL(observer_, OnStateChanged()).Times(3); + EXPECT_CALL(observer_, OnStateChanged()).Times(AnyNumber()); profile_.GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "sync_token"); diff --git a/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc index 4e9b98d..7125cce 100644 --- a/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,7 +6,7 @@ #include "testing/gtest/include/gtest/gtest.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "base/string16.h" #include "base/threading/thread.h" #include "base/time.h" @@ -28,10 +28,10 @@ #include "chrome/browser/sync/syncable/directory_manager.h" #include "chrome/browser/sync/test_profile_sync_service.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/notification_service.h" #include "chrome/test/profile_mock.h" #include "chrome/test/sync/engine/test_id_factory.h" #include "chrome/test/testing_profile.h" +#include "content/common/notification_service.h" #include "googleurl/src/gurl.h" #include "testing/gmock/include/gmock/gmock.h" @@ -130,6 +130,7 @@ class ProfileSyncServiceTypedUrlTest : public AbstractProfileSyncServiceTest { } virtual void SetUp() { + profile_.CreateRequestContext(); history_backend_ = new HistoryBackendMock(); history_service_ = new HistoryServiceMock(); EXPECT_CALL((*history_service_.get()), ScheduleDBTask(_, _)) @@ -148,6 +149,12 @@ class ProfileSyncServiceTypedUrlTest : public AbstractProfileSyncServiceTest { service_.reset(); notification_service_->TearDown(); history_thread_.Stop(); + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile_.ResetRequestContext(); + } MessageLoop::current()->RunAllPending(); } diff --git a/chrome/browser/sync/profile_sync_service_unittest.cc b/chrome/browser/sync/profile_sync_service_unittest.cc index 5226715..376eea1 100644 --- a/chrome/browser/sync/profile_sync_service_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_unittest.cc @@ -1,305 +1,45 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 <stack> -#include <vector> - -#include "testing/gtest/include/gtest/gtest.h" #include "base/file_util.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/scoped_ptr.h" -#include "base/string_number_conversions.h" -#include "base/string_util.h" -#include "base/string16.h" -#include "base/utf_string_conversions.h" #include "base/values.h" -#include "chrome/browser/bookmarks/bookmark_model.h" #include "chrome/browser/net/gaia/token_service.h" -#include "chrome/browser/prefs/pref_service.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sync/abstract_profile_sync_service_test.h" -#include "chrome/browser/sync/engine/syncapi.h" -#include "chrome/browser/sync/glue/change_processor.h" -#include "chrome/browser/sync/glue/bookmark_change_processor.h" #include "chrome/browser/sync/glue/bookmark_data_type_controller.h" -#include "chrome/browser/sync/glue/bookmark_model_associator.h" #include "chrome/browser/sync/glue/data_type_controller.h" -#include "chrome/browser/sync/glue/model_associator.h" -#include "chrome/browser/sync/glue/sync_backend_host.h" -#include "chrome/browser/sync/glue/sync_backend_host_mock.h" #include "chrome/browser/sync/js_arg_list.h" #include "chrome/browser/sync/js_test_util.h" -#include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/profile_sync_factory_mock.h" #include "chrome/browser/sync/test_profile_sync_service.h" -#include "chrome/browser/sync/profile_sync_test_util.h" #include "chrome/common/net/gaia/gaia_constants.h" #include "chrome/common/pref_names.h" -#include "chrome/test/testing_profile.h" #include "chrome/test/testing_pref_service.h" +#include "chrome/test/testing_profile.h" #include "content/browser/browser_thread.h" #include "testing/gmock/include/gmock/gmock.h" +#include "testing/gtest/include/gtest/gtest.h" + +// TODO(akalin): Add tests here that exercise the whole +// ProfileSyncService/SyncBackendHost stack while mocking out as +// little as possible. + +namespace browser_sync { + +namespace { -using std::vector; -using browser_sync::AssociatorInterface; -using browser_sync::BookmarkChangeProcessor; -using browser_sync::BookmarkModelAssociator; -using browser_sync::ChangeProcessor; -using browser_sync::DataTypeController; -using browser_sync::HasArgs; -using browser_sync::JsArgList; -using browser_sync::MockJsEventHandler; -using browser_sync::ModelAssociator; -using browser_sync::SyncBackendHost; -using browser_sync::SyncBackendHostMock; -using browser_sync::UnrecoverableErrorHandler; using testing::_; +using testing::AtLeast; using testing::AtMost; using testing::Return; using testing::StrictMock; -using testing::WithArg; -using testing::Invoke; - -// TODO(akalin): Bookmark-specific tests should be moved into their -// own file. -class TestBookmarkModelAssociator : public BookmarkModelAssociator { - public: - TestBookmarkModelAssociator( - TestProfileSyncService* service, - UnrecoverableErrorHandler* persist_ids_error_handler) - : BookmarkModelAssociator(service, persist_ids_error_handler), - id_factory_(service->id_factory()) {} - - // TODO(akalin): This logic lazily creates any tagged node that is - // requested. A better way would be to have utility functions to - // create sync nodes from some bookmark structure and to use that. - virtual bool GetSyncIdForTaggedNode(const std::string& tag, int64* sync_id) { - std::wstring tag_wide; - if (!UTF8ToWide(tag.c_str(), tag.length(), &tag_wide)) { - NOTREACHED() << "Unable to convert UTF8 to wide for string: " << tag; - return false; - } - - bool root_exists = false; - syncable::ModelType type = model_type(); - { - sync_api::WriteTransaction trans(sync_service()->GetUserShare()); - sync_api::ReadNode uber_root(&trans); - uber_root.InitByRootLookup(); - - sync_api::ReadNode root(&trans); - root_exists = root.InitByTagLookup( - ProfileSyncServiceTestHelper::GetTagForType(type)); - } - - if (!root_exists) { - bool created = ProfileSyncServiceTestHelper::CreateRoot( - type, - sync_service()->GetUserShare(), - id_factory_); - if (!created) - return false; - } - - sync_api::WriteTransaction trans(sync_service()->GetUserShare()); - sync_api::ReadNode root(&trans); - EXPECT_TRUE(root.InitByTagLookup( - ProfileSyncServiceTestHelper::GetTagForType(type))); - - // First, try to find a node with the title among the root's children. - // This will be the case if we are testing model persistence, and - // are reloading a sync repository created earlier in the test. - int64 last_child_id = sync_api::kInvalidId; - for (int64 id = root.GetFirstChildId(); id != sync_api::kInvalidId; /***/) { - sync_api::ReadNode child(&trans); - child.InitByIdLookup(id); - last_child_id = id; - if (tag_wide == child.GetTitle()) { - *sync_id = id; - return true; - } - id = child.GetSuccessorId(); - } - - sync_api::ReadNode predecessor_node(&trans); - sync_api::ReadNode* predecessor = NULL; - if (last_child_id != sync_api::kInvalidId) { - predecessor_node.InitByIdLookup(last_child_id); - predecessor = &predecessor_node; - } - sync_api::WriteNode node(&trans); - // Create new fake tagged nodes at the end of the ordering. - node.InitByCreation(type, root, predecessor); - node.SetIsFolder(true); - node.SetTitle(tag_wide); - node.SetExternalId(0); - *sync_id = node.GetId(); - return true; - } - - private: - browser_sync::TestIdFactory* id_factory_; -}; - -// FakeServerChange constructs a list of sync_api::ChangeRecords while modifying -// the sync model, and can pass the ChangeRecord list to a -// sync_api::SyncObserver (i.e., the ProfileSyncService) to test the client -// change-application behavior. -// Tests using FakeServerChange should be careful to avoid back-references, -// since FakeServerChange will send the edits in the order specified. -class FakeServerChange { - public: - explicit FakeServerChange(sync_api::WriteTransaction* trans) : trans_(trans) { - } - - // Pretend that the server told the syncer to add a bookmark object. - int64 Add(const std::wstring& title, - const std::string& url, - bool is_folder, - int64 parent_id, - int64 predecessor_id) { - sync_api::ReadNode parent(trans_); - EXPECT_TRUE(parent.InitByIdLookup(parent_id)); - sync_api::WriteNode node(trans_); - if (predecessor_id == 0) { - EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent, NULL)); - } else { - sync_api::ReadNode predecessor(trans_); - EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id)); - EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); - EXPECT_TRUE(node.InitByCreation(syncable::BOOKMARKS, parent, - &predecessor)); - } - EXPECT_EQ(node.GetPredecessorId(), predecessor_id); - EXPECT_EQ(node.GetParentId(), parent_id); - node.SetIsFolder(is_folder); - node.SetTitle(title); - if (!is_folder) - node.SetURL(GURL(url)); - sync_api::SyncManager::ChangeRecord record; - record.action = sync_api::SyncManager::ChangeRecord::ACTION_ADD; - record.id = node.GetId(); - changes_.push_back(record); - return node.GetId(); - } - - // Add a bookmark folder. - int64 AddFolder(const std::wstring& title, - int64 parent_id, - int64 predecessor_id) { - return Add(title, std::string(), true, parent_id, predecessor_id); - } - - // Add a bookmark. - int64 AddURL(const std::wstring& title, - const std::string& url, - int64 parent_id, - int64 predecessor_id) { - return Add(title, url, false, parent_id, predecessor_id); - } - - // Pretend that the server told the syncer to delete an object. - void Delete(int64 id) { - { - // Delete the sync node. - sync_api::WriteNode node(trans_); - EXPECT_TRUE(node.InitByIdLookup(id)); - EXPECT_FALSE(node.GetFirstChildId()); - node.Remove(); - } - { - // Verify the deletion. - sync_api::ReadNode node(trans_); - EXPECT_FALSE(node.InitByIdLookup(id)); - } - - sync_api::SyncManager::ChangeRecord record; - record.action = sync_api::SyncManager::ChangeRecord::ACTION_DELETE; - record.id = id; - // Deletions are always first in the changelist, but we can't actually do - // WriteNode::Remove() on the node until its children are moved. So, as - // a practical matter, users of FakeServerChange must move or delete - // children before parents. Thus, we must insert the deletion record - // at the front of the vector. - changes_.insert(changes_.begin(), record); - } - - // Set a new title value, and return the old value. - std::wstring ModifyTitle(int64 id, const std::wstring& new_title) { - sync_api::WriteNode node(trans_); - EXPECT_TRUE(node.InitByIdLookup(id)); - std::wstring old_title = node.GetTitle(); - node.SetTitle(new_title); - SetModified(id); - return old_title; - } - - // Set a new parent and predecessor value. Return the old parent id. - // We could return the old predecessor id, but it turns out not to be - // very useful for assertions. - int64 ModifyPosition(int64 id, int64 parent_id, int64 predecessor_id) { - sync_api::ReadNode parent(trans_); - EXPECT_TRUE(parent.InitByIdLookup(parent_id)); - sync_api::WriteNode node(trans_); - EXPECT_TRUE(node.InitByIdLookup(id)); - int64 old_parent_id = node.GetParentId(); - if (predecessor_id == 0) { - EXPECT_TRUE(node.SetPosition(parent, NULL)); - } else { - sync_api::ReadNode predecessor(trans_); - EXPECT_TRUE(predecessor.InitByIdLookup(predecessor_id)); - EXPECT_EQ(predecessor.GetParentId(), parent.GetId()); - EXPECT_TRUE(node.SetPosition(parent, &predecessor)); - } - SetModified(id); - return old_parent_id; - } - - // Pass the fake change list to |service|. - void ApplyPendingChanges(browser_sync::ChangeProcessor* processor) { - processor->ApplyChangesFromSyncModel(trans_, - changes_.size() ? &changes_[0] : NULL, changes_.size()); - } - - const vector<sync_api::SyncManager::ChangeRecord>& changes() { - return changes_; - } - - private: - // Helper function to push an ACTION_UPDATE record onto the back - // of the changelist. - void SetModified(int64 id) { - // Coalesce multi-property edits. - if (!changes_.empty() && changes_.back().id == id && - changes_.back().action == - sync_api::SyncManager::ChangeRecord::ACTION_UPDATE) - return; - sync_api::SyncManager::ChangeRecord record; - record.action = sync_api::SyncManager::ChangeRecord::ACTION_UPDATE; - record.id = id; - changes_.push_back(record); - } - - // The transaction on which everything happens. - sync_api::WriteTransaction *trans_; - - // The change list we construct. - vector<sync_api::SyncManager::ChangeRecord> changes_; -}; class ProfileSyncServiceTest : public testing::Test { protected: - enum LoadOption { LOAD_FROM_STORAGE, DELETE_EXISTING_STORAGE }; - enum SaveOption { SAVE_TO_STORAGE, DONT_SAVE_TO_STORAGE }; ProfileSyncServiceTest() - : ui_thread_(BrowserThread::UI, &message_loop_), - file_thread_(BrowserThread::FILE, &message_loop_), - model_(NULL), - model_associator_(NULL), - change_processor_(NULL) { + : ui_thread_(BrowserThread::UI, &message_loop_) { profile_.reset(new TestingProfile()); - profile_->set_has_history_service(true); } virtual ~ProfileSyncServiceTest() { // Kill the service before the profile. @@ -309,21 +49,30 @@ class ProfileSyncServiceTest : public testing::Test { // Ensure that the sync objects destruct to avoid memory leaks. MessageLoop::current()->RunAllPending(); } - - BookmarkModelAssociator* associator() { - return model_associator_; + virtual void SetUp() { + profile_->CreateRequestContext(); } - - BookmarkChangeProcessor* change_processor() { - return change_processor_; + virtual void TearDown() { + { + // The request context gets deleted on the I/O thread. To prevent a leak + // supply one here. + BrowserThread io_thread(BrowserThread::IO, MessageLoop::current()); + profile_->ResetRequestContext(); + } + MessageLoop::current()->RunAllPending(); } + // TODO(akalin): Refactor the StartSyncService*() functions below. + void StartSyncService() { - StartSyncServiceAndSetInitialSyncEnded(true, true); + StartSyncServiceAndSetInitialSyncEnded(true, true, false, true); } + void StartSyncServiceAndSetInitialSyncEnded( bool set_initial_sync_ended, - bool issue_auth_token) { + bool issue_auth_token, + bool synchronous_sync_configuration, + bool sync_setup_completed) { if (!service_.get()) { // Set bootstrap to true and it will provide a logged in user for test service_.reset(new TestProfileSyncService(&factory_, @@ -331,23 +80,15 @@ class ProfileSyncServiceTest : public testing::Test { "test", true, NULL)); if (!set_initial_sync_ended) service_->dont_set_initial_sync_ended_on_init(); + if (synchronous_sync_configuration) + service_->set_synchronous_sync_configuration(); + if (!sync_setup_completed) + profile_->GetPrefs()->SetBoolean(prefs::kSyncHasSetupCompleted, false); // Register the bookmark data type. - model_associator_ = new TestBookmarkModelAssociator(service_.get(), - service_.get()); - change_processor_ = new BookmarkChangeProcessor(model_associator_, - service_.get()); - EXPECT_CALL(factory_, CreateBookmarkSyncComponents(_, _)). - WillOnce(Return(ProfileSyncFactory::SyncComponents( - model_associator_, change_processor_))); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). WillOnce(ReturnNewDataTypeManager()); - service_->RegisterDataTypeController( - new browser_sync::BookmarkDataTypeController(&factory_, - profile_.get(), - service_.get())); - if (issue_auth_token) { profile_->GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "token"); @@ -356,162 +97,6 @@ class ProfileSyncServiceTest : public testing::Test { } } - void StopSyncService(SaveOption save) { - if (save == DONT_SAVE_TO_STORAGE) - service_->DisableForUser(); - service_.reset(); - } - - // Load (or re-load) the bookmark model. |load| controls use of the - // bookmarks file on disk. |save| controls whether the newly loaded - // bookmark model will write out a bookmark file as it goes. - void LoadBookmarkModel(LoadOption load, SaveOption save) { - bool delete_bookmarks = load == DELETE_EXISTING_STORAGE; - profile_->CreateBookmarkModel(delete_bookmarks); - model_ = profile_->GetBookmarkModel(); - // Wait for the bookmarks model to load. - profile_->BlockUntilBookmarkModelLoaded(); - // This noticeably speeds up the unit tests that request it. - if (save == DONT_SAVE_TO_STORAGE) - model_->ClearStore(); - } - - void ExpectSyncerNodeMatching(sync_api::BaseTransaction* trans, - const BookmarkNode* bnode) { - sync_api::ReadNode gnode(trans); - EXPECT_TRUE(associator()->InitSyncNodeFromChromeId(bnode->id(), &gnode)); - // Non-root node titles and parents must match. - if (bnode != model_->GetBookmarkBarNode() && - bnode != model_->other_node()) { - EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(gnode.GetTitle())); - EXPECT_EQ(associator()->GetChromeNodeFromSyncId(gnode.GetParentId()), - bnode->GetParent()); - } - EXPECT_EQ(bnode->is_folder(), gnode.GetIsFolder()); - if (bnode->is_url()) - EXPECT_EQ(bnode->GetURL(), gnode.GetURL()); - - // Check for position matches. - int browser_index = bnode->GetParent()->IndexOfChild(bnode); - if (browser_index == 0) { - EXPECT_EQ(gnode.GetPredecessorId(), 0); - } else { - const BookmarkNode* bprev = - bnode->GetParent()->GetChild(browser_index - 1); - sync_api::ReadNode gprev(trans); - ASSERT_TRUE(associator()->InitSyncNodeFromChromeId(bprev->id(), - &gprev)); - EXPECT_EQ(gnode.GetPredecessorId(), gprev.GetId()); - EXPECT_EQ(gnode.GetParentId(), gprev.GetParentId()); - } - if (browser_index == bnode->GetParent()->GetChildCount() - 1) { - EXPECT_EQ(gnode.GetSuccessorId(), 0); - } else { - const BookmarkNode* bnext = - bnode->GetParent()->GetChild(browser_index + 1); - sync_api::ReadNode gnext(trans); - ASSERT_TRUE(associator()->InitSyncNodeFromChromeId(bnext->id(), - &gnext)); - EXPECT_EQ(gnode.GetSuccessorId(), gnext.GetId()); - EXPECT_EQ(gnode.GetParentId(), gnext.GetParentId()); - } - if (bnode->GetChildCount()) { - EXPECT_TRUE(gnode.GetFirstChildId()); - } - } - - void ExpectSyncerNodeMatching(const BookmarkNode* bnode) { - sync_api::ReadTransaction trans(service_->GetUserShare()); - ExpectSyncerNodeMatching(&trans, bnode); - } - - void ExpectBrowserNodeMatching(sync_api::BaseTransaction* trans, - int64 sync_id) { - EXPECT_TRUE(sync_id); - const BookmarkNode* bnode = - associator()->GetChromeNodeFromSyncId(sync_id); - ASSERT_TRUE(bnode); - int64 id = associator()->GetSyncIdFromChromeId(bnode->id()); - EXPECT_EQ(id, sync_id); - ExpectSyncerNodeMatching(trans, bnode); - } - - void ExpectBrowserNodeUnknown(int64 sync_id) { - EXPECT_FALSE(associator()->GetChromeNodeFromSyncId(sync_id)); - } - - void ExpectBrowserNodeKnown(int64 sync_id) { - EXPECT_TRUE(associator()->GetChromeNodeFromSyncId(sync_id)); - } - - void ExpectSyncerNodeKnown(const BookmarkNode* node) { - int64 sync_id = associator()->GetSyncIdFromChromeId(node->id()); - EXPECT_NE(sync_id, sync_api::kInvalidId); - } - - void ExpectSyncerNodeUnknown(const BookmarkNode* node) { - int64 sync_id = associator()->GetSyncIdFromChromeId(node->id()); - EXPECT_EQ(sync_id, sync_api::kInvalidId); - } - - void ExpectBrowserNodeTitle(int64 sync_id, const std::wstring& title) { - const BookmarkNode* bnode = - associator()->GetChromeNodeFromSyncId(sync_id); - ASSERT_TRUE(bnode); - EXPECT_EQ(bnode->GetTitle(), WideToUTF16Hack(title)); - } - - void ExpectBrowserNodeURL(int64 sync_id, const std::string& url) { - const BookmarkNode* bnode = - associator()->GetChromeNodeFromSyncId(sync_id); - ASSERT_TRUE(bnode); - EXPECT_EQ(GURL(url), bnode->GetURL()); - } - - void ExpectBrowserNodeParent(int64 sync_id, int64 parent_sync_id) { - const BookmarkNode* node = associator()->GetChromeNodeFromSyncId(sync_id); - ASSERT_TRUE(node); - const BookmarkNode* parent = - associator()->GetChromeNodeFromSyncId(parent_sync_id); - EXPECT_TRUE(parent); - EXPECT_EQ(node->GetParent(), parent); - } - - void ExpectModelMatch(sync_api::BaseTransaction* trans) { - const BookmarkNode* root = model_->root_node(); - EXPECT_EQ(root->IndexOfChild(model_->GetBookmarkBarNode()), 0); - EXPECT_EQ(root->IndexOfChild(model_->other_node()), 1); - - std::stack<int64> stack; - stack.push(bookmark_bar_id()); - while (!stack.empty()) { - int64 id = stack.top(); - stack.pop(); - if (!id) continue; - - ExpectBrowserNodeMatching(trans, id); - - sync_api::ReadNode gnode(trans); - ASSERT_TRUE(gnode.InitByIdLookup(id)); - stack.push(gnode.GetFirstChildId()); - stack.push(gnode.GetSuccessorId()); - } - } - - void ExpectModelMatch() { - sync_api::ReadTransaction trans(service_->GetUserShare()); - ExpectModelMatch(&trans); - } - - int64 other_bookmarks_id() { - return associator()->GetSyncIdFromChromeId(model_->other_node()->id()); - } - - int64 bookmark_bar_id() { - return associator()->GetSyncIdFromChromeId( - model_->GetBookmarkBarNode()->id()); - } - // This serves as the "UI loop" on which the ProfileSyncService lives and // operates. It is needed because the SyncBackend can post tasks back to // the service, meaning it can't be null. It doesn't have to be running, @@ -521,30 +106,20 @@ class ProfileSyncServiceTest : public testing::Test { // and caller until the task is run). MessageLoop message_loop_; BrowserThread ui_thread_; - BrowserThread file_thread_; scoped_ptr<TestProfileSyncService> service_; scoped_ptr<TestingProfile> profile_; ProfileSyncFactoryMock factory_; - BookmarkModel* model_; - TestBookmarkModelAssociator* model_associator_; - BookmarkChangeProcessor* change_processor_; }; TEST_F(ProfileSyncServiceTest, InitialState) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - + service_.reset(new TestProfileSyncService(&factory_, profile_.get(), + "", true, NULL)); EXPECT_TRUE( - service_->sync_service_url_.spec() == + service_->sync_service_url().spec() == ProfileSyncService::kSyncServerUrl || - service_->sync_service_url_.spec() == + service_->sync_service_url().spec() == ProfileSyncService::kDevServerUrl); - - EXPECT_TRUE(other_bookmarks_id()); - EXPECT_TRUE(bookmark_bar_id()); - - ExpectModelMatch(); } TEST_F(ProfileSyncServiceTest, DisabledByPolicy) { @@ -560,412 +135,27 @@ TEST_F(ProfileSyncServiceTest, DisabledByPolicy) { TEST_F(ProfileSyncServiceTest, AbortedByShutdown) { service_.reset(new TestProfileSyncService(&factory_, profile_.get(), "test", true, NULL)); - service_->set_num_expected_resumes(0); EXPECT_CALL(factory_, CreateDataTypeManager(_, _)).Times(0); EXPECT_CALL(factory_, CreateBookmarkSyncComponents(_, _)).Times(0); service_->RegisterDataTypeController( - new browser_sync::BookmarkDataTypeController(&factory_, - profile_.get(), - service_.get())); + new BookmarkDataTypeController(&factory_, + profile_.get(), + service_.get())); service_->Initialize(); service_.reset(); } - -TEST_F(ProfileSyncServiceTest, BookmarkModelOperations) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - // Test addition. - const BookmarkNode* folder = - model_->AddGroup(model_->other_node(), 0, ASCIIToUTF16("foobar")); - ExpectSyncerNodeMatching(folder); - ExpectModelMatch(); - const BookmarkNode* folder2 = - model_->AddGroup(folder, 0, ASCIIToUTF16("nested")); - ExpectSyncerNodeMatching(folder2); - ExpectModelMatch(); - const BookmarkNode* url1 = model_->AddURL( - folder, 0, ASCIIToUTF16("Internets #1 Pies Site"), - GURL("http://www.easypie.com/")); - ExpectSyncerNodeMatching(url1); - ExpectModelMatch(); - const BookmarkNode* url2 = model_->AddURL( - folder, 1, ASCIIToUTF16("Airplanes"), GURL("http://www.easyjet.com/")); - ExpectSyncerNodeMatching(url2); - ExpectModelMatch(); - - // Test modification. - model_->SetTitle(url2, ASCIIToUTF16("EasyJet")); - ExpectModelMatch(); - model_->Move(url1, folder2, 0); - ExpectModelMatch(); - model_->Move(folder2, model_->GetBookmarkBarNode(), 0); - ExpectModelMatch(); - model_->SetTitle(folder2, ASCIIToUTF16("Not Nested")); - ExpectModelMatch(); - model_->Move(folder, folder2, 0); - ExpectModelMatch(); - model_->SetTitle(folder, ASCIIToUTF16("who's nested now?")); - ExpectModelMatch(); - model_->Copy(url2, model_->GetBookmarkBarNode(), 0); - ExpectModelMatch(); - - // Test deletion. - // Delete a single item. - model_->Remove(url2->GetParent(), url2->GetParent()->IndexOfChild(url2)); - ExpectModelMatch(); - // Delete an item with several children. - model_->Remove(folder2->GetParent(), - folder2->GetParent()->IndexOfChild(folder2)); - ExpectModelMatch(); -} - -TEST_F(ProfileSyncServiceTest, ServerChangeProcessing) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - sync_api::WriteTransaction trans(service_->GetUserShare()); - - FakeServerChange adds(&trans); - int64 f1 = adds.AddFolder(L"Server Folder B", bookmark_bar_id(), 0); - int64 f2 = adds.AddFolder(L"Server Folder A", bookmark_bar_id(), f1); - int64 u1 = adds.AddURL(L"Some old site", "ftp://nifty.andrew.cmu.edu/", - bookmark_bar_id(), f2); - int64 u2 = adds.AddURL(L"Nifty", "ftp://nifty.andrew.cmu.edu/", f1, 0); - // u3 is a duplicate URL - int64 u3 = adds.AddURL(L"Nifty2", "ftp://nifty.andrew.cmu.edu/", f1, u2); - // u4 is a duplicate title, different URL. - adds.AddURL(L"Some old site", "http://slog.thestranger.com/", - bookmark_bar_id(), u1); - // u5 tests an empty-string title. - std::string javascript_url( - "javascript:(function(){var w=window.open(" \ - "'about:blank','gnotesWin','location=0,menubar=0," \ - "scrollbars=0,status=0,toolbar=0,width=300," \ - "height=300,resizable');});"); - adds.AddURL(L"", javascript_url, other_bookmarks_id(), 0); - - vector<sync_api::SyncManager::ChangeRecord>::const_iterator it; - // The bookmark model shouldn't yet have seen any of the nodes of |adds|. - for (it = adds.changes().begin(); it != adds.changes().end(); ++it) - ExpectBrowserNodeUnknown(it->id); - - adds.ApplyPendingChanges(change_processor()); - - // Make sure the bookmark model received all of the nodes in |adds|. - for (it = adds.changes().begin(); it != adds.changes().end(); ++it) - ExpectBrowserNodeMatching(&trans, it->id); - ExpectModelMatch(&trans); - - // Part two: test modifications. - FakeServerChange mods(&trans); - // Mess with u2, and move it into empty folder f2 - // TODO(ncarter): Determine if we allow ModifyURL ops or not. - /* std::wstring u2_old_url = mods.ModifyURL(u2, L"http://www.google.com"); */ - std::wstring u2_old_title = mods.ModifyTitle(u2, L"The Google"); - int64 u2_old_parent = mods.ModifyPosition(u2, f2, 0); - - // Now move f1 after u2. - std::wstring f1_old_title = mods.ModifyTitle(f1, L"Server Folder C"); - int64 f1_old_parent = mods.ModifyPosition(f1, f2, u2); - - // Then add u3 after f1. - int64 u3_old_parent = mods.ModifyPosition(u3, f2, f1); - - // Test that the property changes have not yet taken effect. - ExpectBrowserNodeTitle(u2, u2_old_title); - /* ExpectBrowserNodeURL(u2, u2_old_url); */ - ExpectBrowserNodeParent(u2, u2_old_parent); - - ExpectBrowserNodeTitle(f1, f1_old_title); - ExpectBrowserNodeParent(f1, f1_old_parent); - - ExpectBrowserNodeParent(u3, u3_old_parent); - - // Apply the changes. - mods.ApplyPendingChanges(change_processor()); - - // Check for successful application. - for (it = mods.changes().begin(); it != mods.changes().end(); ++it) - ExpectBrowserNodeMatching(&trans, it->id); - ExpectModelMatch(&trans); - - // Part 3: Test URL deletion. - FakeServerChange dels(&trans); - dels.Delete(u2); - dels.Delete(u3); - - ExpectBrowserNodeKnown(u2); - ExpectBrowserNodeKnown(u3); - - dels.ApplyPendingChanges(change_processor()); - - ExpectBrowserNodeUnknown(u2); - ExpectBrowserNodeUnknown(u3); - ExpectModelMatch(&trans); -} - -// Tests a specific case in ApplyModelChanges where we move the -// children out from under a parent, and then delete the parent -// in the same changelist. The delete shows up first in the changelist, -// requiring the children to be moved to a temporary location. -TEST_F(ProfileSyncServiceTest, ServerChangeRequiringFosterParent) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - sync_api::WriteTransaction trans(service_->GetUserShare()); - - // Stress the immediate children of other_node because that's where - // ApplyModelChanges puts a temporary foster parent node. - std::string url("http://dev.chromium.org/"); - FakeServerChange adds(&trans); - int64 f0 = other_bookmarks_id(); // + other_node - int64 f1 = adds.AddFolder(L"f1", f0, 0); // + f1 - int64 f2 = adds.AddFolder(L"f2", f1, 0); // + f2 - int64 u3 = adds.AddURL( L"u3", url, f2, 0); // + u3 NOLINT - int64 u4 = adds.AddURL( L"u4", url, f2, u3); // + u4 NOLINT - int64 u5 = adds.AddURL( L"u5", url, f1, f2); // + u5 NOLINT - int64 f6 = adds.AddFolder(L"f6", f1, u5); // + f6 - int64 u7 = adds.AddURL( L"u7", url, f0, f1); // + u7 NOLINT - - vector<sync_api::SyncManager::ChangeRecord>::const_iterator it; - // The bookmark model shouldn't yet have seen any of the nodes of |adds|. - for (it = adds.changes().begin(); it != adds.changes().end(); ++it) - ExpectBrowserNodeUnknown(it->id); - - adds.ApplyPendingChanges(change_processor()); - - // Make sure the bookmark model received all of the nodes in |adds|. - for (it = adds.changes().begin(); it != adds.changes().end(); ++it) - ExpectBrowserNodeMatching(&trans, it->id); - ExpectModelMatch(&trans); - - // We have to do the moves before the deletions, but FakeServerChange will - // put the deletion at the front of the changelist. - FakeServerChange ops(&trans); - ops.ModifyPosition(f6, other_bookmarks_id(), 0); - ops.ModifyPosition(u3, other_bookmarks_id(), f1); // Prev == f1 is OK here. - ops.ModifyPosition(f2, other_bookmarks_id(), u7); - ops.ModifyPosition(u7, f2, 0); - ops.ModifyPosition(u4, other_bookmarks_id(), f2); - ops.ModifyPosition(u5, f6, 0); - ops.Delete(f1); - - ops.ApplyPendingChanges(change_processor()); - - ExpectModelMatch(&trans); -} - -// Simulate a server change record containing a valid but non-canonical URL. -TEST_F(ProfileSyncServiceTest, ServerChangeWithNonCanonicalURL) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - { - sync_api::WriteTransaction trans(service_->GetUserShare()); - - FakeServerChange adds(&trans); - std::string url("http://dev.chromium.org"); - EXPECT_NE(GURL(url).spec(), url); - adds.AddURL(L"u1", url, other_bookmarks_id(), 0); - - adds.ApplyPendingChanges(change_processor()); - - EXPECT_TRUE(model_->other_node()->GetChildCount() == 1); - ExpectModelMatch(&trans); - } - - // Now reboot the sync service, forcing a merge step. - StopSyncService(SAVE_TO_STORAGE); - LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - // There should still be just the one bookmark. - EXPECT_TRUE(model_->other_node()->GetChildCount() == 1); - ExpectModelMatch(); -} - -// Simulate a server change record containing an invalid URL (per GURL). -// TODO(ncarter): Disabled due to crashes. Fix bug 1677563. -TEST_F(ProfileSyncServiceTest, DISABLED_ServerChangeWithInvalidURL) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - int child_count = 0; - { - sync_api::WriteTransaction trans(service_->GetUserShare()); - - FakeServerChange adds(&trans); - std::string url("x"); - EXPECT_FALSE(GURL(url).is_valid()); - adds.AddURL(L"u1", url, other_bookmarks_id(), 0); - - adds.ApplyPendingChanges(change_processor()); - - // We're lenient about what should happen -- the model could wind up with - // the node or without it; but things should be consistent, and we - // shouldn't crash. - child_count = model_->other_node()->GetChildCount(); - EXPECT_TRUE(child_count == 0 || child_count == 1); - ExpectModelMatch(&trans); - } - - // Now reboot the sync service, forcing a merge step. - StopSyncService(SAVE_TO_STORAGE); - LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - // Things ought not to have changed. - EXPECT_EQ(model_->other_node()->GetChildCount(), child_count); - ExpectModelMatch(); -} - - -// Test strings that might pose a problem if the titles ever became used as -// file names in the sync backend. -TEST_F(ProfileSyncServiceTest, CornerCaseNames) { - // TODO(ncarter): Bug 1570238 explains the failure of this test. - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - const char* names[] = { - // The empty string. - "", - // Illegal Windows filenames. - "CON", "PRN", "AUX", "NUL", "COM1", "COM2", "COM3", "COM4", - "COM5", "COM6", "COM7", "COM8", "COM9", "LPT1", "LPT2", "LPT3", - "LPT4", "LPT5", "LPT6", "LPT7", "LPT8", "LPT9", - // Current/parent directory markers. - ".", "..", "...", - // Files created automatically by the Windows shell. - "Thumbs.db", ".DS_Store", - // Names including Win32-illegal characters, and path separators. - "foo/bar", "foo\\bar", "foo?bar", "foo:bar", "foo|bar", "foo\"bar", - "foo'bar", "foo<bar", "foo>bar", "foo%bar", "foo*bar", "foo]bar", - "foo[bar", - }; - // Create both folders and bookmarks using each name. - GURL url("http://www.doublemint.com"); - for (size_t i = 0; i < arraysize(names); ++i) { - model_->AddGroup(model_->other_node(), 0, ASCIIToUTF16(names[i])); - model_->AddURL(model_->other_node(), 0, ASCIIToUTF16(names[i]), url); - } - - // Verify that the browser model matches the sync model. - EXPECT_TRUE(model_->other_node()->GetChildCount() == 2*arraysize(names)); - ExpectModelMatch(); -} - -// Stress the internal representation of position by sparse numbers. We want -// to repeatedly bisect the range of available positions, to force the -// syncer code to renumber its ranges. Pick a number big enough so that it -// would exhaust 32bits of room between items a couple of times. -TEST_F(ProfileSyncServiceTest, RepeatedMiddleInsertion) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - static const int kTimesToInsert = 256; - - // Create two book-end nodes to insert between. - model_->AddGroup(model_->other_node(), 0, ASCIIToUTF16("Alpha")); - model_->AddGroup(model_->other_node(), 1, ASCIIToUTF16("Omega")); - int count = 2; - - // Test insertion in first half of range by repeatedly inserting in second - // position. - for (int i = 0; i < kTimesToInsert; ++i) { - string16 title = ASCIIToUTF16("Pre-insertion ") + base::IntToString16(i); - model_->AddGroup(model_->other_node(), 1, title); - count++; - } - - // Test insertion in second half of range by repeatedly inserting in - // second-to-last position. - for (int i = 0; i < kTimesToInsert; ++i) { - string16 title = ASCIIToUTF16("Post-insertion ") + base::IntToString16(i); - model_->AddGroup(model_->other_node(), count - 1, title); - count++; - } - - // Verify that the browser model matches the sync model. - EXPECT_EQ(model_->other_node()->GetChildCount(), count); - ExpectModelMatch(); -} - -// Introduce a consistency violation into the model, and see that it -// puts itself into a lame, error state. -TEST_F(ProfileSyncServiceTest, UnrecoverableErrorSuspendsService) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - // Synchronization should be up and running at this point. - EXPECT_TRUE(service_->ShouldPushChanges()); - - // Add a node which will be the target of the consistency violation. - const BookmarkNode* node = - model_->AddGroup(model_->other_node(), 0, ASCIIToUTF16("node")); - ExpectSyncerNodeMatching(node); - - // Now destroy the syncer node as if we were the ProfileSyncService without - // updating the ProfileSyncService state. This should introduce - // inconsistency between the two models. - { - sync_api::WriteTransaction trans(service_->GetUserShare()); - sync_api::WriteNode sync_node(&trans); - EXPECT_TRUE(associator()->InitSyncNodeFromChromeId(node->id(), - &sync_node)); - sync_node.Remove(); - } - // The models don't match at this point, but the ProfileSyncService - // doesn't know it yet. - ExpectSyncerNodeKnown(node); - EXPECT_TRUE(service_->ShouldPushChanges()); - - // Add a child to the inconsistent node. This should cause detection of the - // problem and the syncer should stop processing changes. - model_->AddGroup(node, 0, ASCIIToUTF16("nested")); - EXPECT_FALSE(service_->ShouldPushChanges()); - - // Try to add a node under a totally different parent. This should also - // fail -- the ProfileSyncService should stop processing changes after - // encountering a consistency violation. - model_->AddGroup(model_->GetBookmarkBarNode(), 0, ASCIIToUTF16("unrelated")); - EXPECT_FALSE(service_->ShouldPushChanges()); - - // TODO(ncarter): We ought to test the ProfileSyncService state machine - // directly here once that's formalized and exposed. -} - -// See what happens if we run model association when there are two exact URL -// duplicate bookmarks. The BookmarkModelAssociator should not fall over when -// this happens. -TEST_F(ProfileSyncServiceTest, MergeDuplicates) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), - GURL("http://dup.com/")); - model_->AddURL(model_->other_node(), 0, ASCIIToUTF16("Dup"), - GURL("http://dup.com/")); - - EXPECT_EQ(2, model_->other_node()->GetChildCount()); - - // Restart the sync service to trigger model association. - StopSyncService(SAVE_TO_STORAGE); - StartSyncService(); - - EXPECT_EQ(2, model_->other_node()->GetChildCount()); - ExpectModelMatch(); -} - -TEST_F(ProfileSyncServiceTest, JsFrontendHandlersBasic) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); +#if defined(OS_CHROMEOS) && defined(GOOGLE_CHROME_BUILD) +#define MAYBE_JsFrontendHandlersBasic DISABLED_JsFrontendHandlersBasic +#else +#define MAYBE_JsFrontendHandlersBasic JsFrontendHandlersBasic +#endif +TEST_F(ProfileSyncServiceTest, MAYBE_JsFrontendHandlersBasic) { StartSyncService(); StrictMock<MockJsEventHandler> event_handler; - browser_sync::SyncBackendHostForProfileSyncTest* test_backend = + SyncBackendHostForProfileSyncTest* test_backend = service_->GetBackendForTest(); EXPECT_TRUE(service_->sync_initialized()); @@ -973,7 +163,7 @@ TEST_F(ProfileSyncServiceTest, JsFrontendHandlersBasic) { ASSERT_TRUE(test_backend->GetJsBackend() != NULL); EXPECT_EQ(NULL, test_backend->GetJsBackend()->GetParentJsEventRouter()); - browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + JsFrontend* js_backend = service_->GetJsFrontend(); js_backend->AddHandler(&event_handler); ASSERT_TRUE(test_backend->GetJsBackend() != NULL); EXPECT_TRUE(test_backend->GetJsBackend()->GetParentJsEventRouter() != NULL); @@ -984,30 +174,34 @@ TEST_F(ProfileSyncServiceTest, JsFrontendHandlersBasic) { TEST_F(ProfileSyncServiceTest, JsFrontendHandlersDelayedBackendInitialization) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncServiceAndSetInitialSyncEnded(true, false); + StartSyncServiceAndSetInitialSyncEnded(true, false, false, true); StrictMock<MockJsEventHandler> event_handler; EXPECT_CALL(event_handler, HandleJsEvent("onSyncServiceStateChanged", - HasArgs(JsArgList()))).Times(3); - // For some reason, these two events don't fire on Linux. + HasArgs(JsArgList()))).Times(AtLeast(3)); + // For some reason, these events may or may not fire. + // + // TODO(akalin): Figure out exactly why there's non-determinism + // here, and if possible remove it. EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _)) .Times(AtMost(1)); EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _)) .Times(AtMost(1)); + EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _)) + .Times(AtMost(1)); EXPECT_EQ(NULL, service_->GetBackendForTest()); EXPECT_FALSE(service_->sync_initialized()); - browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + JsFrontend* js_backend = service_->GetJsFrontend(); js_backend->AddHandler(&event_handler); // Since we're doing synchronous initialization, backend should be // initialized by this call. profile_->GetTokenService()->IssueAuthTokenForTest( GaiaConstants::kSyncService, "token"); - browser_sync::SyncBackendHostForProfileSyncTest* test_backend = + SyncBackendHostForProfileSyncTest* test_backend = service_->GetBackendForTest(); EXPECT_TRUE(service_->sync_initialized()); @@ -1020,10 +214,16 @@ TEST_F(ProfileSyncServiceTest, } TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasic) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); StartSyncService(); StrictMock<MockJsEventHandler> event_handler; + // For some reason, these events may or may not fire. + EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _)) + .Times(AtMost(1)); + EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _)) + .Times(AtMost(1)); + EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _)) + .Times(AtMost(1)); ListValue arg_list1; arg_list1.Append(Value::CreateBooleanValue(true)); @@ -1043,7 +243,7 @@ TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasic) { arg_list3.Append(arg_list2.DeepCopy()); JsArgList args3(arg_list3); - browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + JsFrontend* js_backend = service_->GetJsFrontend(); // Never replied to. js_backend->ProcessMessage("notRepliedTo", args3, &event_handler); @@ -1072,15 +272,16 @@ TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasic) { TEST_F(ProfileSyncServiceTest, JsFrontendProcessMessageBasicDelayedBackendInitialization) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncServiceAndSetInitialSyncEnded(true, false); + StartSyncServiceAndSetInitialSyncEnded(true, false, false, true); StrictMock<MockJsEventHandler> event_handler; - // For some reason, these two events don't fire on Linux. + // For some reason, these events may or may not fire. EXPECT_CALL(event_handler, HandleJsEvent("onChangesApplied", _)) .Times(AtMost(1)); EXPECT_CALL(event_handler, HandleJsEvent("onChangesComplete", _)) .Times(AtMost(1)); + EXPECT_CALL(event_handler, HandleJsEvent("onSyncNotificationStateChange", _)) + .Times(AtMost(1)); ListValue arg_list1; arg_list1.Append(Value::CreateBooleanValue(true)); @@ -1104,9 +305,9 @@ TEST_F(ProfileSyncServiceTest, const JsArgList kNoArgs; EXPECT_CALL(event_handler, HandleJsEvent("onSyncServiceStateChanged", - HasArgs(kNoArgs))).Times(3); + HasArgs(kNoArgs))).Times(AtLeast(3)); - browser_sync::JsFrontend* js_backend = service_->GetJsFrontend(); + JsFrontend* js_backend = service_->GetJsFrontend(); // We expect a reply for this message, even though its sent before // |event_handler| is added as a handler. @@ -1133,472 +334,9 @@ TEST_F(ProfileSyncServiceTest, js_backend->ProcessMessage("notRepliedTo", kNoArgs, &event_handler); } -struct TestData { - const wchar_t* title; - const char* url; -}; - -// TODO(ncarter): Integrate the existing TestNode/PopulateNodeFromString code -// in the bookmark model unittest, to make it simpler to set up test data -// here (and reduce the amount of duplication among tests), and to reduce the -// duplication. -class ProfileSyncServiceTestWithData : public ProfileSyncServiceTest { - protected: - // Populates or compares children of the given bookmark node from/with the - // given test data array with the given size. - void PopulateFromTestData(const BookmarkNode* node, - const TestData* data, - int size); - void CompareWithTestData(const BookmarkNode* node, - const TestData* data, - int size); - - void ExpectBookmarkModelMatchesTestData(); - void WriteTestDataToBookmarkModel(); -}; - -namespace { - -// Constants for bookmark model that looks like: -// |-- Bookmark bar -// | |-- u2, http://www.u2.com/ -// | |-- f1 -// | | |-- f1u4, http://www.f1u4.com/ -// | | |-- f1u2, http://www.f1u2.com/ -// | | |-- f1u3, http://www.f1u3.com/ -// | | +-- f1u1, http://www.f1u1.com/ -// | |-- u1, http://www.u1.com/ -// | +-- f2 -// | |-- f2u2, http://www.f2u2.com/ -// | |-- f2u4, http://www.f2u4.com/ -// | |-- f2u3, http://www.f2u3.com/ -// | +-- f2u1, http://www.f2u1.com/ -// +-- Other bookmarks -// |-- f3 -// | |-- f3u4, http://www.f3u4.com/ -// | |-- f3u2, http://www.f3u2.com/ -// | |-- f3u3, http://www.f3u3.com/ -// | +-- f3u1, http://www.f3u1.com/ -// |-- u4, http://www.u4.com/ -// |-- u3, http://www.u3.com/ -// --- f4 -// | |-- f4u1, http://www.f4u1.com/ -// | |-- f4u2, http://www.f4u2.com/ -// | |-- f4u3, http://www.f4u3.com/ -// | +-- f4u4, http://www.f4u4.com/ -// |-- dup -// | +-- dupu1, http://www.dupu1.com/ -// +-- dup -// +-- dupu2, http://www.dupu1.com/ -// -static TestData kBookmarkBarChildren[] = { - { L"u2", "http://www.u2.com/" }, - { L"f1", NULL }, - { L"u1", "http://www.u1.com/" }, - { L"f2", NULL }, -}; -static TestData kF1Children[] = { - { L"f1u4", "http://www.f1u4.com/" }, - { L"f1u2", "http://www.f1u2.com/" }, - { L"f1u3", "http://www.f1u3.com/" }, - { L"f1u1", "http://www.f1u1.com/" }, -}; -static TestData kF2Children[] = { - { L"f2u2", "http://www.f2u2.com/" }, - { L"f2u4", "http://www.f2u4.com/" }, - { L"f2u3", "http://www.f2u3.com/" }, - { L"f2u1", "http://www.f2u1.com/" }, -}; - -static TestData kOtherBookmarkChildren[] = { - { L"f3", NULL }, - { L"u4", "http://www.u4.com/" }, - { L"u3", "http://www.u3.com/" }, - { L"f4", NULL }, - { L"dup", NULL }, - { L"dup", NULL }, -}; -static TestData kF3Children[] = { - { L"f3u4", "http://www.f3u4.com/" }, - { L"f3u2", "http://www.f3u2.com/" }, - { L"f3u3", "http://www.f3u3.com/" }, - { L"f3u1", "http://www.f3u1.com/" }, -}; -static TestData kF4Children[] = { - { L"f4u1", "http://www.f4u1.com/" }, - { L"f4u2", "http://www.f4u2.com/" }, - { L"f4u3", "http://www.f4u3.com/" }, - { L"f4u4", "http://www.f4u4.com/" }, -}; -static TestData kDup1Children[] = { - { L"dupu1", "http://www.dupu1.com/" }, -}; -static TestData kDup2Children[] = { - { L"dupu2", "http://www.dupu2.com/" }, -}; - -} // anonymous namespace. - -void ProfileSyncServiceTestWithData::PopulateFromTestData( - const BookmarkNode* node, const TestData* data, int size) { - DCHECK(node); - DCHECK(data); - DCHECK(node->is_folder()); - for (int i = 0; i < size; ++i) { - const TestData& item = data[i]; - if (item.url) { - model_->AddURL(node, i, WideToUTF16Hack(item.title), GURL(item.url)); - } else { - model_->AddGroup(node, i, WideToUTF16Hack(item.title)); - } - } -} - -void ProfileSyncServiceTestWithData::CompareWithTestData( - const BookmarkNode* node, const TestData* data, int size) { - DCHECK(node); - DCHECK(data); - DCHECK(node->is_folder()); - for (int i = 0; i < size; ++i) { - const BookmarkNode* child_node = node->GetChild(i); - const TestData& item = data[i]; - EXPECT_EQ(child_node->GetTitle(), WideToUTF16Hack(item.title)); - if (item.url) { - EXPECT_FALSE(child_node->is_folder()); - EXPECT_TRUE(child_node->is_url()); - EXPECT_EQ(child_node->GetURL(), GURL(item.url)); - } else { - EXPECT_TRUE(child_node->is_folder()); - EXPECT_FALSE(child_node->is_url()); - } - } -} - -// TODO(munjal): We should implement some way of generating random data and can -// use the same seed to generate the same sequence. -void ProfileSyncServiceTestWithData::WriteTestDataToBookmarkModel() { - const BookmarkNode* bookmarks_bar_node = model_->GetBookmarkBarNode(); - PopulateFromTestData(bookmarks_bar_node, - kBookmarkBarChildren, - arraysize(kBookmarkBarChildren)); - - ASSERT_GE(bookmarks_bar_node->GetChildCount(), 4); - const BookmarkNode* f1_node = bookmarks_bar_node->GetChild(1); - PopulateFromTestData(f1_node, kF1Children, arraysize(kF1Children)); - const BookmarkNode* f2_node = bookmarks_bar_node->GetChild(3); - PopulateFromTestData(f2_node, kF2Children, arraysize(kF2Children)); - - const BookmarkNode* other_bookmarks_node = model_->other_node(); - PopulateFromTestData(other_bookmarks_node, - kOtherBookmarkChildren, - arraysize(kOtherBookmarkChildren)); - - ASSERT_GE(other_bookmarks_node->GetChildCount(), 6); - const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); - PopulateFromTestData(f3_node, kF3Children, arraysize(kF3Children)); - const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); - PopulateFromTestData(f4_node, kF4Children, arraysize(kF4Children)); - const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); - PopulateFromTestData(dup_node, kDup1Children, arraysize(kDup1Children)); - dup_node = other_bookmarks_node->GetChild(5); - PopulateFromTestData(dup_node, kDup2Children, arraysize(kDup2Children)); - - ExpectBookmarkModelMatchesTestData(); -} - -void ProfileSyncServiceTestWithData::ExpectBookmarkModelMatchesTestData() { - const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); - CompareWithTestData(bookmark_bar_node, - kBookmarkBarChildren, - arraysize(kBookmarkBarChildren)); - - ASSERT_GE(bookmark_bar_node->GetChildCount(), 4); - const BookmarkNode* f1_node = bookmark_bar_node->GetChild(1); - CompareWithTestData(f1_node, kF1Children, arraysize(kF1Children)); - const BookmarkNode* f2_node = bookmark_bar_node->GetChild(3); - CompareWithTestData(f2_node, kF2Children, arraysize(kF2Children)); - - const BookmarkNode* other_bookmarks_node = model_->other_node(); - CompareWithTestData(other_bookmarks_node, - kOtherBookmarkChildren, - arraysize(kOtherBookmarkChildren)); - - ASSERT_GE(other_bookmarks_node->GetChildCount(), 6); - const BookmarkNode* f3_node = other_bookmarks_node->GetChild(0); - CompareWithTestData(f3_node, kF3Children, arraysize(kF3Children)); - const BookmarkNode* f4_node = other_bookmarks_node->GetChild(3); - CompareWithTestData(f4_node, kF4Children, arraysize(kF4Children)); - const BookmarkNode* dup_node = other_bookmarks_node->GetChild(4); - CompareWithTestData(dup_node, kDup1Children, arraysize(kDup1Children)); - dup_node = other_bookmarks_node->GetChild(5); - CompareWithTestData(dup_node, kDup2Children, arraysize(kDup2Children)); -} - -// Tests persistence of the profile sync service by destroying the -// profile sync service and then reloading it from disk. -TEST_F(ProfileSyncServiceTestWithData, Persistence) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - WriteTestDataToBookmarkModel(); - - ExpectModelMatch(); - - // Force both models to discard their data and reload from disk. This - // simulates what would happen if the browser were to shutdown normally, - // and then relaunch. - StopSyncService(SAVE_TO_STORAGE); - LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - ExpectBookmarkModelMatchesTestData(); - - // With the BookmarkModel contents verified, ExpectModelMatch will - // verify the contents of the sync model. - ExpectModelMatch(); -} - -// Tests the merge case when the BookmarkModel is non-empty but the -// sync model is empty. This corresponds to uploading browser -// bookmarks to an initially empty, new account. -TEST_F(ProfileSyncServiceTestWithData, MergeWithEmptySyncModel) { - // Don't start the sync service until we've populated the bookmark model. - LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); - - WriteTestDataToBookmarkModel(); - - // Restart the profile sync service. This should trigger a merge step - // during initialization -- we expect the browser bookmarks to be written - // to the sync service during this call. - StartSyncService(); - - // Verify that the bookmark model hasn't changed, and that the sync model - // matches it exactly. - ExpectBookmarkModelMatchesTestData(); - ExpectModelMatch(); -} - -// Tests the merge case when the BookmarkModel is empty but the sync model is -// non-empty. This corresponds (somewhat) to a clean install of the browser, -// with no bookmarks, connecting to a sync account that has some bookmarks. -TEST_F(ProfileSyncServiceTestWithData, MergeWithEmptyBookmarkModel) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - WriteTestDataToBookmarkModel(); - - ExpectModelMatch(); - - // Force the sync service to shut down and write itself to disk. - StopSyncService(SAVE_TO_STORAGE); - - // Blow away the bookmark model -- it should be empty afterwards. - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - EXPECT_EQ(model_->GetBookmarkBarNode()->GetChildCount(), 0); - EXPECT_EQ(model_->other_node()->GetChildCount(), 0); - - // Now restart the sync service. Starting it should populate the bookmark - // model -- test for consistency. - StartSyncService(); - ExpectBookmarkModelMatchesTestData(); - ExpectModelMatch(); -} - -// Tests the merge cases when both the models are expected to be identical -// after the merge. -TEST_F(ProfileSyncServiceTestWithData, MergeExpectedIdenticalModels) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - WriteTestDataToBookmarkModel(); - ExpectModelMatch(); - StopSyncService(SAVE_TO_STORAGE); - - // At this point both the bookmark model and the server should have the - // exact same data and it should match the test data. - LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - ExpectBookmarkModelMatchesTestData(); - ExpectModelMatch(); - StopSyncService(SAVE_TO_STORAGE); - - // Now reorder some bookmarks in the bookmark model and then merge. Make - // sure we get the order of the server after merge. - LoadBookmarkModel(LOAD_FROM_STORAGE, DONT_SAVE_TO_STORAGE); - ExpectBookmarkModelMatchesTestData(); - const BookmarkNode* bookmark_bar = model_->GetBookmarkBarNode(); - ASSERT_TRUE(bookmark_bar); - ASSERT_GT(bookmark_bar->GetChildCount(), 1); - model_->Move(bookmark_bar->GetChild(0), bookmark_bar, 1); - StartSyncService(); - ExpectModelMatch(); - ExpectBookmarkModelMatchesTestData(); -} - -// Tests the merge cases when both the models are expected to be identical -// after the merge. -TEST_F(ProfileSyncServiceTestWithData, MergeModelsWithSomeExtras) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - WriteTestDataToBookmarkModel(); - ExpectBookmarkModelMatchesTestData(); - - // Remove some nodes and reorder some nodes. - const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); - int remove_index = 2; - ASSERT_GT(bookmark_bar_node->GetChildCount(), remove_index); - const BookmarkNode* child_node = bookmark_bar_node->GetChild(remove_index); - ASSERT_TRUE(child_node); - ASSERT_TRUE(child_node->is_url()); - model_->Remove(bookmark_bar_node, remove_index); - ASSERT_GT(bookmark_bar_node->GetChildCount(), remove_index); - child_node = bookmark_bar_node->GetChild(remove_index); - ASSERT_TRUE(child_node); - ASSERT_TRUE(child_node->is_folder()); - model_->Remove(bookmark_bar_node, remove_index); - - const BookmarkNode* other_node = model_->other_node(); - ASSERT_GE(other_node->GetChildCount(), 1); - const BookmarkNode* f3_node = other_node->GetChild(0); - ASSERT_TRUE(f3_node); - ASSERT_TRUE(f3_node->is_folder()); - remove_index = 2; - ASSERT_GT(f3_node->GetChildCount(), remove_index); - model_->Remove(f3_node, remove_index); - ASSERT_GT(f3_node->GetChildCount(), remove_index); - model_->Remove(f3_node, remove_index); - - StartSyncService(); - ExpectModelMatch(); - StopSyncService(SAVE_TO_STORAGE); - - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - WriteTestDataToBookmarkModel(); - ExpectBookmarkModelMatchesTestData(); - - // Remove some nodes and reorder some nodes. - bookmark_bar_node = model_->GetBookmarkBarNode(); - remove_index = 0; - ASSERT_GT(bookmark_bar_node->GetChildCount(), remove_index); - child_node = bookmark_bar_node->GetChild(remove_index); - ASSERT_TRUE(child_node); - ASSERT_TRUE(child_node->is_url()); - model_->Remove(bookmark_bar_node, remove_index); - ASSERT_GT(bookmark_bar_node->GetChildCount(), remove_index); - child_node = bookmark_bar_node->GetChild(remove_index); - ASSERT_TRUE(child_node); - ASSERT_TRUE(child_node->is_folder()); - model_->Remove(bookmark_bar_node, remove_index); - - ASSERT_GE(bookmark_bar_node->GetChildCount(), 2); - model_->Move(bookmark_bar_node->GetChild(0), bookmark_bar_node, 1); - - other_node = model_->other_node(); - ASSERT_GE(other_node->GetChildCount(), 1); - f3_node = other_node->GetChild(0); - ASSERT_TRUE(f3_node); - ASSERT_TRUE(f3_node->is_folder()); - remove_index = 0; - ASSERT_GT(f3_node->GetChildCount(), remove_index); - model_->Remove(f3_node, remove_index); - ASSERT_GT(f3_node->GetChildCount(), remove_index); - model_->Remove(f3_node, remove_index); - - ASSERT_GE(other_node->GetChildCount(), 4); - model_->Move(other_node->GetChild(0), other_node, 1); - model_->Move(other_node->GetChild(2), other_node, 3); - - StartSyncService(); - ExpectModelMatch(); - - // After the merge, the model should match the test data. - ExpectBookmarkModelMatchesTestData(); -} - -// Tests that when persisted model associations are used, things work fine. -TEST_F(ProfileSyncServiceTestWithData, ModelAssociationPersistence) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - WriteTestDataToBookmarkModel(); - StartSyncService(); - ExpectModelMatch(); - // Force the sync service to shut down and write itself to disk. - StopSyncService(SAVE_TO_STORAGE); - // Now restart the sync service. This time it should use the persistent - // associations. - StartSyncService(); - ExpectModelMatch(); -} - -// Tests that when persisted model associations are used, things work fine. -TEST_F(ProfileSyncServiceTestWithData, ModelAssociationInvalidPersistence) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - WriteTestDataToBookmarkModel(); - StartSyncService(); - ExpectModelMatch(); - // Force the sync service to shut down and write itself to disk. - StopSyncService(SAVE_TO_STORAGE); - // Change the bookmark model before restarting sync service to simulate - // the situation where bookmark model is different from sync model and - // make sure model associator correctly rebuilds associations. - const BookmarkNode* bookmark_bar_node = model_->GetBookmarkBarNode(); - model_->AddURL(bookmark_bar_node, 0, ASCIIToUTF16("xtra"), - GURL("http://www.xtra.com")); - // Now restart the sync service. This time it will try to use the persistent - // associations and realize that they are invalid and hence will rebuild - // associations. - StartSyncService(); - ExpectModelMatch(); -} - -TEST_F(ProfileSyncServiceTestWithData, SortChildren) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, DONT_SAVE_TO_STORAGE); - StartSyncService(); - - // Write test data to bookmark model and verify that the models match. - WriteTestDataToBookmarkModel(); - const BookmarkNode* folder_added = model_->other_node()->GetChild(0); - ASSERT_TRUE(folder_added); - ASSERT_TRUE(folder_added->is_folder()); - - ExpectModelMatch(); - - // Sort the other-bookmarks children and expect that hte models match. - model_->SortChildren(folder_added); - ExpectModelMatch(); -} - -// See what happens if we enable sync but then delete the "Sync Data" -// folder. -TEST_F(ProfileSyncServiceTestWithData, RecoverAfterDeletingSyncDataDirectory) { - LoadBookmarkModel(DELETE_EXISTING_STORAGE, SAVE_TO_STORAGE); - StartSyncService(); - - WriteTestDataToBookmarkModel(); - - // While the service is running. - FilePath sync_data_directory = - service_->GetBackendForTest()->sync_data_folder_path(); - - // Simulate a normal shutdown for the sync service (don't disable it for - // the user, which would reset the preferences and delete the sync data - // directory). - StopSyncService(SAVE_TO_STORAGE); - - // Now pretend that the user has deleted this directory from the disk. - file_util::Delete(sync_data_directory, true); - - // Restart the sync service. Don't fake out setting initial sync ended; lets - // make sure the system does in fact nudge and wait for this to happen. - StartSyncServiceAndSetInitialSyncEnded(false, true); - - // Make sure we're back in sync. In real life, the user would need - // to reauthenticate before this happens, but in the test, authentication - // is sidestepped. - ExpectBookmarkModelMatchesTestData(); - ExpectModelMatch(); -} - // Make sure that things still work if sync is not enabled, but some old sync // databases are lingering in the "Sync Data" folder. -TEST_F(ProfileSyncServiceTestWithData, TestStartupWithOldSyncData) { +TEST_F(ProfileSyncServiceTest, TestStartupWithOldSyncData) { const char* nonsense1 = "reginald"; const char* nonsense2 = "beartato"; const char* nonsense3 = "harrison"; @@ -1607,53 +345,24 @@ TEST_F(ProfileSyncServiceTestWithData, TestStartupWithOldSyncData) { temp_directory.AppendASCII("BookmarkSyncSettings.sqlite3"); FilePath sync_file2 = temp_directory.AppendASCII("SyncData.sqlite3"); FilePath sync_file3 = temp_directory.AppendASCII("nonsense_file"); - file_util::CreateDirectory(temp_directory); - file_util::WriteFile(sync_file1, nonsense1, strlen(nonsense1)); - file_util::WriteFile(sync_file2, nonsense2, strlen(nonsense2)); - file_util::WriteFile(sync_file3, nonsense3, strlen(nonsense3)); - - LoadBookmarkModel(LOAD_FROM_STORAGE, SAVE_TO_STORAGE); - if (!service_.get()) { - service_.reset( - new TestProfileSyncService(&factory_, profile_.get(), - "test", true, NULL)); - service_->dont_set_initial_sync_ended_on_init(); - service_->set_synchronous_sync_configuration(); - profile_->GetPrefs()->SetBoolean(prefs::kSyncHasSetupCompleted, false); - - model_associator_ = new TestBookmarkModelAssociator(service_.get(), - service_.get()); - change_processor_ = new BookmarkChangeProcessor(model_associator_, - service_.get()); - EXPECT_CALL(factory_, CreateBookmarkSyncComponents(_, _)). - WillOnce(Return(ProfileSyncFactory::SyncComponents( - model_associator_, change_processor_))); - EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). - WillOnce(ReturnNewDataTypeManager()); - - service_->RegisterDataTypeController( - new browser_sync::BookmarkDataTypeController(&factory_, - profile_.get(), - service_.get())); - - service_->Initialize(); // will call disableForUser because sync setup - // hasn't been completed. - } + ASSERT_TRUE(file_util::CreateDirectory(temp_directory)); + ASSERT_NE(-1, + file_util::WriteFile(sync_file1, nonsense1, strlen(nonsense1))); + ASSERT_NE(-1, + file_util::WriteFile(sync_file2, nonsense2, strlen(nonsense2))); + ASSERT_NE(-1, + file_util::WriteFile(sync_file3, nonsense3, strlen(nonsense3))); - ASSERT_FALSE(service_->HasSyncSetupCompleted()); + StartSyncServiceAndSetInitialSyncEnded(false, false, true, false); + EXPECT_FALSE(service_->HasSyncSetupCompleted()); - // Create some tokens in the token service; the service will startup when - // it is notified that tokens are available. + // Since we're doing synchronous initialization, backend should be + // initialized by this call. profile_->GetTokenService()->IssueAuthTokenForTest( - GaiaConstants::kSyncService, "sync_token"); - - syncable::ModelTypeSet set; - set.insert(syncable::BOOKMARKS); - service_->OnUserChoseDatatypes(false, set); - - MessageLoop::current()->RunAllPending(); + GaiaConstants::kSyncService, "token"); - // Stop the service so we can read the new Sync Data files that were created. + // Stop the service so we can read the new Sync Data files that were + // created. service_.reset(); // This file should have been deleted when the whole directory was nuked. @@ -1663,6 +372,10 @@ TEST_F(ProfileSyncServiceTestWithData, TestStartupWithOldSyncData) { // This will still exist, but the text should have changed. ASSERT_TRUE(file_util::PathExists(sync_file2)); std::string file2text; - file_util::ReadFileToString(sync_file2, &file2text); - ASSERT_FALSE(file2text.compare(nonsense2) == 0); + ASSERT_TRUE(file_util::ReadFileToString(sync_file2, &file2text)); + ASSERT_NE(file2text.compare(nonsense2), 0); } + +} // namespace + +} // namespace browser_sync diff --git a/chrome/browser/sync/profile_sync_test_util.h b/chrome/browser/sync/profile_sync_test_util.h index 5efa015..9adfe3c 100644 --- a/chrome/browser/sync/profile_sync_test_util.h +++ b/chrome/browser/sync/profile_sync_test_util.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,15 +8,15 @@ #include <string> +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/ref_counted.h" -#include "base/scoped_ptr.h" #include "base/synchronization/waitable_event.h" #include "chrome/browser/sync/profile_sync_service_observer.h" -#include "chrome/common/notification_service.h" -#include "chrome/common/notification_source.h" -#include "chrome/common/notification_type.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_service.h" +#include "content/common/notification_source.h" +#include "content/common/notification_type.h" #include "testing/gmock/include/gmock/gmock.h" namespace base { diff --git a/chrome/browser/sync/protocol/autofill_specifics.proto b/chrome/browser/sync/protocol/autofill_specifics.proto index 520f25f..ae35da2 100644 --- a/chrome/browser/sync/protocol/autofill_specifics.proto +++ b/chrome/browser/sync/protocol/autofill_specifics.proto @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -81,7 +81,7 @@ message AutofillSpecifics { repeated int64 usage_timestamp = 3; // An autofill++ profile object. If present, indicates this entity - // represents an AutoFillProfile exclusively, and no other fields (such as + // represents an AutofillProfile exclusively, and no other fields (such as // name/value or credit_card) should be present. optional AutofillProfileSpecifics profile = 4; diff --git a/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc b/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc index 20e73e4..fef155b 100644 --- a/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc +++ b/chrome/browser/sync/protocol/proto_value_conversions_unittest.cc @@ -6,7 +6,7 @@ #include "chrome/browser/sync/protocol/proto_value_conversions.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/values.h" #include "chrome/browser/sync/protocol/app_specifics.pb.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" diff --git a/chrome/browser/sync/protocol/sync.proto b/chrome/browser/sync/protocol/sync.proto index a87b0b3..48cb214 100644 --- a/chrome/browser/sync/protocol/sync.proto +++ b/chrome/browser/sync/protocol/sync.proto @@ -105,17 +105,20 @@ message SyncEntity { // Present in both GetUpdatesResponse and CommitMessage. optional int64 ctime = 6; - // A unique-in-the parent name for this item. + // The name of this item. + // Historical note: + // Since November 2010, this value is no different from non_unique_name. + // Before then, server implementations would maintain a unique-within-parent + // value separate from its base, "non-unique" value. Clients had not + // depended on the uniqueness of the property since November 2009; it was + // removed from Chromium by http://codereview.chromium.org/371029 . // Present in both GetUpdatesResponse and CommitMessage. required string name = 7; - // non_unique_name holds the base name stored serverside, which is different - // from |name| when |name| has been suffixed in a way to make it unique - // among its siblings. In a GetUpdatesResponse, |non_unique_name| will - // be supplied in addition to |name|, and the client may choose which - // field to use depending on its needs. In a CommitMessage, - // |non_unique_name| takes precedence over the |name| value if both are - // supplied. + // The name of this item. Same as |name|. + // |non_unique_name| should take precedence over the |name| value if both + // are supplied. For efficiency, clients and servers should avoid setting + // this redundant value. // Present in both GetUpdatesResponse and CommitMessage. optional string non_unique_name = 8; @@ -273,7 +276,7 @@ message GetUpdatesCallerInfo { PERIODIC = 4; // The source of the update was periodic polling. SYNC_CYCLE_CONTINUATION = 5; // The source of the update was a // continuation of a previous update. - CLEAR_PRIVATE_DATA = 6; // Source is a call to remove all private data + CLEAR_PRIVATE_DATA = 6; // Source is a call to remove all private data } required GetUpdatesSource source = 1; @@ -363,6 +366,14 @@ message GetUpdatesMessage { // Per-datatype progress marker. If present, the server will ignore // the values of requested_types and from_timestamp, using this instead. repeated DataTypeProgressMarker from_progress_marker = 6; + + // Indicates whether the response should be sent in chunks. This may be + // needed for devices with limited memory resources. If true, the response + // will include one or more ClientToServerResponses, with the frist one + // containing GetUpdatesMetadataResponse, and the remaining ones, if any, + // containing GetUpdatesStreamingResponse. These ClientToServerResponses are + // delimited by a length prefix, which is encoded as a varint. + optional bool streaming = 7 [default = false]; }; message AuthenticateMessage { @@ -487,6 +498,29 @@ message GetUpdatesResponse { repeated DataTypeProgressMarker new_progress_marker = 5; }; +// The metadata response for GetUpdatesMessage. This response is sent when +// streaming is set to true in the request. It is prefixed with a length +// delimiter, which is encoded in varint. +message GetUpdatesMetadataResponse { + // Approximate count of changes remaining. Detailed comment is available in + // GetUpdatesResponse. + optional int64 changes_remaining = 1; + + // Opaque, per-datatype timestamp-like tokens. Detailed comment is available + // in GetUpdatesResponse. + repeated DataTypeProgressMarker new_progress_marker = 2; +}; + +// The streaming response message for GetUpdatesMessage. This message is sent +// when streaming is set to true in the request. There may be multiple +// GetUpdatesStreamingResponse messages in a response. This type of messages +// is preceded by GetUpdatesMetadataResponse. It is prefixed with a length +// delimiter, which is encoded in varint. +message GetUpdatesStreamingResponse { + // New sync entries that the client should apply. + repeated SyncEntity entries = 1; +}; + // A user-identifying struct. For a given Google account the email and display // name can change, but obfuscated_id should be constant. // The obfuscated id is optional because at least one planned use of the proto @@ -526,6 +560,10 @@ message ClientToServerResponse { optional GetUpdatesResponse get_updates = 2; optional AuthenticateResponse authenticate = 3; optional ClearUserDataResponse clear_user_data = 9; + optional GetUpdatesMetadataResponse stream_metadata = 10; + // If GetUpdatesStreamingResponse is contained in the ClientToServerResponse, + // none of the other fields (error_code and etc) will be set. + optional GetUpdatesStreamingResponse stream_data = 11; enum ErrorType { SUCCESS = 0; @@ -544,6 +582,10 @@ message ClientToServerResponse { // come back later. TRANSIENT_ERROR = 8; // A transient error occured (eg. backend // timeout). Client should try again later. + MIGRATION_DONE = 9; // Migration has finished for one or more data + // types. Client should clear the cache for + // these data types only and then re-sync with + // a server. UNKNOWN = 100; // Unknown value. This should never be explicitly // used; it is the default value when an // out-of-date client parses a value it doesn't @@ -567,5 +609,9 @@ message ClientToServerResponse { optional ClientCommand client_command = 7; optional ProfilingData profiling_data = 8; + + // The data types whose storage has been migrated. Present when the value of + // error_code is MIGRATION_DONE. + repeated int32 migrated_data_type_id = 12; }; diff --git a/chrome/browser/sync/protocol/sync_proto.gyp b/chrome/browser/sync/protocol/sync_proto.gyp index a32beff..f505f41 100644 --- a/chrome/browser/sync/protocol/sync_proto.gyp +++ b/chrome/browser/sync/protocol/sync_proto.gyp @@ -5,6 +5,7 @@ { 'variables': { 'chromium_code': 1, + 'protoc_out_dir': '<(SHARED_INTERMEDIATE_DIR)/protoc_out', }, 'targets': [ { @@ -36,14 +37,14 @@ ], 'outputs': [ '<(PRODUCT_DIR)/pyproto/sync_pb/<(RULE_INPUT_ROOT)_pb2.py', - '<(SHARED_INTERMEDIATE_DIR)/protoc_out/chrome/browser/sync/protocol/<(RULE_INPUT_ROOT).pb.h', - '<(SHARED_INTERMEDIATE_DIR)/protoc_out/chrome/browser/sync/protocol/<(RULE_INPUT_ROOT).pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/<(RULE_INPUT_ROOT).pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/<(RULE_INPUT_ROOT).pb.cc', ], 'action': [ '<(PRODUCT_DIR)/<(EXECUTABLE_PREFIX)protoc<(EXECUTABLE_SUFFIX)', '--proto_path=.', './<(RULE_INPUT_ROOT)<(RULE_INPUT_EXT)', - '--cpp_out=<(SHARED_INTERMEDIATE_DIR)/protoc_out/chrome/browser/sync/protocol', + '--cpp_out=<(protoc_out_dir)/chrome/browser/sync/protocol', '--python_out=<(PRODUCT_DIR)/pyproto/sync_pb', ], 'message': 'Generating C++ and Python code from <(RULE_INPUT_PATH)', @@ -55,7 +56,33 @@ }, { 'target_name': 'sync_proto_cpp', - 'type': 'none', + 'type': '<(library)', + 'sources': [ + '<(protoc_out_dir)/chrome/browser/sync/protocol/sync.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/sync.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/encryption.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/encryption.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/app_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/app_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/autofill_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/autofill_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/bookmark_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/bookmark_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/extension_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/extension_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/nigori_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/nigori_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/password_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/password_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/preference_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/preference_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/session_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/session_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/theme_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/theme_specifics.pb.h', + '<(protoc_out_dir)/chrome/browser/sync/protocol/typed_url_specifics.pb.cc', + '<(protoc_out_dir)/chrome/browser/sync/protocol/typed_url_specifics.pb.h', + ], 'export_dependent_settings': [ '../../../../third_party/protobuf/protobuf.gyp:protobuf_lite', 'sync_proto', @@ -66,9 +93,12 @@ ], 'direct_dependent_settings': { 'include_dirs': [ - '<(SHARED_INTERMEDIATE_DIR)/protoc_out', + '<(protoc_out_dir)', ], }, + # This target exports a hard dependency because it includes generated + # header files. + 'hard_dependency': 1, }, ], } diff --git a/chrome/browser/sync/resources/configure.html b/chrome/browser/sync/resources/configure.html index 12b4f30..2c2f790 100644 --- a/chrome/browser/sync/resources/configure.html +++ b/chrome/browser/sync/resources/configure.html @@ -3,17 +3,42 @@ <title></title> <style type="text/css"> body { - line-height: 1.33em; - background: #FFFFFF; - font-size: 11pt; + } -html[os='mac'] body { - line-height: 1.5em; - background: #FFFFFF; + body { + } + +h4 { + margin: 10px 0; +} + +hr { + background-color: #ddd; + border: 0; + height: 1px; + margin: 5px 0; + text-align: left; + width: 100%; +} + form { -webkit-user-select: none; } + +.content-area { + padding: 10px 15px; +} + +.action-area { + -webkit-box-align: center; + -webkit-box-orient: horizontal; + -webkit-box-pack: end; + border-top: 1px solid rgba(188, 193, 208, .5); + display: -webkit-box; + padding: 12px; +} + .sync-header { font-size: 1.2em; font-weight: bold; @@ -22,6 +47,35 @@ form { .sync-select-customization { margin-top: 10px; } + +.action-area-link-container { + -webkit-box-flex: 1; +} + +#sync-passphrase-container { + margin: 10px 0; +} + +#sync-custom-passphrase { + margin: 0 25px; +} + +#sync-passphrase-message { + color: gray; +} + +.sync-custom-passphrase-input { + margin: 10px 0; +} + +#sync-select-container { + margin-bottom: 10px; +} + +#sync-instructions-container { + margin-bottom: 30px; +} + #chooseDataTypesRadio { vertical-align: top; } @@ -29,15 +83,10 @@ form { display: inline-block; } #chooseDataTypesBody { - width: 90%; - -webkit-margin-start: 3ex; + margin: 10px 0; } #chooseDataTypesBody > div { - margin-top: 0px; - -webkit-column-count: 2; - -webkit-column-gap: 10px; - column-count: 2; - column-gap: 10px; + -webkit-column-count: 3; } .sync-config-tab-contents-inactive { @@ -153,10 +202,6 @@ form { margin-bottom: 5px; } -#learn-more-link { - float: right; -} - #change-passphrase { margin: 10px 0; background: #EEE; @@ -186,9 +231,8 @@ html[os='mac'] input[type='submit'] { </style> <script src="chrome://resources/js/cr.js"></script> +<!-- TODO(jhawkins): util.js for $(). --> <script> - var currentTab; - // Called once, when this html/js is loaded. function initializeConfigureDialog(args) { // Allow platform specific rules @@ -198,13 +242,52 @@ html[os='mac'] input[type='submit'] { document.documentElement.setAttribute('os', 'linux'); } + var datatypeSelect = document.getElementById('sync-select-datatypes'); + datatypeSelect.onchange = function() { + var syncAll = this.selectedIndex == 0; + setCheckboxesToKeepEverythingSynced(syncAll); + + document.getElementById('chooseDataTypesBody').hidden = syncAll; + }; + if (args) { - currentTab = args['initialTab']; - switchToTab(currentTab); setCheckboxesAndErrors(args); + + // Whether to display the 'Sync everything' confirmation screen or the + // customize data types screen. + // TODO(jhawkins): Rename |keepEverythingSynced| to |syncAllDataTypes|. + var syncEverything = args['syncEverything']; + var syncAllDataTypes = args['keepEverythingSynced']; + var usePassphrase = args['usePassphrase']; + if (syncEverything == false || syncAllDataTypes == false || + usePassphrase) { + showCustomizePage(); + } } } + function showCustomizePage() { + document.getElementById('confirm-sync-preferences').hidden = true; + document.getElementById('customize-sync-preferences').hidden = false; + + // If the user is shown the 'Customize' page, it's likely he intends to + // change the data types. Select the 'Choose data types' option in this + // case. + document.getElementById('sync-select-datatypes').selectedIndex = 1; + document.getElementById('chooseDataTypesBody').hidden = false; + setDataTypeCheckboxesEnabled(true); + } + + function showSyncEverythingPage() { + document.getElementById('confirm-sync-preferences').hidden = false; + document.getElementById('customize-sync-preferences').hidden = true; + + // The default state is to sync everything and to not use a custom + // passphrase. + setCheckboxesToKeepEverythingSynced(true); + document.getElementById("google-option").checked = true; + } + function setCheckboxesAndErrors(args) { setChooseDataTypesCheckboxes(args); setEncryptionCheckboxes(args); @@ -236,10 +319,10 @@ html[os='mac'] input[type='submit'] { document.getElementById("okButton").focus(); } - document.getElementById("keepEverythingSyncedRadio").checked = + var datatypeSelect = document.getElementById('sync-select-datatypes'); + datatypeSelect.selectedIndex = args.keepEverythingSynced ? 0 : 1; + document.getElementById('chooseDataTypesBody').hidden = args.keepEverythingSynced; - document.getElementById("chooseDataTypesRadio").checked = - !args.keepEverythingSynced; document.getElementById("bookmarksCheckbox").checked = args.syncBookmarks; document.getElementById("preferencesCheckbox").checked = @@ -295,10 +378,9 @@ html[os='mac'] input[type='submit'] { // The passphrase, once set, cannot be unset, but we show a reset link. document.getElementById("explicit-option").disabled = true; document.getElementById("google-option").disabled = true; - document.getElementById("change-passphrase").style.display = "block"; + document.getElementById("sync-custom-passphrase").hidden = true; } else { document.getElementById("google-option").checked = true; - document.getElementById("change-passphrase").style.display = "none"; } switchToMode(""); } @@ -306,7 +388,8 @@ html[os='mac'] input[type='submit'] { function setErrorState(args) { if (!args.was_aborted) return; - document.getElementById("aborted_text").className = "sync-error-show"; + + document.getElementById("aborted-text").className = "sync-error-show"; document.getElementById("okButton").disabled = true; document.getElementById("keepEverythingSyncedRadio").disabled = true; document.getElementById("chooseDataTypesRadio").disabled = true; @@ -325,8 +408,6 @@ html[os='mac'] input[type='submit'] { } } - - // Returns true if at least one data type is enabled and no data types are // checked. (If all data type checkboxes are disabled, it's because "keep // everything synced" is checked.) @@ -348,11 +429,11 @@ html[os='mac'] input[type='submit'] { function sendConfiguration() { // Trying to submit, so hide previous errors. - document.getElementById("aborted_text").className = "sync-error-hide"; - document.getElementById("error_text").className = "sync-error-hide"; + document.getElementById("aborted-text").className = "sync-error-hide"; + document.getElementById("error-text").className = "sync-error-hide"; if (noDataTypesChecked()) { - document.getElementById("error_text").className = "sync-error-show"; + document.getElementById("error-text").className = "sync-error-show"; return; } @@ -361,7 +442,9 @@ html[os='mac'] input[type='submit'] { return false; } - var syncAll = f.keepEverythingSyncedRadio.checked; + var syncAll = + document.getElementById('sync-select-datatypes').selectedIndex == 0; + // These values need to be kept in sync with where they are read in // SyncSetupFlow::GetDataTypeChoiceData(). var result = JSON.stringify({ @@ -381,35 +464,11 @@ html[os='mac'] input[type='submit'] { chrome.send("Configure", [result]); } - function switchToTab(newTab) { - if (currentTab) { - document.getElementById(currentTab + "-tab").className = - "sync-config-tab-inactive"; - document.getElementById(currentTab + "-tab-contents").className = - "sync-config-tab-contents-inactive"; - } - - // Default to the 'Data Types' tab. - if (!newTab) - newTab = "data-type"; - - document.getElementById(newTab + "-tab").className = - "sync-config-tab-active"; - document.getElementById(newTab + "-tab-contents").className = - "sync-config-tab-contents-active"; - - currentTab = newTab; - } - function switchToMode(mode) { - document.getElementById("section-explicit").style.display = "none"; - document.getElementById("section-google").style.display = "none"; - - if (mode == "google") { - document.getElementById("section-google").style.display = "block"; - } else if (mode =="explicit") { - document.getElementById("section-explicit").style.display = "block"; - } + if (mode == "google") + document.getElementById("sync-custom-passphrase").hidden = true; + else if (mode =="explicit") + document.getElementById("sync-custom-passphrase").hidden = false; } function getRadioCheckedValue() { @@ -440,7 +499,10 @@ html[os='mac'] input[type='submit'] { emptyError.style.display = "block"; return false; } - if (f.confirmpassphrase.value != f.passphrase.value) { + + var confirmPassphrase = document.getElementById("confirm-passphrase"); + var passphrase = document.getElementById("passphrase"); + if (confirmPassphrase.value != passphrase.value) { mismatchError.style.display = "block"; return false; } @@ -456,38 +518,37 @@ html[os='mac'] input[type='submit'] { </head> <body i18n-values=".style.fontFamily:fontfamily" onload="initializeConfigureDialog(JSON.parse(chrome.dialogArguments));"> -<form id="chooseDataTypesForm" onSubmit="sendConfiguration(); return false;"> - - <div class="sync-config-tabstrip"> - <div id="data-type-tab" class="sync-config-tab-inactive"> - <a href="#" onclick="switchToTab('data-type'); return false;" - i18n-content="dataTypes"></a> - </div> - <div id="encryption-tab" class="sync-config-tab-inactive"> - <a href="#" onclick="switchToTab('encryption'); return false;" - i18n-content="encryption"></a> +<div id="confirm-sync-preferences"> + <div class="sync-header" i18n-content="confirmSyncPreferences"></div> + <div id="sync-instructions-container" class="content-area"> + <span i18n-content="choosedatatypesinstructions"></span> + <a i18n-values="href:encryptionhelpurl" target="_blank" + i18n-content="learnmore"></a> + </div> + <div> + <div class="action-area"> + <div class="action-area-link-container"> + <a id="customize-link" href="#" i18n-content="customizelinklabel" + onclick="showCustomizePage();"></a> + </div> + <input id="okButton" type="button" i18n-values="value:syncEverything" + onclick="sendConfiguration();"> + <input id="cancelButton" type="button" i18n-values="value:cancel" + onclick="chrome.send('DialogClose', [''])"> </div> </div> - <div id="data-type-tab-contents" class="sync-config-tab-contents-inactive"> - - <div class="sync-header" - i18n-content="choosedatatypesheader"></div> - <div class="sync-choose_data_types_instructions" - i18n-content="choosedatatypesinstructions"></div> - <div class="sync-select-customization"> - <div class="sync-choice_radio"> - <input id="keepEverythingSyncedRadio" type="radio" - name="syncChooseDataTypes" - onclick="setCheckboxesToKeepEverythingSynced(true);"> - <label for="keepEverythingSyncedRadio" - i18n-content="keepeverythingsynced"> - </label> - </div> - <div id="chooseDataTypes" class="sync-choice_radio"> - <input id="chooseDataTypesRadio" type="radio" name="syncChooseDataTypes" - onclick="setCheckboxesToKeepEverythingSynced(false)"> - <label for="chooseDataTypesRadio" i18n-content="choosedatatypes" ></label> - <div id="chooseDataTypesBody"> +</div> +<div id="customize-sync-preferences" hidden> + <form id="chooseDataTypesForm" onSubmit="sendConfiguration(); return false;"> + <div class="sync-header">Customize Sync Preferences</div> + <hr> + <div id="sync-configure-content" class="content-area"> + <div id="sync-select-container"> + <select id="sync-select-datatypes"> + <option i18n-content="keepeverythingsynced"></option> + <option i18n-content="choosedatatypes" selected></option> + </select> + <div id="chooseDataTypesBody" hidden> <div> <!-- Apps --> <div class="sync-item-show" id="appsItem"> @@ -555,70 +616,58 @@ html[os='mac'] input[type='submit'] { </div> </div> <div class="sync-errors"> - <span id="error_text" i18n-content="synczerodatatypeserror" + <span id="error-text" i18n-content="synczerodatatypeserror" class="sync-error-hide"></span> - <span id="aborted_text" i18n-content="abortederror" + <span id="aborted-text" i18n-content="abortederror" class="sync-error-hide"></span> </div> - </div> - - </div> - <div id="encryption-tab-contents" class="sync-config-tab-contents-inactive"> - <div id="sync-encryption-instructions" - i18n-content="encryptionInstructions"></div> - - <div> - <input id="google-option" name="option" type="radio" - value="google" onchange="onRadioChange();"> - <label for="google-option" i18n-content="googleOption"></label> - </input> - </div> - <div> - <input id="explicit-option" name="option" type="radio" value="explicit" - onchange="onRadioChange();"> - <div id="learn-more-link"> - <a i18n-values="href:encryptionhelpurl" target="_blank" - i18n-content="learnmore"></a> - </div> - <label for="explicit-option" i18n-content="explicitOption"></label> - </input> - </div> - - <div class="sync-section" id="section-google"> - <div i18n-content="sectionGoogleMessage"></div> - </div> - <div class="sync-section" id="section-explicit"> - <div i18n-content="sectionExplicitMessage" id="explicit-message"></div> - <div> - <div i18n-content="passphraseLabel" id="passphraseLabel"></div> - <input id="passphrase" name="passphrase" label="passphraseLabel" - type="password" /> - </div> - <div> - <div i18n-content="confirmLabel" id="confirmPassphraseLabel"> + <hr> + <h4 i18n-content="passphraseSectionTitle"></h4> + <div id="sync-passphrase-container"> + <div> + <input id="google-option" name="option" type="radio" value="google" + onchange="onRadioChange();"> + <label for="google-option" i18n-content="googleOption"></label> + </div> + <div> + <input id="explicit-option" name="option" type="radio" + value="explicit" onchange="onRadioChange();"> + <label for="explicit-option" i18n-content="explicitOption"></label> + <a i18n-values="href:encryptionhelpurl" target="_blank" + i18n-content="learnmore"></a> + </div> + <div id="sync-custom-passphrase" hidden> + <div id="sync-passphrase-message"> + <span i18n-content="sectionExplicitMessagePrefix"></span> + <a href="http://google.com/dashboard" target="_blank" + i18n-content="sectionExplicitMessagePostfix"></a> + <span>.</span> + </div> + <div class="sync-custom-passphrase-input"> + <input id="passphrase" type="password" + i18n-values="placeholder:passphraseLabel"> + </div> + <div class="sync-custom-passphrase-input"> + <input id="confirm-passphrase" type="password" + i18n-values="placeholder:confirmLabel"> + </div> + <div class="error" style="display:none" + id="emptyerror" i18n-content="emptyErrorMessage"></div> + <div class="error" style="display:none" + id="mismatcherror" i18n-content="mismatchErrorMessage"></div> </div> - <input id="confirmpassphrase" name="confirmpassphrase" type="password" - label="confirmPassphraseLabel" /> </div> - <div class="error" style="display:none" - id="emptyerror" i18n-content="emptyErrorMessage"></div> - <div class="error" style="display:none" - id="mismatcherror" i18n-content="mismatchErrorMessage"></div> - </div> - - <div id="change-passphrase"> - <div id="sync-passphrase-warning" i18n-content="passphraseWarning"> + <div class="action-area"> + <div class="action-area-link-container"> + <a id="use-default-link" href="#" i18n-content="useDefaultSettings" + onclick="showSyncEverythingPage();"></a> + </div> + <input id="okButton" type="submit" i18n-values="value:ok" /> + <input id="cancelButton" type="button" i18n-values="value:cancel" + onclick='chrome.send("DialogClose", [""])' /> </div> - <a id="clear-data-link" i18n-content="cleardatalink" href="#" - onclick='goToDashboard(); return false;'></a> </div> - </div> - - <div class="sync-footer"> - <input id="okButton" type="submit" i18n-values="value:ok" /> - <input id="cancelButton" type="button" i18n-values="value:cancel" - onclick='chrome.send("DialogClose", [""])' /> - </div> -</form> + </form> +</div> </body> </html> diff --git a/chrome/browser/sync/resources/firstpassphrase.html b/chrome/browser/sync/resources/firstpassphrase.html index 15156ee..adb8b42 100644 --- a/chrome/browser/sync/resources/firstpassphrase.html +++ b/chrome/browser/sync/resources/firstpassphrase.html @@ -43,7 +43,9 @@ form { #learn-more-link { float: right; } - +html[dir='rtl'] #learn-more-link { + float: left; +} html[dir='rtl'] .sync-footer { text-align: left; left: 0px; @@ -144,51 +146,6 @@ html[os='mac'] input[type='submit'] { </script> </head> <body i18n-values=".style.fontFamily:fontfamily" onload="setupDialog();"> -<form id="form" onSubmit="sendValuesAndClose(); return false;"> - <div class="sync-header" id="title" i18n-content="title"></div> - <div class="sync-instructions" id="instructions" - i18n-content="instructions"></div> - <div> - <input name="option" type="radio" value="google" - id="google-option" onchange="onRadioChange();"> - <label for="google-option" i18n-content="googleOption"></label> - </input> - </div> - <div> - <input name="option" type="radio" value="explicit" - id="explicit-option" onchange="onRadioChange();"> - <div id="learn-more-link"> - <a i18n-values="href:encryptionhelpurl" target="_blank" - i18n-content="learnmore"></a> - </div> - <label for="explicit-option" i18n-content="explicitOption"></label> - </input> - </div> - - <div class="sync-section" id="section-explicit"> - <div i18n-content="sectionExplicitMessage"></div> - <div> - <div i18n-content="passphraseLabel" id="passphraseLabel"></div> - <input id="passphrase" name="passphrase" label="passphraseLabel" - type="password"/> - </div> - <div> - <div i18n-content="confirmLabel" id="confirmPassphraseLabel"> - </div> - <input id="confirmpassphrase" name="confirmpassphrase" type="password" - label="confirmPassphraseLabel" /> - </div> - <div class="error" style="display:none" - id="emptyerror" i18n-content="emptyErrorMessage"></div> - <div class="error" style="display:none" - id="mismatcherror" i18n-content="mismatchErrorMessage"></div> - </div> - <div class="sync-footer"> - <input id="okButton" type="submit" i18n-values="value:syncpasswords" /> - <input id="noThanksButton" type="submit" i18n-values="value:nothanks" - onclick="optOutOfPasswordsAndClose(); return false;"/> - </div> -</form> </body> </html> diff --git a/chrome/browser/sync/resources/gaia_login.css b/chrome/browser/sync/resources/gaia_login.css index a442f55..2237ca6 100644 --- a/chrome/browser/sync/resources/gaia_login.css +++ b/chrome/browser/sync/resources/gaia_login.css @@ -38,6 +38,8 @@ input[type='submit'] { #gaia-account-text { font-weight: bold; + position: relative; + top: -7px; } #email-readonly { diff --git a/chrome/browser/sync/resources/gaia_login.html b/chrome/browser/sync/resources/gaia_login.html index fb3910d..5f57c83 100644 --- a/chrome/browser/sync/resources/gaia_login.html +++ b/chrome/browser/sync/resources/gaia_login.html @@ -7,9 +7,6 @@ <body i18n-values=".style.fontFamily:fontfamily;.style.fontSize:fontsize"> <div> <div> - <div id="top-blurb"> - <span i18n-content="introduction"></span> - </div> <div id="top-blurb-error"> <span id="error-signing-in" i18n-content="errorsigningin"></span> <span id="error-custom"></span> @@ -26,17 +23,19 @@ cellpadding="1" cellspacing="0"> <tr> <td colspan="2" align="center"> - <table> - <tr> - <td id="gaia-logo" valign="top"> + <div> + <div> + <span class="gaia-le-lbl" i18n-content="signinprefix"> + </span> + </div> + <div> + <span id="gaia-logo"> <img src="google_transparent.png" alt="Google"> - </td> - <td valign="middle"> - <span id="gaia-account-text" - i18n-content="signinsuffix"></span> - </td> - </tr> - </table> + </span> + <span id="gaia-account-text" + i18n-content="signinsuffix"></span> + </div> + </div> </td> </tr> <tr> diff --git a/chrome/browser/sync/resources/gaia_login.js b/chrome/browser/sync/resources/gaia_login.js index 5b765b3..f19dd29 100644 --- a/chrome/browser/sync/resources/gaia_login.js +++ b/chrome/browser/sync/resources/gaia_login.js @@ -141,8 +141,8 @@ function load() { if (googleIsAtEndOfSentence == ltr) { // We're in ltr and in the translation the word 'Google' is AFTER the // word 'Account' OR we're in rtl and 'Google' is BEFORE 'Account'. - var logo_td = document.getElementById('gaia-logo'); - logo_td.parentNode.appendChild(logo_td); + var logo_div = document.getElementById('gaia-logo'); + logo_div.parentNode.appendChild(logo_div); } acct_text.textContent = translated_text.replace('Google',''); } diff --git a/chrome/browser/sync/resources/passphrase.html b/chrome/browser/sync/resources/passphrase.html index e92df66..954124a 100644 --- a/chrome/browser/sync/resources/passphrase.html +++ b/chrome/browser/sync/resources/passphrase.html @@ -19,10 +19,7 @@ form { font-weight: bold; margin-bottom: 10px; } -.sync-instructions-start-hidden { - margin-top: 10px; - display: none; -} + .sync-footer { position: fixed; right: 0px; @@ -76,6 +73,9 @@ html[os='mac'] input[type='submit'] { float: right; margin-left: 5px; } +#sync-passphrase-warning { + margin-bottom: 5px; +} </style> <script src="chrome://resources/js/cr.js"></script> <script>
@@ -160,7 +160,7 @@ html[os='mac'] input[type='submit'] { <input id="cancel-no-button" type="submit" i18n-values="value:no" onclick="hideCancelWarning(); return false;" /> <input id="cancel-yes-button" type="submit" i18n-values="value:yes" - onclick="chrome.send('PassphraseCancel', ['']);" /> + onclick="chrome.send('PassphraseCancel', ['']); return false;" /> </div> <div class="sync-footer"> diff --git a/chrome/browser/sync/sessions/session_state.cc b/chrome/browser/sync/sessions/session_state.cc index 27457fb..02b6dce 100644 --- a/chrome/browser/sync/sessions/session_state.cc +++ b/chrome/browser/sync/sessions/session_state.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -20,65 +20,16 @@ using std::vector; namespace browser_sync { namespace sessions { -TypePayloadMap MakeTypePayloadMapFromBitSet( - const syncable::ModelTypeBitSet& types, - const std::string& payload) { - TypePayloadMap types_with_payloads; - for (size_t i = syncable::FIRST_REAL_MODEL_TYPE; - i < types.size(); ++i) { - if (types[i]) { - types_with_payloads[syncable::ModelTypeFromInt(i)] = payload; - } - } - return types_with_payloads; -} - -TypePayloadMap MakeTypePayloadMapFromRoutingInfo( - const ModelSafeRoutingInfo& routes, - const std::string& payload) { - TypePayloadMap types_with_payloads; - for (ModelSafeRoutingInfo::const_iterator i = routes.begin(); - i != routes.end(); ++i) { - types_with_payloads[i->first] = payload; - } - return types_with_payloads; -} - -DictionaryValue* TypePayloadMapToValue(const TypePayloadMap& type_payloads) { - DictionaryValue* value = new DictionaryValue(); - for (TypePayloadMap::const_iterator it = type_payloads.begin(); - it != type_payloads.end(); ++it) { - value->SetString(syncable::ModelTypeToString(it->first), it->second); - } - return value; -} - -void CoalescePayloads(TypePayloadMap* original, - const TypePayloadMap& update) { - for (TypePayloadMap::const_iterator i = update.begin(); - i != update.end(); ++i) { - if (original->count(i->first) == 0) { - // If this datatype isn't already in our map, add it with whatever payload - // it has. - (*original)[i->first] = i->second; - } else if (i->second.length() > 0) { - // If this datatype is already in our map, we only overwrite the payload - // if the new one is non-empty. - (*original)[i->first] = i->second; - } - } -} - SyncSourceInfo::SyncSourceInfo() : updates_source(sync_pb::GetUpdatesCallerInfo::UNKNOWN) {} SyncSourceInfo::SyncSourceInfo( - const TypePayloadMap& t) + const syncable::ModelTypePayloadMap& t) : updates_source(sync_pb::GetUpdatesCallerInfo::UNKNOWN), types(t) {} SyncSourceInfo::SyncSourceInfo( const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource& u, - const TypePayloadMap& t) + const syncable::ModelTypePayloadMap& t) : updates_source(u), types(t) {} SyncSourceInfo::~SyncSourceInfo() {} @@ -87,7 +38,7 @@ DictionaryValue* SyncSourceInfo::ToValue() const { DictionaryValue* value = new DictionaryValue(); value->SetString("updatesSource", GetUpdatesSourceString(updates_source)); - value->Set("types", TypePayloadMapToValue(types)); + value->Set("types", syncable::ModelTypePayloadMapToValue(types)); return value; } @@ -101,6 +52,9 @@ SyncerStatus::SyncerStatus() num_tombstone_updates_downloaded_total(0) { } +SyncerStatus::~SyncerStatus() { +} + DictionaryValue* SyncerStatus::ToValue() const { DictionaryValue* value = new DictionaryValue(); value->SetBoolean("invalidStore", invalid_store); diff --git a/chrome/browser/sync/sessions/session_state.h b/chrome/browser/sync/sessions/session_state.h index 479cc7f..4256cd5 100644 --- a/chrome/browser/sync/sessions/session_state.h +++ b/chrome/browser/sync/sessions/session_state.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -24,6 +24,7 @@ #include "chrome/browser/sync/engine/syncproto.h" #include "chrome/browser/sync/sessions/ordered_commit_set.h" #include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" #include "chrome/browser/sync/syncable/syncable.h" class DictionaryValue; @@ -37,51 +38,28 @@ namespace sessions { class UpdateProgress; -// A container that contains a set of datatypes with possible string payloads. -typedef std::map<syncable::ModelType, std::string> TypePayloadMap; - -// Helper utils for building TypePayloadMaps. -// Make a TypePayloadMap from all the types in a ModelTypeBitSet using a -// default payload. -TypePayloadMap MakeTypePayloadMapFromBitSet( - const syncable::ModelTypeBitSet& types, - const std::string& payload); - -// Make a TypePayloadMap for all the enabled types in a ModelSafeRoutingInfo -// using a default payload. -TypePayloadMap MakeTypePayloadMapFromRoutingInfo( - const ModelSafeRoutingInfo& routes, - const std::string& payload); - -// Caller takes ownership of the returned dictionary. -DictionaryValue* TypePayloadMapToValue(const TypePayloadMap& type_payloads); - -// Coalesce |update| into |original|, overwriting only when |update| has -// a non-empty payload. -void CoalescePayloads(TypePayloadMap* original, const TypePayloadMap& update); - // A container for the source of a sync session. This includes the update // source, the datatypes triggering the sync session, and possible session // specific payloads which should be sent to the server. struct SyncSourceInfo { SyncSourceInfo(); - SyncSourceInfo( - const TypePayloadMap& t); + explicit SyncSourceInfo(const syncable::ModelTypePayloadMap& t); SyncSourceInfo( const sync_pb::GetUpdatesCallerInfo::GetUpdatesSource& u, - const TypePayloadMap& t); + const syncable::ModelTypePayloadMap& t); ~SyncSourceInfo(); // Caller takes ownership of the returned dictionary. DictionaryValue* ToValue() const; sync_pb::GetUpdatesCallerInfo::GetUpdatesSource updates_source; - TypePayloadMap types; + syncable::ModelTypePayloadMap types; }; // Data pertaining to the status of an active Syncer object. struct SyncerStatus { SyncerStatus(); + ~SyncerStatus(); // Caller takes ownership of the returned dictionary. DictionaryValue* ToValue() const; @@ -98,6 +76,10 @@ struct SyncerStatus { // Download event counters. int num_updates_downloaded_total; int num_tombstone_updates_downloaded_total; + + // If the syncer encountered a MIGRATION_DONE code, these are the types that + // the client must now "migrate", by purging and re-downloading all updates. + syncable::ModelTypeSet types_needing_local_migration; }; // Counters for various errors that can occur repeatedly during a sync session. diff --git a/chrome/browser/sync/sessions/session_state_unittest.cc b/chrome/browser/sync/sessions/session_state_unittest.cc index 3ac832f..649d520 100644 --- a/chrome/browser/sync/sessions/session_state_unittest.cc +++ b/chrome/browser/sync/sessions/session_state_unittest.cc @@ -7,7 +7,7 @@ #include <string> #include "base/base64.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/values.h" #include "chrome/test/values_test_util.h" #include "testing/gtest/include/gtest/gtest.h" @@ -24,26 +24,14 @@ using test::ExpectStringValue; class SessionStateTest : public testing::Test {}; -TEST_F(SessionStateTest, TypePayloadMapToValue) { - TypePayloadMap payloads; - payloads[syncable::BOOKMARKS] = "bookmarkpayload"; - payloads[syncable::APPS] = ""; - - scoped_ptr<DictionaryValue> value(TypePayloadMapToValue(payloads)); - EXPECT_EQ(2u, value->size()); - ExpectStringValue("bookmarkpayload", *value, "Bookmarks"); - ExpectStringValue("", *value, "Apps"); - EXPECT_FALSE(value->HasKey("Preferences")); -} - TEST_F(SessionStateTest, SyncSourceInfoToValue) { sync_pb::GetUpdatesCallerInfo::GetUpdatesSource updates_source = sync_pb::GetUpdatesCallerInfo::PERIODIC; - TypePayloadMap types; + syncable::ModelTypePayloadMap types; types[syncable::PREFERENCES] = "preferencespayload"; types[syncable::EXTENSIONS] = ""; scoped_ptr<DictionaryValue> expected_types_value( - TypePayloadMapToValue(types)); + syncable::ModelTypePayloadMapToValue(types)); SyncSourceInfo source_info(updates_source, types); diff --git a/chrome/browser/sync/sessions/status_controller.cc b/chrome/browser/sync/sessions/status_controller.cc index c8d6866..8266f0f 100644 --- a/chrome/browser/sync/sessions/status_controller.cc +++ b/chrome/browser/sync/sessions/status_controller.cc @@ -51,6 +51,11 @@ void StatusController::increment_num_updates_downloaded_by(int value) { shared_.syncer_status.mutate()->num_updates_downloaded_total += value; } +void StatusController::set_types_needing_local_migration( + const syncable::ModelTypeSet& types) { + shared_.syncer_status.mutate()->types_needing_local_migration = types; +} + void StatusController::increment_num_tombstone_updates_downloaded_by( int value) { shared_.syncer_status.mutate()->num_tombstone_updates_downloaded_total += diff --git a/chrome/browser/sync/sessions/status_controller.h b/chrome/browser/sync/sessions/status_controller.h index 805ff28..d4c912a 100644 --- a/chrome/browser/sync/sessions/status_controller.h +++ b/chrome/browser/sync/sessions/status_controller.h @@ -212,6 +212,7 @@ class StatusController { void increment_num_successful_bookmark_commits(); void increment_num_updates_downloaded_by(int value); void increment_num_tombstone_updates_downloaded_by(int value); + void set_types_needing_local_migration(const syncable::ModelTypeSet& types); void set_unsynced_handles(const std::vector<int64>& unsynced_handles); void set_commit_set(const OrderedCommitSet& commit_set); diff --git a/chrome/browser/sync/sessions/sync_session.cc b/chrome/browser/sync/sessions/sync_session.cc index 52a44fa..a38e31a 100644 --- a/chrome/browser/sync/sessions/sync_session.cc +++ b/chrome/browser/sync/sessions/sync_session.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,7 +10,7 @@ namespace browser_sync { namespace sessions { SyncSession::SyncSession(SyncSessionContext* context, Delegate* delegate, - SyncSourceInfo source, + const SyncSourceInfo& source, const ModelSafeRoutingInfo& routing_info, const std::vector<ModelSafeWorker*>& workers) : context_(context), @@ -41,11 +41,14 @@ void SyncSession::Coalesce(const SyncSession& session) { std::back_inserter(temp)); workers_.swap(temp); - ModelSafeRoutingInfo temp_r; - std::set_union(routing_info_.begin(), routing_info_.end(), - session.routing_info_.begin(), session.routing_info_.end(), - std::insert_iterator<ModelSafeRoutingInfo>(temp_r, temp_r.begin())); - routing_info_.swap(temp_r); + // We have to update the model safe routing info to the union. In case the + // same key is present in both pick the one from session. + for (ModelSafeRoutingInfo::const_iterator it = + session.routing_info_.begin(); + it != session.routing_info_.end(); + ++it) { + routing_info_[it->first] = it->second; + } } void SyncSession::ResetTransientState() { diff --git a/chrome/browser/sync/sessions/sync_session.h b/chrome/browser/sync/sessions/sync_session.h index 693d652..6a34c91 100644 --- a/chrome/browser/sync/sessions/sync_session.h +++ b/chrome/browser/sync/sessions/sync_session.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -21,7 +21,7 @@ #include <vector> #include "base/basictypes.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/time.h" #include "chrome/browser/sync/engine/model_safe_worker.h" #include "chrome/browser/sync/sessions/ordered_commit_set.h" @@ -82,7 +82,7 @@ class SyncSession { SyncSession(SyncSessionContext* context, Delegate* delegate, - SyncSourceInfo source, + const SyncSourceInfo& source, const ModelSafeRoutingInfo& routing_info, const std::vector<ModelSafeWorker*>& workers); ~SyncSession(); diff --git a/chrome/browser/sync/sessions/sync_session_context.cc b/chrome/browser/sync/sessions/sync_session_context.cc index b5f072b..f0cbce4 100644 --- a/chrome/browser/sync/sessions/sync_session_context.cc +++ b/chrome/browser/sync/sessions/sync_session_context.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -36,10 +36,5 @@ SyncSessionContext::~SyncSessionContext() { } } -void SyncSessionContext::set_last_snapshot( - const SyncSessionSnapshot& snapshot) { - previous_session_snapshot_.reset(new SyncSessionSnapshot(snapshot)); -} - } // namespace sessions } // namespace browser_sync diff --git a/chrome/browser/sync/sessions/sync_session_context.h b/chrome/browser/sync/sessions/sync_session_context.h index 3f402ae..a3a69ec 100644 --- a/chrome/browser/sync/sessions/sync_session_context.h +++ b/chrome/browser/sync/sessions/sync_session_context.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -21,7 +21,7 @@ #include <string> -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "chrome/browser/sync/engine/model_safe_worker.h" #include "chrome/browser/sync/engine/syncer_types.h" @@ -92,13 +92,6 @@ class SyncSessionContext { previous_session_routing_info_ = info; } - // TODO(tim): Deprecated. Bug 26339. - sessions::SyncSessionSnapshot* previous_session_snapshot() { - return previous_session_snapshot_.get(); - } - - void set_last_snapshot(const SyncSessionSnapshot& snapshot); - void NotifyListeners(const SyncEngineEvent& event) { FOR_EACH_OBSERVER(SyncEngineEventListener, listeners_, OnSyncEngineEvent(event)); diff --git a/chrome/browser/sync/sessions/sync_session_unittest.cc b/chrome/browser/sync/sessions/sync_session_unittest.cc index 0f433de..bbd2574 100644 --- a/chrome/browser/sync/sessions/sync_session_unittest.cc +++ b/chrome/browser/sync/sessions/sync_session_unittest.cc @@ -1,10 +1,10 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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/sync/sessions/sync_session.h" -#include "base/ref_counted.h" +#include "base/memory/ref_counted.h" #include "chrome/browser/sync/engine/conflict_resolver.h" #include "chrome/browser/sync/engine/mock_model_safe_workers.h" #include "chrome/browser/sync/engine/syncer_types.h" @@ -266,12 +266,14 @@ TEST_F(SyncSessionTest, ResetTransientState) { TEST_F(SyncSessionTest, Coalesce) { std::vector<ModelSafeWorker*> workers_one, workers_two; ModelSafeRoutingInfo routes_one, routes_two; - TypePayloadMap one_type = sessions::MakeTypePayloadMapFromBitSet( - ParamsMeaningJustOneEnabledType(), - std::string());; - TypePayloadMap all_types = sessions::MakeTypePayloadMapFromBitSet( - ParamsMeaningAllEnabledTypes(), - std::string());; + syncable::ModelTypePayloadMap one_type = + syncable::ModelTypePayloadMapFromBitSet( + ParamsMeaningJustOneEnabledType(), + std::string()); + syncable::ModelTypePayloadMap all_types = + syncable::ModelTypePayloadMapFromBitSet( + ParamsMeaningAllEnabledTypes(), + std::string()); SyncSourceInfo source_one(sync_pb::GetUpdatesCallerInfo::PERIODIC, one_type); SyncSourceInfo source_two(sync_pb::GetUpdatesCallerInfo::LOCAL, all_types); @@ -302,15 +304,16 @@ TEST_F(SyncSessionTest, Coalesce) { TEST_F(SyncSessionTest, MakeTypePayloadMapFromBitSet) { syncable::ModelTypeBitSet types; std::string payload = "test"; - TypePayloadMap types_with_payloads = MakeTypePayloadMapFromBitSet(types, - payload); + syncable::ModelTypePayloadMap types_with_payloads = + syncable::ModelTypePayloadMapFromBitSet(types, + payload); EXPECT_TRUE(types_with_payloads.empty()); types[syncable::BOOKMARKS] = true; types[syncable::PASSWORDS] = true; types[syncable::AUTOFILL] = true; payload = "test2"; - types_with_payloads = MakeTypePayloadMapFromBitSet(types, payload); + types_with_payloads = syncable::ModelTypePayloadMapFromBitSet(types, payload); ASSERT_EQ(3U, types_with_payloads.size()); EXPECT_EQ(types_with_payloads[syncable::BOOKMARKS], payload); @@ -320,8 +323,8 @@ TEST_F(SyncSessionTest, MakeTypePayloadMapFromBitSet) { TEST_F(SyncSessionTest, MakeTypePayloadMapFromRoutingInfo) { std::string payload = "test"; - TypePayloadMap types_with_payloads - = MakeTypePayloadMapFromRoutingInfo(routes_, payload); + syncable::ModelTypePayloadMap types_with_payloads + = syncable::ModelTypePayloadMapFromRoutingInfo(routes_, payload); ASSERT_EQ(routes_.size(), types_with_payloads.size()); for (ModelSafeRoutingInfo::iterator iter = routes_.begin(); iter != routes_.end(); @@ -331,7 +334,7 @@ TEST_F(SyncSessionTest, MakeTypePayloadMapFromRoutingInfo) { } TEST_F(SyncSessionTest, CoalescePayloads) { - TypePayloadMap original; + syncable::ModelTypePayloadMap original; std::string empty_payload; std::string payload1 = "payload1"; std::string payload2 = "payload2"; @@ -341,7 +344,7 @@ TEST_F(SyncSessionTest, CoalescePayloads) { original[syncable::AUTOFILL] = payload2; original[syncable::THEMES] = payload3; - TypePayloadMap update; + syncable::ModelTypePayloadMap update; update[syncable::BOOKMARKS] = empty_payload; // Same. update[syncable::PASSWORDS] = empty_payload; // Overwrite with empty. update[syncable::AUTOFILL] = payload1; // Overwrite with non-empty. diff --git a/chrome/browser/sync/signin_manager.cc b/chrome/browser/sync/signin_manager.cc index 43a6e44..b052175 100644 --- a/chrome/browser/sync/signin_manager.cc +++ b/chrome/browser/sync/signin_manager.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,8 +9,8 @@ #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/common/net/gaia/gaia_constants.h" -#include "chrome/common/notification_service.h" #include "chrome/common/pref_names.h" +#include "content/common/notification_service.h" const char kGetInfoEmailKey[] = "email"; diff --git a/chrome/browser/sync/signin_manager.h b/chrome/browser/sync/signin_manager.h index 24eb553..603c323 100644 --- a/chrome/browser/sync/signin_manager.h +++ b/chrome/browser/sync/signin_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -13,7 +13,7 @@ #include <string> #include "base/logging.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "chrome/common/net/gaia/gaia_auth_consumer.h" #include "chrome/common/net/gaia/google_service_auth_error.h" diff --git a/chrome/browser/sync/sync_constants.cc b/chrome/browser/sync/sync_constants.cc deleted file mode 100644 index 923d2b3..0000000 --- a/chrome/browser/sync/sync_constants.cc +++ /dev/null @@ -1,17 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/sync/sync_constants.h" - -namespace browser_sync { - -const char kSyncLegacyServiceUrl[] = "google:notifier"; -const char kSyncServiceUrl[] = "http://www.google.com/chrome/sync"; -const char kSyncLegacyServiceId[] = "notification"; -const char kSyncServiceId[] = "sync-ping"; -const int kSyncPriority = 200; -const char kSyncServiceSpecificData[] = "sync-ping-p2p"; - -} // namespace browser_sync - diff --git a/chrome/browser/sync/sync_constants.h b/chrome/browser/sync/sync_constants.h deleted file mode 100644 index d395d25..0000000 --- a/chrome/browser/sync/sync_constants.h +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_SYNC_SYNC_CONSTANTS_H_ -#define CHROME_BROWSER_SYNC_SYNC_CONSTANTS_H_ -#pragma once - -namespace browser_sync { - -extern const char kSyncLegacyServiceUrl[]; -extern const char kSyncServiceUrl[]; -extern const char kSyncLegacyServiceId[]; -extern const char kSyncServiceId[]; -extern const int kSyncPriority; -extern const char kSyncServiceSpecificData[]; - -} // namespace browser_sync - -#endif // CHROME_BROWSER_SYNC_SYNC_CONSTANTS_H_ - diff --git a/chrome/browser/sync/sync_setup_flow.cc b/chrome/browser/sync/sync_setup_flow.cc index cf7cc6e..0ce1d20 100644 --- a/chrome/browser/sync/sync_setup_flow.cc +++ b/chrome/browser/sync/sync_setup_flow.cc @@ -15,419 +15,171 @@ #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/profile_sync_service.h" +#include "chrome/browser/sync/sync_setup_flow_handler.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_dialogs.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/common/net/gaia/google_service_auth_error.h" #include "chrome/common/pref_names.h" +#include "chrome/common/url_constants.h" #include "content/browser/renderer_host/render_view_host.h" #include "content/browser/tab_contents/tab_contents.h" -#include "content/browser/webui/web_ui_util.h" +#include "grit/generated_resources.h" #include "grit/locale_settings.h" #include "ui/base/l10n/l10n_font_util.h" #include "ui/gfx/font.h" -// XPath expression for finding specific iframes. -static const wchar_t* kLoginIFrameXPath = L"//iframe[@id='login']"; -static const wchar_t* kChooseDataTypesIFrameXPath = - L"//iframe[@id='configure']"; -static const wchar_t* kPassphraseIFrameXPath = - L"//iframe[@id='passphrase']"; -static const wchar_t* kDoneIframeXPath = L"//iframe[@id='done']"; +namespace { -SyncConfiguration::SyncConfiguration() - : sync_everything(false), - use_secondary_passphrase(false) { -} - -SyncConfiguration::~SyncConfiguration() {} - -void FlowHandler::RegisterMessages() { - web_ui_->RegisterMessageCallback("SubmitAuth", - NewCallback(this, &FlowHandler::HandleSubmitAuth)); - web_ui_->RegisterMessageCallback("Configure", - NewCallback(this, &FlowHandler::HandleConfigure)); - web_ui_->RegisterMessageCallback("Passphrase", - NewCallback(this, &FlowHandler::HandlePassphraseEntry)); - web_ui_->RegisterMessageCallback("PassphraseCancel", - NewCallback(this, &FlowHandler::HandlePassphraseCancel)); - web_ui_->RegisterMessageCallback("FirstPassphrase", - NewCallback(this, &FlowHandler::HandleFirstPassphrase)); - web_ui_->RegisterMessageCallback("GoToDashboard", - NewCallback(this, &FlowHandler::HandleGoToDashboard)); -} - -static bool GetAuthData(const std::string& json, - std::string* username, - std::string* password, - std::string* captcha, - std::string* access_code) { - scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); - if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) - return false; - - DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); - if (!result->GetString("user", username) || - !result->GetString("pass", password) || - !result->GetString("captcha", captcha) || - !result->GetString("access_code", access_code)) { - return false; - } - return true; -} - -bool GetPassphrase(const std::string& json, std::string* passphrase) { - scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); - if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) - return false; - - DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); - return result->GetString("passphrase", passphrase); -} - -bool GetFirstPassphrase(const std::string& json, - std::string* option, - std::string* passphrase) { - scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); - if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) - return false; - - DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); - return result->GetString("option", option) && - result->GetString("passphrase", passphrase); -} - -static bool GetConfiguration(const std::string& json, - SyncConfiguration* config) { - scoped_ptr<Value> parsed_value(base::JSONReader::Read(json, false)); - if (!parsed_value.get() || !parsed_value->IsType(Value::TYPE_DICTIONARY)) - return false; - - DictionaryValue* result = static_cast<DictionaryValue*>(parsed_value.get()); - if (!result->GetBoolean("keepEverythingSynced", &config->sync_everything)) - return false; - - // These values need to be kept in sync with where they are written in - // choose_datatypes.html. - bool sync_bookmarks; - if (!result->GetBoolean("syncBookmarks", &sync_bookmarks)) - return false; - if (sync_bookmarks) - config->data_types.insert(syncable::BOOKMARKS); - - bool sync_preferences; - if (!result->GetBoolean("syncPreferences", &sync_preferences)) - return false; - if (sync_preferences) - config->data_types.insert(syncable::PREFERENCES); - - bool sync_themes; - if (!result->GetBoolean("syncThemes", &sync_themes)) - return false; - if (sync_themes) - config->data_types.insert(syncable::THEMES); - - bool sync_passwords; - if (!result->GetBoolean("syncPasswords", &sync_passwords)) - return false; - if (sync_passwords) - config->data_types.insert(syncable::PASSWORDS); - - bool sync_autofill; - if (!result->GetBoolean("syncAutofill", &sync_autofill)) - return false; - if (sync_autofill) - config->data_types.insert(syncable::AUTOFILL); - - bool sync_extensions; - if (!result->GetBoolean("syncExtensions", &sync_extensions)) - return false; - if (sync_extensions) - config->data_types.insert(syncable::EXTENSIONS); - - bool sync_typed_urls; - if (!result->GetBoolean("syncTypedUrls", &sync_typed_urls)) - return false; - if (sync_typed_urls) - config->data_types.insert(syncable::TYPED_URLS); - - bool sync_sessions; - if (!result->GetBoolean("syncSessions", &sync_sessions)) - return false; - if (sync_sessions) - config->data_types.insert(syncable::SESSIONS); - - bool sync_apps; - if (!result->GetBoolean("syncApps", &sync_apps)) - return false; - if (sync_apps) - config->data_types.insert(syncable::APPS); - - // Encyption settings. - if (!result->GetBoolean("usePassphrase", &config->use_secondary_passphrase)) - return false; - if (config->use_secondary_passphrase && - !result->GetString("passphrase", &config->secondary_passphrase)) - return false; - - return true; -} - -static void DisablePasswordSync(ProfileSyncService* service) { +// Helper function to disable password sync. +void DisablePasswordSync(ProfileSyncService* service) { syncable::ModelTypeSet types; service->GetPreferredDataTypes(&types); types.erase(syncable::PASSWORDS); service->OnUserChoseDatatypes(false, types); } -void FlowHandler::HandleSubmitAuth(const ListValue* args) { - std::string json; - if (!args->GetString(0, &json)) { - NOTREACHED() << "Could not read JSON argument"; - return; - } - if (json.empty()) - return; - - std::string username, password, captcha, access_code; - if (!GetAuthData(json, &username, &password, &captcha, &access_code)) { - // The page sent us something that we didn't understand. - // This probably indicates a programming error. - NOTREACHED(); - return; - } +} // namespace - if (flow_) - flow_->OnUserSubmittedAuth(username, password, captcha, access_code); +SyncConfiguration::SyncConfiguration() + : sync_everything(false), + use_secondary_passphrase(false) { } -void FlowHandler::HandleConfigure(const ListValue* args) { - std::string json; - if (!args->GetString(0, &json)) { - NOTREACHED() << "Could not read JSON argument"; - return; - } - if (json.empty()) - return; - - SyncConfiguration configuration; - if (!GetConfiguration(json, &configuration)) { - // The page sent us something that we didn't understand. - // This probably indicates a programming error. - NOTREACHED(); - return; - } - - DCHECK(flow_); - flow_->OnUserConfigured(configuration); +SyncConfiguration::~SyncConfiguration() {} - return; +SyncSetupFlow::~SyncSetupFlow() { + flow_handler_->SetFlow(NULL); } -void FlowHandler::HandlePassphraseEntry(const ListValue* args) { - std::string json; - if (!args->GetString(0, &json)) { - NOTREACHED() << "Could not read JSON argument"; - return; - } - if (json.empty()) - return; +// static +SyncSetupFlow* SyncSetupFlow::Run(ProfileSyncService* service, + SyncSetupFlowContainer* container, + SyncSetupWizard::State start, + SyncSetupWizard::State end) { + DictionaryValue args; + if (start == SyncSetupWizard::GAIA_LOGIN) + SyncSetupFlow::GetArgsForGaiaLogin(service, &args); + else if (start == SyncSetupWizard::CONFIGURE) + SyncSetupFlow::GetArgsForConfigure(service, &args); + else if (start == SyncSetupWizard::ENTER_PASSPHRASE) + SyncSetupFlow::GetArgsForEnterPassphrase(false, false, &args); + else if (start == SyncSetupWizard::PASSPHRASE_MIGRATION) + args.SetString("iframeToShow", "firstpassphrase"); - std::string passphrase; - if (!GetPassphrase(json, &passphrase)) { - // Couldn't understand what the page sent. Indicates a programming error. - NOTREACHED(); - return; - } + std::string json_args; + base::JSONWriter::Write(&args, false, &json_args); - DCHECK(flow_); - flow_->OnPassphraseEntry(passphrase); -} + SyncSetupFlow* flow = new SyncSetupFlow(start, end, json_args, + container, service); -void FlowHandler::HandlePassphraseCancel(const ListValue* args) { - DCHECK(flow_); - flow_->OnPassphraseCancel(); + Browser* b = BrowserList::GetLastActive(); + b->ShowOptionsTab(chrome::kSyncSetupSubPage); + return flow; } -void FlowHandler::HandleFirstPassphrase(const ListValue* args) { - std::string json; - if (!args->GetString(0, &json)) { - NOTREACHED() << "Could not read JSON argument"; - return; - } - if (json.empty()) - return; - - std::string option; - std::string passphrase; - if (!GetFirstPassphrase(json, &option, &passphrase)) { - // Page sent result which couldn't be parsed. Programming error. - NOTREACHED(); - return; +// static +void SyncSetupFlow::GetArgsForGaiaLogin(const ProfileSyncService* service, + DictionaryValue* args) { + args->SetString("iframeToShow", "login"); + const GoogleServiceAuthError& error = service->GetAuthError(); + if (!service->last_attempted_user_email().empty()) { + args->SetString("user", service->last_attempted_user_email()); + args->SetInteger("error", error.state()); + args->SetBoolean("editable_user", true); + } else { + string16 user; + if (!service->cros_user().empty()) + user = UTF8ToUTF16(service->cros_user()); + else + user = service->GetAuthenticatedUsername(); + args->SetString("user", user); + args->SetInteger("error", 0); + args->SetBoolean("editable_user", user.empty()); } - DCHECK(flow_); - flow_->OnFirstPassphraseEntry(option, passphrase); -} - -void FlowHandler::HandleGoToDashboard(const ListValue* args) { - flow_->OnGoToDashboard(); -} - -// Called by SyncSetupFlow::Advance. -void FlowHandler::ShowGaiaLogin(const DictionaryValue& args) { - // Whenever you start a wizard, you pass in an arg so it starts on the right - // iframe (see setup_flow.html's showTheRightIframe() method). But when you - // transition from one flow to another, you have to explicitly call the JS - // function to show the next iframe. - // So if you ever made a wizard that involved a gaia login as not the first - // frame, this call would be necessary to ensure that this method actually - // shows the gaia login. - if (web_ui_) - web_ui_->CallJavascriptFunction(L"showGaiaLoginIframe"); - - std::string json; - base::JSONWriter::Write(&args, false, &json); - std::wstring javascript = std::wstring(L"showGaiaLogin") + - L"(" + UTF8ToWide(json) + L");"; - ExecuteJavascriptInIFrame(kLoginIFrameXPath, javascript); -} - -void FlowHandler::ShowGaiaSuccessAndClose() { - ExecuteJavascriptInIFrame(kLoginIFrameXPath, L"showGaiaSuccessAndClose();"); -} - -void FlowHandler::ShowGaiaSuccessAndSettingUp() { - ExecuteJavascriptInIFrame(kLoginIFrameXPath, - L"showGaiaSuccessAndSettingUp();"); -} - -// Called by SyncSetupFlow::Advance. -void FlowHandler::ShowConfigure(const DictionaryValue& args) { - // If you're starting the wizard at the configure screen (i.e. from - // "Customize Sync"), this will be redundant. However, if you're coming from - // another wizard state, this will make sure Choose Data Types is on top. - if (web_ui_) - web_ui_->CallJavascriptFunction(L"showConfigure"); - - std::string json; - base::JSONWriter::Write(&args, false, &json); - std::wstring javascript = std::wstring(L"initializeConfigureDialog") + - L"(" + UTF8ToWide(json) + L");"; - ExecuteJavascriptInIFrame(kChooseDataTypesIFrameXPath, javascript); -} - -void FlowHandler::ShowPassphraseEntry(const DictionaryValue& args) { - if (web_ui_) - web_ui_->CallJavascriptFunction(L"showPassphrase"); - - std::string json; - base::JSONWriter::Write(&args, false, &json); - std::wstring script = std::wstring(L"setupPassphraseDialog") + - L"(" + UTF8ToWide(json) + L");"; - ExecuteJavascriptInIFrame(kPassphraseIFrameXPath, script); -} - -void FlowHandler::ShowFirstPassphrase(const DictionaryValue& args) { - if (web_ui_) - web_ui_->CallJavascriptFunction(L"showFirstPassphrase"); - - std::string json; - base::JSONWriter::Write(&args, false, &json); - std::wstring script = std::wstring(L"setupDialog") + - L"(" + UTF8ToWide(json) + L");"; - ExecuteJavascriptInIFrame(kPassphraseIFrameXPath, script); + args->SetString("captchaUrl", error.captcha().image_url.spec()); } -void FlowHandler::ShowSettingUp() { - if (web_ui_) - web_ui_->CallJavascriptFunction(L"showSettingUp"); -} +// static +void SyncSetupFlow::GetArgsForConfigure(ProfileSyncService* service, + DictionaryValue* args) { + args->SetString("iframeToShow", "configure"); -void FlowHandler::ShowSetupDone(const std::wstring& user) { - StringValue synced_to_string(l10n_util::GetStringFUTF8( - IDS_SYNC_NTP_SYNCED_TO, WideToUTF16Hack(user))); - std::string json; - base::JSONWriter::Write(&synced_to_string, false, &json); - std::wstring javascript = std::wstring(L"setSyncedToUser") + - L"(" + UTF8ToWide(json) + L");"; - ExecuteJavascriptInIFrame(kDoneIframeXPath, javascript); + // The SYNC_EVERYTHING case will set this to true. + args->SetBoolean("syncEverything", false); - if (web_ui_) - web_ui_->CallJavascriptFunction(L"showSetupDone", synced_to_string); + args->SetBoolean("keepEverythingSynced", + service->profile()->GetPrefs()->GetBoolean(prefs::kKeepEverythingSynced)); - ExecuteJavascriptInIFrame(kDoneIframeXPath, - L"onPageShown();"); -} + // Bookmarks, Preferences, and Themes are launched for good, there's no + // going back now. Check if the other data types are registered though. + syncable::ModelTypeSet registered_types; + service->GetRegisteredDataTypes(®istered_types); + args->SetBoolean("passwordsRegistered", + registered_types.count(syncable::PASSWORDS) > 0); + args->SetBoolean("autofillRegistered", + registered_types.count(syncable::AUTOFILL) > 0); + args->SetBoolean("extensionsRegistered", + registered_types.count(syncable::EXTENSIONS) > 0); + args->SetBoolean("typedUrlsRegistered", + registered_types.count(syncable::TYPED_URLS) > 0); + args->SetBoolean("appsRegistered", + registered_types.count(syncable::APPS) > 0); + args->SetBoolean("sessionsRegistered", + registered_types.count(syncable::SESSIONS) > 0); + args->SetBoolean("syncBookmarks", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncBookmarks)); + args->SetBoolean("syncPreferences", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPreferences)); + args->SetBoolean("syncThemes", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncThemes)); + args->SetBoolean("syncPasswords", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPasswords)); + args->SetBoolean("syncAutofill", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncAutofill)); + args->SetBoolean("syncExtensions", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncExtensions)); + args->SetBoolean("syncSessions", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncSessions)); + args->SetBoolean("syncTypedUrls", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncTypedUrls)); + args->SetBoolean("syncApps", + service->profile()->GetPrefs()->GetBoolean(prefs::kSyncApps)); -void FlowHandler::ShowFirstTimeDone(const std::wstring& user) { - ExecuteJavascriptInIFrame(kDoneIframeXPath, - L"setShowFirstTimeSetupSummary();"); - ShowSetupDone(user); + // Load the parameters for the encryption tab. + args->SetBoolean("usePassphrase", service->IsUsingSecondaryPassphrase()); } -void FlowHandler::ExecuteJavascriptInIFrame(const std::wstring& iframe_xpath, - const std::wstring& js) { - if (web_ui_) { - RenderViewHost* rvh = web_ui_->tab_contents()->render_view_host(); - rvh->ExecuteJavascriptInWebFrame(WideToUTF16Hack(iframe_xpath), - WideToUTF16Hack(js)); - } +// static +void SyncSetupFlow::GetArgsForEnterPassphrase( + bool tried_creating_explicit_passphrase, + bool tried_setting_explicit_passphrase, + DictionaryValue* args) { + args->SetString("iframeToShow", "passphrase"); + args->SetBoolean("passphrase_creation_rejected", + tried_creating_explicit_passphrase); + args->SetBoolean("passphrase_setting_rejected", + tried_setting_explicit_passphrase); } -// Use static Run method to get an instance. -SyncSetupFlow::SyncSetupFlow(SyncSetupWizard::State start_state, - SyncSetupWizard::State end_state, - const std::string& args, - SyncSetupFlowContainer* container, - ProfileSyncService* service) - : container_(container), - dialog_start_args_(args), - current_state_(start_state), - end_state_(end_state), - login_start_time_(base::TimeTicks::Now()), - flow_handler_(new FlowHandler()), - owns_flow_handler_(true), - service_(service), - html_dialog_window_(NULL) { - flow_handler_->set_flow(this); +void SyncSetupFlow::AttachSyncSetupHandler(SyncSetupFlowHandler* handler) { + flow_handler_ = handler; + ActivateState(current_state_); } -SyncSetupFlow::~SyncSetupFlow() { - flow_handler_->set_flow(NULL); - if (owns_flow_handler_) { - delete flow_handler_; +void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) { + if (!ShouldAdvance(advance_state)) { + LOG(WARNING) << "Invalid state change from " + << current_state_ << " to " << advance_state; + return; } -} -void SyncSetupFlow::GetDialogSize(gfx::Size* size) const { - PrefService* prefs = service_->profile()->GetPrefs(); - gfx::Font approximate_web_font = gfx::Font( - UTF8ToUTF16(prefs->GetString(prefs::kWebKitSansSerifFontFamily)), - prefs->GetInteger(prefs::kWebKitDefaultFontSize)); - - *size = ui::GetLocalizedContentsSizeForFont( - IDS_SYNC_SETUP_WIZARD_WIDTH_CHARS, - IDS_SYNC_SETUP_WIZARD_HEIGHT_LINES, - approximate_web_font); - -#if defined(OS_MACOSX) - // NOTE(akalin): This is a hack to work around a problem with font height on - // windows. Basically font metrics are incorrectly returned in logical units - // instead of pixels on Windows. Logical units are very commonly 96 DPI - // so our localized char/line counts are too small by a factor of 96/72. - // So we compensate for this on non-windows platform. - // - // TODO(akalin): Remove this hack once we fix the windows font problem (or at - // least work around it in some other place). - float scale_hack = 96.f/72.f; - size->set_width(size->width() * scale_hack); - size->set_height(size->height() * scale_hack); -#endif + ActivateState(advance_state); } -std::string SyncSetupFlow::GetDialogArgs() const { - return dialog_start_args_; +void SyncSetupFlow::Focus() { + // TODO(jhawkins): Implement this. } // A callback to notify the delegate that the dialog closed. @@ -452,6 +204,8 @@ void SyncSetupFlow::OnDialogClosed(const std::string& json_retval) { case SyncSetupWizard::CONFIGURE: case SyncSetupWizard::ENTER_PASSPHRASE: case SyncSetupWizard::SETTING_UP: + // TODO(atwilson): Treat a close during ENTER_PASSPHRASE like a + // Cancel + Skip (i.e. call OnPassphraseCancel()). http://crbug.com/74645 ProfileSyncService::SyncEvent( ProfileSyncService::CANCEL_DURING_CONFIGURE); break; @@ -470,108 +224,73 @@ void SyncSetupFlow::OnDialogClosed(const std::string& json_retval) { delete this; } -std::wstring SyncSetupFlow::GetDialogTitle() const { - return UTF16ToWideHack( - l10n_util::GetStringUTF16(IDS_SYNC_MY_BOOKMARKS_LABEL)); +void SyncSetupFlow::OnUserSubmittedAuth(const std::string& username, + const std::string& password, + const std::string& captcha, + const std::string& access_code) { + service_->OnUserSubmittedAuth(username, password, captcha, access_code); } -bool SyncSetupFlow::IsDialogModal() const { - return false; -} +void SyncSetupFlow::OnUserConfigured(const SyncConfiguration& configuration) { + // Go to the "loading..." screen. + Advance(SyncSetupWizard::SETTING_UP); -bool SyncSetupFlow::ShouldShowDialogTitle() const { - return true; -} + // If we are activating the passphrase, we need to have one supplied. + DCHECK(service_->IsUsingSecondaryPassphrase() || + !configuration.use_secondary_passphrase || + configuration.secondary_passphrase.length() > 0); -// static -void SyncSetupFlow::GetArgsForGaiaLogin(const ProfileSyncService* service, - DictionaryValue* args) { - args->SetString("iframeToShow", "login"); - const GoogleServiceAuthError& error = service->GetAuthError(); - if (!service->last_attempted_user_email().empty()) { - args->SetString("user", service->last_attempted_user_email()); - args->SetInteger("error", error.state()); - args->SetBoolean("editable_user", true); - } else { - string16 user; - if (!service->cros_user().empty()) - user = UTF8ToUTF16(service->cros_user()); - else - user = service->GetAuthenticatedUsername(); - args->SetString("user", user); - args->SetInteger("error", 0); - args->SetBoolean("editable_user", user.empty()); + if (configuration.use_secondary_passphrase && + !service_->IsUsingSecondaryPassphrase()) { + service_->SetPassphrase(configuration.secondary_passphrase, true, true); + tried_creating_explicit_passphrase_ = true; } - args->SetString("captchaUrl", error.captcha().image_url.spec()); + service_->OnUserChoseDatatypes(configuration.sync_everything, + configuration.data_types); } -// static -void SyncSetupFlow::GetArgsForEnterPassphrase( - const ProfileSyncService* service, DictionaryValue* args) { - args->SetString("iframeToShow", "passphrase"); - args->SetBoolean("passphrase_creation_rejected", - service->tried_creating_explicit_passphrase()); - args->SetBoolean("passphrase_setting_rejected", - service->tried_setting_explicit_passphrase()); +void SyncSetupFlow::OnPassphraseEntry(const std::string& passphrase) { + Advance(SyncSetupWizard::SETTING_UP); + service_->SetPassphrase(passphrase, true, false); + tried_setting_explicit_passphrase_ = true; } -// static -void SyncSetupFlow::GetArgsForConfigure(ProfileSyncService* service, - DictionaryValue* args) { - args->SetString("iframeToShow", "configure"); - - // By default start on the data types tab. - args->SetString("initialTab", "data-type"); +void SyncSetupFlow::OnPassphraseCancel() { + // If the user cancels when being asked for the passphrase, + // just disable encrypted sync and continue setting up. + if (current_state_ == SyncSetupWizard::ENTER_PASSPHRASE) + DisablePasswordSync(service_); - args->SetBoolean("keepEverythingSynced", - service->profile()->GetPrefs()->GetBoolean(prefs::kKeepEverythingSynced)); + Advance(SyncSetupWizard::SETTING_UP); +} - // Bookmarks, Preferences, and Themes are launched for good, there's no - // going back now. Check if the other data types are registered though. - syncable::ModelTypeSet registered_types; - service->GetRegisteredDataTypes(®istered_types); - args->SetBoolean("passwordsRegistered", - registered_types.count(syncable::PASSWORDS) > 0); - args->SetBoolean("autofillRegistered", - registered_types.count(syncable::AUTOFILL) > 0); - args->SetBoolean("extensionsRegistered", - registered_types.count(syncable::EXTENSIONS) > 0); - args->SetBoolean("typedUrlsRegistered", - registered_types.count(syncable::TYPED_URLS) > 0); - args->SetBoolean("appsRegistered", - registered_types.count(syncable::APPS) > 0); - args->SetBoolean("sessionsRegistered", - registered_types.count(syncable::SESSIONS) > 0); - args->SetBoolean("syncBookmarks", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncBookmarks)); - args->SetBoolean("syncPreferences", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPreferences)); - args->SetBoolean("syncThemes", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncThemes)); - args->SetBoolean("syncPasswords", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncPasswords)); - args->SetBoolean("syncAutofill", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncAutofill)); - args->SetBoolean("syncExtensions", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncExtensions)); - args->SetBoolean("syncSessions", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncSessions)); - args->SetBoolean("syncTypedUrls", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncTypedUrls)); - args->SetBoolean("syncApps", - service->profile()->GetPrefs()->GetBoolean(prefs::kSyncApps)); +// TODO(jhawkins): Remove this method. +void SyncSetupFlow::OnFirstPassphraseEntry(const std::string& option, + const std::string& passphrase) { + NOTREACHED(); +} - // Load the parameters for the encryption tab. - args->SetBoolean("usePassphrase", service->IsUsingSecondaryPassphrase()); +// TODO(jhawkins): Use this method instead of a direct link in the html. +void SyncSetupFlow::OnGoToDashboard() { + BrowserList::GetLastActive()->OpenPrivacyDashboardTabAndActivate(); } -void SyncSetupFlow::GetWebUIMessageHandlers( - std::vector<WebUIMessageHandler*>* handlers) const { - handlers->push_back(flow_handler_); - // We don't own flow_handler_ anymore, but it sticks around until at least - // right after OnDialogClosed() is called (and this object is destroyed). - owns_flow_handler_ = false; +// Use static Run method to get an instance. +SyncSetupFlow::SyncSetupFlow(SyncSetupWizard::State start_state, + SyncSetupWizard::State end_state, + const std::string& args, + SyncSetupFlowContainer* container, + ProfileSyncService* service) + : container_(container), + dialog_start_args_(args), + current_state_(start_state), + end_state_(end_state), + login_start_time_(base::TimeTicks::Now()), + flow_handler_(NULL), + service_(service), + tried_creating_explicit_passphrase_(false), + tried_setting_explicit_passphrase_(false) { } // Returns true if the flow should advance to |state| based on |current_state_|. @@ -583,17 +302,22 @@ bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) { current_state_ == SyncSetupWizard::SETTING_UP; case SyncSetupWizard::GAIA_SUCCESS: return current_state_ == SyncSetupWizard::GAIA_LOGIN; + case SyncSetupWizard::SYNC_EVERYTHING: case SyncSetupWizard::CONFIGURE: return current_state_ == SyncSetupWizard::GAIA_SUCCESS; case SyncSetupWizard::ENTER_PASSPHRASE: - return current_state_ == SyncSetupWizard::CONFIGURE || + return current_state_ == SyncSetupWizard::SYNC_EVERYTHING || + current_state_ == SyncSetupWizard::CONFIGURE || current_state_ == SyncSetupWizard::SETTING_UP; case SyncSetupWizard::PASSPHRASE_MIGRATION: return current_state_ == SyncSetupWizard::GAIA_LOGIN; case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR: - return current_state_ == SyncSetupWizard::CONFIGURE; + DCHECK(current_state_ != SyncSetupWizard::GAIA_LOGIN && + current_state_ != SyncSetupWizard::GAIA_SUCCESS); + return true; case SyncSetupWizard::SETTING_UP: - return current_state_ == SyncSetupWizard::CONFIGURE || + return current_state_ == SyncSetupWizard::SYNC_EVERYTHING || + current_state_ == SyncSetupWizard::CONFIGURE || current_state_ == SyncSetupWizard::ENTER_PASSPHRASE || current_state_ == SyncSetupWizard::PASSPHRASE_MIGRATION; case SyncSetupWizard::FATAL_ERROR: @@ -608,14 +332,8 @@ bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) { } } -void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) { - if (!ShouldAdvance(advance_state)) { - LOG(WARNING) << "Invalid state change from " - << current_state_ << " to " << advance_state; - return; - } - - switch (advance_state) { +void SyncSetupFlow::ActivateState(SyncSetupWizard::State state) { + switch (state) { case SyncSetupWizard::GAIA_LOGIN: { DictionaryValue args; SyncSetupFlow::GetArgsForGaiaLogin(service_, &args); @@ -627,18 +345,27 @@ void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) { flow_handler_->ShowGaiaSuccessAndClose(); break; } - advance_state = SyncSetupWizard::CONFIGURE; + state = SyncSetupWizard::SYNC_EVERYTHING; // Fall through. + case SyncSetupWizard::SYNC_EVERYTHING: { + DictionaryValue args; + SyncSetupFlow::GetArgsForConfigure(service_, &args); + args.SetBoolean("syncEverything", true); + flow_handler_->ShowConfigure(args); + break; + } case SyncSetupWizard::CONFIGURE: { DictionaryValue args; SyncSetupFlow::GetArgsForConfigure(service_, &args); - args.SetString("initialTab", "data-type"); flow_handler_->ShowConfigure(args); break; } case SyncSetupWizard::ENTER_PASSPHRASE: { DictionaryValue args; - SyncSetupFlow::GetArgsForEnterPassphrase(service_, &args); + SyncSetupFlow::GetArgsForEnterPassphrase( + tried_creating_explicit_passphrase_, + tried_setting_explicit_passphrase_, + &args); flow_handler_->ShowPassphraseEntry(args); break; } @@ -677,124 +404,7 @@ void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) { UTF16ToWide(service_->GetAuthenticatedUsername())); break; default: - NOTREACHED() << "Invalid advance state: " << advance_state; - } - current_state_ = advance_state; -} - -void SyncSetupFlow::Focus() { -#if defined(OS_MACOSX) - if (html_dialog_window_) { - platform_util::ActivateWindow(html_dialog_window_); - } -#else - // TODO(csilv): We don't currently have a way to get the reference to the - // dialog on windows/linux. This can be resolved by a cross platform - // implementation of HTML dialogs as described by akalin below. - NOTIMPLEMENTED(); -#endif // defined(OS_MACOSX) -} - -GURL SyncSetupFlow::GetDialogContentURL() const { - return GURL("chrome://syncresources/setup"); -} - -// static -SyncSetupFlow* SyncSetupFlow::Run(ProfileSyncService* service, - SyncSetupFlowContainer* container, - SyncSetupWizard::State start, - SyncSetupWizard::State end, - gfx::NativeWindow parent_window) { - DictionaryValue args; - if (start == SyncSetupWizard::GAIA_LOGIN) - SyncSetupFlow::GetArgsForGaiaLogin(service, &args); - else if (start == SyncSetupWizard::CONFIGURE) - SyncSetupFlow::GetArgsForConfigure(service, &args); - else if (start == SyncSetupWizard::ENTER_PASSPHRASE) - SyncSetupFlow::GetArgsForEnterPassphrase(service, &args); - else if (start == SyncSetupWizard::PASSPHRASE_MIGRATION) - args.SetString("iframeToShow", "firstpassphrase"); - - std::string json_args; - base::JSONWriter::Write(&args, false, &json_args); - - SyncSetupFlow* flow = new SyncSetupFlow(start, end, json_args, - container, service); -#if defined(OS_MACOSX) - // TODO(akalin): Figure out a cleaner way to do this than to have this - // gross per-OS behavior, i.e. have a cross-platform ShowHtmlDialog() - // function that is not tied to a browser instance. Note that if we do - // that, we'll have to fix sync_setup_wizard_unittest.cc as it relies on - // being able to intercept ShowHtmlDialog() calls. - flow->html_dialog_window_ = browser::ShowHtmlDialog(NULL, service->profile(), - flow); -#else - Browser* b = BrowserList::GetLastActive(); - if (b) { - b->BrowserShowHtmlDialog(flow, parent_window); - } else { - delete flow; - return NULL; - } -#endif // defined(OS_MACOSX) - return flow; -} - -void SyncSetupFlow::OnUserSubmittedAuth(const std::string& username, - const std::string& password, - const std::string& captcha, - const std::string& access_code) { - service_->OnUserSubmittedAuth(username, password, captcha, access_code); -} - -void SyncSetupFlow::OnUserConfigured(const SyncConfiguration& configuration) { - // Go to the "loading..." screen." - Advance(SyncSetupWizard::SETTING_UP); - - // If we are activating the passphrase, we need to have one supplied. - DCHECK(service_->IsUsingSecondaryPassphrase() || - !configuration.use_secondary_passphrase || - configuration.secondary_passphrase.length() > 0); - - if (configuration.use_secondary_passphrase && - !service_->IsUsingSecondaryPassphrase()) { - service_->SetPassphrase(configuration.secondary_passphrase, true, true); + NOTREACHED() << "Invalid advance state: " << state; } - - service_->OnUserChoseDatatypes(configuration.sync_everything, - configuration.data_types); -} - -void SyncSetupFlow::OnPassphraseEntry(const std::string& passphrase) { - Advance(SyncSetupWizard::SETTING_UP); - service_->SetPassphrase(passphrase, true, false); -} - -void SyncSetupFlow::OnPassphraseCancel() { - // If the user cancels when being asked for the passphrase, - // just disable encrypted sync and continue setting up. - if (current_state_ == SyncSetupWizard::ENTER_PASSPHRASE) - DisablePasswordSync(service_); - - Advance(SyncSetupWizard::SETTING_UP); -} - -void SyncSetupFlow::OnFirstPassphraseEntry(const std::string& option, - const std::string& passphrase) { - Advance(SyncSetupWizard::SETTING_UP); - - if (option == "explicit") { - service_->SetPassphrase(passphrase, true, true); - } else if (option == "nothanks") { - // User opted out of encrypted sync, need to turn off encrypted - // data types. - DisablePasswordSync(service_); - } else if (option == "google") { - // Implicit passphrase already set up. - Advance(SyncSetupWizard::DONE); - } -} - -void SyncSetupFlow::OnGoToDashboard() { - BrowserList::GetLastActive()->OpenPrivacyDashboardTabAndActivate(); + current_state_ = state; } diff --git a/chrome/browser/sync/sync_setup_flow.h b/chrome/browser/sync/sync_setup_flow.h index 65e0024..377df17 100644 --- a/chrome/browser/sync/sync_setup_flow.h +++ b/chrome/browser/sync/sync_setup_flow.h @@ -15,11 +15,10 @@ #include "chrome/browser/sync/sync_setup_wizard.h" #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/ui/webui/html_dialog_ui.h" -#include "grit/generated_resources.h" #include "ui/base/l10n/l10n_util.h" #include "ui/gfx/native_widget_types.h" -class FlowHandler; +class SyncSetupFlowHandler; class SyncSetupFlowContainer; // A structure which contains all the configuration information for sync. @@ -37,8 +36,9 @@ struct SyncConfiguration { // The state machine used by SyncSetupWizard, exposed in its own header // to facilitate testing of SyncSetupWizard. This class is used to open and -// run the html dialog and deletes itself when the dialog closes. -class SyncSetupFlow : public HtmlDialogUIDelegate { +// run the sync setup overlay in tabbed options and deletes itself when the +// overlay closes. +class SyncSetupFlow { public: virtual ~SyncSetupFlow(); @@ -48,8 +48,7 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { static SyncSetupFlow* Run(ProfileSyncService* service, SyncSetupFlowContainer* container, SyncSetupWizard::State start, - SyncSetupWizard::State end, - gfx::NativeWindow parent_window); + SyncSetupWizard::State end); // Fills |args| with "user" and "error" arguments by querying |service|. static void GetArgsForGaiaLogin( @@ -63,9 +62,12 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { // Fills |args| for the enter passphrase screen. static void GetArgsForEnterPassphrase( - const ProfileSyncService* service, + bool tried_creating_explicit_passphrase, + bool tried_setting_explicit_passphrase, DictionaryValue* args); + void AttachSyncSetupHandler(SyncSetupFlowHandler* handler); + // Triggers a state machine transition to advance_state. void Advance(SyncSetupWizard::State advance_state); @@ -73,36 +75,6 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { // obscured by a browser window. void Focus(); - // HtmlDialogUIDelegate implementation. - // Get the HTML file path for the content to load in the dialog. - virtual GURL GetDialogContentURL() const; - - // HtmlDialogUIDelegate implementation. - virtual void GetWebUIMessageHandlers( - std::vector<WebUIMessageHandler*>* handlers) const; - - // HtmlDialogUIDelegate implementation. - // Get the size of the dialog. - virtual void GetDialogSize(gfx::Size* size) const; - - // HtmlDialogUIDelegate implementation. - // Gets the JSON string input to use when opening the dialog. - virtual std::string GetDialogArgs() const; - - // HtmlDialogUIDelegate implementation. - // A callback to notify the delegate that the dialog closed. - virtual void OnDialogClosed(const std::string& json_retval); - - // HtmlDialogUIDelegate implementation. - virtual void OnCloseContents(TabContents* source, bool* out_close_dialog) {} - - // HtmlDialogUIDelegate implementation. - virtual std::wstring GetDialogTitle() const; - - // HtmlDialogUIDelegate implementation. - virtual bool IsDialogModal() const; - virtual bool ShouldShowDialogTitle() const; - void OnUserSubmittedAuth(const std::string& username, const std::string& password, const std::string& captcha, @@ -119,11 +91,14 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { // The 'first passphrase' screen is for users migrating from a build // without passwords, who are prompted to make a passphrase choice. + // TODO(jhawkins): This is no longer used; remove this method. void OnFirstPassphraseEntry(const std::string& option, const std::string& passphrase); void OnGoToDashboard(); + void OnDialogClosed(const std::string& json_retval); + private: FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, InitialStepLogin); FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, ChooseDataTypesSetsPrefs); @@ -141,7 +116,8 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { // Use static Run method to get an instance. SyncSetupFlow(SyncSetupWizard::State start_state, SyncSetupWizard::State end_state, - const std::string& args, SyncSetupFlowContainer* container, + const std::string& args, + SyncSetupFlowContainer* container, ProfileSyncService* service); // Returns true if |this| should transition its state machine to |state| @@ -149,6 +125,8 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { // a no-op. bool ShouldAdvance(SyncSetupWizard::State state); + void ActivateState(SyncSetupWizard::State state); + SyncSetupFlowContainer* container_; // Our container. Don't own this. std::string dialog_start_args_; // The args to pass to the initial page. @@ -158,17 +136,16 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { // Time that the GAIA_LOGIN step was received. base::TimeTicks login_start_time_; - // The handler needed for the entire flow. - FlowHandler* flow_handler_; - mutable bool owns_flow_handler_; + // The handler needed for the entire flow. Weak reference. + SyncSetupFlowHandler* flow_handler_; - // We need this to propagate back all user settings changes. + // We need this to propagate back all user settings changes. Weak reference. ProfileSyncService* service_; - // Currently used only on OS X - // TODO(akalin): Add the necessary support to the other OSes and use - // this for them. - gfx::NativeWindow html_dialog_window_; + // Set to true if we've tried creating/setting an explicit passphrase, so we + // can appropriately reflect this in the UI. + bool tried_creating_explicit_passphrase_; + bool tried_setting_explicit_passphrase_; DISALLOW_COPY_AND_ASSIGN(SyncSetupFlow); }; @@ -191,45 +168,4 @@ class SyncSetupFlowContainer { DISALLOW_COPY_AND_ASSIGN(SyncSetupFlowContainer); }; -// The FlowHandler connects the state machine to the dialog backing HTML and -// JS namespace by implementing WebUIMessageHandler and being invoked by the -// SyncSetupFlow. Exposed here to facilitate testing. -class FlowHandler : public WebUIMessageHandler { - public: - FlowHandler() {} - virtual ~FlowHandler() {} - - // WebUIMessageHandler implementation. - virtual void RegisterMessages(); - - // Callbacks from the page. - void HandleSubmitAuth(const ListValue* args); - void HandleConfigure(const ListValue* args); - void HandlePassphraseEntry(const ListValue* args); - void HandlePassphraseCancel(const ListValue* args); - void HandleFirstPassphrase(const ListValue* args); - void HandleGoToDashboard(const ListValue* args); - - // These functions control which part of the HTML is visible. - void ShowGaiaLogin(const DictionaryValue& args); - void ShowGaiaSuccessAndClose(); - void ShowGaiaSuccessAndSettingUp(); - void ShowConfigure(const DictionaryValue& args); - void ShowPassphraseEntry(const DictionaryValue& args); - void ShowFirstPassphrase(const DictionaryValue& args); - void ShowSettingUp(); - void ShowSetupDone(const std::wstring& user); - void ShowFirstTimeDone(const std::wstring& user); - - void set_flow(SyncSetupFlow* flow) { - flow_ = flow; - } - - private: - void ExecuteJavascriptInIFrame(const std::wstring& iframe_xpath, - const std::wstring& js); - SyncSetupFlow* flow_; - DISALLOW_COPY_AND_ASSIGN(FlowHandler); -}; - #endif // CHROME_BROWSER_SYNC_SYNC_SETUP_FLOW_H_ diff --git a/chrome/browser/sync/sync_setup_flow_handler.h b/chrome/browser/sync/sync_setup_flow_handler.h new file mode 100644 index 0000000..51455d0 --- /dev/null +++ b/chrome/browser/sync/sync_setup_flow_handler.h @@ -0,0 +1,35 @@ +// Copyright (c) 2011 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_SYNC_SETUP_FLOW_HANDLER_H_ +#define CHROME_BROWSER_SYNC_SYNC_SETUP_FLOW_HANDLER_H_ +#pragma once + +#include <string> + +#include "content/browser/webui/web_ui.h" + +class DictionaryValue; +class SyncSetupFlow; + +class SyncSetupFlowHandler { + public: + // These functions control which part of the HTML is visible. + virtual void ShowGaiaLogin(const DictionaryValue& args) = 0; + virtual void ShowGaiaSuccessAndClose() = 0; + virtual void ShowGaiaSuccessAndSettingUp() = 0; + virtual void ShowConfigure(const DictionaryValue& args) = 0; + virtual void ShowPassphraseEntry(const DictionaryValue& args) = 0; + virtual void ShowFirstPassphrase(const DictionaryValue& args) = 0; + virtual void ShowSettingUp() = 0; + virtual void ShowSetupDone(const std::wstring& user) = 0; + virtual void ShowFirstTimeDone(const std::wstring& user) = 0; + + virtual void SetFlow(SyncSetupFlow* flow) = 0; + + protected: + virtual ~SyncSetupFlowHandler() {} +}; + +#endif // CHROME_BROWSER_SYNC_SYNC_SETUP_FLOW_HANDLER_H_ diff --git a/chrome/browser/sync/sync_setup_wizard.cc b/chrome/browser/sync/sync_setup_wizard.cc index 3201419..252aeff 100644 --- a/chrome/browser/sync/sync_setup_wizard.cc +++ b/chrome/browser/sync/sync_setup_wizard.cc @@ -4,276 +4,15 @@ #include "chrome/browser/sync/sync_setup_wizard.h" -#include "base/message_loop.h" -#include "base/singleton.h" -#include "chrome/browser/google/google_util.h" -#include "chrome/browser/prefs/pref_service.h" -#include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sync/profile_sync_service.h" -#include "chrome/browser/sync/sync_setup_flow.h" -#include "chrome/browser/ui/webui/chrome_url_data_manager.h" -#include "chrome/common/jstemplate_builder.h" -#include "chrome/common/pref_names.h" -#include "chrome/common/url_constants.h" -#include "content/browser/browser_thread.h" -#include "googleurl/src/gurl.h" -#include "grit/app_resources.h" -#include "grit/browser_resources.h" -#include "grit/chromium_strings.h" -#include "grit/locale_settings.h" -#include "ui/base/resource/resource_bundle.h" - -namespace { - -// Utility method to keep dictionary population code streamlined. -void AddString(DictionaryValue* dictionary, - const std::string& key, - int resource_id) { - dictionary->SetString(key, l10n_util::GetStringUTF16(resource_id)); -} - -} - -class SyncResourcesSource : public ChromeURLDataManager::DataSource { - public: - SyncResourcesSource() - : DataSource(chrome::kChromeUISyncResourcesHost, MessageLoop::current()) { - } - - virtual void StartDataRequest(const std::string& path, - bool is_off_the_record, - int request_id); - - virtual std::string GetMimeType(const std::string& path) const { - return "text/html"; - } - - static const char* kInvalidPasswordHelpUrl; - static const char* kCanNotAccessAccountUrl; - static const char* kCreateNewAccountUrl; - static const char* kEncryptionHelpUrl; - - private: - virtual ~SyncResourcesSource() {} - - // Takes a string containing an URL and returns an URL containing a CGI - // parameter of the form "&hl=xy" where 'xy' is the language code of the - // current locale. - std::string GetLocalizedUrl(const std::string& url) const; - - DISALLOW_COPY_AND_ASSIGN(SyncResourcesSource); -}; - -const char* SyncResourcesSource::kInvalidPasswordHelpUrl = - "http://www.google.com/support/accounts/bin/answer.py?ctx=ch&answer=27444"; -const char* SyncResourcesSource::kCanNotAccessAccountUrl = - "http://www.google.com/support/accounts/bin/answer.py?answer=48598"; -#if defined(OS_CHROMEOS) -const char* SyncResourcesSource::kEncryptionHelpUrl = - "http://www.google.com/support/chromeos/bin/answer.py?answer=1181035"; -#else -const char* SyncResourcesSource::kEncryptionHelpUrl = - "http://www.google.com/support/chrome/bin/answer.py?answer=1181035"; -#endif -const char* SyncResourcesSource::kCreateNewAccountUrl = - "https://www.google.com/accounts/NewAccount?service=chromiumsync"; - -void SyncResourcesSource::StartDataRequest(const std::string& path_raw, - bool is_off_the_record, int request_id) { - using l10n_util::GetStringUTF16; - using l10n_util::GetStringFUTF16; - - const char kSyncSetupFlowPath[] = "setup"; - const char kSyncGaiaLoginPath[] = "gaialogin"; - const char kSyncConfigurePath[] = "configure"; - const char kSyncPassphrasePath[] = "passphrase"; - const char kSyncFirstPassphrasePath[] = "firstpassphrase"; - const char kSyncSettingUpPath[] = "settingup"; - const char kSyncSetupDonePath[] = "setupdone"; - - std::string response; - DictionaryValue strings; - DictionaryValue* dict = &strings; - int html_resource_id = 0; - if (path_raw == kSyncGaiaLoginPath) { - html_resource_id = IDR_GAIA_LOGIN_HTML; - - // Start by setting the per-locale URLs we show on the setup wizard. - dict->SetString("invalidpasswordhelpurl", - GetLocalizedUrl(kInvalidPasswordHelpUrl)); - dict->SetString("cannotaccessaccounturl", - GetLocalizedUrl(kCanNotAccessAccountUrl)); - dict->SetString("createnewaccounturl", - GetLocalizedUrl(kCreateNewAccountUrl)); - AddString(dict, "settingupsync", IDS_SYNC_LOGIN_SETTING_UP_SYNC); - dict->SetString("introduction", - GetStringFUTF16(IDS_SYNC_LOGIN_INTRODUCTION, - GetStringUTF16(IDS_PRODUCT_NAME))); - AddString(dict, "signinprefix", IDS_SYNC_LOGIN_SIGNIN_PREFIX); - AddString(dict, "signinsuffix", IDS_SYNC_LOGIN_SIGNIN_SUFFIX); - AddString(dict, "cannotbeblank", IDS_SYNC_CANNOT_BE_BLANK); - AddString(dict, "emaillabel", IDS_SYNC_LOGIN_EMAIL); - AddString(dict, "passwordlabel", IDS_SYNC_LOGIN_PASSWORD); - AddString(dict, "invalidcredentials", IDS_SYNC_INVALID_USER_CREDENTIALS); - AddString(dict, "signin", IDS_SYNC_SIGNIN); - AddString(dict, "couldnotconnect", IDS_SYNC_LOGIN_COULD_NOT_CONNECT); - AddString(dict, "cannotaccessaccount", IDS_SYNC_CANNOT_ACCESS_ACCOUNT); - AddString(dict, "createaccount", IDS_SYNC_CREATE_ACCOUNT); - AddString(dict, "cancel", IDS_CANCEL); - AddString(dict, "settingup", IDS_SYNC_LOGIN_SETTING_UP); - AddString(dict, "success", IDS_SYNC_SUCCESS); - AddString(dict, "errorsigningin", IDS_SYNC_ERROR_SIGNING_IN); - AddString(dict, "captchainstructions", IDS_SYNC_GAIA_CAPTCHA_INSTRUCTIONS); - AddString(dict, "invalidaccesscode", IDS_SYNC_INVALID_ACCESS_CODE_LABEL); - AddString(dict, "enteraccesscode", IDS_SYNC_ENTER_ACCESS_CODE_LABEL); - AddString(dict, "getaccesscodehelp", IDS_SYNC_ACCESS_CODE_HELP_LABEL); - AddString(dict, "getaccesscodeurl", IDS_SYNC_GET_ACCESS_CODE_URL); - } else if (path_raw == kSyncConfigurePath) { - html_resource_id = IDR_SYNC_CONFIGURE_HTML; - - AddString(dict, "dataTypes", IDS_SYNC_DATA_TYPES_TAB_NAME); - AddString(dict, "encryption", IDS_SYNC_ENCRYPTION_TAB_NAME); - - // Stuff for the choose data types localized. - AddString(dict, "choosedatatypesheader", IDS_SYNC_CHOOSE_DATATYPES_HEADER); - dict->SetString("choosedatatypesinstructions", - GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, - GetStringUTF16(IDS_PRODUCT_NAME))); - AddString(dict, "keepeverythingsynced", IDS_SYNC_EVERYTHING); - AddString(dict, "choosedatatypes", IDS_SYNC_CHOOSE_DATATYPES); - AddString(dict, "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS); - AddString(dict, "preferences", IDS_SYNC_DATATYPE_PREFERENCES); - AddString(dict, "autofill", IDS_SYNC_DATATYPE_AUTOFILL); - AddString(dict, "themes", IDS_SYNC_DATATYPE_THEMES); - AddString(dict, "passwords", IDS_SYNC_DATATYPE_PASSWORDS); - AddString(dict, "extensions", IDS_SYNC_DATATYPE_EXTENSIONS); - AddString(dict, "typedurls", IDS_SYNC_DATATYPE_TYPED_URLS); - AddString(dict, "apps", IDS_SYNC_DATATYPE_APPS); - AddString(dict, "foreignsessions", IDS_SYNC_DATATYPE_SESSIONS); - AddString(dict, "synczerodatatypeserror", IDS_SYNC_ZERO_DATA_TYPES_ERROR); - AddString(dict, "abortederror", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR); +#include <stddef.h> +#include <ostream> - // Stuff for the encryption tab. - dict->SetString("encryptionInstructions", - GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, - GetStringUTF16(IDS_PRODUCT_NAME))); - AddString(dict, "encryptAllLabel", IDS_SYNC_ENCRYPT_ALL_LABEL); - - AddString(dict, "googleOption", IDS_SYNC_PASSPHRASE_OPT_GOOGLE); - AddString(dict, "explicitOption", IDS_SYNC_PASSPHRASE_OPT_EXPLICIT); - AddString(dict, "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE); - AddString(dict, "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT); - AddString(dict, "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL); - AddString(dict, "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL); - AddString(dict, "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR); - AddString(dict, "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR); - - AddString(dict, "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING); - AddString(dict, "cleardata", IDS_SYNC_CLEAR_DATA_FOR_PASSPHRASE); - AddString(dict, "cleardatalink", IDS_SYNC_CLEAR_DATA_LINK); - - AddString(dict, "learnmore", IDS_LEARN_MORE); - dict->SetString("encryptionhelpurl", - GetLocalizedUrl(kEncryptionHelpUrl)); - - // Stuff for the footer. - AddString(dict, "ok", IDS_OK); - AddString(dict, "cancel", IDS_CANCEL); - } else if (path_raw == kSyncPassphrasePath) { - html_resource_id = IDR_SYNC_PASSPHRASE_HTML; - AddString(dict, "enterPassphraseTitle", IDS_SYNC_ENTER_PASSPHRASE_TITLE); - AddString(dict, "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY); - AddString(dict, "enterOtherPassphraseBody", - IDS_SYNC_ENTER_OTHER_PASSPHRASE_BODY); - AddString(dict, "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL); - AddString(dict, "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE); - AddString(dict, "passphraseRecover", IDS_SYNC_PASSPHRASE_RECOVER); - AddString(dict, "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING); - AddString(dict, "cleardatalink", IDS_SYNC_CLEAR_DATA_LINK); - - AddString(dict, "cancelWarningHeader", - IDS_SYNC_PASSPHRASE_CANCEL_WARNING_HEADER); - AddString(dict, "cancelWarning", IDS_SYNC_PASSPHRASE_CANCEL_WARNING); - AddString(dict, "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES); - AddString(dict, "no", IDS_SYNC_PASSPHRASE_CANCEL_NO); - - AddString(dict, "ok", IDS_OK); - AddString(dict, "cancel", IDS_CANCEL); - } else if (path_raw == kSyncFirstPassphrasePath) { - html_resource_id = IDR_SYNC_FIRST_PASSPHRASE_HTML; - AddString(dict, "title", IDS_SYNC_FIRST_PASSPHRASE_TITLE); - dict->SetString("instructions", - GetStringFUTF16(IDS_SYNC_FIRST_PASSPHRASE_MESSAGE, - GetStringUTF16(IDS_PRODUCT_NAME))); - AddString(dict, "googleOption", IDS_SYNC_PASSPHRASE_OPT_GOOGLE); - AddString(dict, "explicitOption", IDS_SYNC_PASSPHRASE_OPT_EXPLICIT); - AddString(dict, "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE); - AddString(dict, "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT); - AddString(dict, "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL); - AddString(dict, "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL); - AddString(dict, "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR); - AddString(dict, "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR); - - AddString(dict, "learnmore", IDS_LEARN_MORE); - dict->SetString("encryptionhelpurl", - GetLocalizedUrl(kEncryptionHelpUrl)); - - AddString(dict, "syncpasswords", IDS_SYNC_FIRST_PASSPHRASE_OK); - AddString(dict, "nothanks", IDS_SYNC_FIRST_PASSPHRASE_CANCEL); - } else if (path_raw == kSyncSettingUpPath) { - html_resource_id = IDR_SYNC_SETTING_UP_HTML; - - AddString(dict, "settingup", IDS_SYNC_LOGIN_SETTING_UP); - AddString(dict, "cancel", IDS_CANCEL); - } else if (path_raw == kSyncSetupDonePath) { - html_resource_id = IDR_SYNC_SETUP_DONE_HTML; - - AddString(dict, "success", IDS_SYNC_SUCCESS); - dict->SetString("setupsummary", - GetStringFUTF16(IDS_SYNC_SETUP_ALL_DONE, - GetStringUTF16(IDS_PRODUCT_NAME))); - AddString(dict, "firsttimesummary", IDS_SYNC_SETUP_FIRST_TIME_ALL_DONE); - AddString(dict, "okay", IDS_SYNC_SETUP_OK_BUTTON_LABEL); - } else if (path_raw == kSyncSetupFlowPath) { - html_resource_id = IDR_SYNC_SETUP_FLOW_HTML; - } - - if (html_resource_id > 0) { - const base::StringPiece html( - ResourceBundle::GetSharedInstance().GetRawDataResource( - html_resource_id)); - SetFontAndTextDirection(dict); - response = jstemplate_builder::GetI18nTemplateHtml(html, dict); - } - - // Send the response. - scoped_refptr<RefCountedBytes> html_bytes(new RefCountedBytes); - html_bytes->data.resize(response.size()); - std::copy(response.begin(), response.end(), html_bytes->data.begin()); - SendResponse(request_id, html_bytes); -} - -std::string SyncResourcesSource::GetLocalizedUrl( - const std::string& url) const { - GURL original_url(url); - DCHECK(original_url.is_valid()); - GURL localized_url = google_util::AppendGoogleLocaleParam(original_url); - return localized_url.spec(); -} +#include "base/logging.h" +#include "chrome/browser/sync/sync_setup_flow.h" SyncSetupWizard::SyncSetupWizard(ProfileSyncService* service) : service_(service), - flow_container_(new SyncSetupFlowContainer()), - parent_window_(NULL) { - // If we're in a unit test, we may not have an IO thread or profile. Avoid - // creating a SyncResourcesSource since we may leak it (since it's - // DeleteOnUIThread). - if (BrowserThread::IsMessageLoopValid(BrowserThread::IO) && - service_->profile()) { - // Add our network layer data source for 'cloudy' URLs. - SyncResourcesSource* sync_source = new SyncResourcesSource(); - service_->profile()->GetChromeURLDataManager()->AddDataSource(sync_source); - } + flow_container_(new SyncSetupFlowContainer()) { } SyncSetupWizard::~SyncSetupWizard() { @@ -285,23 +24,20 @@ void SyncSetupWizard::Step(State advance_state) { if (flow) { // A setup flow is in progress and dialog is currently showing. flow->Advance(advance_state); - } else if (!service_->profile()->GetPrefs()->GetBoolean( - prefs::kSyncHasSetupCompleted)) { + } else if (!service_->HasSyncSetupCompleted()) { if (IsTerminalState(advance_state)) return; // No flow is in progress, and we have never escorted the user all the // way through the wizard flow. flow_container_->set_flow( - SyncSetupFlow::Run(service_, flow_container_, advance_state, DONE, - parent_window_)); + SyncSetupFlow::Run(service_, flow_container_, advance_state, DONE)); } else { - // No flow in in progress, but we've finished the wizard flow once before. + // No flow in progress, but we've finished the wizard flow once before. // This is just a discrete run. if (IsTerminalState(advance_state)) return; // Nothing to do. flow_container_->set_flow(SyncSetupFlow::Run(service_, flow_container_, - advance_state, GetEndStateForDiscreteRun(advance_state), - parent_window_)); + advance_state, GetEndStateForDiscreteRun(advance_state))); } } @@ -325,8 +61,14 @@ void SyncSetupWizard::Focus() { } } -void SyncSetupWizard::SetParent(gfx::NativeWindow parent_window) { - parent_window_ = parent_window; +SyncSetupFlow* SyncSetupWizard::AttachSyncSetupHandler( + SyncSetupFlowHandler* handler) { + SyncSetupFlow* flow = flow_container_->get_flow(); + if (!flow) + return NULL; + + flow->AttachSyncSetupHandler(handler); + return flow; } // static @@ -336,6 +78,7 @@ SyncSetupWizard::State SyncSetupWizard::GetEndStateForDiscreteRun( if (start_state == GAIA_LOGIN) { result = GAIA_SUCCESS; } else if (start_state == ENTER_PASSPHRASE || + start_state == SYNC_EVERYTHING || start_state == CONFIGURE || start_state == PASSPHRASE_MIGRATION) { result = DONE; diff --git a/chrome/browser/sync/sync_setup_wizard.h b/chrome/browser/sync/sync_setup_wizard.h index ce08266..7989761 100644 --- a/chrome/browser/sync/sync_setup_wizard.h +++ b/chrome/browser/sync/sync_setup_wizard.h @@ -1,4 +1,4 @@ -// Copyright (c) 2006-2008 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -7,9 +7,10 @@ #pragma once #include "base/basictypes.h" -#include "ui/gfx/native_widget_types.h" +class SyncSetupFlow; class SyncSetupFlowContainer; +class SyncSetupFlowHandler; class ProfileSyncService; @@ -21,6 +22,8 @@ class SyncSetupWizard { // A login attempt succeeded. This will wait for an explicit transition // (via Step) to the next state. GAIA_SUCCESS, + // Show the screen that confirms everything will be synced. + SYNC_EVERYTHING, // Show the screen that lets you configure sync. // There are two tabs: // Data Types -- @@ -69,7 +72,7 @@ class SyncSetupWizard { // not visible. void Focus(); - void SetParent(gfx::NativeWindow parent_window); + SyncSetupFlow* AttachSyncSetupHandler(SyncSetupFlowHandler* handler); private: // If we just need to pop open an individual dialog, say to collect @@ -85,7 +88,7 @@ class SyncSetupWizard { SyncSetupFlowContainer* flow_container_; - gfx::NativeWindow parent_window_; + SyncSetupFlowHandler* flow_handler_; DISALLOW_COPY_AND_ASSIGN(SyncSetupWizard); }; diff --git a/chrome/browser/sync/sync_setup_wizard_unittest.cc b/chrome/browser/sync/sync_setup_wizard_unittest.cc index d705706..72ef033 100644 --- a/chrome/browser/sync/sync_setup_wizard_unittest.cc +++ b/chrome/browser/sync/sync_setup_wizard_unittest.cc @@ -2,16 +2,19 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +// TODO(jhawkins): Rewrite these tests to handle the new inlined sync UI. + #include "chrome/browser/sync/sync_setup_wizard.h" #include "base/json/json_writer.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/stl_util-inl.h" #include "base/utf_string_conversions.h" #include "chrome/browser/prefs/pref_service.h" #include "chrome/browser/sync/profile_sync_factory_mock.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/sync_setup_flow.h" +#include "chrome/browser/sync/sync_setup_flow_handler.h" #include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser_list.h" #include "chrome/common/net/gaia/google_service_auth_error.h" @@ -126,27 +129,13 @@ class TestBrowserWindowForWizardTest : public TestBrowserWindow { virtual ~TestBrowserWindowForWizardTest() { if (flow_.get()) { - // In real life, the handlers are destroyed by the WebUI infrastructure, - // which calls GetWebUIMessageHandlers to take ownership. This does not - // exist in our test, so we perform cleanup manually. - std::vector<WebUIMessageHandler*> handlers; - flow_->GetWebUIMessageHandlers(&handlers); // The handler contract is that they are valid for the lifetime of the - // HTMLDialogUIDelegate, but are cleaned up after the dialog is closed + // sync login overlay, but are cleaned up after the dialog is closed // and/or deleted. flow_.reset(); - STLDeleteElements(&handlers); } } - // We intercept this call to hijack the flow created and then use it to - // drive the wizard as if we were the HTML page. - virtual void ShowHTMLDialog(HtmlDialogUIDelegate* delegate, - gfx::NativeWindow parent_window) { - flow_.reset(static_cast<SyncSetupFlow*>(delegate)); - was_show_html_dialog_called_ = true; - } - bool TestAndResetWasShowHTMLDialogCalled() { bool ret = was_show_html_dialog_called_; was_show_html_dialog_called_ = false; @@ -157,11 +146,8 @@ class TestBrowserWindowForWizardTest : public TestBrowserWindow { // Handles cleaning up the delegate and associated handlers. void CloseDialog() { if (flow_.get()) { - std::vector<WebUIMessageHandler*> handlers; - flow_->GetWebUIMessageHandlers(&handlers); // The flow deletes itself here. Don't use reset(). flow_.release()->OnDialogClosed(""); - STLDeleteElements(&handlers); } } @@ -178,8 +164,7 @@ class TestBrowserWindowForWizardTest : public TestBrowserWindow { class SyncSetupWizardTest : public BrowserWithTestWindowTest { public: SyncSetupWizardTest() - : file_thread_(BrowserThread::FILE, MessageLoop::current()), - test_window_(NULL), + : test_window_(NULL), wizard_(NULL) { } virtual ~SyncSetupWizardTest() { } virtual void SetUp() { @@ -203,7 +188,6 @@ class SyncSetupWizardTest : public BrowserWithTestWindowTest { wizard_.reset(); } - BrowserThread file_thread_; TestBrowserWindowForWizardTest* test_window_; scoped_ptr<SyncSetupWizard> wizard_; ProfileSyncServiceForWizardTest* service_; @@ -220,7 +204,7 @@ class SyncSetupWizardTest : public BrowserWithTestWindowTest { #define SKIP_TEST_ON_MACOSX() do {} while (0) #endif -TEST_F(SyncSetupWizardTest, InitialStepLogin) { +TEST_F(SyncSetupWizardTest, DISABLED_InitialStepLogin) { SKIP_TEST_ON_MACOSX(); DictionaryValue dialog_args; SyncSetupFlow::GetArgsForGaiaLogin(service_, &dialog_args); @@ -244,6 +228,7 @@ TEST_F(SyncSetupWizardTest, InitialStepLogin) { EXPECT_EQ(SyncSetupWizard::DONE, test_window_->flow()->end_state_); EXPECT_EQ(json_start_args, test_window_->flow()->dialog_start_args_); +#if 0 // Simulate the user submitting credentials. test_window_->flow()->flow_handler_->HandleSubmitAuth(&credentials); EXPECT_TRUE(wizard_->IsVisible()); @@ -253,6 +238,7 @@ TEST_F(SyncSetupWizardTest, InitialStepLogin) { EXPECT_EQ(kTestCaptcha, service_->captcha_); EXPECT_FALSE(service_->user_cancelled_dialog_); service_->ResetTestStats(); +#endif // Simulate failed credentials. AuthError invalid_gaia(AuthError::INVALID_GAIA_CREDENTIALS); @@ -297,8 +283,8 @@ TEST_F(SyncSetupWizardTest, InitialStepLogin) { EXPECT_TRUE(wizard_->IsVisible()); EXPECT_FALSE(test_window_->TestAndResetWasShowHTMLDialogCalled()); // In a non-discrete run, GAIA_SUCCESS immediately transitions you to - // CONFIGURE. - EXPECT_EQ(SyncSetupWizard::CONFIGURE, + // SYNC_EVERYTHING. + EXPECT_EQ(SyncSetupWizard::SYNC_EVERYTHING, test_window_->flow()->current_state_); // That's all we're testing here, just move on to DONE. We'll test the @@ -310,7 +296,7 @@ TEST_F(SyncSetupWizardTest, InitialStepLogin) { EXPECT_EQ(SyncSetupWizard::DONE, test_window_->flow()->current_state_); } -TEST_F(SyncSetupWizardTest, ChooseDataTypesSetsPrefs) { +TEST_F(SyncSetupWizardTest, DISABLED_ChooseDataTypesSetsPrefs) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); @@ -325,6 +311,7 @@ TEST_F(SyncSetupWizardTest, ChooseDataTypesSetsPrefs) { data_type_choices += "\"syncSessions\":false,\"usePassphrase\":false}"; data_type_choices_value.Append(new StringValue(data_type_choices)); +#if 0 // Simulate the user choosing data types; bookmarks, prefs, typed // URLS, and apps are on, the rest are off. test_window_->flow()->flow_handler_->HandleConfigure( @@ -340,11 +327,11 @@ TEST_F(SyncSetupWizardTest, ChooseDataTypesSetsPrefs) { EXPECT_EQ(service_->chosen_data_types_.count(syncable::EXTENSIONS), 0U); EXPECT_EQ(service_->chosen_data_types_.count(syncable::TYPED_URLS), 1U); EXPECT_EQ(service_->chosen_data_types_.count(syncable::APPS), 1U); - +#endif test_window_->CloseDialog(); } -TEST_F(SyncSetupWizardTest, EnterPassphraseRequired) { +TEST_F(SyncSetupWizardTest, DISABLED_EnterPassphraseRequired) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); @@ -354,16 +341,19 @@ TEST_F(SyncSetupWizardTest, EnterPassphraseRequired) { wizard_->Step(SyncSetupWizard::ENTER_PASSPHRASE); EXPECT_EQ(SyncSetupWizard::ENTER_PASSPHRASE, test_window_->flow()->current_state_); +#if 0 ListValue value; value.Append(new StringValue("{\"passphrase\":\"myPassphrase\"," "\"mode\":\"gaia\"}")); test_window_->flow()->flow_handler_->HandlePassphraseEntry(&value); EXPECT_EQ("myPassphrase", service_->passphrase_); +#endif } -TEST_F(SyncSetupWizardTest, PassphraseMigration) { +TEST_F(SyncSetupWizardTest, DISABLED_PassphraseMigration) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::PASSPHRASE_MIGRATION); +#if 0 ListValue value; value.Append(new StringValue("{\"option\":\"explicit\"," "\"passphrase\":\"myPassphrase\"}")); @@ -375,9 +365,10 @@ TEST_F(SyncSetupWizardTest, PassphraseMigration) { "\"passphrase\":\"myPassphrase\"}")); test_window_->flow()->flow_handler_->HandleFirstPassphrase(&value2); EXPECT_EQ(service_->chosen_data_types_.count(syncable::PASSWORDS), 0U); +#endif } -TEST_F(SyncSetupWizardTest, DialogCancelled) { +TEST_F(SyncSetupWizardTest, DISABLED_DialogCancelled) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); // Simulate the user closing the dialog. @@ -400,7 +391,7 @@ TEST_F(SyncSetupWizardTest, DialogCancelled) { EXPECT_EQ(std::string(), service_->password_); } -TEST_F(SyncSetupWizardTest, InvalidTransitions) { +TEST_F(SyncSetupWizardTest, DISABLED_InvalidTransitions) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); EXPECT_FALSE(wizard_->IsVisible()); @@ -426,14 +417,14 @@ TEST_F(SyncSetupWizardTest, InvalidTransitions) { test_window_->flow()->current_state_); wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); - EXPECT_EQ(SyncSetupWizard::CONFIGURE, + EXPECT_EQ(SyncSetupWizard::SYNC_EVERYTHING, test_window_->flow()->current_state_); wizard_->Step(SyncSetupWizard::FATAL_ERROR); EXPECT_EQ(SyncSetupWizard::FATAL_ERROR, test_window_->flow()->current_state_); } -TEST_F(SyncSetupWizardTest, FullSuccessfulRunSetsPref) { +TEST_F(SyncSetupWizardTest, DISABLED_FullSuccessfulRunSetsPref) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); @@ -445,7 +436,7 @@ TEST_F(SyncSetupWizardTest, FullSuccessfulRunSetsPref) { prefs::kSyncHasSetupCompleted)); } -TEST_F(SyncSetupWizardTest, FirstFullSuccessfulRunSetsPref) { +TEST_F(SyncSetupWizardTest, DISABLED_FirstFullSuccessfulRunSetsPref) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); @@ -457,7 +448,7 @@ TEST_F(SyncSetupWizardTest, FirstFullSuccessfulRunSetsPref) { prefs::kSyncHasSetupCompleted)); } -TEST_F(SyncSetupWizardTest, AbortedByPendingClear) { +TEST_F(SyncSetupWizardTest, DISABLED_AbortedByPendingClear) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); @@ -468,7 +459,7 @@ TEST_F(SyncSetupWizardTest, AbortedByPendingClear) { EXPECT_FALSE(wizard_->IsVisible()); } -TEST_F(SyncSetupWizardTest, DiscreteRunChooseDataTypes) { +TEST_F(SyncSetupWizardTest, DISABLED_DiscreteRunChooseDataTypes) { SKIP_TEST_ON_MACOSX(); // For a discrete run, we need to have ran through setup once. wizard_->Step(SyncSetupWizard::GAIA_LOGIN); @@ -486,7 +477,8 @@ TEST_F(SyncSetupWizardTest, DiscreteRunChooseDataTypes) { EXPECT_FALSE(wizard_->IsVisible()); } -TEST_F(SyncSetupWizardTest, DiscreteRunChooseDataTypesAbortedByPendingClear) { +TEST_F(SyncSetupWizardTest, + DISABLED_DiscreteRunChooseDataTypesAbortedByPendingClear) { SKIP_TEST_ON_MACOSX(); // For a discrete run, we need to have ran through setup once. wizard_->Step(SyncSetupWizard::GAIA_LOGIN); @@ -506,7 +498,7 @@ TEST_F(SyncSetupWizardTest, DiscreteRunChooseDataTypesAbortedByPendingClear) { EXPECT_FALSE(wizard_->IsVisible()); } -TEST_F(SyncSetupWizardTest, DiscreteRunGaiaLogin) { +TEST_F(SyncSetupWizardTest, DISABLED_DiscreteRunGaiaLogin) { SKIP_TEST_ON_MACOSX(); DictionaryValue dialog_args; // For a discrete run, we need to have ran through setup once. diff --git a/chrome/browser/sync/sync_ui_util.cc b/chrome/browser/sync/sync_ui_util.cc index 8cfe948..1f1e453 100644 --- a/chrome/browser/sync/sync_ui_util.cc +++ b/chrome/browser/sync/sync_ui_util.cc @@ -12,6 +12,7 @@ #include "chrome/browser/profiles/profile.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/ui/browser.h" +#include "chrome/browser/ui/browser_window.h" #include "chrome/browser/ui/options/options_window.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/net/gaia/google_service_auth_error.h" @@ -38,8 +39,7 @@ void GetStatusLabelsForAuthError(const AuthError& auth_error, link_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_RELOGIN_LINK_LABEL)); if (auth_error.state() == AuthError::INVALID_GAIA_CREDENTIALS || auth_error.state() == AuthError::ACCOUNT_DELETED || - auth_error.state() == AuthError::ACCOUNT_DISABLED || - auth_error.state() == AuthError::SERVICE_UNAVAILABLE) { + auth_error.state() == AuthError::ACCOUNT_DISABLED) { // If the user name is empty then the first login failed, otherwise the // credentials are out-of-date. if (service->GetAuthenticatedUsername().empty()) @@ -48,6 +48,10 @@ void GetStatusLabelsForAuthError(const AuthError& auth_error, else status_label->assign( l10n_util::GetStringUTF16(IDS_SYNC_LOGIN_INFO_OUT_OF_DATE)); + } else if (auth_error.state() == AuthError::SERVICE_UNAVAILABLE) { + DCHECK(service->GetAuthenticatedUsername().empty()); + status_label->assign( + l10n_util::GetStringUTF16(IDS_SYNC_SERVICE_UNAVAILABLE)); } else if (auth_error.state() == AuthError::CONNECTION_FAILED) { // Note that there is little the user can do if the server is not // reachable. Since attempting to re-connect is done automatically by @@ -71,9 +75,13 @@ string16 GetSyncedStateStatusLabel(ProfileSyncService* service) { if (user_name.empty()) return label; - return l10n_util::GetStringFUTF16(IDS_SYNC_ACCOUNT_SYNCED_TO_USER_WITH_TIME, - user_name, - service->GetLastSyncedTimeString()); + const CommandLine& browser_command_line = *CommandLine::ForCurrentProcess(); + return l10n_util::GetStringFUTF16( + browser_command_line.HasSwitch(switches::kMultiProfiles) ? + IDS_PROFILES_SYNCED_TO_USER_WITH_TIME : + IDS_SYNC_ACCOUNT_SYNCED_TO_USER_WITH_TIME, + user_name, + service->GetLastSyncedTimeString()); } // TODO(akalin): Write unit tests for these three functions below. @@ -166,12 +174,6 @@ MessageType GetStatusInfo(ProfileSyncService* service, if (status_label) { status_label->assign(l10n_util::GetStringUTF16(IDS_SYNC_SETUP_ERROR)); } - } else { - if (status_label) { - status_label->assign( - l10n_util::GetStringFUTF16(IDS_SYNC_NOT_SET_UP_INFO, - l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); - } } } return result_type; @@ -216,20 +218,6 @@ MessageType GetStatusInfoForNewTabPage(ProfileSyncService* service, } // namespace -// Returns an HTML chunk for a login prompt related to encryption. -string16 GetLoginMessageForEncryption() { - std::vector<std::string> subst; - const base::StringPiece html( - ResourceBundle::GetSharedInstance().GetRawDataResource( - IDR_SYNC_ENCRYPTION_LOGIN_HTML)); - subst.push_back(l10n_util::GetStringUTF8(IDS_SYNC_PLEASE_SIGN_IN)); - subst.push_back( - l10n_util::GetStringFUTF8(IDS_SYNC_LOGIN_FOR_ENCRYPTION, - l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); - - return UTF8ToUTF16(ReplaceStringPlaceholders(html, subst, NULL)); -} - MessageType GetStatusLabels(ProfileSyncService* service, string16* status_label, string16* link_label) { @@ -252,8 +240,11 @@ MessageType GetStatus(ProfileSyncService* service) { } bool ShouldShowSyncErrorButton(ProfileSyncService* service) { - return service && !service->IsManaged() && service->HasSyncSetupCompleted() && - (GetStatus(service) == sync_ui_util::SYNC_ERROR); + return service && + ((!service->IsManaged() && + service->HasSyncSetupCompleted()) && + (GetStatus(service) == sync_ui_util::SYNC_ERROR || + service->observed_passphrase_required())); } string16 GetSyncMenuLabel(ProfileSyncService* service) { @@ -277,20 +268,13 @@ void OpenSyncMyBookmarksDialog(Profile* profile, return; } - bool use_tabbed_options = !CommandLine::ForCurrentProcess()->HasSwitch( - switches::kDisableTabbedOptions); - if (service->HasSyncSetupCompleted()) { - if (use_tabbed_options) { - bool create_window = browser == NULL; - if (create_window) - browser = Browser::Create(profile); - browser->ShowOptionsTab(chrome::kPersonalOptionsSubPage); - if (create_window) - browser->window()->Show(); - } else { - ShowOptionsWindow(OPTIONS_PAGE_CONTENT, OPTIONS_GROUP_NONE, profile); - } + bool create_window = browser == NULL; + if (create_window) + browser = Browser::Create(profile); + browser->ShowOptionsTab(chrome::kPersonalOptionsSubPage); + if (create_window) + browser->window()->Show(); } else { service->ShowLoginDialog(NULL); ProfileSyncService::SyncEvent(code); // UMA stats diff --git a/chrome/browser/sync/sync_ui_util.h b/chrome/browser/sync/sync_ui_util.h index 819916d..68738bc 100644 --- a/chrome/browser/sync/sync_ui_util.h +++ b/chrome/browser/sync/sync_ui_util.h @@ -32,8 +32,6 @@ enum MessageType { // TODO(akalin): audit the use of ProfileSyncService* service below, // and use const ProfileSyncService& service where possible. -string16 GetLoginMessageForEncryption(); - // Create status and link labels for the current status labels and link text // by querying |service|. MessageType GetStatusLabels(ProfileSyncService* service, diff --git a/chrome/browser/sync/sync_ui_util_mac_unittest.mm b/chrome/browser/sync/sync_ui_util_mac_unittest.mm index bebb06e..6206fd4 100644 --- a/chrome/browser/sync/sync_ui_util_mac_unittest.mm +++ b/chrome/browser/sync/sync_ui_util_mac_unittest.mm @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,7 +6,7 @@ #import <Cocoa/Cocoa.h> -#include "base/scoped_nsobject.h" +#include "base/memory/scoped_nsobject.h" #include "chrome/app/chrome_command_ids.h" #include "chrome/browser/ui/cocoa/cocoa_test_helper.h" #include "grit/generated_resources.h" diff --git a/chrome/browser/sync/syncable/autofill_migration.h b/chrome/browser/sync/syncable/autofill_migration.h index 81a16be..81b555d 100644 --- a/chrome/browser/sync/syncable/autofill_migration.h +++ b/chrome/browser/sync/syncable/autofill_migration.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -33,11 +33,14 @@ enum AutofillMigrationState { struct AutofillMigrationDebugInfo { enum PropertyToSet { MIGRATION_TIME, - BOOKMARK_ADDED, ENTRIES_ADDED, PROFILES_ADDED }; int64 autofill_migration_time; + // NOTE(akalin): We don't increment + // |bookmarks_added_during_migration| anymore, although it's not + // worth the effort to remove it from the code. Eventually, this + // will go away once we remove all the autofill migration code. int bookmarks_added_during_migration; int autofill_entries_added_during_migration; int autofill_profile_added_during_migration; diff --git a/chrome/browser/sync/syncable/directory_backing_store.cc b/chrome/browser/sync/syncable/directory_backing_store.cc index fcadba1..7f5721e 100644 --- a/chrome/browser/sync/syncable/directory_backing_store.cc +++ b/chrome/browser/sync/syncable/directory_backing_store.cc @@ -437,7 +437,7 @@ DirOpenResult DirectoryBackingStore::InitializeTables() { return FAILED_DISK_FULL; } int version_on_disk = GetVersion(); - int last_result = SQLITE_OK; + int last_result = SQLITE_DONE; // Upgrade from version 67. Version 67 was widely distributed as the original // Bookmark Sync release. Version 68 removed unique naming. @@ -504,14 +504,10 @@ DirOpenResult DirectoryBackingStore::InitializeTables() { // Fallback (re-sync everything) migration path. VLOG(1) << "Old/null sync database, version " << version_on_disk; // Delete the existing database (if any), and create a fresh one. - if (SQLITE_OK == last_result) { - DropAllTables(); - if (SQLITE_DONE == CreateTables()) { - last_result = SQLITE_OK; - } - } + DropAllTables(); + last_result = CreateTables(); } - if (SQLITE_OK == last_result) { + if (SQLITE_DONE == last_result) { { SQLStatement statement; statement.prepare(load_dbhandle_, @@ -943,11 +939,11 @@ bool DirectoryBackingStore::MigrateVersion70To71() { CreateShareInfoTableVersion71(kCreateAsTempShareInfo); if (result != SQLITE_DONE) return false; - ExecQuery(load_dbhandle_, - "INSERT INTO temp_share_info (id, name, store_birthday, " - "db_create_version, db_create_time, next_id, cache_guid) " - "SELECT id, name, store_birthday, db_create_version, " - "db_create_time, next_id, cache_guid FROM share_info"); + result = ExecQuery(load_dbhandle_, + "INSERT INTO temp_share_info (id, name, store_birthday, " + "db_create_version, db_create_time, next_id, cache_guid) " + "SELECT id, name, store_birthday, db_create_version, " + "db_create_time, next_id, cache_guid FROM share_info"); if (result != SQLITE_DONE) return false; SafeDropTable("share_info"); diff --git a/chrome/browser/sync/syncable/directory_backing_store_unittest.cc b/chrome/browser/sync/syncable/directory_backing_store_unittest.cc index db40397..40ab3e3 100644 --- a/chrome/browser/sync/syncable/directory_backing_store_unittest.cc +++ b/chrome/browser/sync/syncable/directory_backing_store_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,8 +10,8 @@ #include "app/sql/statement.h" #include "base/file_path.h" #include "base/file_util.h" -#include "base/scoped_ptr.h" -#include "base/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_temp_dir.h" #include "base/stl_util-inl.h" #include "base/string_number_conversions.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" @@ -20,7 +20,6 @@ #include "chrome/browser/sync/syncable/directory_manager.h" #include "chrome/browser/sync/syncable/syncable-inl.h" #include "chrome/browser/sync/syncable/syncable.h" -#include "chrome/common/sqlite_utils.h" #include "testing/gtest/include/gtest/gtest-param-test.h" namespace syncable { diff --git a/chrome/browser/sync/syncable/directory_change_listener.h b/chrome/browser/sync/syncable/directory_change_listener.h new file mode 100644 index 0000000..0867cb7 --- /dev/null +++ b/chrome/browser/sync/syncable/directory_change_listener.h @@ -0,0 +1,42 @@ +// Copyright (c) 2011 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_SYNCABLE_DIRECTORY_CHANGE_LISTENER_H_ +#define CHROME_BROWSER_SYNC_SYNCABLE_DIRECTORY_CHANGE_LISTENER_H_ +#pragma once + +#include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/syncable.h" + +namespace syncable { + +// This is an interface for listening to directory change events, triggered by +// the releasing of the syncable transaction. The listener performs work to +// 1. Calculate changes, depending on the source of the transaction +// (HandleCalculateChangesChangeEventFromSyncer/Syncapi). +// 2. Perform final work while the transaction is held +// (HandleTransactionEndingChangeEvent). +// 3. Perform any work that should be done after the transaction is released. +// (HandleTransactionCompleteChangeEvent). +class DirectoryChangeListener { + public: + virtual void HandleCalculateChangesChangeEventFromSyncApi( + const OriginalEntries& originals, + const WriterTag& writer, + BaseTransaction* trans) = 0; + virtual void HandleCalculateChangesChangeEventFromSyncer( + const OriginalEntries& originals, + const WriterTag& writer, + BaseTransaction* trans) = 0; + virtual ModelTypeBitSet HandleTransactionEndingChangeEvent( + BaseTransaction* trans) = 0; + virtual void HandleTransactionCompleteChangeEvent( + const ModelTypeBitSet& models_with_changes) = 0; + protected: + virtual ~DirectoryChangeListener() {} +}; + +} // namespace syncable + +#endif // CHROME_BROWSER_SYNC_SYNCABLE_DIRECTORY_CHANGE_LISTENER_H_ diff --git a/chrome/browser/sync/syncable/directory_manager.cc b/chrome/browser/sync/syncable/directory_manager.cc index bf6e5e0..73d9f8a 100644 --- a/chrome/browser/sync/syncable/directory_manager.cc +++ b/chrome/browser/sync/syncable/directory_manager.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -9,8 +9,8 @@ #include <iterator> #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/port.h" -#include "base/scoped_ptr.h" #include "base/string_util.h" #include "chrome/browser/sync/syncable/syncable.h" #include "chrome/common/deprecated/event_sys-inl.h" diff --git a/chrome/browser/sync/syncable/directory_manager.h b/chrome/browser/sync/syncable/directory_manager.h index 4258f87..944e6d5 100644 --- a/chrome/browser/sync/syncable/directory_manager.h +++ b/chrome/browser/sync/syncable/directory_manager.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. // @@ -25,6 +25,7 @@ #include "chrome/common/deprecated/event_sys.h" namespace sync_api { class BaseTransaction; } +namespace syncable { class BaseTransaction; } namespace syncable { @@ -72,11 +73,20 @@ class DirectoryManager { Channel* channel() const { return channel_; } + // Wrappers for cryptographer() that enforce holding a transaction. + // Note: the Cryptographer is NOT thread safe. It must only be accessed while + // the transaction is still active. The Cryptographer's pointer should not be + // stored separately. + browser_sync::Cryptographer* GetCryptographer( + const sync_api::BaseTransaction* trans) const { return cryptographer(); } + browser_sync::Cryptographer* GetCryptographer( + const syncable::BaseTransaction* trans) const { return cryptographer(); } + + protected: browser_sync::Cryptographer* cryptographer() const { return cryptographer_.get(); } - protected: DirOpenResult OpenImpl(const std::string& name, const FilePath& path, bool* was_open); diff --git a/chrome/browser/sync/syncable/model_type.cc b/chrome/browser/sync/syncable/model_type.cc index b9c4645..273eadd 100644 --- a/chrome/browser/sync/syncable/model_type.cc +++ b/chrome/browser/sync/syncable/model_type.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -215,6 +215,18 @@ std::string ModelTypeToString(ModelType model_type) { return "INVALID"; } +StringValue* ModelTypeToValue(ModelType model_type) { + if (model_type >= syncable::FIRST_REAL_MODEL_TYPE) { + return Value::CreateStringValue(ModelTypeToString(model_type)); + } else if (model_type == syncable::TOP_LEVEL_FOLDER) { + return Value::CreateStringValue("Top-level folder"); + } else if (model_type == syncable::UNSPECIFIED) { + return Value::CreateStringValue("Unspecified"); + } + NOTREACHED(); + return Value::CreateStringValue(""); +} + std::string ModelTypeSetToString(const ModelTypeSet& model_types) { std::string result; for (ModelTypeSet::const_iterator iter = model_types.begin(); diff --git a/chrome/browser/sync/syncable/model_type.h b/chrome/browser/sync/syncable/model_type.h index 58999d8..f0e96fb 100644 --- a/chrome/browser/sync/syncable/model_type.h +++ b/chrome/browser/sync/syncable/model_type.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -18,6 +18,7 @@ #include "base/time.h" class ListValue; +class StringValue; namespace sync_pb { class EntitySpecifics; @@ -106,6 +107,11 @@ int GetExtensionFieldNumberFromModelType(ModelType model_type); // Returns a string that represents the name of |model_type|. std::string ModelTypeToString(ModelType model_type); +// Handles all model types, and not just real ones. +// +// Caller takes ownership of returned value. +StringValue* ModelTypeToValue(ModelType model_type); + std::string ModelTypeSetToString(const ModelTypeSet& model_types); // Returns the ModelType corresponding to the name |model_type_string|. diff --git a/chrome/browser/sync/syncable/model_type_payload_map.cc b/chrome/browser/sync/syncable/model_type_payload_map.cc new file mode 100644 index 0000000..af1bbca --- /dev/null +++ b/chrome/browser/sync/syncable/model_type_payload_map.cc @@ -0,0 +1,61 @@ +// Copyright (c) 2011 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/sync/syncable/model_type_payload_map.h" + +#include "base/values.h" + +namespace syncable { + +ModelTypePayloadMap ModelTypePayloadMapFromBitSet( + const syncable::ModelTypeBitSet& types, + const std::string& payload) { + ModelTypePayloadMap types_with_payloads; + for (size_t i = syncable::FIRST_REAL_MODEL_TYPE; + i < types.size(); ++i) { + if (types[i]) { + types_with_payloads[syncable::ModelTypeFromInt(i)] = payload; + } + } + return types_with_payloads; +} + +ModelTypePayloadMap ModelTypePayloadMapFromRoutingInfo( + const browser_sync::ModelSafeRoutingInfo& routes, + const std::string& payload) { + ModelTypePayloadMap types_with_payloads; + for (browser_sync::ModelSafeRoutingInfo::const_iterator i = routes.begin(); + i != routes.end(); ++i) { + types_with_payloads[i->first] = payload; + } + return types_with_payloads; +} + +DictionaryValue* ModelTypePayloadMapToValue( + const ModelTypePayloadMap& type_payloads) { + DictionaryValue* value = new DictionaryValue(); + for (ModelTypePayloadMap::const_iterator it = type_payloads.begin(); + it != type_payloads.end(); ++it) { + value->SetString(syncable::ModelTypeToString(it->first), it->second); + } + return value; +} + +void CoalescePayloads(ModelTypePayloadMap* original, + const ModelTypePayloadMap& update) { + for (ModelTypePayloadMap::const_iterator i = update.begin(); + i != update.end(); ++i) { + if (original->count(i->first) == 0) { + // If this datatype isn't already in our map, add it with + // whatever payload it has. + (*original)[i->first] = i->second; + } else if (i->second.length() > 0) { + // If this datatype is already in our map, we only overwrite the + // payload if the new one is non-empty. + (*original)[i->first] = i->second; + } + } +} + +} // namespace syncable diff --git a/chrome/browser/sync/syncable/model_type_payload_map.h b/chrome/browser/sync/syncable/model_type_payload_map.h new file mode 100644 index 0000000..212220e --- /dev/null +++ b/chrome/browser/sync/syncable/model_type_payload_map.h @@ -0,0 +1,50 @@ +// Copyright (c) 2011 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. +// +// Definition of ModelTypePayloadMap and various utility functions. + +#ifndef CHROME_BROWSER_SYNC_SYNCABLE_MODEL_TYPE_PAYLOAD_MAP_H_ +#define CHROME_BROWSER_SYNC_SYNCABLE_MODEL_TYPE_PAYLOAD_MAP_H_ +#pragma once + +#include <map> +#include <string> + +#include "chrome/browser/sync/engine/model_safe_worker.h" +#include "chrome/browser/sync/syncable/model_type.h" + +class DictionaryValue; + +namespace syncable { + +// A container that contains a set of datatypes with possible string +// payloads. +typedef std::map<ModelType, std::string> ModelTypePayloadMap; + +// Helper functions for building ModelTypePayloadMaps. + +// Make a TypePayloadMap from all the types in a ModelTypeBitSet using +// a default payload. +ModelTypePayloadMap ModelTypePayloadMapFromBitSet( + const ModelTypeBitSet& model_types, + const std::string& payload); + +// Make a TypePayloadMap for all the enabled types in a +// ModelSafeRoutingInfo using a default payload. +ModelTypePayloadMap ModelTypePayloadMapFromRoutingInfo( + const browser_sync::ModelSafeRoutingInfo& routes, + const std::string& payload); + +// Caller takes ownership of the returned dictionary. +DictionaryValue* ModelTypePayloadMapToValue( + const ModelTypePayloadMap& model_type_payloads); + +// Coalesce |update| into |original|, overwriting only when |update| has +// a non-empty payload. +void CoalescePayloads(ModelTypePayloadMap* original, + const ModelTypePayloadMap& update); + +} // namespace syncable + +#endif // CHROME_BROWSER_SYNC_SYNCABLE_MODEL_TYPE_PAYLOAD_MAP_H_ diff --git a/chrome/browser/sync/syncable/model_type_payload_map_unittest.cc b/chrome/browser/sync/syncable/model_type_payload_map_unittest.cc new file mode 100644 index 0000000..9350f41 --- /dev/null +++ b/chrome/browser/sync/syncable/model_type_payload_map_unittest.cc @@ -0,0 +1,38 @@ +// Copyright (c) 2011 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/sync/syncable/model_type_payload_map.h" + +#include <string> + +#include "base/memory/scoped_ptr.h" +#include "base/values.h" +#include "chrome/test/values_test_util.h" +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncable { +namespace { + +using test::ExpectBooleanValue; +using test::ExpectDictionaryValue; +using test::ExpectIntegerValue; +using test::ExpectListValue; +using test::ExpectStringValue; + +class ModelTypePayloadMapTest : public testing::Test {}; + +TEST_F(ModelTypePayloadMapTest, TypePayloadMapToValue) { + ModelTypePayloadMap payloads; + payloads[syncable::BOOKMARKS] = "bookmarkpayload"; + payloads[syncable::APPS] = ""; + + scoped_ptr<DictionaryValue> value(ModelTypePayloadMapToValue(payloads)); + EXPECT_EQ(2u, value->size()); + ExpectStringValue("bookmarkpayload", *value, "Bookmarks"); + ExpectStringValue("", *value, "Apps"); + EXPECT_FALSE(value->HasKey("Preferences")); +} + +} // namespace +} // namespace syncable diff --git a/chrome/browser/sync/syncable/model_type_unittest.cc b/chrome/browser/sync/syncable/model_type_unittest.cc index df8ca8d..cb846f6 100644 --- a/chrome/browser/sync/syncable/model_type_unittest.cc +++ b/chrome/browser/sync/syncable/model_type_unittest.cc @@ -6,7 +6,7 @@ #include <string> -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/values.h" #include "testing/gtest/include/gtest/gtest.h" @@ -15,6 +15,30 @@ namespace { class ModelTypeTest : public testing::Test {}; +// TODO(akalin): Move this to values_test_util.h. + +// Takes ownership of |actual|. +void ExpectStringValue(const std::string& expected_str, + StringValue* actual) { + scoped_ptr<StringValue> scoped_actual(actual); + std::string actual_str; + EXPECT_TRUE(scoped_actual->GetAsString(&actual_str)); + EXPECT_EQ(expected_str, actual_str); +} + +TEST_F(ModelTypeTest, ModelTypeToValue) { + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + ModelType model_type = ModelTypeFromInt(i); + ExpectStringValue(ModelTypeToString(model_type), + ModelTypeToValue(model_type)); + } + ExpectStringValue("Top-level folder", + ModelTypeToValue(TOP_LEVEL_FOLDER)); + ExpectStringValue("Unspecified", + ModelTypeToValue(UNSPECIFIED)); +} + TEST_F(ModelTypeTest, ModelTypeBitSetToValue) { ModelTypeBitSet model_types; model_types.set(syncable::BOOKMARKS); diff --git a/chrome/browser/sync/syncable/syncable.cc b/chrome/browser/sync/syncable/syncable.cc index 73c1111..0674457 100644 --- a/chrome/browser/sync/syncable/syncable.cc +++ b/chrome/browser/sync/syncable/syncable.cc @@ -30,22 +30,25 @@ #include "base/hash_tables.h" #include "base/file_util.h" #include "base/logging.h" +#include "base/memory/scoped_ptr.h" #include "base/perftimer.h" -#include "base/scoped_ptr.h" #include "base/string_number_conversions.h" #include "base/string_util.h" #include "base/stl_util-inl.h" #include "base/time.h" +#include "base/values.h" #include "chrome/browser/sync/engine/syncer.h" #include "chrome/browser/sync/engine/syncer_util.h" +#include "chrome/browser/sync/protocol/proto_value_conversions.h" #include "chrome/browser/sync/protocol/service_constants.h" -#include "chrome/browser/sync/protocol/theme_specifics.pb.h" -#include "chrome/browser/sync/protocol/typed_url_specifics.pb.h" #include "chrome/browser/sync/syncable/directory_backing_store.h" +#include "chrome/browser/sync/syncable/directory_change_listener.h" #include "chrome/browser/sync/syncable/directory_manager.h" +#include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/syncable/syncable-inl.h" #include "chrome/browser/sync/syncable/syncable_changes_version.h" #include "chrome/browser/sync/syncable/syncable_columns.h" +#include "chrome/browser/sync/syncable/syncable_enum_conversions.h" #include "chrome/browser/sync/util/crypto_helpers.h" #include "chrome/common/deprecated/event_sys-inl.h" #include "net/base/escape.h" @@ -177,12 +180,96 @@ EntryKernel::EntryKernel() : dirty_(false) { EntryKernel::~EntryKernel() {} +namespace { + +// Utility function to loop through a set of enum values and add the +// field keys/values in the kernel to the given dictionary. +// +// V should be convertible to Value. +template <class T, class U, class V> +void SetFieldValues(const EntryKernel& kernel, + DictionaryValue* dictionary_value, + const char* (*enum_key_fn)(T), + V* (*enum_value_fn)(U), + int field_key_min, int field_key_max) { + DCHECK_LE(field_key_min, field_key_max); + for (int i = field_key_min; i <= field_key_max; ++i) { + T field = static_cast<T>(i); + const std::string& key = enum_key_fn(field); + V* value = enum_value_fn(kernel.ref(field)); + dictionary_value->Set(key, value); + } +} + +// Helper functions for SetFieldValues(). + +StringValue* Int64ToValue(int64 i) { + return Value::CreateStringValue(base::Int64ToString(i)); +} + +StringValue* IdToValue(const Id& id) { + return id.ToValue(); +} + +} // namespace + +DictionaryValue* EntryKernel::ToValue() const { + DictionaryValue* kernel_info = new DictionaryValue(); + kernel_info->SetBoolean("isDirty", is_dirty()); + + // Int64 fields. + SetFieldValues(*this, kernel_info, + &GetMetahandleFieldString, &Int64ToValue, + INT64_FIELDS_BEGIN, META_HANDLE); + SetFieldValues(*this, kernel_info, + &GetBaseVersionString, &Int64ToValue, + META_HANDLE + 1, BASE_VERSION); + SetFieldValues(*this, kernel_info, + &GetInt64FieldString, &Int64ToValue, + BASE_VERSION + 1, INT64_FIELDS_END - 1); + + // ID fields. + SetFieldValues(*this, kernel_info, + &GetIdFieldString, &IdToValue, + ID_FIELDS_BEGIN, ID_FIELDS_END - 1); + + // Bit fields. + SetFieldValues(*this, kernel_info, + &GetIndexedBitFieldString, &Value::CreateBooleanValue, + BIT_FIELDS_BEGIN, INDEXED_BIT_FIELDS_END - 1); + SetFieldValues(*this, kernel_info, + &GetIsDelFieldString, &Value::CreateBooleanValue, + INDEXED_BIT_FIELDS_END, IS_DEL); + SetFieldValues(*this, kernel_info, + &GetBitFieldString, &Value::CreateBooleanValue, + IS_DEL + 1, BIT_FIELDS_END - 1); + + // String fields. + { + // Pick out the function overload we want. + StringValue* (*string_to_value)(const std::string&) = + &Value::CreateStringValue; + SetFieldValues(*this, kernel_info, + &GetStringFieldString, string_to_value, + STRING_FIELDS_BEGIN, STRING_FIELDS_END - 1); + } + + // Proto fields. + SetFieldValues(*this, kernel_info, + &GetProtoFieldString, &browser_sync::EntitySpecificsToValue, + PROTO_FIELDS_BEGIN, PROTO_FIELDS_END - 1); + + // Bit temps. + SetFieldValues(*this, kernel_info, + &GetBitTempString, &Value::CreateBooleanValue, + BIT_TEMPS_BEGIN, BIT_TEMPS_END - 1); + + return kernel_info; +} + /////////////////////////////////////////////////////////////////////////// // Directory -static const DirectoryChangeEvent kShutdownChangesEvent = - { DirectoryChangeEvent::SHUTDOWN, 0, 0 }; - void Directory::init_kernel(const std::string& name) { DCHECK(kernel_ == NULL); kernel_ = new Kernel(FilePath(), name, KernelLoadInfo()); @@ -229,6 +316,7 @@ Directory::Kernel::Kernel(const FilePath& db_path, dirty_metahandles(new MetahandleSet), metahandles_to_purge(new MetahandleSet), channel(new Directory::Channel(syncable::DIRECTORY_DESTROYED)), + change_listener_(NULL), info_status(Directory::KERNEL_SHARE_INFO_VALID), persisted_info(info.kernel_info), cache_guid(info.cache_guid), @@ -247,7 +335,6 @@ void Directory::Kernel::Release() { Directory::Kernel::~Kernel() { CHECK_EQ(0, refcount); delete channel; - changes_channel.Notify(kShutdownChangesEvent); delete unsynced_metahandles; delete unapplied_update_metahandles; delete dirty_metahandles; @@ -687,6 +774,7 @@ void Directory::SetDownloadProgress( const sync_pb::DataTypeProgressMarker& new_progress) { ScopedKernelLock lock(this); kernel_->persisted_info.download_progress[model_type].CopyFrom(new_progress); + kernel_->info_status = KERNEL_SHARE_INFO_DIRTY; } bool Directory::initial_sync_ended_for_type(ModelType type) const { @@ -727,14 +815,6 @@ void Directory::set_autofill_migration_state_debug_info( &info.autofill_migration_time); break; } - case AutofillMigrationDebugInfo::BOOKMARK_ADDED: { - AutofillMigrationDebugInfo& debug_info = - kernel_->persisted_info.autofill_migration_debug_info; - TestAndSet<int>( - &debug_info.bookmarks_added_during_migration, - &info.bookmarks_added_during_migration); - break; - } case AutofillMigrationDebugInfo::ENTRIES_ADDED: { AutofillMigrationDebugInfo& debug_info = kernel_->persisted_info.autofill_migration_debug_info; @@ -797,7 +877,7 @@ string Directory::store_birthday() const { return kernel_->persisted_info.store_birthday; } -void Directory::set_store_birthday(string store_birthday) { +void Directory::set_store_birthday(const string& store_birthday) { ScopedKernelLock lock(this); if (kernel_->persisted_info.store_birthday == store_birthday) return; @@ -921,9 +1001,7 @@ void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans, void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans, const MetahandleSet& handles, const IdFilter& idfilter) { - int64 max_ms = kInvariantCheckMaxMs; - if (max_ms < 0) - max_ms = std::numeric_limits<int64>::max(); + const int64 max_ms = kInvariantCheckMaxMs; PerfTimer check_timer; MetahandleSet::const_iterator i; int entries_done = 0; @@ -1008,9 +1086,9 @@ void Directory::CheckTreeInvariants(syncable::BaseTransaction* trans, } } -browser_sync::ChannelHookup<DirectoryChangeEvent>* Directory::AddChangeObserver( - browser_sync::ChannelEventHandler<DirectoryChangeEvent>* observer) { - return kernel_->changes_channel.AddObserver(observer); +void Directory::SetChangeListener(DirectoryChangeListener* listener) { + DCHECK(!kernel_->change_listener_); + kernel_->change_listener_ = listener; } /////////////////////////////////////////////////////////////////////////////// @@ -1058,23 +1136,22 @@ BaseTransaction::BaseTransaction(Directory* directory) BaseTransaction::~BaseTransaction() {} -void BaseTransaction::UnlockAndLog(OriginalEntries* originals_arg) { - // Triggers the CALCULATE_CHANGES and TRANSACTION_ENDING events while - // holding dir_kernel_'s transaction_mutex and changes_channel mutex. - // Releases all mutexes upon completion. - if (!NotifyTransactionChangingAndEnding(originals_arg)) { +void BaseTransaction::UnlockAndLog(OriginalEntries* entries) { + // Work while trasnaction mutex is held + ModelTypeBitSet models_with_changes; + if (!NotifyTransactionChangingAndEnding(entries, &models_with_changes)) return; - } - // Triggers the TRANSACTION_COMPLETE event (and does not hold any mutexes). - NotifyTransactionComplete(); + // Work after mutex is relased. + NotifyTransactionComplete(models_with_changes); } bool BaseTransaction::NotifyTransactionChangingAndEnding( - OriginalEntries* originals_arg) { + OriginalEntries* entries, + ModelTypeBitSet* models_with_changes) { dirkernel_->transaction_mutex.AssertAcquired(); - scoped_ptr<OriginalEntries> originals(originals_arg); + scoped_ptr<OriginalEntries> originals(entries); const base::TimeDelta elapsed = base::TimeTicks::Now() - time_acquired_; if (LOG_IS_ON(INFO) && (1 <= logging::GetVlogLevelHelper( @@ -1085,43 +1162,40 @@ bool BaseTransaction::NotifyTransactionChangingAndEnding( << " seconds."; } - if (NULL == originals.get() || originals->empty()) { + if (NULL == originals.get() || originals->empty() || + !dirkernel_->change_listener_) { dirkernel_->transaction_mutex.Release(); return false; } - - { - // Scoped_lock is only active through the calculate_changes and - // transaction_ending events. - base::AutoLock scoped_lock(dirkernel_->changes_channel_mutex); - - // Tell listeners to calculate changes while we still have the mutex. - DirectoryChangeEvent event = { DirectoryChangeEvent::CALCULATE_CHANGES, - originals.get(), this, writer_ }; - dirkernel_->changes_channel.Notify(event); - - // Necessary for reads to be performed prior to transaction mutex release. - // Allows the listener to use the current transaction to perform reads. - DirectoryChangeEvent ending_event = - { DirectoryChangeEvent::TRANSACTION_ENDING, - NULL, this, INVALID }; - dirkernel_->changes_channel.Notify(ending_event); - - dirkernel_->transaction_mutex.Release(); + if (writer_ == syncable::SYNCAPI) { + dirkernel_->change_listener_->HandleCalculateChangesChangeEventFromSyncApi( + *originals.get(), + writer_, + this); + } else { + dirkernel_->change_listener_->HandleCalculateChangesChangeEventFromSyncer( + *originals.get(), + writer_, + this); } + *models_with_changes = dirkernel_->change_listener_-> + HandleTransactionEndingChangeEvent(this); + + // Release the transaction. Note, once the transaction is released this thread + // can be interrupted by another that was waiting for the transaction, + // resulting in this code possibly being interrupted with another thread + // performing following the same code path. From this point foward, only + // local state can be touched. + dirkernel_->transaction_mutex.Release(); return true; } -void BaseTransaction::NotifyTransactionComplete() { - // Transaction is no longer holding any locks/mutexes, notify that we're - // complete (and commit any outstanding changes that should not be performed - // while holding mutexes). - DirectoryChangeEvent complete_event = - { DirectoryChangeEvent::TRANSACTION_COMPLETE, - NULL, NULL, INVALID }; - dirkernel_->changes_channel.Notify(complete_event); +void BaseTransaction::NotifyTransactionComplete( + ModelTypeBitSet models_with_changes) { + dirkernel_->change_listener_->HandleTransactionCompleteChangeEvent( + models_with_changes); } ReadTransaction::ReadTransaction(Directory* directory, const char* file, @@ -1208,21 +1282,33 @@ Id Entry::ComputePrevIdFromServerPosition(const Id& parent_id) const { return dir()->ComputePrevIdFromServerPosition(kernel_, parent_id); } +DictionaryValue* Entry::ToValue() const { + DictionaryValue* entry_info = new DictionaryValue(); + entry_info->SetBoolean("good", good()); + if (good()) { + entry_info->Set("kernel", kernel_->ToValue()); + entry_info->Set("serverModelType", + ModelTypeToValue(GetServerModelTypeHelper())); + entry_info->Set("modelType", + ModelTypeToValue(GetModelType())); + entry_info->SetBoolean("shouldMaintainPosition", + ShouldMaintainPosition()); + entry_info->SetBoolean("existsOnClientBecauseNameIsNonEmpty", + ExistsOnClientBecauseNameIsNonEmpty()); + entry_info->SetBoolean("isRoot", IsRoot()); + } + return entry_info; +} + const string& Entry::Get(StringField field) const { DCHECK(kernel_); return kernel_->ref(field); } syncable::ModelType Entry::GetServerModelType() const { - ModelType specifics_type = GetModelTypeFromSpecifics(Get(SERVER_SPECIFICS)); + ModelType specifics_type = GetServerModelTypeHelper(); if (specifics_type != UNSPECIFIED) return specifics_type; - if (IsRoot()) - return TOP_LEVEL_FOLDER; - // Loose check for server-created top-level folders that aren't - // bound to a particular model type. - if (!Get(UNIQUE_SERVER_TAG).empty() && Get(SERVER_IS_DIR)) - return TOP_LEVEL_FOLDER; // Otherwise, we don't have a server type yet. That should only happen // if the item is an uncommitted locally created item. @@ -1236,6 +1322,20 @@ syncable::ModelType Entry::GetServerModelType() const { return UNSPECIFIED; } +syncable::ModelType Entry::GetServerModelTypeHelper() const { + ModelType specifics_type = GetModelTypeFromSpecifics(Get(SERVER_SPECIFICS)); + if (specifics_type != UNSPECIFIED) + return specifics_type; + if (IsRoot()) + return TOP_LEVEL_FOLDER; + // Loose check for server-created top-level folders that aren't + // bound to a particular model type. + if (!Get(UNIQUE_SERVER_TAG).empty() && Get(SERVER_IS_DIR)) + return TOP_LEVEL_FOLDER; + + return UNSPECIFIED; +} + syncable::ModelType Entry::GetModelType() const { ModelType specifics_type = GetModelTypeFromSpecifics(Get(SPECIFICS)); if (specifics_type != UNSPECIFIED) @@ -1413,6 +1513,15 @@ bool MutableEntry::Put(ProtoField field, return true; } +bool MutableEntry::Put(BitField field, bool value) { + DCHECK(kernel_); + if (kernel_->ref(field) != value) { + kernel_->put(field, value); + kernel_->mark_dirty(GetDirtyIndexHelper()); + } + return true; +} + MetahandleSet* MutableEntry::GetDirtyIndexHelper() { return dir()->kernel_->dirty_metahandles; } @@ -1565,6 +1674,11 @@ bool MutableEntry::PutPredecessor(const Id& predecessor_id) { return true; } +bool MutableEntry::Put(BitTemp field, bool value) { + DCHECK(kernel_); + kernel_->put(field, value); + return true; +} /////////////////////////////////////////////////////////////////////////// // High-level functions diff --git a/chrome/browser/sync/syncable/syncable.h b/chrome/browser/sync/syncable/syncable.h index 3a3c597..c97da92 100644 --- a/chrome/browser/sync/syncable/syncable.h +++ b/chrome/browser/sync/syncable/syncable.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -27,10 +27,10 @@ #include "chrome/browser/sync/syncable/directory_event.h" #include "chrome/browser/sync/syncable/syncable_id.h" #include "chrome/browser/sync/syncable/model_type.h" -#include "chrome/browser/sync/util/channel.h" #include "chrome/browser/sync/util/dbgq.h" #include "chrome/common/deprecated/event_sys.h" +class DictionaryValue; struct PurgeInfo; namespace sync_api { @@ -40,6 +40,7 @@ class ReadNode; } namespace syncable { +class DirectoryChangeListener; class Entry; std::ostream& operator<<(std::ostream& s, const Entry& e); @@ -48,6 +49,9 @@ class DirectoryBackingStore; static const int64 kInvalidMetaHandle = 0; +// Update syncable_enum_conversions{.h,.cc,_unittest.cc} if you change +// any fields in this file. + enum { BEGIN_FIELDS = 0, INT64_FIELDS_BEGIN = BEGIN_FIELDS @@ -330,6 +334,10 @@ struct EntryKernel { return id_fields[field - ID_FIELDS_BEGIN]; } + // Dumps all kernel info into a DictionaryValue and returns it. + // Transfers ownership of the DictionaryValue to the caller. + DictionaryValue* ToValue() const; + private: // Tracks whether this entry needs to be saved to the database. bool dirty_; @@ -422,6 +430,10 @@ class Entry { // SERVER_POSITION_IN_PARENT ordering. Id ComputePrevIdFromServerPosition(const Id& parent_id) const; + // Dumps all entry info into a DictionaryValue and returns it. + // Transfers ownership of the DictionaryValue to the caller. + DictionaryValue* ToValue() const; + protected: // Don't allow creation on heap, except by sync API wrappers. friend class sync_api::ReadNode; void* operator new(size_t size) { return (::operator new)(size); } @@ -436,6 +448,10 @@ class Entry { EntryKernel* kernel_; + private: + // Like GetServerModelType() but without the DCHECKs. + ModelType GetServerModelTypeHelper() const; + DISALLOW_COPY_AND_ASSIGN(Entry); }; @@ -481,9 +497,7 @@ class MutableEntry : public Entry { bool Put(BaseVersion field, int64 value); bool Put(ProtoField field, const sync_pb::EntitySpecifics& value); - inline bool Put(BitField field, bool value) { - return PutField(field, value); - } + bool Put(BitField field, bool value); inline bool Put(IsDelField field, bool value) { return PutIsDel(value); } @@ -495,30 +509,11 @@ class MutableEntry : public Entry { // ID to put the node in first position. bool PutPredecessor(const Id& predecessor_id); - inline bool Put(BitTemp field, bool value) { - return PutTemp(field, value); - } + bool Put(BitTemp field, bool value); protected: syncable::MetahandleSet* GetDirtyIndexHelper(); - template <typename FieldType, typename ValueType> - inline bool PutField(FieldType field, const ValueType& value) { - DCHECK(kernel_); - if (kernel_->ref(field) != value) { - kernel_->put(field, value); - kernel_->mark_dirty(GetDirtyIndexHelper()); - } - return true; - } - - template <typename TempType, typename ValueType> - inline bool PutTemp(TempType field, const ValueType& value) { - DCHECK(kernel_); - kernel_->put(field, value); - return true; - } - bool PutIsDel(bool value); private: // Don't allow creation on heap, except by sync API wrappers. @@ -638,35 +633,6 @@ enum WriterTag { SYNCAPI }; -// A separate Event type and channel for very frequent changes, caused -// by anything, not just the user. -struct DirectoryChangeEvent { - enum { - // Means listener should go through list of original entries and - // calculate what it needs to notify. It should *not* call any - // callbacks or attempt to lock anything because a - // WriteTransaction is being held until the listener returns. - CALCULATE_CHANGES, - // Means the WriteTransaction is ending, and this is the absolute - // last chance to perform any read operations in the current transaction. - // It is not recommended that the listener perform any writes. - TRANSACTION_ENDING, - // Means the WriteTransaction has been released and the listener - // can now take action on the changes it calculated. - TRANSACTION_COMPLETE, - // Channel is closing. - SHUTDOWN - } todo; - // These members are only valid for CALCULATE_CHANGES. - const OriginalEntries* originals; - BaseTransaction* trans; // This is valid also for TRANSACTION_ENDING - WriterTag writer; - typedef DirectoryChangeEvent EventType; - static inline bool IsChannelShutdownEvent(const EventType& e) { - return SHUTDOWN == e.todo; - } -}; - // The name Directory in this case means the entire directory // structure within a single user account. // @@ -815,11 +781,11 @@ class Directory { const std::string& name() const { return kernel_->name; } - // (Account) Store birthday is opaque to the client, - // so we keep it in the format it is in the proto buffer - // in case we switch to a binary birthday later. + // (Account) Store birthday is opaque to the client, so we keep it in the + // format it is in the proto buffer in case we switch to a binary birthday + // later. std::string store_birthday() const; - void set_store_birthday(std::string store_birthday); + void set_store_birthday(const std::string& store_birthday); std::string GetAndClearNotificationState(); void SetNotificationState(const std::string& notification_state); @@ -827,8 +793,7 @@ class Directory { // Unique to each account / client pair. std::string cache_guid() const; - browser_sync::ChannelHookup<DirectoryChangeEvent>* AddChangeObserver( - browser_sync::ChannelEventHandler<DirectoryChangeEvent>* observer); + void SetChangeListener(DirectoryChangeListener* listener); protected: // for friends, mainly used by Entry constructors virtual EntryKernel* GetEntryByHandle(int64 handle); @@ -1050,12 +1015,10 @@ class Directory { // TODO(ncarter): Figure out what the hell this is, and comment it. Channel* const channel; - // The changes channel mutex is explicit because it must be locked - // while holding the transaction mutex and released after - // releasing the transaction mutex. - browser_sync::Channel<DirectoryChangeEvent> changes_channel; + // The listener for directory change events, triggered when the transaction + // is ending. + DirectoryChangeListener* change_listener_; - base::Lock changes_channel_mutex; KernelShareInfoStatus info_status; // These 3 members are backed in the share_info table, and @@ -1132,8 +1095,10 @@ class BaseTransaction { explicit BaseTransaction(Directory* directory); void UnlockAndLog(OriginalEntries* entries); - bool NotifyTransactionChangingAndEnding(OriginalEntries* entries); - virtual void NotifyTransactionComplete(); + virtual bool NotifyTransactionChangingAndEnding( + OriginalEntries* entries, + ModelTypeBitSet* models_with_changes); + virtual void NotifyTransactionComplete(ModelTypeBitSet models_with_changes); Directory* const directory_; Directory::Kernel* const dirkernel_; // for brevity diff --git a/chrome/browser/sync/syncable/syncable_enum_conversions.cc b/chrome/browser/sync/syncable/syncable_enum_conversions.cc new file mode 100644 index 0000000..95afe49 --- /dev/null +++ b/chrome/browser/sync/syncable/syncable_enum_conversions.cc @@ -0,0 +1,153 @@ +// Copyright (c) 2011 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. + +// Keep this file in sync with syncable.h. + +#include "chrome/browser/sync/syncable/syncable_enum_conversions.h" + +#include "base/basictypes.h" +#include "base/logging.h" + +namespace syncable { + +// We can't tokenize expected_min/expected_max since it can be a +// general expression. +#define ASSERT_ENUM_BOUNDS(enum_min, enum_max, expected_min, expected_max) \ + COMPILE_ASSERT(static_cast<int>(enum_min) == \ + static_cast<int>(expected_min), \ + enum_min##_not_expected_min); \ + COMPILE_ASSERT(static_cast<int>(enum_max) == \ + static_cast<int>(expected_max), \ + enum_max##_not_expected_max); + +#define ENUM_CASE(enum_value) case enum_value: return #enum_value + +const char* GetMetahandleFieldString(MetahandleField metahandle_field) { + ASSERT_ENUM_BOUNDS(META_HANDLE, META_HANDLE, + INT64_FIELDS_BEGIN, BASE_VERSION - 1); + switch (metahandle_field) { + ENUM_CASE(META_HANDLE); + } + NOTREACHED(); + return ""; +} + +const char* GetBaseVersionString(BaseVersion base_version) { + ASSERT_ENUM_BOUNDS(BASE_VERSION, BASE_VERSION, + META_HANDLE + 1, SERVER_VERSION - 1); + switch (base_version) { + ENUM_CASE(BASE_VERSION); + } + NOTREACHED(); + return ""; +} + +const char* GetInt64FieldString(Int64Field int64_field) { + ASSERT_ENUM_BOUNDS(SERVER_VERSION, LOCAL_EXTERNAL_ID, + BASE_VERSION + 1, INT64_FIELDS_END - 1); + switch (int64_field) { + ENUM_CASE(SERVER_VERSION); + ENUM_CASE(MTIME); + ENUM_CASE(SERVER_MTIME); + ENUM_CASE(CTIME); + ENUM_CASE(SERVER_CTIME); + ENUM_CASE(SERVER_POSITION_IN_PARENT); + ENUM_CASE(LOCAL_EXTERNAL_ID); + case INT64_FIELDS_END: break; + } + NOTREACHED(); + return ""; +} + +const char* GetIdFieldString(IdField id_field) { + ASSERT_ENUM_BOUNDS(ID, NEXT_ID, + ID_FIELDS_BEGIN, ID_FIELDS_END - 1); + switch (id_field) { + ENUM_CASE(ID); + ENUM_CASE(PARENT_ID); + ENUM_CASE(SERVER_PARENT_ID); + ENUM_CASE(PREV_ID); + ENUM_CASE(NEXT_ID); + case ID_FIELDS_END: break; + } + NOTREACHED(); + return ""; +} + +const char* GetIndexedBitFieldString(IndexedBitField indexed_bit_field) { + ASSERT_ENUM_BOUNDS(IS_UNSYNCED, IS_UNAPPLIED_UPDATE, + BIT_FIELDS_BEGIN, INDEXED_BIT_FIELDS_END - 1); + switch (indexed_bit_field) { + ENUM_CASE(IS_UNSYNCED); + ENUM_CASE(IS_UNAPPLIED_UPDATE); + case INDEXED_BIT_FIELDS_END: break; + } + NOTREACHED(); + return ""; +} + +const char* GetIsDelFieldString(IsDelField is_del_field) { + ASSERT_ENUM_BOUNDS(IS_DEL, IS_DEL, + INDEXED_BIT_FIELDS_END, IS_DIR - 1); + switch (is_del_field) { + ENUM_CASE(IS_DEL); + } + NOTREACHED(); + return ""; +} + +const char* GetBitFieldString(BitField bit_field) { + ASSERT_ENUM_BOUNDS(IS_DIR, SERVER_IS_DEL, + IS_DEL + 1, BIT_FIELDS_END - 1); + switch (bit_field) { + ENUM_CASE(IS_DIR); + ENUM_CASE(SERVER_IS_DIR); + ENUM_CASE(SERVER_IS_DEL); + case BIT_FIELDS_END: break; + } + NOTREACHED(); + return ""; +} + +const char* GetStringFieldString(StringField string_field) { + ASSERT_ENUM_BOUNDS(NON_UNIQUE_NAME, UNIQUE_CLIENT_TAG, + STRING_FIELDS_BEGIN, STRING_FIELDS_END - 1); + switch (string_field) { + ENUM_CASE(NON_UNIQUE_NAME); + ENUM_CASE(SERVER_NON_UNIQUE_NAME); + ENUM_CASE(UNIQUE_SERVER_TAG); + ENUM_CASE(UNIQUE_CLIENT_TAG); + case STRING_FIELDS_END: break; + } + NOTREACHED(); + return ""; +} + +const char* GetProtoFieldString(ProtoField proto_field) { + ASSERT_ENUM_BOUNDS(SPECIFICS, SERVER_SPECIFICS, + PROTO_FIELDS_BEGIN, PROTO_FIELDS_END - 1); + switch (proto_field) { + ENUM_CASE(SPECIFICS); + ENUM_CASE(SERVER_SPECIFICS); + case PROTO_FIELDS_END: break; + } + NOTREACHED(); + return ""; +} + +const char* GetBitTempString(BitTemp bit_temp) { + ASSERT_ENUM_BOUNDS(SYNCING, SYNCING, + BIT_TEMPS_BEGIN, BIT_TEMPS_END - 1); + switch (bit_temp) { + ENUM_CASE(SYNCING); + case BIT_TEMPS_END: break; + } + NOTREACHED(); + return ""; +} + +#undef ENUM_CASE +#undef ASSERT_ENUM_BOUNDS + +} // namespace syncable diff --git a/chrome/browser/sync/syncable/syncable_enum_conversions.h b/chrome/browser/sync/syncable/syncable_enum_conversions.h new file mode 100644 index 0000000..eede89e --- /dev/null +++ b/chrome/browser/sync/syncable/syncable_enum_conversions.h @@ -0,0 +1,43 @@ +// Copyright (c) 2011 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_SYNCABLE_SYNCABLE_ENUM_CONVERSIONS_H_ +#define CHROME_BROWSER_SYNC_SYNCABLE_SYNCABLE_ENUM_CONVERSIONS_H_ +#pragma once + +// Keep this file in sync with syncable.h. + +#include "chrome/browser/sync/syncable/syncable.h" + +// Utility functions to get the string equivalent for some syncable +// enums. + +namespace syncable { + +// The returned strings (which don't have to be freed) are in ASCII. +// The result of passing in an invalid enum value is undefined. + +const char* GetMetahandleFieldString(MetahandleField metahandle_field); + +const char* GetBaseVersionString(BaseVersion base_version); + +const char* GetInt64FieldString(Int64Field int64_field); + +const char* GetIdFieldString(IdField id_field); + +const char* GetIndexedBitFieldString(IndexedBitField indexed_bit_field); + +const char* GetIsDelFieldString(IsDelField is_del_field); + +const char* GetBitFieldString(BitField bit_field); + +const char* GetStringFieldString(StringField string_field); + +const char* GetProtoFieldString(ProtoField proto_field); + +const char* GetBitTempString(BitTemp bit_temp); + +} // namespace syncable + +#endif // CHROME_BROWSER_SYNC_SYNCABLE_SYNCABLE_ENUM_CONVERSIONS_H_ diff --git a/chrome/browser/sync/syncable/syncable_enum_conversions_unittest.cc b/chrome/browser/sync/syncable/syncable_enum_conversions_unittest.cc new file mode 100644 index 0000000..3661765 --- /dev/null +++ b/chrome/browser/sync/syncable/syncable_enum_conversions_unittest.cc @@ -0,0 +1,80 @@ +// Copyright (c) 2011 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. + +// Keep this file in sync with syncable.h. + +#include "chrome/browser/sync/syncable/syncable_enum_conversions.h" + +#include <string> + +#include "testing/gtest/include/gtest/gtest.h" + +namespace syncable { +namespace { + +class SyncableEnumConversionsTest : public testing::Test { +}; + +template <class T> +void TestEnumStringFunction(const char* (*enum_string_fn)(T), + int enum_min, int enum_max) { + EXPECT_LE(enum_min, enum_max); + for (int i = enum_min; i <= enum_max; ++i) { + const std::string& str = enum_string_fn(static_cast<T>(i)); + EXPECT_FALSE(str.empty()); + } +} + +TEST_F(SyncableEnumConversionsTest, GetMetahandleFieldString) { + TestEnumStringFunction( + GetMetahandleFieldString, INT64_FIELDS_BEGIN, META_HANDLE); +} + +TEST_F(SyncableEnumConversionsTest, GetBaseVersionString) { + TestEnumStringFunction( + GetBaseVersionString, META_HANDLE + 1, BASE_VERSION); +} + +TEST_F(SyncableEnumConversionsTest, GetInt64FieldString) { + TestEnumStringFunction( + GetInt64FieldString, BASE_VERSION + 1, INT64_FIELDS_END - 1); +} + +TEST_F(SyncableEnumConversionsTest, GetIdFieldString) { + TestEnumStringFunction( + GetIdFieldString, ID_FIELDS_BEGIN, ID_FIELDS_END - 1); +} + +TEST_F(SyncableEnumConversionsTest, GetIndexedBitFieldString) { + TestEnumStringFunction( + GetIndexedBitFieldString, BIT_FIELDS_BEGIN, INDEXED_BIT_FIELDS_END - 1); +} + +TEST_F(SyncableEnumConversionsTest, GetIsDelFieldString) { + TestEnumStringFunction( + GetIsDelFieldString, INDEXED_BIT_FIELDS_END, IS_DEL); +} + +TEST_F(SyncableEnumConversionsTest, GetBitFieldString) { + TestEnumStringFunction( + GetBitFieldString, IS_DEL + 1, BIT_FIELDS_END - 1); +} + +TEST_F(SyncableEnumConversionsTest, GetStringFieldString) { + TestEnumStringFunction( + GetStringFieldString, STRING_FIELDS_BEGIN, STRING_FIELDS_END - 1); +} + +TEST_F(SyncableEnumConversionsTest, GetProtoFieldString) { + TestEnumStringFunction( + GetProtoFieldString, PROTO_FIELDS_BEGIN, PROTO_FIELDS_END - 1); +} + +TEST_F(SyncableEnumConversionsTest, GetBitTempString) { + TestEnumStringFunction( + GetBitTempString, BIT_TEMPS_BEGIN, BIT_TEMPS_END - 1); +} + +} // namespace +} // namespace syncable diff --git a/chrome/browser/sync/syncable/syncable_id.cc b/chrome/browser/sync/syncable/syncable_id.cc index ea8aebd..ea34404 100644 --- a/chrome/browser/sync/syncable/syncable_id.cc +++ b/chrome/browser/sync/syncable/syncable_id.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -7,6 +7,7 @@ #include <iosfwd> #include "base/string_util.h" +#include "base/values.h" using std::ostream; using std::string; @@ -19,6 +20,10 @@ ostream& operator<<(ostream& out, const Id& id) { return out; } +StringValue* Id::ToValue() const { + return Value::CreateStringValue(s_); +} + string Id::GetServerId() const { // Currently root is the string "0". We need to decide on a true value. // "" would be convenient here, as the IsRoot call would not be needed. diff --git a/chrome/browser/sync/syncable/syncable_id.h b/chrome/browser/sync/syncable/syncable_id.h index abc7a02..33ed3b8 100644 --- a/chrome/browser/sync/syncable/syncable_id.h +++ b/chrome/browser/sync/syncable/syncable_id.h @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -13,15 +13,12 @@ #include "base/hash_tables.h" -extern "C" { -struct sqlite3; -struct sqlite3_stmt; -} +class StringValue; namespace syncable { struct EntryKernel; class Id; -} // namespace syncable +} class MockConnectionManager; class SQLStatement; @@ -96,6 +93,10 @@ class Id { // by operator<. Id GetLexicographicSuccessor() const; + // Dumps the ID as a value and returns it. Transfers ownership of + // the StringValue to the caller. + StringValue* ToValue() const; + // Three functions are used to work with our proto buffers. std::string GetServerId() const; static Id CreateFromServerId(const std::string& server_id); diff --git a/chrome/browser/sync/syncable/syncable_id_unittest.cc b/chrome/browser/sync/syncable/syncable_id_unittest.cc index 77517c7..7fdbb71 100644 --- a/chrome/browser/sync/syncable/syncable_id_unittest.cc +++ b/chrome/browser/sync/syncable/syncable_id_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,6 +6,8 @@ #include <vector> +#include "base/memory/scoped_ptr.h" +#include "base/values.h" #include "chrome/test/sync/engine/test_id_factory.h" #include "testing/gtest/include/gtest/gtest.h" @@ -81,4 +83,27 @@ TEST(SyncableIdTest, GetLeastIdForLexicographicComparison) { } } +namespace { + +// TODO(akalin): Move this to values_test_util.h. + +// Takes ownership of |actual|. +void ExpectStringValue(const std::string& expected_str, + StringValue* actual) { + scoped_ptr<StringValue> scoped_actual(actual); + std::string actual_str; + EXPECT_TRUE(scoped_actual->GetAsString(&actual_str)); + EXPECT_EQ(expected_str, actual_str); +} + +} // namespace + +TEST(SyncableIdTest, ToValue) { + ExpectStringValue("r", Id::CreateFromServerId("0").ToValue()); + ExpectStringValue("svalue", Id::CreateFromServerId("value").ToValue()); + + ExpectStringValue("r", Id::CreateFromClientString("0").ToValue()); + ExpectStringValue("cvalue", Id::CreateFromClientString("value").ToValue()); +} + } // namespace syncable diff --git a/chrome/browser/sync/syncable/syncable_unittest.cc b/chrome/browser/sync/syncable/syncable_unittest.cc index 952aada..d9e3319 100644 --- a/chrome/browser/sync/syncable/syncable_unittest.cc +++ b/chrome/browser/sync/syncable/syncable_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -23,10 +23,11 @@ #include "base/file_path.h" #include "base/file_util.h" #include "base/logging.h" -#include "base/scoped_ptr.h" -#include "base/scoped_temp_dir.h" +#include "base/memory/scoped_ptr.h" +#include "base/memory/scoped_temp_dir.h" #include "base/string_util.h" #include "base/threading/platform_thread.h" +#include "base/values.h" #include "chrome/browser/sync/engine/syncproto.h" #include "chrome/browser/sync/protocol/bookmark_specifics.pb.h" #include "chrome/browser/sync/syncable/directory_backing_store.h" @@ -34,13 +35,32 @@ #include "chrome/common/deprecated/event_sys-inl.h" #include "chrome/test/sync/engine/test_id_factory.h" #include "chrome/test/sync/engine/test_syncable_utils.h" +#include "chrome/test/values_test_util.h" #include "testing/gtest/include/gtest/gtest.h" #include "third_party/sqlite/sqlite3.h" using browser_sync::TestIdFactory; +using test::ExpectBooleanValue; +using test::ExpectStringValue; namespace syncable { +class SyncableKernelTest : public testing::Test {}; + +TEST_F(SyncableKernelTest, ToValue) { + EntryKernel kernel; + scoped_ptr<DictionaryValue> value(kernel.ToValue()); + if (value.get()) { + // Not much to check without repeating the ToValue() code. + EXPECT_TRUE(value->HasKey("isDirty")); + // The extra +1 is for "isDirty". + EXPECT_EQ(BIT_TEMPS_END - BEGIN_FIELDS + 1, + static_cast<int>(value->size())); + } else { + ADD_FAILURE(); + } +} + namespace { void PutDataAsBookmarkFavicon(WriteTransaction* wtrans, MutableEntry* e, @@ -237,6 +257,42 @@ TEST_F(SyncableGeneralTest, ClientIndexRebuildsDeletedProperly) { } } +TEST_F(SyncableGeneralTest, ToValue) { + Directory dir; + dir.Open(db_path_, "SimpleTest"); + + const Id id = TestIdFactory::FromNumber(99); + { + ReadTransaction rtrans(&dir, __FILE__, __LINE__); + Entry e(&rtrans, GET_BY_ID, id); + EXPECT_FALSE(e.good()); // Hasn't been written yet. + + scoped_ptr<DictionaryValue> value(e.ToValue()); + ExpectBooleanValue(false, *value, "good"); + EXPECT_EQ(1u, value->size()); + } + + // Test creating a new meta entry. + { + WriteTransaction wtrans(&dir, UNITTEST, __FILE__, __LINE__); + MutableEntry me(&wtrans, CREATE, wtrans.root_id(), "new"); + ASSERT_TRUE(me.good()); + me.Put(ID, id); + me.Put(BASE_VERSION, 1); + + scoped_ptr<DictionaryValue> value(me.ToValue()); + ExpectBooleanValue(true, *value, "good"); + EXPECT_TRUE(value->HasKey("kernel")); + ExpectStringValue("Unspecified", *value, "serverModelType"); + ExpectStringValue("Unspecified", *value, "modelType"); + ExpectBooleanValue(false, *value, "shouldMaintainPosition"); + ExpectBooleanValue(true, *value, "existsOnClientBecauseNameIsNonEmpty"); + ExpectBooleanValue(false, *value, "isRoot"); + } + + dir.SaveChanges(); +} + // A Directory whose backing store always fails SaveChanges by returning false. class TestUnsaveableDirectory : public Directory { public: diff --git a/chrome/browser/sync/test_profile_sync_service.cc b/chrome/browser/sync/test_profile_sync_service.cc index ed4668e..7f6d82a 100644 --- a/chrome/browser/sync/test_profile_sync_service.cc +++ b/chrome/browser/sync/test_profile_sync_service.cc @@ -24,41 +24,20 @@ using syncable::ModelType; using syncable::ScopedDirLookup; using sync_api::UserShare; -ACTION_P(CallOnPaused, core) { - core->OnPaused(); -}; - -ACTION_P(CallOnResumed, core) { - core->OnResumed(); -} - namespace browser_sync { +using ::testing::_; SyncBackendHostForProfileSyncTest::SyncBackendHostForProfileSyncTest( Profile* profile, - int num_expected_resumes, - int num_expected_pauses, bool set_initial_sync_ended_on_init, bool synchronous_init) : browser_sync::SyncBackendHost(profile), synchronous_init_(synchronous_init) { - // By default, the RequestPause and RequestResume methods will - // send the confirmation notification and return true. - ON_CALL(*this, RequestPause()). - WillByDefault(testing::DoAll(CallOnPaused(core_), - testing::Return(true))); - ON_CALL(*this, RequestResume()). - WillByDefault(testing::DoAll(CallOnResumed(core_), - testing::Return(true))); - ON_CALL(*this, RequestNudge()).WillByDefault( + ON_CALL(*this, RequestNudge(_)).WillByDefault( testing::Invoke(this, &SyncBackendHostForProfileSyncTest:: SimulateSyncCycleCompletedInitialSyncEnded)); - - EXPECT_CALL(*this, RequestPause()).Times(num_expected_pauses); - EXPECT_CALL(*this, RequestResume()).Times(num_expected_resumes); - EXPECT_CALL(*this, - RequestNudge()).Times(set_initial_sync_ended_on_init ? 0 : 1); + EXPECT_CALL(*this, RequestNudge(_)).Times(testing::AnyNumber()); } SyncBackendHostForProfileSyncTest::~SyncBackendHostForProfileSyncTest() {} @@ -73,7 +52,8 @@ void SyncBackendHostForProfileSyncTest::ConfigureDataTypes( } void SyncBackendHostForProfileSyncTest:: - SimulateSyncCycleCompletedInitialSyncEnded() { + SimulateSyncCycleCompletedInitialSyncEnded( + const tracked_objects::Location& location) { syncable::ModelTypeBitSet sync_ended; ModelSafeRoutingInfo enabled_types; GetModelSafeRoutingInfo(&enabled_types); @@ -90,13 +70,13 @@ void SyncBackendHostForProfileSyncTest:: sync_api::HttpPostProviderFactory* SyncBackendHostForProfileSyncTest::MakeHttpBridgeFactory( - URLRequestContextGetter* getter) { + net::URLRequestContextGetter* getter) { return new browser_sync::TestHttpBridgeFactory; } void SyncBackendHostForProfileSyncTest::InitCore( const Core::DoInitializeOptions& options) { - std::wstring user = L"testuser"; + std::wstring user = L"testuser@gmail.com"; core_loop()->PostTask( FROM_HERE, NewRunnableMethod(core_.get(), @@ -147,6 +127,10 @@ void SyncBackendHostForProfileSyncTest::ProcessMessage( } } +void SyncBackendHostForProfileSyncTest::StartConfiguration(Callback0::Type*) { + SyncBackendHost::FinishConfigureDataTypesOnFrontendLoop(); +} + void SyncBackendHostForProfileSyncTest:: SetDefaultExpectationsForWorkerCreation(ProfileMock* profile) { EXPECT_CALL(*profile, GetPasswordStore(testing::_)). @@ -181,8 +165,6 @@ TestProfileSyncService::TestProfileSyncService( synchronous_backend_initialization_( synchronous_backend_initialization), synchronous_sync_configuration_(false), - num_expected_resumes_(1), - num_expected_pauses_(1), initial_condition_setup_task_(initial_condition_setup_task), set_initial_sync_ended_on_init_(true) { RegisterPreferences(); @@ -219,6 +201,7 @@ void TestProfileSyncService::OnBackendInitialized() { // Pretend we downloaded initial updates and set initial sync ended bits // if we were asked to. + bool send_passphrase_required = false; if (set_initial_sync_ended_on_init_) { UserShare* user_share = GetUserShare(); DirectoryManager* dir_manager = user_share->dir_manager.get(); @@ -231,12 +214,18 @@ void TestProfileSyncService::OnBackendInitialized() { ProfileSyncServiceTestHelper::CreateRoot( syncable::NIGORI, GetUserShare(), id_factory()); + + // A side effect of adding the NIGORI mode (normally done by the syncer) + // is a decryption attempt, which will fail the first time. + send_passphrase_required = true; } SetInitialSyncEndedForEnabledTypes(); } ProfileSyncService::OnBackendInitialized(); + if (send_passphrase_required) + OnPassphraseRequired(true); // TODO(akalin): Figure out a better way to do this. if (synchronous_backend_initialization_) { @@ -254,12 +243,6 @@ void TestProfileSyncService::Observe(NotificationType type, } } -void TestProfileSyncService::set_num_expected_resumes(int times) { - num_expected_resumes_ = times; -} -void TestProfileSyncService::set_num_expected_pauses(int num) { - num_expected_pauses_ = num; -} void TestProfileSyncService::dont_set_initial_sync_ended_on_init() { set_initial_sync_ended_on_init_ = false; } @@ -270,7 +253,6 @@ void TestProfileSyncService::set_synchronous_sync_configuration() { void TestProfileSyncService::CreateBackend() { backend_.reset(new browser_sync::SyncBackendHostForProfileSyncTest( profile(), - num_expected_resumes_, num_expected_pauses_, set_initial_sync_ended_on_init_, synchronous_backend_initialization_)); } diff --git a/chrome/browser/sync/test_profile_sync_service.h b/chrome/browser/sync/test_profile_sync_service.h index c027725..0aaf07b 100644 --- a/chrome/browser/sync/test_profile_sync_service.h +++ b/chrome/browser/sync/test_profile_sync_service.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -36,15 +36,11 @@ class SyncBackendHostForProfileSyncTest // completed setting itself up and called us back. SyncBackendHostForProfileSyncTest( Profile* profile, - int num_expected_resumes, - int num_expected_pauses, bool set_initial_sync_ended_on_init, bool synchronous_init); virtual ~SyncBackendHostForProfileSyncTest(); - MOCK_METHOD0(RequestPause, bool()); - MOCK_METHOD0(RequestResume, bool()); - MOCK_METHOD0(RequestNudge, void()); + MOCK_METHOD1(RequestNudge, void(const tracked_objects::Location&)); virtual void ConfigureDataTypes( const DataTypeController::TypeMap& data_type_controllers, @@ -52,10 +48,11 @@ class SyncBackendHostForProfileSyncTest CancelableTask* ready_task); // Called when a nudge comes in. - void SimulateSyncCycleCompletedInitialSyncEnded(); + void SimulateSyncCycleCompletedInitialSyncEnded( + const tracked_objects::Location&); virtual sync_api::HttpPostProviderFactory* MakeHttpBridgeFactory( - URLRequestContextGetter* getter); + net::URLRequestContextGetter* getter); virtual void InitCore(const Core::DoInitializeOptions& options); @@ -71,6 +68,8 @@ class SyncBackendHostForProfileSyncTest virtual void ProcessMessage(const std::string& name, const JsArgList& args, const JsEventHandler* sender); + virtual void StartConfiguration(Callback0::Type* callback); + static void SetDefaultExpectationsForWorkerCreation(ProfileMock* profile); static void SetHistoryServiceExpectations(ProfileMock* profile); @@ -101,9 +100,6 @@ class TestProfileSyncService : public ProfileSyncService { const NotificationSource& source, const NotificationDetails& details); - void set_num_expected_resumes(int times); - void set_num_expected_pauses(int num); - // If this is called, configuring data types will require a syncer // nudge. void dont_set_initial_sync_ended_on_init(); @@ -133,8 +129,6 @@ class TestProfileSyncService : public ProfileSyncService { // step is performed synchronously. bool synchronous_sync_configuration_; bool set_expect_resume_expectations_; - int num_expected_resumes_; - int num_expected_pauses_; Task* initial_condition_setup_task_; bool set_initial_sync_ended_on_init_; diff --git a/chrome/browser/sync/token_migrator.cc b/chrome/browser/sync/token_migrator.cc deleted file mode 100644 index a2f4e62..0000000 --- a/chrome/browser/sync/token_migrator.cc +++ /dev/null @@ -1,53 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/sync/token_migrator.h" - -#include "chrome/browser/sync/profile_sync_service.h" -#include "chrome/browser/sync/util/user_settings.h" -#include "chrome/common/net/gaia/gaia_constants.h" - -const FilePath::CharType kSyncDataFolderName[] = - FILE_PATH_LITERAL("Sync Data"); - -const FilePath::CharType kBookmarkSyncUserSettingsDatabase[] = - FILE_PATH_LITERAL("BookmarkSyncSettings.sqlite3"); - -using browser_sync::UserSettings; - -TokenMigrator::TokenMigrator(ProfileSyncService* service, - const FilePath& profile_path) - : service_(service), - database_location_(profile_path.Append(kSyncDataFolderName)) { -} - -TokenMigrator::~TokenMigrator() { -} - -void TokenMigrator::TryMigration() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - BrowserThread::PostTask(BrowserThread::DB, FROM_HERE, - NewRunnableMethod(this, &TokenMigrator::LoadTokens)); -} - -void TokenMigrator::LoadTokens() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - scoped_ptr<UserSettings> user_settings(new UserSettings()); - FilePath settings_db_file = - database_location_.Append(FilePath(kBookmarkSyncUserSettingsDatabase)); - if (!user_settings->Init(settings_db_file)) - return; - - if (!user_settings->GetLastUserAndServiceToken(GaiaConstants::kSyncService, - &username_, &token_)) - return; - - BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, - NewRunnableMethod(this, &TokenMigrator::PostTokensBack)); -} - -void TokenMigrator::PostTokensBack() { - DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); - service_->LoadMigratedCredentials(username_, token_); -} diff --git a/chrome/browser/sync/token_migrator.h b/chrome/browser/sync/token_migrator.h deleted file mode 100644 index 81d6f6d..0000000 --- a/chrome/browser/sync/token_migrator.h +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_SYNC_TOKEN_MIGRATOR_H_ -#define CHROME_BROWSER_SYNC_TOKEN_MIGRATOR_H_ -#pragma once - -#include <string> - -#include "base/file_path.h" -#include "base/task.h" - -class ProfileSyncService; - -// The TokenMigrator provides a bridge between the IO thread and -// the UI thread to load the old-style credentials from the user -// settings DB, and post them back to the UI thread to store the -// token in the token service. -class TokenMigrator { - public: - TokenMigrator(ProfileSyncService* service, - const FilePath& profile_path); - ~TokenMigrator(); - - // This is called on the UI thread, it only posts a task. - // If migration succeeds, the tokens will become available in - // the token service. - void TryMigration(); - - private: - // This runs as a deferred task on the DB thread. - void LoadTokens(); - - // This runs as a deferred task on the UI thread (only after the DB thread is - // finished with the data. - void PostTokensBack(); - - // The username and token retrieved from the user settings DB. - std::string username_; - std::string token_; - - // The ProfileSyncService owns this object, so this pointer is valid when - // PostTokensBack is called. - ProfileSyncService* service_; - - // The directory to search for the user settings database. - FilePath database_location_; - - DISALLOW_COPY_AND_ASSIGN(TokenMigrator); -}; - -// We ensure this object will outlive its tasks, so don't need refcounting. -DISABLE_RUNNABLE_METHOD_REFCOUNT(TokenMigrator); - -#endif // CHROME_BROWSER_SYNC_TOKEN_MIGRATOR_H_ diff --git a/chrome/browser/sync/tools/sync_listen_notifications.cc b/chrome/browser/sync/tools/sync_listen_notifications.cc index 0eb9459..1a4382e 100644 --- a/chrome/browser/sync/tools/sync_listen_notifications.cc +++ b/chrome/browser/sync/tools/sync_listen_notifications.cc @@ -1,229 +1,70 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 <cstdio> #include <string> -#include <vector> #include "base/at_exit.h" #include "base/base64.h" #include "base/command_line.h" #include "base/logging.h" +#include "base/memory/ref_counted.h" +#include "base/memory/scoped_ptr.h" #include "base/message_loop.h" -#include "base/observer_list.h" -#include "base/string_util.h" -#include "base/task.h" -#include "base/weak_ptr.h" -#include "chrome/browser/sync/notifier/cache_invalidation_packet_handler.h" -#include "chrome/browser/sync/notifier/chrome_invalidation_client.h" -#include "chrome/browser/sync/notifier/chrome_system_resources.h" -#include "chrome/browser/sync/sync_constants.h" -#include "jingle/notifier/base/xmpp_connection.h" -#include "jingle/notifier/listener/listen_task.h" -#include "jingle/notifier/listener/notification_constants.h" -#include "jingle/notifier/listener/notification_defines.h" -#include "jingle/notifier/listener/send_update_task.h" -#include "jingle/notifier/listener/subscribe_task.h" -#include "jingle/notifier/listener/xml_element_util.h" -#include "net/base/cert_verifier.h" -#include "net/base/ssl_config_service.h" -#include "net/socket/client_socket_factory.h" -#include "talk/base/cryptstring.h" -#include "talk/base/logging.h" -#include "talk/base/sigslot.h" -#include "talk/base/task.h" -#include "talk/xmpp/jid.h" -#include "talk/xmpp/xmppclientsettings.h" -#include "talk/xmpp/constants.h" -#include "talk/xmpp/xmppengine.h" +#include "chrome/browser/sync/notifier/sync_notifier.h" +#include "chrome/browser/sync/notifier/sync_notifier_factory.h" +#include "chrome/browser/sync/notifier/sync_notifier_observer.h" +#include "chrome/browser/sync/syncable/model_type.h" +#include "chrome/browser/sync/syncable/model_type_payload_map.h" +#include "chrome/test/test_url_request_context_getter.h" +#include "content/browser/browser_thread.h" -// This is a simple utility that logs into an XMPP server, subscribes -// to Sync notifications, and prints out any such notifications that -// are received. +// This is a simple utility that initializes a sync notifier and +// listens to any received notifications. namespace { -// Main class that listens for and handles messages from the XMPP -// client. -class XmppNotificationClient : public notifier::XmppConnection::Delegate { +// Class to print received notifications events. +class NotificationPrinter : public sync_notifier::SyncNotifierObserver { public: - // An observer is notified when we are logged into XMPP or when an - // error occurs. - class Observer { - public: - virtual ~Observer() {} - - virtual void OnConnect(base::WeakPtr<talk_base::Task> base_task) = 0; - - virtual void OnError() = 0; - }; - - template <class T> XmppNotificationClient(T begin, T end) { - for (T it = begin; it != end; ++it) { - observer_list_.AddObserver(*it); - } - } - - virtual ~XmppNotificationClient() {} - - // Connect with the given XMPP settings and run until disconnected. - void Run(const buzz::XmppClientSettings& xmpp_client_settings, - net::CertVerifier* cert_verifier) { - DCHECK(!xmpp_connection_.get()); - xmpp_connection_.reset( - new notifier::XmppConnection(xmpp_client_settings, cert_verifier, - this, NULL)); - MessageLoop::current()->Run(); - DCHECK(!xmpp_connection_.get()); - } - - virtual void OnConnect(base::WeakPtr<talk_base::Task> base_task) { - FOR_EACH_OBSERVER(Observer, observer_list_, OnConnect(base_task)); - } - - virtual void OnError(buzz::XmppEngine::Error error, int subcode, - const buzz::XmlElement* stream_error) { - LOG(INFO) << "Error: " << error << ", subcode: " << subcode; - if (stream_error) { - LOG(INFO) << "Stream error: " - << notifier::XmlElementToString(*stream_error); - } - FOR_EACH_OBSERVER(Observer, observer_list_, OnError()); - // This has to go before the message loop quits. - xmpp_connection_.reset(); - MessageLoop::current()->Quit(); - } - - private: - ObserverList<Observer> observer_list_; - scoped_ptr<notifier::XmppConnection> xmpp_connection_; - - DISALLOW_COPY_AND_ASSIGN(XmppNotificationClient); -}; - -// Delegate for legacy notifications. -class LegacyNotifierDelegate - : public XmppNotificationClient::Observer, - public sigslot::has_slots<> { - public: - explicit LegacyNotifierDelegate(bool send_initial_update) - : send_initial_update_(send_initial_update) {} - - virtual ~LegacyNotifierDelegate() {} - - virtual void OnConnect(base::WeakPtr<talk_base::Task> base_task) { - LOG(INFO) << "Logged in"; - std::vector<std::string> subscribed_services_list; - subscribed_services_list.push_back(browser_sync::kSyncServiceUrl); - // Owned by base_task. - notifier::SubscribeTask* subscribe_task = - new notifier::SubscribeTask(base_task, subscribed_services_list); - subscribe_task->Start(); - // Owned by xmpp_client. - notifier::ListenTask* listen_task = - new notifier::ListenTask(base_task); - listen_task->SignalUpdateAvailable.connect( - this, &LegacyNotifierDelegate::OnUpdateAvailable); - listen_task->Start(); - if (send_initial_update_) { - // Owned by xmpp_client. - notifier::SendUpdateTask* send_update_task = - new notifier::SendUpdateTask(base_task, - OutgoingNotificationData()); - send_update_task->Start(); - } - } - - virtual void OnError() {} - - void OnUpdateAvailable( - const IncomingNotificationData& notification_data) { - LOG(INFO) << "Notification received: " - << notification_data.service_url << " " - << notification_data.service_specific_data; - } - - private: - bool send_initial_update_; -}; - -// The actual listener for sync notifications. -class ChromeInvalidationListener - : public sync_notifier::ChromeInvalidationClient::Listener { - public: - ChromeInvalidationListener() {} - - virtual void OnInvalidate(syncable::ModelType model_type, - const std::string& payload) { - LOG(INFO) << "OnInvalidate: " - << syncable::ModelTypeToString(model_type) << " - " << payload; - // A real implementation would respond to the invalidation. - } - - virtual void OnInvalidateAll() { - LOG(INFO) << "InvalidateAll"; - // A real implementation would loop over the current registered - // data types and send notifications for those. - } - - private: - DISALLOW_COPY_AND_ASSIGN(ChromeInvalidationListener); -}; - -// Delegate for server-issued notifications. -class ServerNotifierDelegate - : public XmppNotificationClient::Observer, - public sync_notifier::StateWriter { - public: - explicit ServerNotifierDelegate( - const std::string& server_notifier_state) - : server_notifier_state_(server_notifier_state) {} - - virtual ~ServerNotifierDelegate() {} - - virtual void OnConnect(base::WeakPtr<talk_base::Task> base_task) { - LOG(INFO) << "Logged in"; - - // TODO(akalin): app_name should be per-client unique. - const std::string kAppName = "cc_sync_listen_notifications"; - const std::string kAppInfo = kAppName; - chrome_invalidation_client_.Start(kAppName, kAppInfo, - server_notifier_state_, - &chrome_invalidation_listener_, - this, base_task); - syncable::ModelTypeSet all_types; - for (int i = syncable::FIRST_REAL_MODEL_TYPE; - i < syncable::MODEL_TYPE_COUNT; ++i) { - syncable::ModelType model_type = syncable::ModelTypeFromInt(i); - all_types.insert(model_type); + NotificationPrinter() {} + virtual ~NotificationPrinter() {} + + virtual void OnIncomingNotification( + const syncable::ModelTypePayloadMap& type_payloads) { + for (syncable::ModelTypePayloadMap::const_iterator it = + type_payloads.begin(); it != type_payloads.end(); ++it) { + LOG(INFO) << "Notification: type = " + << syncable::ModelTypeToString(it->first) + << ", payload = " << it->second; } - - chrome_invalidation_client_.RegisterTypes(all_types); } - virtual void OnError() { - chrome_invalidation_client_.Stop(); + virtual void OnNotificationStateChange(bool notifications_enabled) { + LOG(INFO) << "Notifications enabled: " << notifications_enabled; } - virtual void WriteState(const std::string& state) { + virtual void StoreState(const std::string& state) { std::string base64_state; CHECK(base::Base64Encode(state, &base64_state)); - LOG(INFO) << "New state: " << base64_state; + LOG(INFO) << "Got state to store: " << base64_state; } private: - ChromeInvalidationListener chrome_invalidation_listener_; - // Opaque blob capturing the notifications state of a previous run - // (i.e., the base64-decoded value of a string output by - // WriteState()). - std::string server_notifier_state_; - sync_notifier::ChromeInvalidationClient chrome_invalidation_client_; + DISALLOW_COPY_AND_ASSIGN(NotificationPrinter); }; } // namespace int main(int argc, char* argv[]) { base::AtExitManager exit_manager; + scoped_refptr<TestURLRequestContextGetter> request_context_getter( + new TestURLRequestContextGetter); + BrowserThread io_thread(BrowserThread::IO); + base::Thread::Options options; + options.message_loop_type = MessageLoop::TYPE_IO; + io_thread.StartWithOptions(options); CommandLine::Init(argc, argv); logging::InitLogging( NULL, @@ -231,96 +72,49 @@ int main(int argc, char* argv[]) { logging::LOCK_LOG_FILE, logging::DELETE_OLD_LOG_FILE, logging::DISABLE_DCHECK_FOR_NON_OFFICIAL_RELEASE_BUILDS); - // TODO(akalin): Make sure that all log messages are printed to the - // console, even on Windows (SetMinLogLevel isn't enough). - talk_base::LogMessage::LogToDebug(talk_base::LS_VERBOSE); // Parse command line. const CommandLine& command_line = *CommandLine::ForCurrentProcess(); std::string email = command_line.GetSwitchValueASCII("email"); - if (email.empty()) { - printf("Usage: %s --email=foo@bar.com [--password=mypassword] " - "[--server=talk.google.com] [--port=5222] [--allow-plain] " - "[--disable-tls] [--use-ssl-tcp] [--server-notifier-state] " - "[--use-legacy-notifier] " - "[--legacy-notifier-send-initial-update]\n", - argv[0]); + std::string token = command_line.GetSwitchValueASCII("token"); + // TODO(akalin): Write a wrapper script that gets a token for an + // email and password and passes that in to this utility. + if (email.empty() || token.empty()) { + std::printf("Usage: %s --email=foo@bar.com --token=token\n\n" + "See sync_notifier_factory.cc for more switches.\n\n" + "Run chrome and set a breakpoint on " + "sync_api::SyncManager::SyncInternal::UpdateCredentials() " + "after logging into sync to get the token to pass into this " + "utility.\n", + argv[0]); return -1; } - std::string password = command_line.GetSwitchValueASCII("password"); - std::string server = command_line.GetSwitchValueASCII("server"); - if (server.empty()) { - server = "talk.google.com"; - } - std::string port_str = command_line.GetSwitchValueASCII("port"); - int port = 5222; - if (!port_str.empty()) { - int port_from_port_str = std::strtol(port_str.c_str(), NULL, 10); - if (port_from_port_str == 0) { - LOG(WARNING) << "Invalid port " << port_str << "; using default"; - } else { - port = port_from_port_str; - } - } - bool allow_plain = command_line.HasSwitch("allow-plain"); - bool disable_tls = command_line.HasSwitch("disable-tls"); - bool use_ssl_tcp = command_line.HasSwitch("use-ssl-tcp"); - if (use_ssl_tcp && (port != 443)) { - LOG(WARNING) << "--use-ssl-tcp is set but port is " << port - << " instead of 443"; - } - std::string server_notifier_state; - std::string server_notifier_state_encoded = - command_line.GetSwitchValueASCII("server-notifier-state"); - if (!server_notifier_state_encoded.empty() && - !base::Base64Decode(server_notifier_state_encoded, - &server_notifier_state)) { - LOG(ERROR) << "Could not decode state: " - << server_notifier_state_encoded; - } - bool use_legacy_notifier = command_line.HasSwitch("use-legacy-notifier"); - bool legacy_notifier_send_initial_update = - command_line.HasSwitch("legacy-notifier-send-initial-update"); - // Build XMPP client settings. - buzz::XmppClientSettings xmpp_client_settings; - buzz::Jid jid(email); - xmpp_client_settings.set_user(jid.node()); - xmpp_client_settings.set_resource("cc_sync_listen_notifications"); - xmpp_client_settings.set_host(jid.domain()); - xmpp_client_settings.set_allow_plain(allow_plain); - xmpp_client_settings.set_use_tls(!disable_tls); - if (use_ssl_tcp) { - xmpp_client_settings.set_protocol(cricket::PROTO_SSLTCP); - } - talk_base::InsecureCryptStringImpl insecure_crypt_string; - insecure_crypt_string.password() = password; - xmpp_client_settings.set_pass( - talk_base::CryptString(insecure_crypt_string)); - talk_base::SocketAddress addr(server, port); - if (!addr.ResolveIP()) { - LOG(ERROR) << "Could not resolve " << addr.ToString(); - return -1; - } - xmpp_client_settings.set_server(addr); + // Needed by the SyncNotifier implementations. + MessageLoop main_loop; - net::CertVerifier cert_verifier; - MessageLoopForIO message_loop; + const char kClientInfo[] = "sync_listen_notifications"; + sync_notifier::SyncNotifierFactory sync_notifier_factory(kClientInfo); + scoped_ptr<sync_notifier::SyncNotifier> sync_notifier( + sync_notifier_factory.CreateSyncNotifier(command_line, + request_context_getter.get())); + NotificationPrinter notification_printer; + sync_notifier->AddObserver(¬ification_printer); - // Connect and listen. - ServerNotifierDelegate server_notifier_delegate( - server_notifier_state); - LegacyNotifierDelegate legacy_notifier_delegate( - legacy_notifier_send_initial_update); - std::vector<XmppNotificationClient::Observer*> observers; - if (use_legacy_notifier) { - observers.push_back(&legacy_notifier_delegate); - } else { - observers.push_back(&server_notifier_delegate); + sync_notifier->UpdateCredentials(email, token); + { + // Listen for notifications for all known types. + syncable::ModelTypeSet types; + for (int i = syncable::FIRST_REAL_MODEL_TYPE; + i < syncable::MODEL_TYPE_COUNT; ++i) { + types.insert(syncable::ModelTypeFromInt(i)); + } + sync_notifier->UpdateEnabledTypes(types); } - XmppNotificationClient xmpp_notification_client( - observers.begin(), observers.end()); - xmpp_notification_client.Run(xmpp_client_settings, &cert_verifier); + main_loop.Run(); + + sync_notifier->RemoveObserver(¬ification_printer); + io_thread.Stop(); return 0; } diff --git a/chrome/browser/sync/tools/sync_tools.gyp b/chrome/browser/sync/tools/sync_tools.gyp index 18d163c..4ebe6ea 100644 --- a/chrome/browser/sync/tools/sync_tools.gyp +++ b/chrome/browser/sync/tools/sync_tools.gyp @@ -9,18 +9,12 @@ 'type': 'executable', 'sources': [ 'sync_listen_notifications.cc', - # We are directly including the sync_constants.cc and h files to avoid - # pulling in browser.lib. - # TODO(akalin): Fix this. - '<(DEPTH)/chrome/browser/sync/sync_constants.cc', - '<(DEPTH)/chrome/browser/sync/sync_constants.h', ], 'dependencies': [ '<(DEPTH)/base/base.gyp:base', - '<(DEPTH)/chrome/chrome.gyp:common_constants', '<(DEPTH)/chrome/chrome.gyp:sync_notifier', - '<(DEPTH)/jingle/jingle.gyp:notifier', - '<(DEPTH)/third_party/libjingle/libjingle.gyp:libjingle', + '<(DEPTH)/chrome/chrome.gyp:test_support_common', + '<(DEPTH)/content/content.gyp:content_browser', ], }, ], diff --git a/chrome/browser/sync/util/channel.h b/chrome/browser/sync/util/channel.h deleted file mode 100644 index 839d653..0000000 --- a/chrome/browser/sync/util/channel.h +++ /dev/null @@ -1,145 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#ifndef CHROME_BROWSER_SYNC_UTIL_CHANNEL_H_ -#define CHROME_BROWSER_SYNC_UTIL_CHANNEL_H_ -#pragma once - -/////////////////////////////////////////////////////////////////////////////// -// -// OVERVIEW: -// -// A threadsafe container for a list of observers. Observers are able to -// remove themselves during iteration, and can be added on any thread. This -// allows observers to safely remove themselves during notifications. It -// also provides a handler when an observer is added that will remove the -// observer on destruction. -// -// It is expected that all observers are removed before destruction. -// The channel owner should notify all observers to disconnect on shutdown if -// needed to ensure this. -// -// TYPICAL USAGE: -// -// class MyWidget { -// public: -// ... -// -// class Observer : public ChannelEventHandler<FooEvent> { -// public: -// virtual void HandleChannelEvent(const FooEvent& w) = 0; -// }; -// -// ChannelHookup<MyEvent>* AddObserver(Observer* obs) { -// return channel_.AddObserver(obs); -// } -// -// void RemoveObserver(Observer* obs) { -// channel_.RemoveObserver(obs); -// } -// -// void NotifyFoo(FooEvent& event) { -// channel_.Notify(event); -// } -// -// private: -// Channel<FooEvent> channel_; -// }; -// -// -/////////////////////////////////////////////////////////////////////////////// - -#include "base/observer_list.h" -#include "base/synchronization/lock.h" -#include "base/threading/platform_thread.h" - -namespace browser_sync { - -template <typename EventType> -class Channel; - -class EventHandler { -}; - -template <typename EventType> -class ChannelEventHandler : public EventHandler { - public: - virtual void HandleChannelEvent(const EventType& event) = 0; - - protected: - virtual ~ChannelEventHandler() {} -}; - -// This class manages a connection to a channel. When it is destroyed, it -// will remove the listener from the channel observer list. -template <typename EventType> -class ChannelHookup { - public: - ChannelHookup(Channel<EventType>* channel, - browser_sync::ChannelEventHandler<EventType>* handler) - : channel_(channel), - handler_(handler) {} - ~ChannelHookup() { - channel_->RemoveObserver(handler_); - } - - private: - Channel<EventType>* channel_; - browser_sync::ChannelEventHandler<EventType>* handler_; -}; - -template <typename EventType> -class Channel { - public: - typedef ObserverListBase<EventHandler> ChannelObserverList; - - Channel() : locking_thread_(0) {} - - ChannelHookup<EventType>* AddObserver( - ChannelEventHandler<EventType>* observer) { - base::AutoLock scoped_lock(event_handlers_mutex_); - event_handlers_.AddObserver(observer); - return new ChannelHookup<EventType>(this, observer); - } - - void RemoveObserver(ChannelEventHandler<EventType>* observer) { - // This can be called in response to a notification, so we may already have - // locked this channel on this thread. - bool need_lock = (locking_thread_ != base::PlatformThread::CurrentId()); - if (need_lock) - event_handlers_mutex_.Acquire(); - - event_handlers_mutex_.AssertAcquired(); - event_handlers_.RemoveObserver(observer); - if (need_lock) - event_handlers_mutex_.Release(); - } - - void Notify(const EventType& event) { - base::AutoLock scoped_lock(event_handlers_mutex_); - - // This may result in an observer trying to remove itself, so keep track - // of the thread we're locked on. - locking_thread_ = base::PlatformThread::CurrentId(); - - ChannelObserverList::Iterator it(event_handlers_); - EventHandler* obs; - while ((obs = it.GetNext()) != NULL) { - static_cast<ChannelEventHandler<EventType>* >(obs)-> - HandleChannelEvent(event); - } - - // Set back to an invalid thread id. - locking_thread_ = 0; - } - - private: - base::Lock event_handlers_mutex_; - base::PlatformThreadId locking_thread_; - ObserverList<EventHandler> event_handlers_; -}; - -} // namespace browser_sync - -#endif // CHROME_BROWSER_SYNC_UTIL_CHANNEL_H_ diff --git a/chrome/browser/sync/util/channel_unittest.cc b/chrome/browser/sync/util/channel_unittest.cc deleted file mode 100644 index f2317dc..0000000 --- a/chrome/browser/sync/util/channel_unittest.cc +++ /dev/null @@ -1,32 +0,0 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. -// Use of this source code is governed by a BSD-style license that can be -// found in the LICENSE file. - -#include "chrome/browser/sync/util/channel.h" -#include "testing/gtest/include/gtest/gtest.h" - -struct TestEvent { - explicit TestEvent(int foo) : data(foo) {} - int data; -}; - -class TestObserver : public browser_sync::ChannelEventHandler<TestEvent> { - public: - virtual void HandleChannelEvent(const TestEvent& event) { - delete hookup; - hookup = 0; - } - - browser_sync::ChannelHookup<TestEvent>* hookup; -}; - -TEST(ChannelTest, RemoveOnNotify) { - browser_sync::Channel<TestEvent> channel; - TestObserver observer; - - observer.hookup = channel.AddObserver(&observer); - - ASSERT_TRUE(0 != observer.hookup); - channel.Notify(TestEvent(1)); - ASSERT_EQ(0, observer.hookup); -} diff --git a/chrome/browser/sync/util/cryptographer.cc b/chrome/browser/sync/util/cryptographer.cc index da94681..82ec2c2 100644 --- a/chrome/browser/sync/util/cryptographer.cc +++ b/chrome/browser/sync/util/cryptographer.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -36,6 +36,11 @@ bool Cryptographer::CanDecrypt(const sync_pb::EncryptedData& data) const { return nigoris_.end() != nigoris_.find(data.key_name()); } +bool Cryptographer::CanDecryptUsingDefaultKey( + const sync_pb::EncryptedData& data) const { + return default_nigori_ && (data.key_name() == default_nigori_->first); +} + bool Cryptographer::Encrypt(const ::google::protobuf::MessageLite& message, sync_pb::EncryptedData* encrypted) const { DCHECK(encrypted); diff --git a/chrome/browser/sync/util/cryptographer.h b/chrome/browser/sync/util/cryptographer.h index adb809b..d5410a2 100644 --- a/chrome/browser/sync/util/cryptographer.h +++ b/chrome/browser/sync/util/cryptographer.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,8 +10,8 @@ #include <string> #include "base/gtest_prod_util.h" -#include "base/linked_ptr.h" -#include "base/scoped_ptr.h" +#include "base/memory/linked_ptr.h" +#include "base/memory/scoped_ptr.h" #include "chrome/browser/sync/protocol/nigori_specifics.pb.h" #include "chrome/browser/sync/util/nigori.h" @@ -60,6 +60,10 @@ class Cryptographer { // about. bool CanDecrypt(const sync_pb::EncryptedData& encrypted) const; + // Returns whether |encrypted| can be decrypted using the default encryption + // key. + bool CanDecryptUsingDefaultKey(const sync_pb::EncryptedData& encrypted) const; + // Encrypts |message| into |encrypted|. Returns true unless encryption fails. // Note that encryption will fail if |message| isn't valid (eg. a required // field isn't set). @@ -94,7 +98,7 @@ class Cryptographer { // false. void SetPendingKeys(const sync_pb::EncryptedData& encrypted); - // Attepmts to decrypt the set of keys that was copied in the previous call to + // Attempts to decrypt the set of keys that was copied in the previous call to // SetPendingKeys using |params|. Returns true if the pending keys were // successfully decrypted and installed. bool DecryptPendingKeys(const KeyParams& params); diff --git a/chrome/browser/sync/util/cryptographer_unittest.cc b/chrome/browser/sync/util/cryptographer_unittest.cc index 30b3be0..f6881f4 100644 --- a/chrome/browser/sync/util/cryptographer_unittest.cc +++ b/chrome/browser/sync/util/cryptographer_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,7 +6,7 @@ #include <string> -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/string_util.h" #include "chrome/browser/password_manager/encryptor.h" #include "chrome/browser/sync/protocol/password_specifics.pb.h" diff --git a/chrome/browser/sync/util/extensions_activity_monitor.cc b/chrome/browser/sync/util/extensions_activity_monitor.cc index 1048ee2..60f6d1a 100644 --- a/chrome/browser/sync/util/extensions_activity_monitor.cc +++ b/chrome/browser/sync/util/extensions_activity_monitor.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -7,8 +7,8 @@ #include "base/task.h" #include "chrome/browser/extensions/extension_bookmarks_module.h" #include "chrome/common/extensions/extension.h" -#include "chrome/common/notification_service.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_service.h" namespace browser_sync { diff --git a/chrome/browser/sync/util/extensions_activity_monitor.h b/chrome/browser/sync/util/extensions_activity_monitor.h index f19090d..3cce238 100644 --- a/chrome/browser/sync/util/extensions_activity_monitor.h +++ b/chrome/browser/sync/util/extensions_activity_monitor.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -10,8 +10,8 @@ #include "base/message_loop.h" #include "base/synchronization/lock.h" -#include "chrome/common/notification_observer.h" -#include "chrome/common/notification_registrar.h" +#include "content/common/notification_observer.h" +#include "content/common/notification_registrar.h" namespace browser_sync { diff --git a/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc index ed51a36..b7e8e7c 100644 --- a/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc +++ b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2009 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -11,8 +11,8 @@ #include "chrome/browser/extensions/extension_bookmarks_module.h" #include "chrome/common/extensions/extension.h" #include "chrome/common/extensions/extension_constants.h" -#include "chrome/common/notification_service.h" #include "content/browser/browser_thread.h" +#include "content/common/notification_service.h" #include "testing/gtest/include/gtest/gtest.h" using browser_sync::ExtensionsActivityMonitor; @@ -73,8 +73,8 @@ class BookmarkAPIEventGenerator { input.SetString(keys::kVersion, kTestExtensionVersion); input.SetString(keys::kName, kTestExtensionName); scoped_refptr<Extension> extension(Extension::Create( - FilePath(extension_path), Extension::INVALID, input, false, true, - &error)); + FilePath(extension_path), Extension::INVALID, input, + Extension::STRICT_ERROR_CHECKS, &error)); bookmarks_function->set_name(T::function_name()); base::WaitableEvent done_event(false, false); BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, @@ -132,8 +132,8 @@ class ExtensionsActivityMonitorTest : public testing::Test { input.SetString(keys::kVersion, kTestExtensionVersion); input.SetString(keys::kName, kTestExtensionName); scoped_refptr<Extension> extension(Extension::Create( - FilePath(extension_path), Extension::INVALID, input, false, true, - &error)); + FilePath(extension_path), Extension::INVALID, input, + Extension::STRICT_ERROR_CHECKS, &error)); EXPECT_EQ("", error); return extension->id(); } diff --git a/chrome/browser/sync/util/nigori.cc b/chrome/browser/sync/util/nigori.cc index 51f767b..cc17499 100644 --- a/chrome/browser/sync/util/nigori.cc +++ b/chrome/browser/sync/util/nigori.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -14,18 +14,18 @@ #include <vector> #include "base/base64.h" -#include "base/crypto/encryptor.h" -#include "base/hmac.h" #include "base/logging.h" #include "base/rand_util.h" #include "base/string_util.h" +#include "crypto/encryptor.h" +#include "crypto/hmac.h" using base::Base64Encode; using base::Base64Decode; -using base::Encryptor; -using base::HMAC; using base::RandInt; -using base::SymmetricKey; +using crypto::Encryptor; +using crypto::HMAC; +using crypto::SymmetricKey; namespace browser_sync { diff --git a/chrome/browser/sync/util/nigori.h b/chrome/browser/sync/util/nigori.h index 38d6fde..74fd6ec 100644 --- a/chrome/browser/sync/util/nigori.h +++ b/chrome/browser/sync/util/nigori.h @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -8,8 +8,8 @@ #include <string> -#include "base/crypto/symmetric_key.h" -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" +#include "crypto/symmetric_key.h" namespace browser_sync { @@ -73,9 +73,9 @@ class Nigori { static const size_t kSigningIterations = 1004; private: - scoped_ptr<base::SymmetricKey> user_key_; - scoped_ptr<base::SymmetricKey> encryption_key_; - scoped_ptr<base::SymmetricKey> mac_key_; + scoped_ptr<crypto::SymmetricKey> user_key_; + scoped_ptr<crypto::SymmetricKey> encryption_key_; + scoped_ptr<crypto::SymmetricKey> mac_key_; }; } // namespace browser_sync diff --git a/chrome/browser/sync/util/nigori_unittest.cc b/chrome/browser/sync/util/nigori_unittest.cc index 8505e55..9c17762 100644 --- a/chrome/browser/sync/util/nigori_unittest.cc +++ b/chrome/browser/sync/util/nigori_unittest.cc @@ -1,4 +1,4 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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. @@ -6,7 +6,7 @@ #include <string> -#include "base/scoped_ptr.h" +#include "base/memory/scoped_ptr.h" #include "base/string_util.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/chrome/browser/sync/util/user_settings_unittest.cc b/chrome/browser/sync/util/user_settings_unittest.cc index 0545b94..c4fcfcf 100644 --- a/chrome/browser/sync/util/user_settings_unittest.cc +++ b/chrome/browser/sync/util/user_settings_unittest.cc @@ -1,35 +1,36 @@ -// Copyright (c) 2010 The Chromium Authors. All rights reserved. +// Copyright (c) 2011 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 <limits> #include <string> +#include "app/sql/statement.h" #include "base/file_util.h" -#include "base/scoped_temp_dir.h" +#include "base/memory/scoped_temp_dir.h" +#include "base/utf_string_conversions.h" +#include "build/build_config.h" #include "chrome/browser/password_manager/encryptor.h" #include "chrome/browser/sync/syncable/directory_manager.h" #include "chrome/browser/sync/util/user_settings.h" -#include "chrome/common/sqlite_utils.h" #include "testing/gtest/include/gtest/gtest.h" -using browser_sync::APEncode; -using browser_sync::APDecode; -using browser_sync::ExecOrDie; -using browser_sync::UserSettings; - using std::numeric_limits; -static const FilePath::CharType kV10UserSettingsDB[] = +namespace { + +const FilePath::CharType kV10UserSettingsDB[] = FILE_PATH_LITERAL("Version10Settings.sqlite3"); -static const FilePath::CharType kV11UserSettingsDB[] = +const FilePath::CharType kV11UserSettingsDB[] = FILE_PATH_LITERAL("Version11Settings.sqlite3"); -static const FilePath::CharType kOldStyleSyncDataDB[] = +const FilePath::CharType kOldStyleSyncDataDB[] = FILE_PATH_LITERAL("OldStyleSyncData.sqlite3"); +} // namespace + class UserSettingsTest : public testing::Test { public: - UserSettingsTest() : sync_data_("Some sync data") { } + UserSettingsTest() : sync_data_("Some sync data") {} virtual void SetUp() { #if defined(OS_MACOSX) @@ -42,12 +43,11 @@ class UserSettingsTest : public testing::Test { // Creates and populates the V10 database files within // |destination_directory|. void SetUpVersion10Databases(const FilePath& destination_directory) { - sqlite3* primer_handle = NULL; v10_user_setting_db_path_ = destination_directory.Append(FilePath(kV10UserSettingsDB)); - ASSERT_EQ(SQLITE_OK, sqlite_utils::OpenSqliteDb(v10_user_setting_db_path_, - &primer_handle)); - sqlite_utils::scoped_sqlite_db_ptr db(primer_handle); + + sql::Connection db; + ASSERT_TRUE(db.Open(v10_user_setting_db_path_)); old_style_sync_data_path_ = destination_directory.Append(FilePath(kOldStyleSyncDataDB)); @@ -58,84 +58,89 @@ class UserSettingsTest : public testing::Test { sync_data_.length()))); // Create settings table. - ExecOrDie(primer_handle, "CREATE TABLE settings" - " (email, key, value, " - " PRIMARY KEY(email, key) ON CONFLICT REPLACE)"); + ASSERT_TRUE(db.Execute( + "CREATE TABLE settings (email, key, value, PRIMARY KEY(email, key)" + " ON CONFLICT REPLACE)")); + // Add a blank signin table. - ExecOrDie(primer_handle, "CREATE TABLE signin_types" - " (signin, signin_type)"); + ASSERT_TRUE(db.Execute( + "CREATE TABLE signin_types (signin, signin_type)")); + // Create and populate version table. - ExecOrDie(primer_handle, "CREATE TABLE db_version ( version )"); + ASSERT_TRUE(db.Execute("CREATE TABLE db_version (version)")); { - SQLStatement statement; - const char query[] = "INSERT INTO db_version values ( ? )"; - statement.prepare(primer_handle, query); - statement.bind_int(0, 10); - if (SQLITE_DONE != statement.step()) { - LOG(FATAL) << query << "\n" << sqlite3_errmsg(primer_handle); - } + const char* query = "INSERT INTO db_version VALUES(?)"; + sql::Statement s(db.GetUniqueStatement(query)); + if (!s) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); + + s.BindInt(0, 10); + if (!s.Run()) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); } + // Create shares table. - ExecOrDie(primer_handle, "CREATE TABLE shares" - " (email, share_name, file_name," - " PRIMARY KEY(email, share_name) ON CONFLICT REPLACE)"); + ASSERT_TRUE(db.Execute( + "CREATE TABLE shares (email, share_name, file_name," + " PRIMARY KEY(email, share_name) ON CONFLICT REPLACE)")); // Populate a share. { - SQLStatement statement; - const char query[] = "INSERT INTO shares values ( ?, ?, ? )"; - statement.prepare(primer_handle, query); - statement.bind_string(0, "foo@foo.com"); - statement.bind_string(1, "foo@foo.com"); + const char* query = "INSERT INTO shares VALUES(?, ?, ?)"; + sql::Statement s(db.GetUniqueStatement(query)); + if (!s) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); + + s.BindString(0, "foo@foo.com"); + s.BindString(1, "foo@foo.com"); #if defined(OS_WIN) - statement.bind_string(2, WideToUTF8(old_style_sync_data_path_.value())); + s.BindString(2, WideToUTF8(old_style_sync_data_path_.value())); #elif defined(OS_POSIX) - statement.bind_string(2, old_style_sync_data_path_.value()); + s.BindString(2, old_style_sync_data_path_.value()); #endif - if (SQLITE_DONE != statement.step()) { - LOG(FATAL) << query << "\n" << sqlite3_errmsg(primer_handle); - } + if (!s.Run()) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); } } // Creates and populates the V11 database file within // |destination_directory|. void SetUpVersion11Database(const FilePath& destination_directory) { - sqlite3* primer_handle = NULL; v11_user_setting_db_path_ = destination_directory.Append(FilePath(kV11UserSettingsDB)); - ASSERT_EQ(SQLITE_OK, sqlite_utils::OpenSqliteDb(v11_user_setting_db_path_, - &primer_handle)); - sqlite_utils::scoped_sqlite_db_ptr db(primer_handle); + + sql::Connection db; + ASSERT_TRUE(db.Open(v11_user_setting_db_path_)); // Create settings table. - ExecOrDie(primer_handle, "CREATE TABLE settings" - " (email, key, value, " - " PRIMARY KEY(email, key) ON CONFLICT REPLACE)"); + ASSERT_TRUE(db.Execute( + "CREATE TABLE settings (email, key, value, PRIMARY KEY(email, key)" + " ON CONFLICT REPLACE)")); // Create and populate version table. - ExecOrDie(primer_handle, "CREATE TABLE db_version ( version )"); + ASSERT_TRUE(db.Execute("CREATE TABLE db_version (version)")); { - SQLStatement statement; - const char query[] = "INSERT INTO db_version values ( ? )"; - statement.prepare(primer_handle, query); - statement.bind_int(0, 11); - if (SQLITE_DONE != statement.step()) { - LOG(FATAL) << query << "\n" << sqlite3_errmsg(primer_handle); - } + const char* query = "INSERT INTO db_version VALUES(?)"; + sql::Statement s(db.GetUniqueStatement(query)); + if (!s) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); + + s.BindInt(0, 11); + if (!s.Run()) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); } - ExecOrDie(primer_handle, "CREATE TABLE signin_types" - " (signin, signin_type)"); - + ASSERT_TRUE(db.Execute( + "CREATE TABLE signin_types (signin, signin_type)")); { - SQLStatement statement; - const char query[] = "INSERT INTO signin_types values ( ?, ? )"; - statement.prepare(primer_handle, query); - statement.bind_string(0, "test"); - statement.bind_string(1, "test"); - if (SQLITE_DONE != statement.step()) { - LOG(FATAL) << query << "\n" << sqlite3_errmsg(primer_handle); - } + const char* query = "INSERT INTO signin_types VALUES(?, ?)"; + sql::Statement s(db.GetUniqueStatement(query)); + if (!s) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); + + s.BindString(0, "test"); + s.BindString(1, "test"); + if (!s.Run()) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); } } @@ -167,23 +172,24 @@ TEST_F(UserSettingsTest, MigrateFromV10ToV11) { // Create a UserSettings, which should trigger migration code. We do this // inside a scoped block so it closes itself and we can poke around to see // what happened later. - UserSettings settings; + browser_sync::UserSettings settings; settings.Init(v10_user_setting_db_path()); } // Now poke around using sqlite to see if UserSettings migrated properly. - sqlite3* handle = NULL; - ASSERT_EQ(SQLITE_OK, sqlite_utils::OpenSqliteDb(v10_user_setting_db_path(), - &handle)); - sqlite_utils::scoped_sqlite_db_ptr db(handle); + sql::Connection db; + ASSERT_TRUE(db.Open(v10_user_setting_db_path())); // Note that we don't use ScopedStatement to avoid closing the sqlite handle // before finalizing the statement. { - SQLStatement version_query; - version_query.prepare(handle, "SELECT version FROM db_version"); - ASSERT_EQ(SQLITE_ROW, version_query.step()); - const int version = version_query.column_int(0); + const char* query = "SELECT version FROM db_version"; + sql::Statement version_query(db.GetUniqueStatement(query)); + if (!version_query) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); + + ASSERT_TRUE(version_query.Step()); + const int version = version_query.ColumnInt(0); EXPECT_GE(version, 11); } @@ -202,25 +208,29 @@ TEST_F(UserSettingsTest, MigrateFromV11ToV12) { ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); SetUpVersion11Database(temp_dir.path()); { - UserSettings settings; + browser_sync::UserSettings settings; settings.Init(v11_user_setting_db_path()); } - sqlite3* handle = NULL; - ASSERT_EQ(SQLITE_OK, sqlite_utils::OpenSqliteDb(v11_user_setting_db_path(), - &handle)); - sqlite_utils::scoped_sqlite_db_ptr db(handle); + sql::Connection db; + ASSERT_TRUE(db.Open(v11_user_setting_db_path())); { - SQLStatement version_query; - version_query.prepare(handle, "SELECT version FROM db_version"); - ASSERT_EQ(SQLITE_ROW, version_query.step()); - const int version = version_query.column_int(0); + const char* query = "SELECT version FROM db_version"; + sql::Statement version_query(db.GetUniqueStatement(query)); + if (!version_query) + LOG(FATAL) << query << "\n" << db.GetErrorMessage(); + + ASSERT_TRUE(version_query.Step()); + const int version = version_query.ColumnInt(0); EXPECT_GE(version, 12); - SQLStatement table_query; - table_query.prepare(handle, "SELECT name FROM sqlite_master " - "WHERE type='table' AND name='signin_types'"); - ASSERT_NE(SQLITE_ROW, table_query.step()); + const char* query2 = "SELECT name FROM sqlite_master " + "WHERE type='table' AND name='signin_types'"; + sql::Statement table_query(db.GetUniqueStatement(query2)); + if (!table_query) + LOG(FATAL) << query2 << "\n" << db.GetErrorMessage(); + + ASSERT_FALSE(table_query.Step()); } } @@ -230,15 +240,15 @@ TEST_F(UserSettingsTest, APEncode) { for (i = numeric_limits<char>::min(); i < numeric_limits<char>::max(); ++i) test.push_back(i); test.push_back(i); - const std::string encoded = APEncode(test); - const std::string decoded = APDecode(encoded); + const std::string encoded = browser_sync::APEncode(test); + const std::string decoded = browser_sync::APDecode(encoded); ASSERT_EQ(test, decoded); } TEST_F(UserSettingsTest, PersistEmptyToken) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - UserSettings settings; + browser_sync::UserSettings settings; settings.Init(temp_dir.path().AppendASCII("UserSettings.sqlite3")); settings.SetAuthTokenForService("username", "service", ""); std::string username; @@ -252,7 +262,7 @@ TEST_F(UserSettingsTest, PersistEmptyToken) { TEST_F(UserSettingsTest, PersistNonEmptyToken) { ScopedTempDir temp_dir; ASSERT_TRUE(temp_dir.CreateUniqueTempDir()); - UserSettings settings; + browser_sync::UserSettings settings; settings.Init(temp_dir.path().AppendASCII("UserSettings.sqlite3")); settings.SetAuthTokenForService("username", "service", "oonetuhasonteuhasonetuhasonetuhasonetuhasouhasonetuhasonetuhasonetuhah" |