diff options
author | Kristian Monsen <kristianm@google.com> | 2011-05-24 16:24:13 +0100 |
---|---|---|
committer | Kristian Monsen <kristianm@google.com> | 2011-05-25 14:13:32 +0100 |
commit | 3f50c38dc070f4bb515c1b64450dae14f316474e (patch) | |
tree | 29f309f9534e05c47244eedb438fc612578d133b /chrome/browser/sync | |
parent | e23bef148f7be2bdf9c3cb2cd3aa5ceebf1190fb (diff) | |
download | external_chromium-3f50c38dc070f4bb515c1b64450dae14f316474e.zip external_chromium-3f50c38dc070f4bb515c1b64450dae14f316474e.tar.gz external_chromium-3f50c38dc070f4bb515c1b64450dae14f316474e.tar.bz2 |
Merge Chromium at r10.0.634.0: Initial merge by git.
Change-Id: Iac2af492818d119bcc2562eb5fdabf5ab0b6df9c
Diffstat (limited to 'chrome/browser/sync')
71 files changed, 3040 insertions, 1373 deletions
diff --git a/chrome/browser/sync/abstract_profile_sync_service_test.h b/chrome/browser/sync/abstract_profile_sync_service_test.h index 57b3791..b5fc1c2 100644 --- a/chrome/browser/sync/abstract_profile_sync_service_test.h +++ b/chrome/browser/sync/abstract_profile_sync_service_test.h @@ -15,6 +15,7 @@ #include "chrome/browser/net/gaia/token_service.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/autofill_model_associator.h" +#include "chrome/browser/sync/glue/autofill_profile_model_associator.h" #include "chrome/browser/sync/glue/password_model_associator.h" #include "chrome/browser/sync/glue/preference_model_associator.h" #include "chrome/browser/sync/glue/session_model_associator.h" @@ -65,6 +66,9 @@ class ProfileSyncServiceTestHelper { case syncable::AUTOFILL: tag_name = browser_sync::kAutofillTag; break; + case syncable::AUTOFILL_PROFILE: + tag_name = browser_sync::kAutofillProfileTag; + break; case syncable::PREFERENCES: tag_name = browser_sync::kPreferencesTag; break; diff --git a/chrome/browser/sync/engine/read_node_mock.h b/chrome/browser/sync/engine/read_node_mock.h index f13eddd..564f01d 100644 --- a/chrome/browser/sync/engine/read_node_mock.h +++ b/chrome/browser/sync/engine/read_node_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.
@@ -13,8 +13,9 @@ class ReadNodeMock : public sync_api::ReadNode {
public:
- ReadNodeMock() { }
+ ReadNodeMock() {}
virtual ~ReadNodeMock() {}
+
MOCK_METHOD2(InitByClientTagLookup,
bool(syncable::ModelType model_type, const std::string& tag));
MOCK_CONST_METHOD0(GetAutofillProfileSpecifics,
@@ -25,5 +26,6 @@ class ReadNodeMock : public sync_api::ReadNode { MOCK_CONST_METHOD0(GetSuccessorId, int64());
MOCK_METHOD1(InitByIdLookup, bool(int64 id));
};
+
#endif // CHROME_BROWSER_SYNC_ENGINE_READ_NODE_MOCK_H_
diff --git a/chrome/browser/sync/engine/syncapi.cc b/chrome/browser/sync/engine/syncapi.cc index 112878d..206125b 100644 --- a/chrome/browser/sync/engine/syncapi.cc +++ b/chrome/browser/sync/engine/syncapi.cc @@ -14,7 +14,6 @@ #include "base/lock.h" #include "base/logging.h" #include "base/message_loop.h" -#include "base/platform_thread.h" #include "base/scoped_ptr.h" #include "base/sha1.h" #include "base/string_util.h" @@ -217,6 +216,10 @@ int64 BaseNode::GetId() const { return GetEntry()->Get(syncable::META_HANDLE); } +int64 BaseNode::GetModificationTime() const { + return GetEntry()->Get(syncable::MTIME); +} + bool BaseNode::GetIsFolder() const { return GetEntry()->Get(syncable::IS_DIR); } diff --git a/chrome/browser/sync/engine/syncapi.h b/chrome/browser/sync/engine/syncapi.h index e2503cb..d86ed71 100644 --- a/chrome/browser/sync/engine/syncapi.h +++ b/chrome/browser/sync/engine/syncapi.h @@ -153,6 +153,9 @@ class BaseNode { // different ID value. virtual int64 GetId() const; + // Returns the modification time of the object (in TimeTicks internal format). + int64 GetModificationTime() const; + // Nodes are hierarchically arranged into a single-rooted tree. // InitByRootLookup on ReadNode allows access to the root. GetParentId is // how you find a node's parent. diff --git a/chrome/browser/sync/engine/syncer_thread.cc b/chrome/browser/sync/engine/syncer_thread.cc index 501577d..3c0cd06 100644 --- a/chrome/browser/sync/engine/syncer_thread.cc +++ b/chrome/browser/sync/engine/syncer_thread.cc @@ -59,7 +59,7 @@ void SyncerThread::NudgeSyncerWithDataTypes( int milliseconds_from_now, NudgeSource source, const syncable::ModelTypeBitSet& model_types) { - AutoLock lock(lock_); + base::AutoLock lock(lock_); if (vault_.syncer_ == NULL) { return; } @@ -70,7 +70,7 @@ void SyncerThread::NudgeSyncerWithDataTypes( void SyncerThread::NudgeSyncer( int milliseconds_from_now, NudgeSource source) { - AutoLock lock(lock_); + base::AutoLock lock(lock_); if (vault_.syncer_ == NULL) { return; } @@ -107,7 +107,7 @@ SyncerThread::~SyncerThread() { // and false otherwise. bool SyncerThread::Start() { { - AutoLock lock(lock_); + base::AutoLock lock(lock_); if (thread_.IsRunning()) { return true; } @@ -141,7 +141,7 @@ bool SyncerThread::Stop(int max_wait) { void SyncerThread::RequestSyncerExitAndSetThreadStopConditions() { { - AutoLock lock(lock_); + 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 @@ -170,7 +170,7 @@ void SyncerThread::RequestSyncerExitAndSetThreadStopConditions() { } bool SyncerThread::RequestPause() { - AutoLock lock(lock_); + base::AutoLock lock(lock_); if (vault_.pause_requested_ || vault_.paused_) return false; @@ -195,7 +195,7 @@ void SyncerThread::Notify(SyncEngineEvent::EventCause cause) { } bool SyncerThread::RequestResume() { - AutoLock lock(lock_); + 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_)) @@ -530,7 +530,7 @@ SyncerThread::WaitInterval SyncerThread::CalculatePollingWaitTime( } void SyncerThread::ThreadMain() { - AutoLock lock(lock_); + 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(); @@ -637,7 +637,7 @@ SyncSourceInfo SyncerThread::MakeSyncSourceInfo(bool nudged, } void SyncerThread::CreateSyncer(const std::string& dirname) { - AutoLock lock(lock_); + base::AutoLock lock(lock_); VLOG(1) << "Creating syncer up for: " << dirname; // The underlying database structure is ready, and we should create // the syncer. @@ -655,7 +655,7 @@ void SyncerThread::CreateSyncer(const std::string& dirname) { // server. static inline void CheckConnected(bool* connected, HttpResponse::ServerConnectionCode code, - ConditionVariable* condvar) { + 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 @@ -686,7 +686,7 @@ void SyncerThread::WatchConnectionManager(ServerConnectionManager* conn_mgr) { void SyncerThread::HandleServerConnectionEvent( const ServerConnectionEvent& event) { if (ServerConnectionEvent::STATUS_CHANGED == event.what_happened) { - AutoLock lock(lock_); + base::AutoLock lock(lock_); CheckConnected(&vault_.connected_, event.connection_code, &vault_field_changed_); } @@ -771,7 +771,7 @@ void SyncerThread::NudgeSyncImpl(int milliseconds_from_now, } void SyncerThread::SetNotificationsEnabled(bool notifications_enabled) { - AutoLock lock(lock_); + base::AutoLock lock(lock_); session_context_->set_notifications_enabled(notifications_enabled); } diff --git a/chrome/browser/sync/engine/syncer_thread.h b/chrome/browser/sync/engine/syncer_thread.h index 6681225..22531d8 100644 --- a/chrome/browser/sync/engine/syncer_thread.h +++ b/chrome/browser/sync/engine/syncer_thread.h @@ -14,21 +14,22 @@ #include <vector> #include "base/basictypes.h" -#include "base/condition_variable.h" #include "base/gtest_prod_util.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" -#include "base/thread.h" +#include "base/synchronization/condition_variable.h" +#include "base/threading/thread.h" #include "base/time.h" -#include "base/waitable_event.h" -#if defined(OS_LINUX) -#include "chrome/browser/sync/engine/idle_query_linux.h" -#endif +#include "base/synchronization/waitable_event.h" #include "chrome/browser/sync/engine/syncer_types.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; namespace browser_sync { @@ -213,10 +214,10 @@ class SyncerThread : public base::RefCountedThreadSafe<SyncerThread>, // Gets signaled whenever a thread outside of the syncer thread changes a // protected field in the vault_. - ConditionVariable vault_field_changed_; + base::ConditionVariable vault_field_changed_; // Used to lock everything in |vault_|. - Lock lock_; + base::Lock lock_; private: // Threshold multipler for how long before user should be considered idle. diff --git a/chrome/browser/sync/engine/syncer_thread_unittest.cc b/chrome/browser/sync/engine/syncer_thread_unittest.cc index 887bcc1..df0733b 100644 --- a/chrome/browser/sync/engine/syncer_thread_unittest.cc +++ b/chrome/browser/sync/engine/syncer_thread_unittest.cc @@ -8,7 +8,7 @@ #include "base/lock.h" #include "base/scoped_ptr.h" #include "base/time.h" -#include "base/waitable_event.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" diff --git a/chrome/browser/sync/glue/autofill_data_type_controller.h b/chrome/browser/sync/glue/autofill_data_type_controller.h index d73edf2..2daedb4 100644 --- a/chrome/browser/sync/glue/autofill_data_type_controller.h +++ b/chrome/browser/sync/glue/autofill_data_type_controller.h @@ -10,7 +10,7 @@ #include "base/basictypes.h" #include "base/scoped_ptr.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/profile_sync_service.h" 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 6ff8af6..127b591 100644 --- a/chrome/browser/sync/glue/autofill_data_type_controller_unittest.cc +++ b/chrome/browser/sync/glue/autofill_data_type_controller_unittest.cc @@ -8,7 +8,7 @@ #include "base/message_loop.h" #include "base/ref_counted.h" #include "base/scoped_ptr.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/autofill/personal_data_manager.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/sync/glue/autofill_data_type_controller.h" diff --git a/chrome/browser/sync/glue/autofill_model_associator.cc b/chrome/browser/sync/glue/autofill_model_associator.cc index f1340df..289d5fb 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.cc +++ b/chrome/browser/sync/glue/autofill_model_associator.cc @@ -237,8 +237,7 @@ bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( bool autofill_profile_not_migrated = HasNotMigratedYet(write_trans); - if (MigrationLoggingEnabled() && - autofill_profile_not_migrated) { + if (VLOG_IS_ON(1) && autofill_profile_not_migrated) { VLOG(1) << "[AUTOFILL MIGRATION]" << "Printing profiles from web db"; @@ -251,7 +250,7 @@ bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( } } - if (MigrationLoggingEnabled() && autofill_profile_not_migrated) { + if (autofill_profile_not_migrated) { VLOG(1) << "[AUTOFILL MIGRATION]" << "Iterating over sync db"; } @@ -271,11 +270,9 @@ bool AutofillModelAssociator::TraverseAndAssociateAllSyncNodes( } else if (autofill.has_profile()) { // Ignore autofill profiles if we are not upgrading. if (autofill_profile_not_migrated) { - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION] Looking for " - << autofill.profile().name_first() - << autofill.profile().name_last(); - } + VLOG(1) << "[AUTOFILL MIGRATION] Looking for " + << autofill.profile().name_first() + << autofill.profile().name_last(); AddNativeProfileIfNeeded( autofill.profile(), bundle, @@ -355,19 +352,15 @@ void AutofillModelAssociator::AddNativeProfileIfNeeded( profile, all_profiles_from_db); if (profile_in_web_db != NULL) { - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "Node found in web db. So associating"; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "Node found in web db. So associating"; int64 sync_id = node.GetId(); std::string guid = profile_in_web_db->guid(); Associate(&guid, sync_id); return; } else { // Create a new node. - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "Node not found in web db so creating and associating"; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "Node not found in web db so creating and associating"; std::string guid = guid::GenerateGUID(); Associate(&guid, node.GetId()); AutoFillProfile* p = new AutoFillProfile(guid); @@ -555,11 +548,9 @@ bool AutofillModelAssociator::HasNotMigratedYet( } if (autofill_migration_state == syncable::INSUFFICIENT_INFO_TO_DETERMINE) { - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "current autofill migration state is insufficient info to" - << "determine."; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "current autofill migration state is insufficient info to" + << "determine."; sync_api::ReadNode autofill_profile_root_node(trans); if (!autofill_profile_root_node.InitByTagLookup( browser_sync::kAutofillProfileTag) || @@ -568,29 +559,21 @@ bool AutofillModelAssociator::HasNotMigratedYet( sync_service()->backend()->SetAutofillMigrationState( syncable::NOT_MIGRATED); - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "Current autofill migration state is NOT Migrated because" - << "legacy autofill root node is present whereas new " - << "Autofill profile root node is absent."; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "Current autofill migration state is NOT Migrated because" + << "legacy autofill root node is present whereas new " + << "Autofill profile root node is absent."; return true; } sync_service()->backend()->SetAutofillMigrationState(syncable::MIGRATED); - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "Current autofill migration state is migrated."; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "Current autofill migration state is migrated."; } return false; } -bool AutofillModelAssociator::MigrationLoggingEnabled() { - // [TODO] enable logging via a command line flag. - return false; -} } // namespace browser_sync diff --git a/chrome/browser/sync/glue/autofill_model_associator.h b/chrome/browser/sync/glue/autofill_model_associator.h index 3f1104e..553580f 100644 --- a/chrome/browser/sync/glue/autofill_model_associator.h +++ b/chrome/browser/sync/glue/autofill_model_associator.h @@ -173,8 +173,6 @@ class AutofillModelAssociator // user requested an abort. bool IsAbortPending(); - bool MigrationLoggingEnabled(); - ProfileSyncService* sync_service_; WebDatabase* web_database_; PersonalDataManager* personal_data_; diff --git a/chrome/browser/sync/glue/autofill_profile_model_associator.cc b/chrome/browser/sync/glue/autofill_profile_model_associator.cc index b2d2e2c..c517e2c 100644 --- a/chrome/browser/sync/glue/autofill_profile_model_associator.cc +++ b/chrome/browser/sync/glue/autofill_profile_model_associator.cc @@ -46,7 +46,7 @@ bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( std::vector<AutoFillProfile*>* new_profiles, std::vector<std::string>* profiles_to_delete) { - if (MigrationLoggingEnabled()) { + if (VLOG_IS_ON(1)) { VLOG(1) << "[AUTOFILL MIGRATION]" << "Printing profiles from web db"; @@ -60,10 +60,9 @@ bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( } } - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "Looking for the above data in sync db.."; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "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(); @@ -72,15 +71,18 @@ bool AutofillProfileModelAssociator::TraverseAndAssociateChromeAutoFillProfiles( std::string guid((*ix)->guid()); ReadNode node(write_trans); - if (node.InitByClientTagLookup(syncable::AUTOFILL_PROFILE, guid)) { - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << " Found in sync db: " - << (*ix)->GetFieldText(AutoFillType(NAME_FIRST)) - << (*ix)->GetFieldText(AutoFillType(NAME_LAST)) - << (*ix)->guid() - << " so associating"; - } + if (node.InitByClientTagLookup(syncable::AUTOFILL_PROFILE, guid) && + // The following check is to ensure the given sync node is not already + // associated with another profile. That could happen if the user has + // the same profile duplicated. + current_profiles->find(guid) == current_profiles->end()) { + + VLOG(1) << "[AUTOFILL MIGRATION]" + << " Found in sync db: " + << (*ix)->GetFieldText(AutoFillType(NAME_FIRST)) + << (*ix)->GetFieldText(AutoFillType(NAME_LAST)) + << (*ix)->guid() + << " so associating with node id " << node.GetId(); const sync_pb::AutofillProfileSpecifics& autofill( node.GetAutofillProfileSpecifics()); if (OverwriteProfileWithServerData(*ix, autofill)) { @@ -138,11 +140,9 @@ bool AutofillProfileModelAssociator::AssociateModels() { return false; } - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << " Now associating to the new autofill profile model associator" - << " root node"; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << " Now associating to the new autofill profile model associator" + << " root node"; DataBundle bundle; { // The write transaction lock is held inside this block. @@ -251,7 +251,8 @@ 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); @@ -263,9 +264,14 @@ int64 AutofillProfileModelAssociator::FindSyncNodeWithProfile( } const sync_pb::AutofillProfileSpecifics& autofill_specifics( read_node.GetAutofillProfileSpecifics()); - OverwriteProfileWithServerData(&p, autofill_specifics); - if (p.Compare(profile_from_db) == 0) { - return sync_child_id; + + // This find should be fast as the set uses tree. + if (current_profiles->find(autofill_specifics.guid()) + == current_profiles->end()) { + OverwriteProfileWithServerData(&p, autofill_specifics); + if (p.Compare(profile_from_db) == 0) { + return sync_child_id; + } } sync_child_id = read_node.GetSuccessorId(); } @@ -280,7 +286,10 @@ bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( std::set<std::string>* current_profiles, std::vector<std::string>* profiles_to_delete) { - int64 sync_node_id = FindSyncNodeWithProfile(trans, autofill_root, profile); + int64 sync_node_id = FindSyncNodeWithProfile(trans, + autofill_root, + profile, + current_profiles); if (sync_node_id != sync_api::kInvalidId) { // In case of duplicates throw away the local profile and apply the // server profile.(The only difference between the 2 profiles are the guids) @@ -298,14 +307,13 @@ bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( std::string guid = autofill_specifics.guid(); Associate(&guid, sync_node_id); current_profiles->insert(autofill_specifics.guid()); - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "Found in sync db but with a different guid: " - << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_FIRST))) - << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_LAST))) - << "New guid " << autofill_specifics.guid() - << " so associating"; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "Found in sync db but with a different guid: " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_FIRST))) + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_LAST))) + << "New guid " << autofill_specifics.guid() << " sync node id " + << sync_node_id << " so associating. Profile to be deleted " + << profile.guid(); } else { sync_api::WriteNode node(trans); if (!node.InitUniqueByCreation( @@ -314,16 +322,17 @@ bool AutofillProfileModelAssociator::MakeNewAutofillProfileSyncNodeIfNeeded( return false; } node.SetTitle(UTF8ToWide(profile.guid())); - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION]" - << "NOT Found in sync db " - << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_FIRST))) - << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_LAST))) - << profile.guid() - << " so creating a new sync node."; - } + VLOG(1) << "[AUTOFILL MIGRATION]" + << "NOT Found in sync db " + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_FIRST))) + << UTF16ToUTF8(profile.GetFieldText(AutoFillType(NAME_LAST))) + << profile.guid() + << " so creating a new sync node. Sync node id " + << node.GetId(); AutofillProfileChangeProcessor::WriteAutofillProfile(profile, &node); current_profiles->insert(profile.guid()); + std::string guid = profile.guid(); + Associate(&guid, node.GetId()); number_of_profiles_created_++; } @@ -335,11 +344,9 @@ bool AutofillProfileModelAssociator::TraverseAndAssociateAllSyncNodes( const sync_api::ReadNode& autofill_root, DataBundle* bundle) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); + VLOG(1) << "[AUTOFILL MIGRATION] " + << " Iterating over sync nodes of autofill profile root node"; - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION] " - << " Iterating over sync nodes of autofill profile root node"; - } int64 sync_child_id = autofill_root.GetFirstChildId(); while (sync_child_id != sync_api::kInvalidId) { ReadNode sync_child(write_trans); @@ -363,14 +370,15 @@ void AutofillProfileModelAssociator::AddNativeProfileIfNeeded( const sync_api::ReadNode& node) { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::DB)); - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION] " - << "Trying to lookup " - << profile.name_first() - << " " - << profile.name_last() - << " in the web db"; - } + VLOG(1) << "[AUTOFILL MIGRATION] " + << "Trying to lookup " + << profile.name_first() + << " " + << profile.name_last() + << "sync node id " << node.GetId() + << " Guid " << profile.guid() + << " in the web db"; + if (bundle->current_profiles.find(profile.guid()) == bundle->current_profiles.end()) { std::string guid(profile.guid()); @@ -378,15 +386,11 @@ void AutofillProfileModelAssociator::AddNativeProfileIfNeeded( AutoFillProfile* p = new AutoFillProfile(profile.guid()); OverwriteProfileWithServerData(p, profile); bundle->new_profiles.push_back(p); - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION] " - << " Did not find one so creating it on web db"; - } + VLOG(1) << "[AUTOFILL MIGRATION] " + << " Did not find one so creating it on web db"; } else { - if (MigrationLoggingEnabled()) { - VLOG(1) << "[AUTOFILL MIGRATION] " - << " Found it on web db. Moving on "; - } + VLOG(1) << "[AUTOFILL MIGRATION] " + << " Found it on web db. Moving on "; } } @@ -469,10 +473,5 @@ bool AutofillProfileModelAssociator::IsAbortPending() { return abort_association_pending_; } -bool AutofillProfileModelAssociator::MigrationLoggingEnabled() { - // TODO(lipalani) enable logging via a command line flag. - return false; -} - } // 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 f636b63..e2abdaf 100644 --- a/chrome/browser/sync/glue/autofill_profile_model_associator.h +++ b/chrome/browser/sync/glue/autofill_profile_model_associator.h @@ -161,9 +161,8 @@ class AutofillProfileModelAssociator int64 FindSyncNodeWithProfile(sync_api::WriteTransaction* trans, const sync_api::BaseNode& autofill_root, - const AutoFillProfile& profile); - - bool MigrationLoggingEnabled(); + const AutoFillProfile& profile, + std::set<std::string>* current_profiles); ProfileSyncService* sync_service_; WebDatabase* web_database_; diff --git a/chrome/browser/sync/glue/change_processor_mock.cc b/chrome/browser/sync/glue/change_processor_mock.cc new file mode 100644 index 0000000..faea86b --- /dev/null +++ b/chrome/browser/sync/glue/change_processor_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/change_processor_mock.h" + +namespace browser_sync { + +ChangeProcessorMock::ChangeProcessorMock() : ChangeProcessor(NULL) {} + +ChangeProcessorMock::~ChangeProcessorMock() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/change_processor_mock.h b/chrome/browser/sync/glue/change_processor_mock.h index e138937..d74e40e 100644 --- a/chrome/browser/sync/glue/change_processor_mock.h +++ b/chrome/browser/sync/glue/change_processor_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. @@ -17,7 +17,8 @@ namespace browser_sync { class ChangeProcessorMock : public ChangeProcessor { public: - ChangeProcessorMock() : ChangeProcessor(NULL) {} + ChangeProcessorMock(); + virtual ~ChangeProcessorMock(); MOCK_METHOD3(ApplyChangesFromSyncModel, void(const sync_api::BaseTransaction* trans, const sync_api::SyncManager::ChangeRecord* changes, diff --git a/chrome/browser/sync/glue/data_type_controller_mock.cc b/chrome/browser/sync/glue/data_type_controller_mock.cc new file mode 100644 index 0000000..7909b86 --- /dev/null +++ b/chrome/browser/sync/glue/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/data_type_controller_mock.h" + +namespace browser_sync { + +DataTypeControllerMock::DataTypeControllerMock() {} + +DataTypeControllerMock::~DataTypeControllerMock() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/data_type_controller_mock.h b/chrome/browser/sync/glue/data_type_controller_mock.h index 09ec652..83d0b6b 100644 --- a/chrome/browser/sync/glue/data_type_controller_mock.h +++ b/chrome/browser/sync/glue/data_type_controller_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. @@ -13,6 +13,9 @@ namespace browser_sync { class DataTypeControllerMock : public DataTypeController { public: + DataTypeControllerMock(); + virtual ~DataTypeControllerMock(); + MOCK_METHOD1(Start, void(StartCallback* start_callback)); MOCK_METHOD0(Stop, void()); MOCK_METHOD0(enabled, bool()); diff --git a/chrome/browser/sync/glue/data_type_manager_mock.cc b/chrome/browser/sync/glue/data_type_manager_mock.cc new file mode 100644 index 0000000..9cc445e --- /dev/null +++ b/chrome/browser/sync/glue/data_type_manager_mock.cc @@ -0,0 +1,24 @@ +// 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_mock.h" + +namespace browser_sync { + +DataTypeManagerMock::DataTypeManagerMock() + : result_(OK) { + // By default, calling Configure will send a SYNC_CONFIGURE_START + // and SYNC_CONFIGURE_DONE notification with a DataTypeManager::OK + // detail. + ON_CALL(*this, Configure(testing::_)). + WillByDefault(testing::DoAll( + NotifyFromDataTypeManager(this, + NotificationType::SYNC_CONFIGURE_START), + NotifyFromDataTypeManagerWithResult + (this, NotificationType::SYNC_CONFIGURE_DONE, &result_))); +} + +DataTypeManagerMock::~DataTypeManagerMock() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/data_type_manager_mock.h b/chrome/browser/sync/glue/data_type_manager_mock.h index 2a1705a..05a3665 100644 --- a/chrome/browser/sync/glue/data_type_manager_mock.h +++ b/chrome/browser/sync/glue/data_type_manager_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. @@ -30,17 +30,8 @@ namespace browser_sync { class DataTypeManagerMock : public DataTypeManager { public: - DataTypeManagerMock() : result_(OK) { - // By default, calling Configure will send a SYNC_CONFIGURE_START - // and SYNC_CONFIGURE_DONE notification with a DataTypeManager::OK - // detail. - ON_CALL(*this, Configure(testing::_)). - WillByDefault(testing::DoAll( - NotifyFromDataTypeManager(this, - NotificationType::SYNC_CONFIGURE_START), - NotifyFromDataTypeManagerWithResult - (this, NotificationType::SYNC_CONFIGURE_DONE, &result_))); - } + DataTypeManagerMock(); + virtual ~DataTypeManagerMock(); MOCK_METHOD1(Configure, void(const TypeSet&)); MOCK_METHOD0(Stop, void()); diff --git a/chrome/browser/sync/glue/database_model_worker.cc b/chrome/browser/sync/glue/database_model_worker.cc index b09c6c6..91bba5b 100644 --- a/chrome/browser/sync/glue/database_model_worker.cc +++ b/chrome/browser/sync/glue/database_model_worker.cc @@ -4,7 +4,7 @@ #include "chrome/browser/sync/glue/database_model_worker.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/browser_thread.h" using base::WaitableEvent; diff --git a/chrome/browser/sync/glue/database_model_worker_unittest.cc b/chrome/browser/sync/glue/database_model_worker_unittest.cc index 8d399b1..7f60cea 100644 --- a/chrome/browser/sync/glue/database_model_worker_unittest.cc +++ b/chrome/browser/sync/glue/database_model_worker_unittest.cc @@ -5,7 +5,7 @@ #include "base/callback.h" #include "base/message_loop.h" #include "base/scoped_ptr.h" -#include "base/thread.h" +#include "base/threading/thread.h" #include "base/timer.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/sync/glue/database_model_worker.h" diff --git a/chrome/browser/sync/glue/foreign_session_tracker.cc b/chrome/browser/sync/glue/foreign_session_tracker.cc new file mode 100644 index 0000000..6db5c9c --- /dev/null +++ b/chrome/browser/sync/glue/foreign_session_tracker.cc @@ -0,0 +1,143 @@ +// 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/glue/foreign_session_tracker.h" +#include "chrome/browser/sync/glue/session_model_associator.h" + +namespace browser_sync { + + +ForeignSessionTracker::ForeignSessionTracker() { +} + +ForeignSessionTracker::~ForeignSessionTracker() { + clear(); +} + +bool ForeignSessionTracker::LookupAllForeignSessions( + std::vector<const ForeignSession*>* sessions) { + DCHECK(sessions); + // Fill vector of sessions from our foreign session map. + for (ForeignSessionMap::const_iterator i = + foreign_session_map_.begin(); i != foreign_session_map_.end(); ++i) { + // Only include sessions with open tabs. + ForeignSession* foreign_session = i->second; + if (foreign_session->windows.size() > 0 && + !SessionModelAssociator::SessionWindowHasNoTabsToSync( + *foreign_session->windows[0])) { + sessions->push_back(foreign_session); + } + } + + if (sessions->size() > 0) + return true; + else + return false; +} + +bool ForeignSessionTracker::LookupSessionWindows( + const std::string& foreign_session_tag, + std::vector<SessionWindow*>* windows) { + DCHECK(windows); + ForeignSessionMap::iterator iter = foreign_session_map_.find( + foreign_session_tag); + if (iter == foreign_session_map_.end()) + return false; + *windows = iter->second->windows; + return true; +} + +bool ForeignSessionTracker::LookupSessionTab( + const std::string& tag, + SessionID::id_type tab_id, + const SessionTab** tab) { + DCHECK(tab); + if (foreign_tab_map_.find(tag) == foreign_tab_map_.end()) { + // We have no record of this session. + *tab = NULL; + return false; + } + if (foreign_tab_map_[tag]->find(tab_id) == foreign_tab_map_[tag]->end()) { + // We have no record of this tab. + *tab = NULL; + return false; + } + *tab = (*foreign_tab_map_[tag])[tab_id]; + return true; +} + +ForeignSession* ForeignSessionTracker::GetForeignSession( + const std::string& foreign_session_tag) { + scoped_ptr<ForeignSession> foreign_session; + if (foreign_session_map_.find(foreign_session_tag) != + foreign_session_map_.end()) { + foreign_session.reset(foreign_session_map_[foreign_session_tag]); + } else { + foreign_session.reset(new ForeignSession); + foreign_session->foreign_session_tag = foreign_session_tag; + foreign_session_map_[foreign_session_tag] = foreign_session.get(); + } + DCHECK(foreign_session.get()); + return foreign_session.release(); +} + +bool ForeignSessionTracker::DeleteForeignSession( + const std::string& foreign_session_tag) { + ForeignSessionMap::iterator iter = + foreign_session_map_.find(foreign_session_tag); + if (iter != foreign_session_map_.end()) { + delete iter->second; // Delete the ForeignSession object. + foreign_session_map_.erase(iter); + return true; + } else { + return false; + } +} + +SessionTab* ForeignSessionTracker::GetSessionTab( + const std::string& foreign_session_tag, + SessionID::id_type tab_id, + bool has_window) { + if (foreign_tab_map_.find(foreign_session_tag) == foreign_tab_map_.end()) + foreign_tab_map_[foreign_session_tag] = new IDToSessionTabMap; + scoped_ptr<SessionTab> tab; + IDToSessionTabMap::iterator iter = + foreign_tab_map_[foreign_session_tag]->find(tab_id); + if (iter != foreign_tab_map_[foreign_session_tag]->end()) { + tab.reset(iter->second); + if (has_window) // This tab is linked to a window, so it's not an orphan. + unmapped_tabs_.erase(tab.get()); + VLOG(1) << "Associating " << foreign_session_tag << "'s seen tab " << + tab_id << " at " << tab.get(); + } else { + tab.reset(new SessionTab()); + (*foreign_tab_map_[foreign_session_tag])[tab_id] = tab.get(); + if (!has_window) // This tab is not linked to a window, it's an orphan. + unmapped_tabs_.insert(tab.get()); + VLOG(1) << "Associating " << foreign_session_tag << "'s new tab " << + tab_id << " at " << tab.get(); + } + DCHECK(tab.get()); + return tab.release(); +} + +void ForeignSessionTracker::clear() { + // Delete ForeignSession objects (which also deletes all their windows/tabs). + STLDeleteContainerPairSecondPointers(foreign_session_map_.begin(), + foreign_session_map_.end()); + foreign_session_map_.clear(); + + // Delete IDToSessTab maps. Does not delete the SessionTab objects, because + // they should already be referenced through foreign_session_map_. + STLDeleteContainerPairSecondPointers(foreign_tab_map_.begin(), + foreign_tab_map_.end()); + foreign_tab_map_.clear(); + + // Go through and delete any tabs we had allocated but had not yet placed into + // a ForeignSessionobject. + STLDeleteContainerPointers(unmapped_tabs_.begin(), unmapped_tabs_.end()); + unmapped_tabs_.clear(); +} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/foreign_session_tracker.h b/chrome/browser/sync/glue/foreign_session_tracker.h new file mode 100644 index 0000000..dd99455 --- /dev/null +++ b/chrome/browser/sync/glue/foreign_session_tracker.h @@ -0,0 +1,111 @@ +// 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_GLUE_FOREIGN_SESSION_TRACKER_H_ +#define CHROME_BROWSER_SYNC_GLUE_FOREIGN_SESSION_TRACKER_H_ +#pragma once + +#include <map> +#include <set> +#include <string> +#include <vector> + +#include "base/basictypes.h" +#include "base/scoped_vector.h" +#include "chrome/browser/sessions/session_id.h" +#include "chrome/browser/sessions/session_types.h" + +namespace browser_sync { + +// Class to manage foreign sessions. The tracker will own all ForeignSession +// and SessionTab objects it creates, and deletes them appropriately on +// destruction. +class ForeignSessionTracker { + public: + ForeignSessionTracker(); + ~ForeignSessionTracker(); + + // Fill a preallocated vector with all foreign sessions we're tracking. + // Returns true if we had foreign sessions to fill it with, false otherwise. + bool LookupAllForeignSessions(std::vector<const ForeignSession*>* sessions); + + // Attempts to look up the session windows associatd with the foreign session + // given by |foreign_session_tag|. + // If lookup succeeds: + // - Fills windows with the SessionWindow pointers, returns true. + // Else + // - Returns false. + bool LookupSessionWindows(const std::string& foreign_session_tag, + std::vector<SessionWindow*>* windows); + + // Attempts to look up the foreign tab associated with the given tag and tab + // id. + // If lookup succeeds: + // - Sets tab to point to the SessionTab, and returns true. + // Else + // - Returns false, tab is set to NULL. + bool LookupSessionTab(const std::string& foreign_session_tag, + SessionID::id_type tab_id, + const SessionTab** tab); + + // Returns a pointer to the ForeignSession object associated with + // foreign_session_tag. If none exists, creates one and returns its pointer. + ForeignSession* GetForeignSession(const std::string& foreign_session_tag); + + // Deletes the foreign session associated with |foreign_session_tag| if it + // exists. + // Returns true if the session existed and was deleted, false otherwise. + bool DeleteForeignSession(const std::string& foreign_session_tag); + + // Returns a pointer to the SessionTab object associated with |tab_id| for + // the session specified with |foreign_session_tag|. If none exists, creates + // one and returns its pointer. + // |has_window| determines if newly created tabs are added to the pool of + // orphaned tabs (thos which can't be reached by traversing foreign sessions). + SessionTab* GetSessionTab(const std::string& foreign_session_tag, + SessionID::id_type tab_id, + bool has_window); + + // Free the memory for all dynamically allocated objects and clear the + // tracking structures. + void clear(); + + inline bool empty() { + return foreign_tab_map_.empty() && foreign_session_map_.empty(); + } + + inline size_t num_foreign_sessions() { + return foreign_session_map_.size(); + } + + inline size_t num_foreign_tabs(const std::string& foreign_session_tag) { + if (foreign_tab_map_.find(foreign_session_tag) != foreign_tab_map_.end()) { + return foreign_tab_map_[foreign_session_tag]->size(); + } else { + return 0; + } + } + private: + // Datatypes for accessing foreign tab data. + typedef std::map<SessionID::id_type, SessionTab*> IDToSessionTabMap; + typedef std::map<std::string, IDToSessionTabMap*> ForeignTabMap; + typedef std::map<std::string, ForeignSession*> ForeignSessionMap; + + // Per foreign client mapping of their tab id's to their SessionTab objects. + ForeignTabMap foreign_tab_map_; + + // Map of foreign sessions, accessed by the foreign client id. + ForeignSessionMap foreign_session_map_; + + // The set of foreign tabs that we have seen, and created SessionTab objects + // for, but have not yet mapped to ForeignSessions. These are temporarily + // orphaned tabs, and won't be deleted if we delete foreign_session_map_. + std::set<SessionTab*> unmapped_tabs_; + + DISALLOW_COPY_AND_ASSIGN(ForeignSessionTracker); +}; + +} // namespace browser_sync + +#endif // CHROME_BROWSER_SYNC_GLUE_FOREIGN_SESSION_TRACKER_H_ diff --git a/chrome/browser/sync/glue/history_model_worker.cc b/chrome/browser/sync/glue/history_model_worker.cc index 9b0ea50..e880476 100644 --- a/chrome/browser/sync/glue/history_model_worker.cc +++ b/chrome/browser/sync/glue/history_model_worker.cc @@ -7,7 +7,7 @@ #include "base/message_loop.h" #include "base/ref_counted.h" #include "base/task.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/history/history.h" using base::WaitableEvent; diff --git a/chrome/browser/sync/glue/http_bridge.cc b/chrome/browser/sync/glue/http_bridge.cc index 8dc2b6f..2ec5a1b 100644 --- a/chrome/browser/sync/glue/http_bridge.cc +++ b/chrome/browser/sync/glue/http_bridge.cc @@ -193,6 +193,7 @@ void HttpBridge::MakeAsynchronousPost() { 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(); } diff --git a/chrome/browser/sync/glue/http_bridge.h b/chrome/browser/sync/glue/http_bridge.h index 530c215..5fb2419 100644 --- a/chrome/browser/sync/glue/http_bridge.h +++ b/chrome/browser/sync/glue/http_bridge.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. @@ -9,7 +9,7 @@ #include <string> #include "base/ref_counted.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/common/net/url_fetcher.h" #include "chrome/common/net/url_request_context_getter.h" @@ -41,13 +41,13 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, // 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 URLRequestContext { + class RequestContext : public net::URLRequestContext { public: // |baseline_context| is used to obtain the accept-language, // accept-charsets, and proxy service information for bridged requests. // Typically |baseline_context| should be the URLRequestContext of the // currently active profile. - explicit RequestContext(URLRequestContext* baseline_context); + explicit RequestContext(net::URLRequestContext* baseline_context); // Set the user agent for requests using this context. The default is // the browser's UA string. @@ -57,11 +57,7 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, // If the user agent is set explicitly return that, otherwise call the // base class method to return default value. return user_agent_.empty() ? - URLRequestContext::GetUserAgent(url) : user_agent_; - } - - virtual bool AllowSendingCookies(const net::URLRequest* request) const { - return false; + net::URLRequestContext::GetUserAgent(url) : user_agent_; } private: @@ -69,7 +65,7 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, ~RequestContext(); std::string user_agent_; - URLRequestContext* baseline_context_; + net::URLRequestContext* baseline_context_; DISALLOW_COPY_AND_ASSIGN(RequestContext); }; @@ -84,7 +80,7 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, bool is_user_agent_set() const { return !user_agent_.empty(); } // URLRequestContextGetter implementation. - virtual URLRequestContext* GetURLRequestContext(); + virtual net::URLRequestContext* GetURLRequestContext(); virtual scoped_refptr<base::MessageLoopProxy> GetIOMessageLoopProxy() const; private: @@ -121,8 +117,9 @@ class HttpBridge : public base::RefCountedThreadSafe<HttpBridge>, const std::string& name) const; // URLFetcher::Delegate implementation. - virtual void OnURLFetchComplete(const URLFetcher* source, const GURL& url, - const URLRequestStatus& status, + virtual void OnURLFetchComplete(const URLFetcher* source, + const GURL& url, + const net::URLRequestStatus& status, int response_code, const ResponseCookies& cookies, const std::string& data); diff --git a/chrome/browser/sync/glue/http_bridge_unittest.cc b/chrome/browser/sync/glue/http_bridge_unittest.cc index 57fca13..f9aa32c 100644 --- a/chrome/browser/sync/glue/http_bridge_unittest.cc +++ b/chrome/browser/sync/glue/http_bridge_unittest.cc @@ -3,7 +3,7 @@ // found in the LICENSE file. #include "base/message_loop_proxy.h" -#include "base/thread.h" +#include "base/threading/thread.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/sync/glue/http_bridge.h" #include "chrome/common/net/test_url_fetcher_factory.h" diff --git a/chrome/browser/sync/glue/password_model_worker.cc b/chrome/browser/sync/glue/password_model_worker.cc index c06f33f..6132a03 100644 --- a/chrome/browser/sync/glue/password_model_worker.cc +++ b/chrome/browser/sync/glue/password_model_worker.cc @@ -7,7 +7,7 @@ #include "base/callback.h" #include "base/ref_counted.h" #include "base/task.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/password_manager/password_store.h" using base::WaitableEvent; diff --git a/chrome/browser/sync/glue/session_change_processor.cc b/chrome/browser/sync/glue/session_change_processor.cc index ca0b166..8823af0 100644 --- a/chrome/browser/sync/glue/session_change_processor.cc +++ b/chrome/browser/sync/glue/session_change_processor.cc @@ -10,9 +10,10 @@ #include "base/logging.h" #include "base/scoped_vector.h" -#include "chrome/browser/browser_thread.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/session_model_associator.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/tab_contents.h" #include "chrome/common/notification_details.h" #include "chrome/common/notification_service.h" #include "chrome/common/notification_source.h" @@ -40,18 +41,95 @@ void SessionChangeProcessor::Observe(NotificationType type, DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(running()); DCHECK(profile_); + + // Track which windows and/or tabs are modified. + std::vector<TabContents*> modified_tabs; + bool windows_changed = false; switch (type.value) { - case NotificationType::SESSION_SERVICE_SAVED: { - std::string tag = session_model_associator_->GetCurrentMachineTag(); - DCHECK_EQ(Source<Profile>(source).ptr(), profile_); - session_model_associator_->UpdateSyncModelDataFromClient(); + case NotificationType::BROWSER_OPENED: { + Browser* browser = Source<Browser>(source).ptr(); + if (browser->profile() != profile_) { + return; + } + + windows_changed = true; + break; + } + + case NotificationType::TAB_PARENTED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + if (controller->profile() != profile_) { + return; + } + windows_changed = true; + modified_tabs.push_back(controller->tab_contents()); + break; + } + + case NotificationType::TAB_CLOSED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + if (controller->profile() != profile_) { + return; + } + windows_changed = true; + modified_tabs.push_back(controller->tab_contents()); + break; + } + + case NotificationType::NAV_LIST_PRUNED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + if (controller->profile() != profile_) { + return; + } + modified_tabs.push_back(controller->tab_contents()); + break; + } + + case NotificationType::NAV_ENTRY_CHANGED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + if (controller->profile() != profile_) { + return; + } + modified_tabs.push_back(controller->tab_contents()); + break; + } + + case NotificationType::NAV_ENTRY_COMMITTED: { + NavigationController* controller = + Source<NavigationController>(source).ptr(); + if (controller->profile() != profile_) { + return; + } + modified_tabs.push_back(controller->tab_contents()); + break; + } + + case NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED: { + TabContents* tab_contents = Source<TabContents>(source).ptr(); + DCHECK(tab_contents); + if (tab_contents->profile() != profile_) { + return; + } + if (tab_contents->extension_app()) { + modified_tabs.push_back(tab_contents); + } break; } default: - LOG(DFATAL) << "Received unexpected notification of type " + LOG(ERROR) << "Received unexpected notification of type " << type.value; break; } + + // Associate windows first to ensure tabs have homes. + if (windows_changed) + session_model_associator_->ReassociateWindows(false); + if (!modified_tabs.empty()) + session_model_associator_->ReassociateTabs(modified_tabs); } void SessionChangeProcessor::ApplyChangesFromSyncModel( @@ -65,10 +143,50 @@ void SessionChangeProcessor::ApplyChangesFromSyncModel( StopObserving(); - // This currently ignores the tracked changes and rebuilds the sessions from - // all the session sync nodes. - // TODO(zea): Make use of |changes| to adjust only modified sessions. - session_model_associator_->UpdateFromSyncModel(trans); + sync_api::ReadNode root(trans); + if (!root.InitByTagLookup(kSessionsTag)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Sessions root node lookup failed."); + return; + } + + for (int i = 0; i < change_count; ++i) { + const sync_api::SyncManager::ChangeRecord& change = changes[i]; + sync_api::SyncManager::ChangeRecord::Action action(change.action); + if (sync_api::SyncManager::ChangeRecord::ACTION_DELETE == action) { + // Deletions should only be for a foreign client itself, and hence affect + // the header node, never a tab node. + sync_api::ReadNode node(trans); + if (!node.InitByIdLookup(change.id)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Session node lookup failed."); + return; + } + DCHECK_EQ(node.GetModelType(), syncable::SESSIONS); + const sync_pb::SessionSpecifics& specifics = node.GetSessionSpecifics(); + session_model_associator_->DisassociateForeignSession( + specifics.session_tag()); + continue; + } + + // Handle an update or add. + sync_api::ReadNode sync_node(trans); + if (!sync_node.InitByIdLookup(change.id)) { + error_handler()->OnUnrecoverableError(FROM_HERE, + "Session node lookup failed."); + return; + } + + // Check that the changed node is a child of the session folder. + DCHECK(root.GetId() == sync_node.GetParentId()); + DCHECK(syncable::SESSIONS == sync_node.GetModelType()); + + const sync_pb::SessionSpecifics& specifics( + sync_node.GetSessionSpecifics()); + const int64 mtime = sync_node.GetModificationTime(); + // Model associator handles foreign session update and add the same. + session_model_associator_->AssociateForeignSpecifics(specifics, mtime); + } // Notify foreign session handlers that there are new sessions. NotificationService::current()->Notify( @@ -96,9 +214,21 @@ void SessionChangeProcessor::StopImpl() { void SessionChangeProcessor::StartObserving() { DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI)); DCHECK(profile_); - notification_registrar_.Add( - this, NotificationType::SESSION_SERVICE_SAVED, - Source<Profile>(profile_)); + notification_registrar_.Add(this, NotificationType::TAB_PARENTED, + NotificationService::AllSources()); + notification_registrar_.Add(this, NotificationType::TAB_CLOSED, + NotificationService::AllSources()); + notification_registrar_.Add(this, NotificationType::NAV_LIST_PRUNED, + NotificationService::AllSources()); + notification_registrar_.Add(this, NotificationType::NAV_ENTRY_CHANGED, + NotificationService::AllSources()); + notification_registrar_.Add(this, NotificationType::NAV_ENTRY_COMMITTED, + NotificationService::AllSources()); + notification_registrar_.Add(this, NotificationType::BROWSER_OPENED, + NotificationService::AllSources()); + notification_registrar_.Add(this, + NotificationType::TAB_CONTENTS_APPLICATION_EXTENSION_CHANGED, + NotificationService::AllSources()); } void SessionChangeProcessor::StopObserving() { diff --git a/chrome/browser/sync/glue/session_model_associator.cc b/chrome/browser/sync/glue/session_model_associator.cc index 5ec89be..8c8cb6d 100644 --- a/chrome/browser/sync/glue/session_model_associator.cc +++ b/chrome/browser/sync/glue/session_model_associator.cc @@ -4,15 +4,19 @@ #include "chrome/browser/sync/glue/session_model_associator.h" +#include <algorithm> #include <utility> #include "base/logging.h" -#include "base/string_util.h" -#include "base/utf_string_conversions.h" +#include "chrome/browser/browser_list.h" +#include "chrome/browser/browser_window.h" #include "chrome/browser/profiles/profile.h" -#include "chrome/browser/sessions/session_id.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/syncable/syncable.h" +#include "chrome/browser/tab_contents/navigation_controller.h" +#include "chrome/browser/tab_contents/navigation_entry.h" +#include "chrome/browser/tabs/tab_strip_model.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" @@ -20,15 +24,18 @@ namespace browser_sync { namespace { - static const char kNoSessionsFolderError[] = "Server did not create the top-level sessions node. We " "might be running against an out-of-date server."; +// The maximum number of navigations in each direction we care to sync. +static const int max_sync_navigation_count = 6; } // namespace -SessionModelAssociator::SessionModelAssociator( - ProfileSyncService* sync_service) : sync_service_(sync_service) { +SessionModelAssociator::SessionModelAssociator(ProfileSyncService* sync_service) + : tab_pool_(sync_service), + local_session_syncid_(sync_api::kInvalidId), + sync_service_(sync_service) { DCHECK(CalledOnValidThread()); DCHECK(sync_service_); } @@ -37,80 +44,10 @@ SessionModelAssociator::~SessionModelAssociator() { DCHECK(CalledOnValidThread()); } -bool SessionModelAssociator::AssociateModels() { - DCHECK(CalledOnValidThread()); - - // Make sure we have a machine tag. - if (current_machine_tag_.empty()) - InitializeCurrentMachineTag(); // Creates a syncable::BaseTransaction. - - { - // Do an initial update from sync model (in case we just re-enabled and - // already had data). - sync_api::ReadTransaction trans( - sync_service_->backend()->GetUserShareHandle()); - UpdateFromSyncModel(&trans); - } - - // Check if anything has changed on the client side. - UpdateSyncModelDataFromClient(); - return true; -} - -bool SessionModelAssociator::ChromeModelHasUserCreatedNodes( - bool* has_nodes) { - DCHECK(CalledOnValidThread()); - CHECK(has_nodes); - // This is wrong, but this function is unused, anyway. - *has_nodes = true; - return true; -} - -bool SessionModelAssociator::DisassociateModels() { - specifics_.clear(); - - // There is no local model stored with which to disassociate, just notify - // foreign session handlers. - NotificationService::current()->Notify( - NotificationType::FOREIGN_SESSION_DISABLED, - NotificationService::AllSources(), - NotificationService::NoDetails()); - return true; -} - -const sync_pb::SessionSpecifics* SessionModelAssociator:: - GetChromeNodeFromSyncId(int64 sync_id) { - sync_api::ReadTransaction trans( - sync_service_->backend()->GetUserShareHandle()); - sync_api::ReadNode node(&trans); - if (!node.InitByIdLookup(sync_id)) - return NULL; - return new sync_pb::SessionSpecifics(node.GetSessionSpecifics()); -} - -bool SessionModelAssociator::GetSyncIdForTaggedNode(const std::string* tag, - int64* sync_id) { - sync_api::ReadTransaction trans( - sync_service_->backend()->GetUserShareHandle()); - sync_api::ReadNode node(&trans); - if (!node.InitByClientTagLookup(syncable::SESSIONS, *tag)) - return false; - *sync_id = node.GetId(); - return true; -} - -int64 SessionModelAssociator::GetSyncIdFromChromeId(const std::string& id) { - sync_api::ReadTransaction trans( - sync_service_->backend()->GetUserShareHandle()); - sync_api::ReadNode node(&trans); - if (!node.InitByClientTagLookup(syncable::SESSIONS, id)) - return sync_api::kInvalidId; - return node.GetId(); -} - bool SessionModelAssociator::InitSyncNodeFromChromeId( const std::string& id, sync_api::BaseNode* sync_node) { + NOTREACHED(); return false; } @@ -131,138 +68,212 @@ bool SessionModelAssociator::SyncModelHasUserCreatedNodes(bool* has_nodes) { return true; } -std::string SessionModelAssociator::GetCurrentMachineTag() { - DCHECK(!current_machine_tag_.empty()); - return current_machine_tag_; +int64 SessionModelAssociator::GetSyncIdFromChromeId(const size_t& id) { + DCHECK(CalledOnValidThread()); + return GetSyncIdFromSessionTag(TabIdToTag(GetCurrentMachineTag(), id)); } -void SessionModelAssociator::UpdateSyncModelDataFromClient() { +int64 SessionModelAssociator::GetSyncIdFromSessionTag(const std::string& tag) { DCHECK(CalledOnValidThread()); - SessionService::SessionCallback* callback = - NewCallback(this, &SessionModelAssociator::OnGotSession); - // TODO(jerrica): Stop current race condition, possibly make new method in - // session service, which only grabs the windows from memory. - GetSessionService()->GetCurrentSession(&consumer_, callback); + sync_api::ReadTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode node(&trans); + if (!node.InitByClientTagLookup(syncable::SESSIONS, tag)) + return sync_api::kInvalidId; + return node.GetId(); } - -// TODO(zea): Don't recreate sessions_ vector from scratch each time. This -// will involve knowing which sessions have been changed (a different data -// structure will probably be better too). -bool SessionModelAssociator::UpdateFromSyncModel( - const sync_api::BaseTransaction* trans) { +void SessionModelAssociator::ReassociateWindows(bool reload_tabs) { DCHECK(CalledOnValidThread()); + sync_pb::SessionSpecifics specifics; + specifics.set_session_tag(GetCurrentMachineTag()); + sync_pb::SessionHeader* header_s = specifics.mutable_header(); + + for (BrowserList::const_iterator i = BrowserList::begin(); + i != BrowserList::end(); ++i) { + // Make sure the browser has tabs and a window. Browsers destructor + // removes itself from the BrowserList. When a browser is closed the + // destructor is not necessarily run immediately. This means its possible + // for us to get a handle to a browser that is about to be removed. If + // the tab count is 0 or the window is NULL, the browser is about to be + // deleted, so we ignore it. + if (ShouldSyncWindowType((*i)->type()) && (*i)->tab_count() && + (*i)->window()) { + sync_pb::SessionWindow window_s; + SessionID::id_type window_id = (*i)->session_id().id(); + 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()); + if ((*i)->type() == + Browser::TYPE_NORMAL) { + window_s.set_browser_type( + sync_pb::SessionWindow_BrowserType_TYPE_NORMAL); + } else { + window_s.set_browser_type( + sync_pb::SessionWindow_BrowserType_TYPE_POPUP); + } - // Rebuild specifics_ vector - specifics_.clear(); - if (!QuerySyncModel(trans, specifics_)) { - LOG(ERROR) << "SessionModelAssociator failed to updated from sync model"; - return false; + // Store the order of tabs. + bool found_tabs = false; + for (int j = 0; j < (*i)->tab_count(); ++j) { + TabContents* tab = (*i)->GetTabContentsAt(j); + DCHECK(tab); + if (IsValidTab(*tab)) { + found_tabs = true; + window_s.add_tab(tab->controller().session_id().id()); + if (reload_tabs) { + ReassociateTab(*tab); + } + } + } + // Only add a window if it contains valid tabs. + if (found_tabs) { + sync_pb::SessionWindow* header_window = header_s->add_window(); + *header_window = window_s; + } + } } - return true; + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::WriteNode header_node(&trans); + if (!header_node.InitByIdLookup(local_session_syncid_)) { + LOG(ERROR) << "Failed to load local session header node."; + return; + } + header_node.SetSessionSpecifics(specifics); } -bool SessionModelAssociator::QuerySyncModel( - const sync_api::BaseTransaction* trans, - std::vector<const sync_pb::SessionSpecifics*>& specifics) { - DCHECK(CalledOnValidThread()); - sync_api::ReadNode root(trans); - if (!root.InitByTagLookup(kSessionsTag)) { - LOG(ERROR) << kNoSessionsFolderError; - return false; +// Static. +bool SessionModelAssociator::ShouldSyncWindowType(const Browser::Type& type) { + switch (type) { + case Browser::TYPE_POPUP: + return true; + case Browser::TYPE_APP: + return false; + case Browser::TYPE_APP_POPUP: + return false; + case Browser::TYPE_DEVTOOLS: + return false; + case Browser::TYPE_APP_PANEL: + return false; + case Browser::TYPE_NORMAL: + default: + return true; } - sync_api::ReadNode current_machine(trans); - int64 current_id = (current_machine.InitByClientTagLookup(syncable::SESSIONS, - GetCurrentMachineTag())) ? current_machine.GetId() : sync_api::kInvalidId; +} - // Iterate through the nodes and populate the session model. - int64 id = root.GetFirstChildId(); - while (id != sync_api::kInvalidId) { - sync_api::ReadNode sync_node(trans); - if (!sync_node.InitByIdLookup(id)) { - LOG(ERROR) << "Failed to fetch sync node for id " << id; - return false; - } - if (id != current_id) { - specifics.insert(specifics.end(), &sync_node.GetSessionSpecifics()); - } - id = sync_node.GetSuccessorId(); +void SessionModelAssociator::ReassociateTabs( + const std::vector<TabContents*>& tabs) { + DCHECK(CalledOnValidThread()); + for (std::vector<TabContents*>::const_iterator i = tabs.begin(); + i != tabs.end(); + ++i) { + ReassociateTab(**i); } - return true; } -bool SessionModelAssociator::GetSessionData( - std::vector<ForeignSession*>* sessions) { +void SessionModelAssociator::ReassociateTab(const TabContents& tab) { DCHECK(CalledOnValidThread()); + if (!IsValidTab(tab)) + return; - // Build vector of sessions from specifics data - for (std::vector<const sync_pb::SessionSpecifics*>::const_iterator i = - specifics_.begin(); i != specifics_.end(); ++i) { - // Only include sessions with open windows. - if ((*i)->session_window_size() > 0) - AppendForeignSessionFromSpecifics(*i, sessions); + int64 sync_id; + SessionID::id_type id = tab.controller().session_id().id(); + if (tab.is_being_destroyed()) { + // This tab is closing. + TabLinksMap::iterator tab_iter = tab_map_.find(id); + if (tab_iter == tab_map_.end()) { + // We aren't tracking this tab (for example, sync setting page). + return; + } + tab_pool_.FreeTabNode(tab_iter->second.sync_id()); + tab_map_.erase(tab_iter); + return; } - return true; -} - -void SessionModelAssociator::AppendForeignSessionFromSpecifics( - const sync_pb::SessionSpecifics* specifics, - std::vector<ForeignSession*>* session) { - ForeignSession* foreign_session = new ForeignSession(); - foreign_session->foreign_session_tag = specifics->session_tag(); - session->insert(session->end(), foreign_session); - for (int i = 0; i < specifics->session_window_size(); i++) { - const sync_pb::SessionWindow* window = &specifics->session_window(i); - SessionWindow* session_window = new SessionWindow(); - PopulateSessionWindowFromSpecifics(session_window, window); - foreign_session->windows.insert( - foreign_session->windows.end(), session_window); + TabLinksMap::const_iterator tablink = tab_map_.find(id); + if (tablink == tab_map_.end()) { + // This is a new tab, get a sync node for it. + sync_id = tab_pool_.GetFreeTabNode(); + } else { + // This tab is already associated with a sync node, reuse it. + sync_id = tablink->second.sync_id(); } + Associate(&tab, sync_id); } -// Fills the given vector with foreign session windows to restore. -void SessionModelAssociator::AppendForeignSessionWithID(int64 id, - std::vector<ForeignSession*>* session, sync_api::BaseTransaction* trans) { - if (id == sync_api::kInvalidId) - return; - sync_api::ReadNode node(trans); - if (!node.InitByIdLookup(id)) - return; - const sync_pb::SessionSpecifics* ref = &node.GetSessionSpecifics(); - AppendForeignSessionFromSpecifics(ref, session); -} +void SessionModelAssociator::Associate(const TabContents* tab, int64 sync_id) { + DCHECK(CalledOnValidThread()); + SessionID::id_type session_id = tab->controller().session_id().id(); -SessionService* SessionModelAssociator::GetSessionService() { - DCHECK(sync_service_); - Profile* profile = sync_service_->profile(); - DCHECK(profile); - SessionService* sessions_service = profile->GetSessionService(); - DCHECK(sessions_service); - return sessions_service; + TabLinks t(sync_id, tab); + tab_map_[session_id] = t; + + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + WriteTabContentsToSyncModel(*tab, sync_id, &trans); } -void SessionModelAssociator::InitializeCurrentMachineTag() { - sync_api::WriteTransaction trans(sync_service_->backend()-> - GetUserShareHandle()); - syncable::Directory* dir = - trans.GetWrappedWriteTrans()->directory(); +bool SessionModelAssociator::WriteTabContentsToSyncModel( + const TabContents& tab, + int64 sync_id, + sync_api::WriteTransaction* trans) { + DCHECK(CalledOnValidThread()); + sync_api::WriteNode tab_node(trans); + if (!tab_node.InitByIdLookup(sync_id)) { + LOG(ERROR) << "Failed to look up tab node " << sync_id; + return false; + } - // TODO(zea): We need a better way of creating a machine tag. The directory - // kernel's cache_guid changes every time syncing is turned on and off. This - // will result in session's associated with stale machine tags persisting on - // the server since that tag will not be reused. Eventually this should - // become some string identifiable to the user. (Home, Work, Laptop, etc.) - // See issue 59672 - current_machine_tag_ = "session_sync"; - current_machine_tag_.append(dir->cache_guid()); - VLOG(1) << "Creating machine tag: " << current_machine_tag_; + sync_pb::SessionSpecifics session_s; + session_s.set_session_tag(GetCurrentMachineTag()); + sync_pb::SessionTab* tab_s = session_s.mutable_tab(); + + SessionID::id_type tab_id = tab.controller().session_id().id(); + tab_s->set_tab_id(tab_id); + tab_s->set_window_id(tab.controller().window_id().id()); + const int current_index = tab.controller().GetCurrentEntryIndex(); + const int min_index = std::max(0, + current_index - max_sync_navigation_count); + const int max_index = std::min(current_index + max_sync_navigation_count, + tab.controller().entry_count()); + const int pending_index = tab.controller().pending_entry_index(); + Browser* browser = BrowserList::FindBrowserWithID( + tab.controller().window_id().id()); + DCHECK(browser); + 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()); + for (int i = min_index; i < max_index; ++i) { + const NavigationEntry* entry = (i == pending_index) ? + tab.controller().pending_entry() : tab.controller().GetEntryAtIndex(i); + DCHECK(entry); + if (entry->virtual_url().is_valid()) { + if (i == max_index - 1) { + VLOG(1) << "Associating tab " << tab_id << " with sync id " << sync_id + << " and url " << entry->virtual_url().possibly_invalid_spec(); + } + TabNavigation tab_nav; + tab_nav.SetFromNavigationEntry(*entry); + sync_pb::TabNavigation* nav_s = tab_s->add_navigation(); + PopulateSessionSpecificsNavigation(&tab_nav, nav_s); + } + } + tab_s->set_current_navigation_index(current_index); + + tab_node.SetSessionSpecifics(session_s); + return true; } -// See PopulateSessionSpecificsTab for use. May add functionality that includes -// the state later. +// Static +// TODO(zea): perhaps sync state (scroll position, form entries, etc.) as well? +// See http://crbug.com/67068. void SessionModelAssociator::PopulateSessionSpecificsNavigation( - const TabNavigation* navigation, sync_pb::TabNavigation* tab_navigation) { + const TabNavigation* navigation, + sync_pb::TabNavigation* tab_navigation) { tab_navigation->set_index(navigation->index()); tab_navigation->set_virtual_url(navigation->virtual_url().spec()); tab_navigation->set_referrer(navigation->referrer().spec()); @@ -334,125 +345,280 @@ void SessionModelAssociator::PopulateSessionSpecificsNavigation( } } -// See PopulateSessionSpecificsWindow for use. -void SessionModelAssociator::PopulateSessionSpecificsTab( - const SessionTab* tab, sync_pb::SessionTab* session_tab) { - session_tab->set_tab_visual_index(tab->tab_visual_index); - session_tab->set_current_navigation_index( - tab->current_navigation_index); - session_tab->set_pinned(tab->pinned); - session_tab->set_extension_app_id(tab->extension_app_id); - for (std::vector<TabNavigation>::const_iterator i3 = - tab->navigations.begin(); i3 != tab->navigations.end(); ++i3) { - const TabNavigation navigation = *i3; - sync_pb::TabNavigation* tab_navigation = - session_tab->add_navigation(); - PopulateSessionSpecificsNavigation(&navigation, tab_navigation); - } +void SessionModelAssociator::Disassociate(int64 sync_id) { + DCHECK(CalledOnValidThread()); + NOTIMPLEMENTED(); + // TODO(zea): we will need this once we support deleting foreign sessions. } -// Called when populating session specifics to send to the sync model, called -// when associating models, or updating the sync model. -void SessionModelAssociator::PopulateSessionSpecificsWindow( - const SessionWindow* window, sync_pb::SessionWindow* session_window) { - session_window->set_selected_tab_index(window->selected_tab_index); - if (window->type == 1) { - session_window->set_browser_type( - sync_pb::SessionWindow_BrowserType_TYPE_NORMAL); - } else { - session_window->set_browser_type( - sync_pb::SessionWindow_BrowserType_TYPE_POPUP); +bool SessionModelAssociator::AssociateModels() { + DCHECK(CalledOnValidThread()); + + // Ensure that we disassociated properly, otherwise memory might leak. + DCHECK(foreign_session_tracker_.empty()); + DCHECK_EQ(0U, tab_pool_.capacity()); + + local_session_syncid_ = sync_api::kInvalidId; + + // Read any available foreign sessions and load any session data we may have. + // If we don't have any local session data in the db, create a header node. + { + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + + sync_api::ReadNode root(&trans); + if (!root.InitByTagLookup(kSessionsTag)) { + LOG(ERROR) << kNoSessionsFolderError; + return false; } - for (std::vector<SessionTab*>::const_iterator i2 = window->tabs.begin(); - i2 != window->tabs.end(); ++i2) { - const SessionTab* tab = *i2; - if (tab->navigations.at(tab->current_navigation_index).virtual_url() == - GURL(chrome::kChromeUINewTabURL)) { - continue; + + // Make sure we have a machine tag. + if (current_machine_tag_.empty()) + InitializeCurrentMachineTag(&trans); + + UpdateAssociationsFromSyncModel(root, &trans); + + if (local_session_syncid_ == sync_api::kInvalidId) { + // The sync db didn't have a header node for us, we need to create one. + sync_api::WriteNode write_node(&trans); + if (!write_node.InitUniqueByCreation(syncable::SESSIONS, root, + current_machine_tag_)) { + LOG(ERROR) << "Failed to create sessions header sync node."; + return false; } - sync_pb::SessionTab* session_tab = session_window->add_session_tab(); - PopulateSessionSpecificsTab(tab, session_tab); + write_node.SetTitle(UTF8ToWide(current_machine_tag_)); + local_session_syncid_ = write_node.GetId(); } + } + + // Check if anything has changed on the client side. + UpdateSyncModelDataFromClient(); + + VLOG(1) << "Session models associated."; + + return true; } -bool SessionModelAssociator::WindowHasNoTabsToSync( - const SessionWindow* window) { - int num_populated = 0; - for (std::vector<SessionTab*>::const_iterator i = window->tabs.begin(); - i != window->tabs.end(); ++i) { - const SessionTab* tab = *i; - if (tab->navigations.at(tab->current_navigation_index).virtual_url() == - GURL(chrome::kChromeUINewTabURL)) { - continue; +bool SessionModelAssociator::DisassociateModels() { + DCHECK(CalledOnValidThread()); + foreign_session_tracker_.clear(); + tab_map_.clear(); + tab_pool_.clear(); + local_session_syncid_ = sync_api::kInvalidId; + + // There is no local model stored with which to disassociate, just notify + // foreign session handlers. + NotificationService::current()->Notify( + NotificationType::FOREIGN_SESSION_DISABLED, + NotificationService::AllSources(), + NotificationService::NoDetails()); + return true; +} + +void SessionModelAssociator::InitializeCurrentMachineTag( + sync_api::WriteTransaction* trans) { + DCHECK(CalledOnValidThread()); + syncable::Directory* dir = trans->GetWrappedWriteTrans()->directory(); + + // TODO(zea): We need a better way of creating a machine tag. The directory + // kernel's cache_guid changes every time syncing is turned on and off. This + // will result in session's associated with stale machine tags persisting on + // the server since that tag will not be reused. Eventually this should + // become some string identifiable to the user. (Home, Work, Laptop, etc.) + // See issue at http://crbug.com/59672 + current_machine_tag_ = "session_sync"; + current_machine_tag_.append(dir->cache_guid()); + VLOG(1) << "Creating machine tag: " << current_machine_tag_; + tab_pool_.set_machine_tag(current_machine_tag_); +} + +bool SessionModelAssociator::UpdateAssociationsFromSyncModel( + const sync_api::ReadNode& root, + const sync_api::BaseTransaction* trans) { + DCHECK(CalledOnValidThread()); + + // Iterate through the nodes and associate any foreign sessions. + int64 id = root.GetFirstChildId(); + while (id != sync_api::kInvalidId) { + sync_api::ReadNode sync_node(trans); + if (!sync_node.InitByIdLookup(id)) { + LOG(ERROR) << "Failed to fetch sync node for id " << id; + return false; } - num_populated++; + + const sync_pb::SessionSpecifics& specifics = + sync_node.GetSessionSpecifics(); + const int64 modification_time = sync_node.GetModificationTime(); + if (specifics.session_tag() != GetCurrentMachineTag()) { + if (!AssociateForeignSpecifics(specifics, modification_time)) { + return false; + } + } else if (id != local_session_syncid_) { + // This is previously stored local session information. + if (specifics.has_header()) { + DCHECK_EQ(sync_api::kInvalidId, local_session_syncid_); + + // This is our previous header node, reuse it. + local_session_syncid_ = id; + } else { + DCHECK(specifics.has_tab()); + + // This is a tab node. We want to track these to reuse them in our free + // tab node pool. They will be overwritten eventually, so need to do + // anything else. + tab_pool_.AddTabNode(id); + } + } + + id = sync_node.GetSuccessorId(); } - if (num_populated == 0) - return true; - return false; + + // After updating from sync model all tabid's should be free. + DCHECK(tab_pool_.full()); + + return true; } -void SessionModelAssociator::OnGotSession(int handle, - std::vector<SessionWindow*>* windows) { - sync_pb::SessionSpecifics specifics; - // Set the tag, then iterate through the vector of windows, extracting the - // window data, along with the tabs data and tab navigation data to populate - // the session specifics. - specifics.set_session_tag(GetCurrentMachineTag()); - FillSpecificsFromSessions(windows, &specifics); - bool has_nodes = false; - if (!SyncModelHasUserCreatedNodes(&has_nodes)) - return; - if (specifics.session_window_size() == 0 && has_nodes) - return; - sync_api::WriteTransaction trans( - sync_service_->backend()->GetUserShareHandle()); - sync_api::ReadNode root(&trans); - if (!root.InitByTagLookup(kSessionsTag)) { - LOG(ERROR) << kNoSessionsFolderError; - return; +bool SessionModelAssociator::AssociateForeignSpecifics( + const sync_pb::SessionSpecifics& specifics, + const int64 modification_time) { + DCHECK(CalledOnValidThread()); + std::string foreign_session_tag = specifics.session_tag(); + DCHECK(foreign_session_tag != GetCurrentMachineTag() || + sync_service_->cros_user() == "test user"); // For tests. + + if (specifics.has_header()) { + // Read in the header data for this foreign session. + // Header data contains window information and ordered tab id's for each + // window. + + // Load (or create) the ForeignSession object for this client. + ForeignSession* foreign_session = + foreign_session_tracker_.GetForeignSession(foreign_session_tag); + + const sync_pb::SessionHeader& header = specifics.header(); + foreign_session->windows.reserve(header.window_size()); + VLOG(1) << "Associating " << foreign_session_tag << " with " << + header.window_size() << " windows."; + size_t i; + for (i = 0; i < static_cast<size_t>(header.window_size()); ++i) { + if (i >= foreign_session->windows.size()) { + // This a new window, create it. + foreign_session->windows.push_back(new SessionWindow()); + } + const sync_pb::SessionWindow& window_s = header.window(i); + PopulateSessionWindowFromSpecifics(foreign_session_tag, + window_s, + modification_time, + foreign_session->windows[i], + &foreign_session_tracker_); + } + // Remove any remaining windows (in case windows were closed) + for (; i < foreign_session->windows.size(); ++i) { + delete foreign_session->windows[i]; + } + foreign_session->windows.resize(header.window_size()); + } else if (specifics.has_tab()) { + const sync_pb::SessionTab& tab_s = specifics.tab(); + SessionID::id_type tab_id = tab_s.tab_id(); + SessionTab* tab = + foreign_session_tracker_.GetSessionTab(foreign_session_tag, + tab_id, + false); + PopulateSessionTabFromSpecifics(tab_s, modification_time, tab); + } else { + NOTREACHED(); + return false; } - UpdateSyncModel(&specifics, &trans, &root); + + return true; } -void SessionModelAssociator::FillSpecificsFromSessions( - std::vector<SessionWindow*>* windows, - sync_pb::SessionSpecifics* session) { - for (std::vector<SessionWindow*>::const_iterator i = windows->begin(); - i != windows->end(); ++i) { - const SessionWindow* window = *i; - if (WindowHasNoTabsToSync(window)) { - continue; +void SessionModelAssociator::DisassociateForeignSession( + const std::string& foreign_session_tag) { + DCHECK(CalledOnValidThread()); + foreign_session_tracker_.DeleteForeignSession(foreign_session_tag); +} + +// Static +void SessionModelAssociator::PopulateSessionWindowFromSpecifics( + std::string foreign_session_tag, + const sync_pb::SessionWindow& specifics, + int64 mtime, + SessionWindow* session_window, + ForeignSessionTracker* tracker) { + if (specifics.has_window_id()) + session_window->window_id.set_id(specifics.window_id()); + if (specifics.has_selected_tab_index()) + session_window->selected_tab_index = specifics.selected_tab_index(); + if (specifics.has_browser_type()) { + if (specifics.browser_type() == + sync_pb::SessionWindow_BrowserType_TYPE_NORMAL) { + session_window->type = 1; + } else { + session_window->type = 2; } - sync_pb::SessionWindow* session_window = session->add_session_window(); - PopulateSessionSpecificsWindow(window, session_window); + } + session_window->timestamp = base::Time::FromInternalValue(mtime); + session_window->tabs.resize(specifics.tab_size()); + for (int i = 0; i < specifics.tab_size(); i++) { + SessionID::id_type tab_id = specifics.tab(i); + session_window->tabs[i] = + tracker->GetSessionTab(foreign_session_tag, tab_id, true); } } +// Static +void SessionModelAssociator::PopulateSessionTabFromSpecifics( + const sync_pb::SessionTab& specifics, + const int64 mtime, + SessionTab* tab) { + if (specifics.has_tab_id()) + tab->tab_id.set_id(specifics.tab_id()); + if (specifics.has_window_id()) + tab->window_id.set_id(specifics.window_id()); + if (specifics.has_tab_visual_index()) + tab->tab_visual_index = specifics.tab_visual_index(); + if (specifics.has_current_navigation_index()) + tab->current_navigation_index = specifics.current_navigation_index(); + if (specifics.has_pinned()) + tab->pinned = specifics.pinned(); + if (specifics.has_extension_app_id()) + tab->extension_app_id = specifics.extension_app_id(); + tab->timestamp = base::Time::FromInternalValue(mtime); + tab->navigations.clear(); // In case we are reusing a previous SessionTab. + for (int i = 0; i < specifics.navigation_size(); i++) { + AppendSessionTabNavigation(specifics.navigation(i), &tab->navigations); + } +} + +// Static void SessionModelAssociator::AppendSessionTabNavigation( - std::vector<TabNavigation>* navigations, - const sync_pb::TabNavigation* navigation) { + const sync_pb::TabNavigation& specifics, + std::vector<TabNavigation>* navigations) { int index = 0; GURL virtual_url; GURL referrer; string16 title; std::string state; PageTransition::Type transition(PageTransition::LINK); - if (navigation->has_index()) - index = navigation->index(); - if (navigation->has_virtual_url()) { - GURL gurl(navigation->virtual_url()); + if (specifics.has_index()) + index = specifics.index(); + if (specifics.has_virtual_url()) { + GURL gurl(specifics.virtual_url()); virtual_url = gurl; } - if (navigation->has_referrer()) { - GURL gurl(navigation->referrer()); + if (specifics.has_referrer()) { + GURL gurl(specifics.referrer()); referrer = gurl; } - if (navigation->has_title()) - title = UTF8ToUTF16(navigation->title()); - if (navigation->has_page_transition() || - navigation->has_navigation_qualifier()) { - switch (navigation->page_transition()) { + if (specifics.has_title()) + title = UTF8ToUTF16(specifics.title()); + if (specifics.has_state()) + state = specifics.state(); + if (specifics.has_page_transition() || + specifics.has_navigation_qualifier()) { + switch (specifics.page_transition()) { case sync_pb::TabNavigation_PageTransition_LINK: transition = PageTransition::LINK; break; @@ -493,7 +659,7 @@ void SessionModelAssociator::AppendSessionTabNavigation( transition = PageTransition::CHAIN_END; break; default: - switch (navigation->navigation_qualifier()) { + switch (specifics.navigation_qualifier()) { case sync_pb:: TabNavigation_PageTransitionQualifier_CLIENT_REDIRECT: transition = PageTransition::CLIENT_REDIRECT; @@ -512,67 +678,291 @@ void SessionModelAssociator::AppendSessionTabNavigation( navigations->insert(navigations->end(), tab_navigation); } -void SessionModelAssociator::PopulateSessionTabFromSpecifics( - SessionTab* session_tab, - const sync_pb::SessionTab* tab, SessionID id) { - session_tab->window_id = id; - SessionID tabID; - session_tab->tab_id = tabID; - if (tab->has_tab_visual_index()) - session_tab->tab_visual_index = tab->tab_visual_index(); - if (tab->has_current_navigation_index()) { - session_tab->current_navigation_index = - tab->current_navigation_index(); +void SessionModelAssociator::UpdateSyncModelDataFromClient() { + DCHECK(CalledOnValidThread()); + // TODO(zea): the logic for determining if we want to sync and the loading of + // the previous session should go here. We can probably reuse the code for + // loading the current session from the old session implementation. + // SessionService::SessionCallback* callback = + // NewCallback(this, &SessionModelAssociator::OnGotSession); + // GetSessionService()->GetCurrentSession(&consumer_, callback); + + // Associate all open windows and their tabs. + ReassociateWindows(true); +} + +SessionModelAssociator::TabNodePool::TabNodePool( + ProfileSyncService* sync_service) + : tab_pool_fp_(-1), + sync_service_(sync_service) { +} + +void SessionModelAssociator::TabNodePool::AddTabNode(int64 sync_id) { + tab_syncid_pool_.resize(tab_syncid_pool_.size() + 1); + tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id; +} + +int64 SessionModelAssociator::TabNodePool::GetFreeTabNode() { + DCHECK_GT(machine_tag_.length(), 0U); + if (tab_pool_fp_ == -1) { + // Tab pool has no free nodes, allocate new one. + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode root(&trans); + if (!root.InitByTagLookup(kSessionsTag)) { + LOG(ERROR) << kNoSessionsFolderError; + return 0; + } + size_t tab_node_id = tab_syncid_pool_.size(); + std::string tab_node_tag = TabIdToTag(machine_tag_, tab_node_id); + sync_api::WriteNode tab_node(&trans); + if (!tab_node.InitUniqueByCreation(syncable::SESSIONS, root, + tab_node_tag)) { + LOG(ERROR) << "Could not create new node!"; + return -1; + } + tab_node.SetTitle(UTF8ToWide(tab_node_tag)); + + // Grow the pool by 1 since we created a new node. We don't actually need + // to put the node's id in the pool now, since the pool is still empty. + // The id will be added when that tab is closed and the node is freed. + tab_syncid_pool_.resize(tab_node_id + 1); + VLOG(1) << "Adding sync node " << tab_node.GetId() << " to tab syncid pool"; + return tab_node.GetId(); + } else { + // There are nodes available, grab next free and decrement free pointer. + return tab_syncid_pool_[static_cast<size_t>(tab_pool_fp_--)]; } - if (tab->has_pinned()) - session_tab->pinned = tab->pinned(); - if (tab->has_extension_app_id()) - session_tab->extension_app_id = tab->extension_app_id(); - for (int i3 = 0; i3 < tab->navigation_size(); i3++) { - const sync_pb::TabNavigation* navigation = &tab->navigation(i3); - AppendSessionTabNavigation(&session_tab->navigations, navigation); +} + +void SessionModelAssociator::TabNodePool::FreeTabNode(int64 sync_id) { + // Pool size should always match # of free tab nodes. + DCHECK_LT(tab_pool_fp_, static_cast<int64>(tab_syncid_pool_.size())); + tab_syncid_pool_[static_cast<size_t>(++tab_pool_fp_)] = sync_id; +} + +bool SessionModelAssociator::GetAllForeignSessions( + std::vector<const ForeignSession*>* sessions) { + DCHECK(CalledOnValidThread()); + return foreign_session_tracker_.LookupAllForeignSessions(sessions); +} + +bool SessionModelAssociator::GetForeignSession( + const std::string& tag, + std::vector<SessionWindow*>* windows) { + DCHECK(CalledOnValidThread()); + return foreign_session_tracker_.LookupSessionWindows(tag, windows); +} + +bool SessionModelAssociator::GetForeignTab( + const std::string& tag, + const SessionID::id_type tab_id, + const SessionTab** tab) { + DCHECK(CalledOnValidThread()); + return foreign_session_tracker_.LookupSessionTab(tag, tab_id, tab); +} + +// Static +bool SessionModelAssociator::SessionWindowHasNoTabsToSync( + const SessionWindow& window) { + int num_populated = 0; + for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin(); + i != window.tabs.end(); ++i) { + const SessionTab* tab = *i; + if (IsValidSessionTab(*tab)) + num_populated++; } + if (num_populated == 0) + return true; + return false; } -void SessionModelAssociator::PopulateSessionWindowFromSpecifics( - SessionWindow* session_window, const sync_pb::SessionWindow* window) { - SessionID id; - session_window->window_id = id; - if (window->has_selected_tab_index()) - session_window->selected_tab_index = window->selected_tab_index(); - if (window->has_browser_type()) { - if (window->browser_type() == - sync_pb::SessionWindow_BrowserType_TYPE_NORMAL) { - session_window->type = 1; - } else { - session_window->type = 2; +// Valid local tab? +bool SessionModelAssociator::IsValidTab(const TabContents& tab) { + DCHECK(CalledOnValidThread()); + if ((tab.profile() == sync_service_->profile() || + sync_service_->profile() == NULL)) { + const NavigationEntry* entry = tab.controller().GetActiveEntry(); + if (!entry) + return false; + if (entry->virtual_url().is_valid() && + (entry->virtual_url() != GURL(chrome::kChromeUINewTabURL) || + tab.controller().entry_count() > 1)) { + return true; } } - for (int i = 0; i < window->session_tab_size(); i++) { - const sync_pb::SessionTab& tab = window->session_tab(i); - SessionTab* session_tab = new SessionTab(); - PopulateSessionTabFromSpecifics(session_tab, &tab, id); - session_window->tabs.insert(session_window->tabs.end(), session_tab); + return false; +} + +// Static +bool SessionModelAssociator::IsValidSessionTab(const SessionTab& tab) { + if (tab.navigations.empty()) + return false; + int selected_index = tab.current_navigation_index; + selected_index = std::max( + 0, + std::min(selected_index, + static_cast<int>(tab.navigations.size() - 1))); + if (selected_index == 0 && + tab.navigations.size() == 1 && + tab.navigations.at(selected_index).virtual_url() == + GURL(chrome::kChromeUINewTabURL)) { + // This is a new tab with no further history, skip. + return false; + } + return true; +} + +// ========================================================================== +// The following methods are not currently used but will likely become useful +// if we choose to sync the previous browser session. + +SessionService* SessionModelAssociator::GetSessionService() { + DCHECK(CalledOnValidThread()); + DCHECK(sync_service_); + Profile* profile = sync_service_->profile(); + DCHECK(profile); + SessionService* sessions_service = profile->GetSessionService(); + DCHECK(sessions_service); + return sessions_service; +} + +void SessionModelAssociator::OnGotSession( + int handle, + std::vector<SessionWindow*>* windows) { + DCHECK(CalledOnValidThread()); + DCHECK(local_session_syncid_); + + sync_pb::SessionSpecifics specifics; + specifics.set_session_tag(GetCurrentMachineTag()); + sync_pb::SessionHeader* header_s = specifics.mutable_header(); + PopulateSessionSpecificsHeader(*windows, header_s); + + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + sync_api::ReadNode root(&trans); + if (!root.InitByTagLookup(kSessionsTag)) { + LOG(ERROR) << kNoSessionsFolderError; + return; + } + + sync_api::WriteNode header_node(&trans); + if (!header_node.InitByIdLookup(local_session_syncid_)) { + LOG(ERROR) << "Failed to load local session header node."; + return; + } + + header_node.SetSessionSpecifics(specifics); +} + +void SessionModelAssociator::PopulateSessionSpecificsHeader( + const std::vector<SessionWindow*>& windows, + sync_pb::SessionHeader* header_s) { + DCHECK(CalledOnValidThread()); + + // Iterate through the vector of windows, extracting the window data, along + // with the tab data to populate the session specifics. + for (size_t i = 0; i < windows.size(); ++i) { + if (SessionWindowHasNoTabsToSync(*(windows[i]))) + continue; + sync_pb::SessionWindow* window_s = header_s->add_window(); + PopulateSessionSpecificsWindow(*(windows[i]), window_s); + if (!SyncLocalWindowToSyncModel(*(windows[i]))) + return; + } +} + +// Called when populating session specifics to send to the sync model, called +// when associating models, or updating the sync model. +void SessionModelAssociator::PopulateSessionSpecificsWindow( + const SessionWindow& window, + sync_pb::SessionWindow* session_window) { + DCHECK(CalledOnValidThread()); + session_window->set_window_id(window.window_id.id()); + session_window->set_selected_tab_index(window.selected_tab_index); + if (window.type == Browser::TYPE_NORMAL) { + session_window->set_browser_type( + sync_pb::SessionWindow_BrowserType_TYPE_NORMAL); + } else if (window.type == Browser::TYPE_POPUP) { + session_window->set_browser_type( + sync_pb::SessionWindow_BrowserType_TYPE_POPUP); + } else { + // ignore + LOG(WARNING) << "Session Sync unable to handle windows of type" << + window.type; + return; + } + for (std::vector<SessionTab*>::const_iterator i = window.tabs.begin(); + i != window.tabs.end(); ++i) { + const SessionTab* tab = *i; + if (!IsValidSessionTab(*tab)) + continue; + session_window->add_tab(tab->tab_id.id()); } } -bool SessionModelAssociator::UpdateSyncModel( - sync_pb::SessionSpecifics* session_data, - sync_api::WriteTransaction* trans, - const sync_api::ReadNode* root) { - const std::string id = session_data->session_tag(); - sync_api::WriteNode write_node(trans); - if (!write_node.InitByClientTagLookup(syncable::SESSIONS, id)) { - sync_api::WriteNode create_node(trans); - if (!create_node.InitUniqueByCreation(syncable::SESSIONS, *root, id)) { - LOG(ERROR) << "Could not create node for session " << id; +bool SessionModelAssociator::SyncLocalWindowToSyncModel( + const SessionWindow& window) { + DCHECK(CalledOnValidThread()); + DCHECK(tab_map_.empty()); + for (size_t i = 0; i < window.tabs.size(); ++i) { + SessionTab* tab = window.tabs[i]; + int64 id = tab_pool_.GetFreeTabNode(); + if (id == -1) { + LOG(ERROR) << "Failed to find/generate free sync node for tab."; return false; } - create_node.SetSessionSpecifics(*session_data); - return true; + + sync_api::WriteTransaction trans( + sync_service_->backend()->GetUserShareHandle()); + if (!WriteSessionTabToSyncModel(*tab, id, &trans)) { + return false; + } + + TabLinks t(id, tab); + tab_map_[tab->tab_id.id()] = t; } - write_node.SetSessionSpecifics(*session_data); return true; } +bool SessionModelAssociator::WriteSessionTabToSyncModel( + const SessionTab& tab, + const int64 sync_id, + sync_api::WriteTransaction* trans) { + DCHECK(CalledOnValidThread()); + sync_api::WriteNode tab_node(trans); + if (!tab_node.InitByIdLookup(sync_id)) { + LOG(ERROR) << "Failed to look up tab node " << sync_id; + return false; + } + + sync_pb::SessionSpecifics specifics; + specifics.set_session_tag(GetCurrentMachineTag()); + sync_pb::SessionTab* tab_s = specifics.mutable_tab(); + PopulateSessionSpecificsTab(tab, tab_s); + tab_node.SetSessionSpecifics(specifics); + return true; +} + +// See PopulateSessionSpecificsWindow for use. +void SessionModelAssociator::PopulateSessionSpecificsTab( + const SessionTab& tab, + sync_pb::SessionTab* session_tab) { + DCHECK(CalledOnValidThread()); + session_tab->set_tab_id(tab.tab_id.id()); + session_tab->set_window_id(tab.window_id.id()); + session_tab->set_tab_visual_index(tab.tab_visual_index); + session_tab->set_current_navigation_index( + tab.current_navigation_index); + session_tab->set_pinned(tab.pinned); + session_tab->set_extension_app_id(tab.extension_app_id); + for (std::vector<TabNavigation>::const_iterator i = + tab.navigations.begin(); i != tab.navigations.end(); ++i) { + const TabNavigation navigation = *i; + sync_pb::TabNavigation* tab_navigation = + session_tab->add_navigation(); + PopulateSessionSpecificsNavigation(&navigation, tab_navigation); + } +} + } // namespace browser_sync diff --git a/chrome/browser/sync/glue/session_model_associator.h b/chrome/browser/sync/glue/session_model_associator.h index 502af18..cff376f 100644 --- a/chrome/browser/sync/glue/session_model_associator.h +++ b/chrome/browser/sync/glue/session_model_associator.h @@ -6,6 +6,7 @@ #define CHROME_BROWSER_SYNC_GLUE_SESSION_MODEL_ASSOCIATOR_H_ #pragma once +#include <map> #include <string> #include <vector> @@ -13,12 +14,19 @@ #include "base/gtest_prod_util.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" #include "chrome/browser/sync/engine/syncapi.h" +#include "chrome/browser/sync/glue/foreign_session_tracker.h" #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/tab_contents/tab_contents.h" #include "chrome/common/notification_registrar.h" class Profile; @@ -40,59 +48,76 @@ static const char kSessionsTag[] = "google_chrome_sessions"; // Contains all logic for associating the Chrome sessions model and // the sync sessions model. -// In the case of sessions, our local model is nothing but a buffer (specifics_) -// that gets overwritten everytime there is an update. From it, we build a new -// foreign session windows list each time |GetSessionData| is called by the -// ForeignSessionHandler. -class SessionModelAssociator : public PerDataTypeAssociatorInterface< - sync_pb::SessionSpecifics, std::string>, public NonThreadSafe { +class SessionModelAssociator + : public PerDataTypeAssociatorInterface<TabContents, size_t>, + public base::NonThreadSafe { public: - // Does not take ownership of sync_service. explicit SessionModelAssociator(ProfileSyncService* sync_service); virtual ~SessionModelAssociator(); + // The has_nodes out parameter is set to true if the sync model has + // nodes other than the permanent tagged nodes. The method may + // return false if an error occurred. + virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes); + // AssociatorInterface and PerDataTypeAssociator Interface implementation. virtual void AbortAssociation() { - // No implementation needed, this associator runs on the main - // thread. - } - - // Dummy method, we do everything all-at-once in UpdateFromSyncModel. - virtual void Associate(const sync_pb::SessionSpecifics* specifics, - int64 sync_id) { + // No implementation needed, this associator runs on the main thread. } - // Updates the sync model with the local client data. (calls - // UpdateFromSyncModel) - virtual bool AssociateModels(); + // 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. + virtual int64 GetSyncIdFromChromeId(const size_t& id); - // The has_nodes out parameter is set to true if the chrome model - // has user-created nodes. The method may return false if an error - // occurred. - virtual bool ChromeModelHasUserCreatedNodes(bool* has_nodes); + // Returns sync id for the given session tag. + // Returns sync_api::kInvalidId if the sync node is not found for the given + // tag + virtual int64 GetSyncIdFromSessionTag(const std::string& tag); - // Dummy method, we do everything all-at-once in UpdateFromSyncModel. - virtual void Disassociate(int64 sync_id) { + // Not used. + virtual const TabContents* GetChromeNodeFromSyncId(int64 sync_id) { + NOTREACHED(); + return NULL; } - // Clear specifics_ buffer and notify foreign session handlers. - virtual bool DisassociateModels(); - - // Returns the chrome session specifics for the given sync id. - // Returns NULL if no specifics are found for the given sync id. - virtual const sync_pb::SessionSpecifics* GetChromeNodeFromSyncId( - int64 sync_id); - - // Returns whether a node with the given permanent tag was found and update - // |sync_id| with that node's id. - virtual bool GetSyncIdForTaggedNode(const std::string* tag, int64* sync_id); - - // 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. - virtual int64 GetSyncIdFromChromeId(const std::string& id); + // Not used. + bool InitSyncNodeFromChromeId(const size_t& id, + sync_api::BaseNode* sync_node) { + NOTREACHED(); + return false; + } + // Resync local window information. Updates the local sessions header node + // with the status of open windows and the order of tabs they contain. Should + // only be called for changes that affect a window, not a change within a + // single tab. + // + // If |reload_tabs| is true, will also resync all tabs (same as calling + // ReassociateTabs with a vector of all tabs). + void ReassociateWindows(bool reload_tabs); + + // Loads and reassociates the local tabs referenced in |tabs|. + void ReassociateTabs(const std::vector<TabContents*>& tabs); + + // Reassociates a single tab with the sync model. Will check if the tab + // already is associated with a sync node and allocate one if necessary. + void ReassociateTab(const TabContents& tab); + + // Associate a local tab and it's sync node. Will overwrite the contents of + // the sync node with new specifics built from the tab. + virtual void Associate(const TabContents* tab, int64 sync_id); + + // Looks up the specified sync node, and marks that tab as closed, then marks + // the node as free and deletes association. + virtual void Disassociate(int64 sync_id); + + // Load any foreign session info stored in sync db and update the sync db + // with local client data. Processes/reuses any sync nodes owned by this + // client and creates any further sync nodes needed to store local header and + // tab info. + virtual bool AssociateModels(); // Initializes the given sync node from the given chrome node id. // Returns false if no sync node was found for the given chrome node id or @@ -100,46 +125,53 @@ class SessionModelAssociator : public PerDataTypeAssociatorInterface< virtual bool InitSyncNodeFromChromeId(const std::string& id, sync_api::BaseNode* sync_node); - // The has_nodes out parameter is set to true if the sync model has - // nodes other than the permanent tagged nodes. The method may - // return false if an error occurred. - virtual bool SyncModelHasUserCreatedNodes(bool* has_nodes); + // Clear local sync data buffers. Does not delete sync nodes to avoid + // tombstones. TODO(zea): way to eventually delete orphaned nodes. + virtual bool DisassociateModels(); // Returns the tag used to uniquely identify this machine's session in the // sync model. - std::string GetCurrentMachineTag(); + inline const std::string& GetCurrentMachineTag() { + DCHECK(!current_machine_tag_.empty()); + return current_machine_tag_; + } - // Updates the server data based upon the current client session. If no node - // corresponding to this machine exists in the sync model, one is created. - void UpdateSyncModelDataFromClient(); + // Load and associate window and tab data for a foreign session + bool AssociateForeignSpecifics(const sync_pb::SessionSpecifics& specifics, + int64 modification_time); - // Pulls the current sync model from the sync database and returns true upon - // update of the client model. Called by SessionChangeProcessor. - // Note that the specifics_ vector is reset and built from scratch each time. - bool UpdateFromSyncModel(const sync_api::BaseTransaction* trans); + // Removes a foreign session from our internal bookkeeping. + void DisassociateForeignSession(const std::string& foreign_session_tag); + + // Builds a list of all foreign sessions. + // Caller does NOT own ForeignSession objects. + bool GetAllForeignSessions(std::vector<const ForeignSession*>* sessions); - // Helper for UpdateFromSyncModel. Appends sync data to a vector of specifics. - bool QuerySyncModel(const sync_api::BaseTransaction* trans, - std::vector<const sync_pb::SessionSpecifics*>& specifics); + // Loads all windows for foreign session with session tag |tag|. + // Caller does NOT own ForeignSession objects. + bool GetForeignSession(const std::string& tag, + std::vector<SessionWindow*>* windows); - // Builds sessions from buffered specifics data - bool GetSessionData(std::vector<ForeignSession*>* sessions); + // Looks up the foreign tab identified by |tab_id| and belonging to foreign + // session |tag|. + // Caller does NOT own the SessionTab object. + bool GetForeignTab(const std::string& tag, + const SessionID::id_type tab_id, + const SessionTab** tab); - // Helper method to generate session specifics from session windows. - void FillSpecificsFromSessions(std::vector<SessionWindow*>* windows, - sync_pb::SessionSpecifics* session); + // Specifies whether the window has tabs to sync. The new tab page does not + // count. If no tabs to sync, it returns true, otherwise false; + static bool SessionWindowHasNoTabsToSync(const SessionWindow& window); - // Helper method for converting session specifics to windows. - void AppendForeignSessionFromSpecifics( - const sync_pb::SessionSpecifics* specifics, - std::vector<ForeignSession*>* session); + // Control which local tabs we're interested in syncing. + // Ensures the profile matches sync's profile and that the tab has at least + // one navigation entry and is not an empty tab. + bool IsValidTab(const TabContents& tab); - // Fills the given empty vector with foreign session windows to restore. - // If the vector is returned empty, then the session data could not be - // converted back into windows. - void AppendForeignSessionWithID(int64 id, - std::vector<ForeignSession*>* session, - sync_api::BaseTransaction* trans); + // Control which foreign tabs we're interested in displaying. + // Checks that the tab has navigations and is not a new tab. + // Note: a new tab page with back/forward history is valid. + static bool IsValidSessionTab(const SessionTab& tab); // Returns the syncable model type. static syncable::ModelType model_type() { return syncable::SESSIONS; } @@ -147,60 +179,225 @@ class SessionModelAssociator : public PerDataTypeAssociatorInterface< private: FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, WriteSessionToNode); FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, + WriteFilledSessionToNode); + FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, WriteForeignSessionToNode); - - // Returns the session service from |sync_service_|. - SessionService* GetSessionService(); + FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, TabNodePoolEmpty); + FRIEND_TEST_ALL_PREFIXES(ProfileSyncServiceSessionTest, TabNodePoolNonEmpty); + FRIEND_TEST_ALL_PREFIXES(SessionModelAssociatorTest, PopulateSessionWindow); + FRIEND_TEST_ALL_PREFIXES(SessionModelAssociatorTest, PopulateSessionTab); + + // Keep all the links to local tab data in one place. + class TabLinks { + public: + // To support usage as second value in maps we need default and copy + // constructors. + TabLinks() + : sync_id_(0), + session_tab_(NULL), + tab_(NULL) {} + + // We only ever have either a SessionTab (for foreign tabs), or a + // TabContents (for local tabs). + TabLinks(int64 sync_id, const TabContents* tab) + : sync_id_(sync_id), + session_tab_(NULL) { + tab_ = const_cast<TabContents*>(tab); + } + TabLinks(int64 sync_id, const SessionTab* session_tab) + : sync_id_(sync_id), + tab_(NULL) { + session_tab_ = const_cast<SessionTab*>(session_tab); + } + + inline int64 sync_id() const { return sync_id_; } + inline const SessionTab* session_tab() const { return session_tab_; } + inline const TabContents* tab() const { return tab_; } + private: + int64 sync_id_; + SessionTab* session_tab_; + TabContents* tab_; + }; + + // A pool for managing free/used tab sync nodes. Performs lazy creation + // of sync nodes when necessary. + class TabNodePool { + public: + explicit TabNodePool(ProfileSyncService* sync_service); + + // Add a previously allocated tab sync node to our pool. Increases the size + // of tab_syncid_pool_ by one and marks the new tab node as free. + // Note: this should only be called when we discover tab sync nodes from + // previous sessions, not for freeing tab nodes we created through + // GetFreeTabNode (use FreeTabNode below for that). + void AddTabNode(int64 sync_id); + + // Returns the sync_id for the next free tab node. If none are available, + // creates a new tab node. + // Note: We make use of the following "id's" + // - a sync_id: an int64 used in |sync_api::InitByIdLookup| + // - a tab_id: created by session service, unique to this client + // - a tab_node_id: the id for a particular sync tab node. This is used + // to generate the sync tab node tag through: + // tab_tag = StringPrintf("%s_%ui", local_session_tag, tab_node_id); + // tab_node_id and sync_id are both unique to a particular sync node. The + // difference is that tab_node_id is controlled by the model associator and + // is used when creating a new sync node, which returns the sync_id, created + // by the sync db. + int64 GetFreeTabNode(); + + // Return a tab node to our free pool. + // Note: the difference between FreeTabNode and AddTabNode is that + // FreeTabNode does not modify the size of |tab_syncid_pool_|, while + // AddTabNode increases it by one. In the case of FreeTabNode, the size of + // the |tab_syncid_pool_| should always be equal to the amount of tab nodes + // associated with this machine. + void FreeTabNode(int64 sync_id); + + // Clear tab pool. + inline void clear() { tab_syncid_pool_.clear(); } + + // Return the number of tab nodes this client currently has allocated + // (including both free and used nodes) + inline size_t capacity() const { return tab_syncid_pool_.size(); } + + // Return empty status (all tab nodes are in use). + inline bool empty() const { return tab_pool_fp_ == -1; } + + // Return full status (no tab nodes are in use). + inline bool full() { + return tab_pool_fp_ == static_cast<int64>(tab_syncid_pool_.size())-1; + } + + inline void set_machine_tag(const std::string& machine_tag) { + machine_tag_ = machine_tag; + } + private: + // Pool of all available syncid's for tab's we have created. + std::vector<int64> tab_syncid_pool_; + + // Free pointer for tab pool. Only those node id's, up to and including the + // one indexed by the free pointer, are valid and free. The rest of the + // |tab_syncid_pool_| is invalid because the nodes are in use. + // To get the next free node, use tab_syncid_pool_[tab_pool_fp_--]. + int64 tab_pool_fp_; + + // The machiine tag associated with this tab pool. Used in the title of new + // sync nodes. + std::string machine_tag_; + + // Our sync service profile (for making changes to the sync db) + ProfileSyncService* sync_service_; + + DISALLOW_COPY_AND_ASSIGN(TabNodePool); + }; + + // Datatypes for accessing local tab data. + typedef std::map<SessionID::id_type, TabLinks> TabLinksMap; + + // Delete all foreign session/window/tab objects allocated dynamically. + // This is comprised of ForeignSession*, IDToSessionTabMap*, and any orphaned + // SessionTab*'s. + void DeleteForeignSessions(); + + // Determine if a window is of a type we're interested in syncing. + static bool ShouldSyncWindowType(const Browser::Type& type); + + // Build a sync tag from tab_node_id. + static inline std::string TabIdToTag( + const std::string machine_tag, + size_t tab_node_id) { + return StringPrintf("%s %lu", + machine_tag.c_str(), static_cast<unsigned long>(tab_node_id)); + } // Initializes the tag corresponding to this machine. - // Note: creates a syncable::BaseTransaction. - void InitializeCurrentMachineTag(); + void InitializeCurrentMachineTag(sync_api::WriteTransaction* trans); - // Populates the navigation portion of the session specifics. - void PopulateSessionSpecificsNavigation(const TabNavigation* navigation, - sync_pb::TabNavigation* tab_navigation); + // Updates the server data based upon the current client session. If no node + // corresponding to this machine exists in the sync model, one is created. + void UpdateSyncModelDataFromClient(); - // Populates the tab portion of the session specifics. - void PopulateSessionSpecificsTab(const SessionTab* tab, - sync_pb::SessionTab* session_tab); + // Pulls the current sync model from the sync database and returns true upon + // update of the client model. Will associate any foreign sessions as well as + // keep track of any local tab nodes, adding them to our free tab node pool. + bool UpdateAssociationsFromSyncModel(const sync_api::ReadNode& root, + const sync_api::BaseTransaction* trans); - // Populates the window portion of the session specifics. - void PopulateSessionSpecificsWindow(const SessionWindow* window, - sync_pb::SessionWindow* session_window); + // Fills a tab sync node with data from a TabContents object. + // (from a local navigation event) + bool WriteTabContentsToSyncModel(const TabContents& tab, + const int64 sync_id, + sync_api::WriteTransaction* trans); + + // 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 sync_pb::SessionWindow& window, + const int64 mtime, + SessionWindow* session_window, + ForeignSessionTracker* tracker); + + // Used to populate a session tab from the session specifics tab provided. + static void PopulateSessionTabFromSpecifics(const sync_pb::SessionTab& tab, + const int64 mtime, + SessionTab* session_tab); - // Specifies whether the window has tabs to sync. The new tab page does not - // count. If no tabs to sync, it returns true, otherwise false; - bool WindowHasNoTabsToSync(const SessionWindow* window); + // Used to populate a session tab from the session specifics tab provided. + static void AppendSessionTabNavigation( + const sync_pb::TabNavigation& navigation, + std::vector<TabNavigation>* navigations); + + // Populates the navigation portion of the session specifics. + static void PopulateSessionSpecificsNavigation( + const TabNavigation* navigation, + sync_pb::TabNavigation* tab_navigation); + + // Returns the session service from |sync_service_|. + SessionService* GetSessionService(); // Internal method used in the callback to obtain the current session. // We don't own |windows|. void OnGotSession(int handle, std::vector<SessionWindow*>* windows); - // Used to populate a session tab from the session specifics tab provided. - void AppendSessionTabNavigation(std::vector<TabNavigation>* navigations, - const sync_pb::TabNavigation* navigation); + // Populate a session specifics header from a list of SessionWindows + void PopulateSessionSpecificsHeader( + const std::vector<SessionWindow*>& windows, + sync_pb::SessionHeader* header_s); - // Used to populate a session tab from the session specifics tab provided. - void PopulateSessionTabFromSpecifics(SessionTab* session_tab, - const sync_pb::SessionTab* tab, SessionID id); + // Populates the window portion of the session specifics. + void PopulateSessionSpecificsWindow(const SessionWindow& window, + sync_pb::SessionWindow* session_window); - // Used to populate a session window from the session specifics window - // provided. - void PopulateSessionWindowFromSpecifics(SessionWindow* session_window, - const sync_pb::SessionWindow* window); - - // Updates the current session on the server. Creates a node for this machine - // if there is not one already. - bool UpdateSyncModel(sync_pb::SessionSpecifics* session_data, - sync_api::WriteTransaction* trans, - const sync_api::ReadNode* root); - // Stores the machine tag. + // Syncs all the tabs in |window| with the local sync db. Will allocate tab + // nodes if needed. + bool SyncLocalWindowToSyncModel(const SessionWindow& window); + + // Fills a tab sync node with data from a SessionTab object. + // (from ReadCurrentSessions) + bool WriteSessionTabToSyncModel(const SessionTab& tab, + const int64 sync_id, + sync_api::WriteTransaction* trans); + + // Populates the tab portion of the session specifics. + void PopulateSessionSpecificsTab(const SessionTab& tab, + sync_pb::SessionTab* session_tab); + + // Local client name. std::string current_machine_tag_; - // Stores the current set of foreign session specifics. - // Used by ForeignSessionHandler through |GetSessionData|. - // Built by |QuerySyncModel| via |UpdateFromSyncModel|. - std::vector<const sync_pb::SessionSpecifics*> specifics_; + // Pool of all used/available sync nodes associated with tabs. + TabNodePool tab_pool_; + + // SyncID for the sync node containing all the window information for this + // client. + int64 local_session_syncid_; + + // Mapping of current open (local) tabs to their sync identifiers. + TabLinksMap tab_map_; + + ForeignSessionTracker foreign_session_tracker_; // Weak pointer. ProfileSyncService* sync_service_; diff --git a/chrome/browser/sync/glue/session_model_associator_unittest.cc b/chrome/browser/sync/glue/session_model_associator_unittest.cc new file mode 100644 index 0000000..77f3866 --- /dev/null +++ b/chrome/browser/sync/glue/session_model_associator_unittest.cc @@ -0,0 +1,163 @@ +// 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 <vector> + +#include "base/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 "chrome/test/testing_profile.h" +#include "testing/gtest/include/gtest/gtest.h" + +using browser_sync::SessionModelAssociator; +using browser_sync::ForeignSessionTracker; +namespace browser_sync { + +typedef testing::Test SessionModelAssociatorTest; + +TEST_F(SessionModelAssociatorTest, SessionWindowHasNoTabsToSync) { + SessionWindow win; + ASSERT_TRUE(SessionModelAssociator::SessionWindowHasNoTabsToSync(win)); + scoped_ptr<SessionTab> tab(new SessionTab()); + win.tabs.push_back(tab.release()); + ASSERT_TRUE(SessionModelAssociator::SessionWindowHasNoTabsToSync(win)); + TabNavigation nav(0, GURL("about:bubba"), GURL("about:referrer"), + string16(ASCIIToUTF16("title")), + std::string("state"), 0U); + win.tabs[0]->navigations.push_back(nav); + ASSERT_FALSE(SessionModelAssociator::SessionWindowHasNoTabsToSync(win)); +} + +TEST_F(SessionModelAssociatorTest, IsValidSessionTab) { + SessionTab tab; + ASSERT_FALSE(SessionModelAssociator::IsValidSessionTab(tab)); + TabNavigation nav(0, GURL(chrome::kChromeUINewTabURL), + GURL("about:referrer"), + string16(ASCIIToUTF16("title")), + std::string("state"), 0U); + tab.navigations.push_back(nav); + // NewTab does not count as valid if it's the only navigation. + ASSERT_FALSE(SessionModelAssociator::IsValidSessionTab(tab)); + TabNavigation nav2(0, GURL("about:bubba"), + GURL("about:referrer"), + string16(ASCIIToUTF16("title")), + std::string("state"), 0U); + tab.navigations.push_back(nav2); + // Once there's another navigation, the tab is valid. + ASSERT_TRUE(SessionModelAssociator::IsValidSessionTab(tab)); +} + +TEST_F(SessionModelAssociatorTest, PopulateSessionWindow) { + sync_pb::SessionWindow window_s; + window_s.add_tab(0); + window_s.set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_NORMAL); + window_s.set_selected_tab_index(1); + + std::string tag = "tag"; + ForeignSessionTracker tracker; + ForeignSession* session = tracker.GetForeignSession(tag); + SessionWindow* win = new SessionWindow(); + session->windows.push_back(win); + SessionModelAssociator::PopulateSessionWindowFromSpecifics( + tag, window_s, 0, win, &tracker); + ASSERT_EQ(1U, win->tabs.size()); + ASSERT_EQ(1, win->selected_tab_index); + ASSERT_EQ(1, win->type); + ASSERT_EQ(1U, tracker.num_foreign_sessions()); + ASSERT_EQ(1U, tracker.num_foreign_tabs(std::string("tag"))); + + // We do this so that when the destructor for the tracker is called, it will + // be able to delete the session, window, and tab. We can't delete these + // ourselves, otherwise we would run into double free errors when the + // destructor was invoked (the true argument tells the tracker the tab + // is now associated with a window). + ASSERT_TRUE(tracker.GetSessionTab(tag, 0, true)); +} + +TEST_F(SessionModelAssociatorTest, PopulateSessionTab) { + sync_pb::SessionTab tab_s; + tab_s.set_tab_visual_index(13); + tab_s.set_current_navigation_index(3); + tab_s.set_pinned(true); + tab_s.set_extension_app_id("app_id"); + sync_pb::TabNavigation* navigation = tab_s.add_navigation(); + navigation->set_index(12); + navigation->set_virtual_url("http://foo/1"); + navigation->set_referrer("referrer"); + navigation->set_title("title"); + navigation->set_page_transition(sync_pb::TabNavigation_PageTransition_TYPED); + + SessionTab tab; + SessionModelAssociator::PopulateSessionTabFromSpecifics(tab_s, 0, &tab); + ASSERT_EQ(13, tab.tab_visual_index); + ASSERT_EQ(3, tab.current_navigation_index); + ASSERT_TRUE(tab.pinned); + ASSERT_EQ("app_id", tab.extension_app_id); + ASSERT_EQ(12, tab.navigations[0].index()); + ASSERT_EQ(GURL("referrer"), tab.navigations[0].referrer()); + ASSERT_EQ(string16(ASCIIToUTF16("title")), tab.navigations[0].title()); + ASSERT_EQ(PageTransition::TYPED, tab.navigations[0].transition()); + ASSERT_EQ(GURL("http://foo/1"), tab.navigations[0].virtual_url()); +} + +TEST_F(SessionModelAssociatorTest, ForeignSessionTracker) { + const std::string tag1 = "tag"; + const std::string tag2 = "tag2"; + const std::string tag3 = "tag3"; + ForeignSessionTracker tracker; + ASSERT_TRUE(tracker.empty()); + ASSERT_EQ(0U, tracker.num_foreign_sessions()); + ASSERT_EQ(0U, tracker.num_foreign_tabs(tag1)); + SessionTab* tab = tracker.GetSessionTab(tag1, 0, false); + ASSERT_EQ(1U, tracker.num_foreign_tabs(tag1)); + ASSERT_EQ(0U, tracker.num_foreign_sessions()); + SessionTab* tab2 = tracker.GetSessionTab(tag1, 0, false); + ASSERT_EQ(1U, tracker.num_foreign_tabs(tag1)); + ASSERT_EQ(0U, tracker.num_foreign_sessions()); + ASSERT_EQ(tab, tab2); + tab2 = tracker.GetSessionTab(tag2, 0, false); + ASSERT_EQ(1U, tracker.num_foreign_tabs(tag1)); + ASSERT_EQ(1U, tracker.num_foreign_tabs(tag2)); + ASSERT_EQ(0U, tracker.num_foreign_sessions()); + + ASSERT_FALSE(tracker.DeleteForeignSession(tag1)); + ASSERT_FALSE(tracker.DeleteForeignSession(tag3)); + + ForeignSession* session = tracker.GetForeignSession(tag1); + ForeignSession* session2 = tracker.GetForeignSession(tag2); + ForeignSession* session3 = tracker.GetForeignSession(tag3); + ASSERT_EQ(3U, tracker.num_foreign_sessions()); + + ASSERT_TRUE(session); + ASSERT_TRUE(session2); + ASSERT_TRUE(session3); + ASSERT_NE(session, session2); + ASSERT_NE(session2, session3); + ASSERT_TRUE(tracker.DeleteForeignSession(tag3)); + ASSERT_EQ(2U, tracker.num_foreign_sessions()); + + const SessionTab *tab_ptr; + ASSERT_TRUE(tracker.LookupSessionTab(tag1, 0, &tab_ptr)); + ASSERT_EQ(tab_ptr, tab); + + std::vector<SessionWindow*> windows; + ASSERT_TRUE(tracker.LookupSessionWindows(tag1, &windows)); + ASSERT_EQ(0U, windows.size()); + + // The sessions don't have valid windows, lookup should not succeed. + std::vector<const ForeignSession*> sessions; + ASSERT_FALSE(tracker.LookupAllForeignSessions(&sessions)); + + tracker.clear(); + ASSERT_EQ(0U, tracker.num_foreign_tabs(tag1)); + ASSERT_EQ(0U, tracker.num_foreign_tabs(tag2)); + ASSERT_EQ(0U, tracker.num_foreign_sessions()); +} + +} // namespace browser_sync + diff --git a/chrome/browser/sync/glue/sync_backend_host.h b/chrome/browser/sync/glue/sync_backend_host.h index fdfc7b4..3543a4f 100644 --- a/chrome/browser/sync/glue/sync_backend_host.h +++ b/chrome/browser/sync/glue/sync_backend_host.h @@ -14,7 +14,7 @@ #include "base/lock.h" #include "base/message_loop.h" #include "base/ref_counted.h" -#include "base/thread.h" +#include "base/threading/thread.h" #include "base/timer.h" #include "base/utf_string_conversions.h" #include "chrome/browser/sync/engine/syncapi.h" diff --git a/chrome/browser/sync/glue/sync_backend_host_mock.cc b/chrome/browser/sync/glue/sync_backend_host_mock.cc new file mode 100644 index 0000000..fe488ad --- /dev/null +++ b/chrome/browser/sync/glue/sync_backend_host_mock.cc @@ -0,0 +1,26 @@ +// 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_mock.h" + +namespace browser_sync { + +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::_)). + WillByDefault(InvokeTask()); +} + +SyncBackendHostMock::~SyncBackendHostMock() {} + +} // namespace browser_sync diff --git a/chrome/browser/sync/glue/sync_backend_host_mock.h b/chrome/browser/sync/glue/sync_backend_host_mock.h index 4035f97..e133a57 100644 --- a/chrome/browser/sync/glue/sync_backend_host_mock.h +++ b/chrome/browser/sync/glue/sync_backend_host_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. @@ -23,20 +23,8 @@ ACTION(InvokeTask) { class SyncBackendHostMock : public SyncBackendHost { public: - 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::_)). - WillByDefault(InvokeTask()); - } + SyncBackendHostMock(); + virtual ~SyncBackendHostMock(); MOCK_METHOD2(ConfigureDataTypes, void(const std::set<syncable::ModelType>&, CancelableTask*)); diff --git a/chrome/browser/sync/glue/ui_model_worker.cc b/chrome/browser/sync/glue/ui_model_worker.cc index 24622ae..38f4a98 100644 --- a/chrome/browser/sync/glue/ui_model_worker.cc +++ b/chrome/browser/sync/glue/ui_model_worker.cc @@ -6,7 +6,7 @@ #include "base/message_loop.h" #include "base/third_party/dynamic_annotations/dynamic_annotations.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" namespace browser_sync { @@ -32,7 +32,7 @@ void UIModelWorker::DoWorkAndWaitUntilDone(Callback0::Type* work) { // We lock only to avoid PostTask'ing a NULL pending_work_ (because it // could get Run() in Stop() and call OnTaskCompleted before we post). // The task is owned by the message loop as per usual. - AutoLock lock(lock_); + base::AutoLock lock(lock_); DCHECK(!pending_work_); pending_work_ = new CallDoWorkAndSignalTask(work, &work_done, this); ui_loop_->PostTask(FROM_HERE, pending_work_); @@ -54,7 +54,7 @@ UIModelWorker::~UIModelWorker() { } void UIModelWorker::OnSyncerShutdownComplete() { - AutoLock lock(lock_); + base::AutoLock lock(lock_); // The SyncerThread has terminated and we are no longer needed by syncapi. // The UI loop initiated shutdown and is (or will be) waiting in Stop(). // We could either be WORKING or RUNNING_MANUAL_SHUTDOWN_PUMP, depending @@ -69,7 +69,7 @@ void UIModelWorker::OnSyncerShutdownComplete() { void UIModelWorker::Stop() { DCHECK_EQ(MessageLoop::current(), ui_loop_); - AutoLock lock(lock_); + base::AutoLock lock(lock_); DCHECK_EQ(state_, WORKING); // We're on our own now, the beloved UI MessageLoop is no longer running. diff --git a/chrome/browser/sync/glue/ui_model_worker.h b/chrome/browser/sync/glue/ui_model_worker.h index f3a988b..503d8cb 100644 --- a/chrome/browser/sync/glue/ui_model_worker.h +++ b/chrome/browser/sync/glue/ui_model_worker.h @@ -7,8 +7,8 @@ #pragma once #include "base/callback.h" -#include "base/condition_variable.h" -#include "base/lock.h" +#include "base/synchronization/lock.h" +#include "base/synchronization/condition_variable.h" #include "base/task.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/engine/model_safe_worker.h" @@ -119,14 +119,14 @@ class UIModelWorker : public browser_sync::ModelSafeWorker { // barrier permits instructions to be reordered by compiler optimizations. // Possible or not, that route makes for very fragile code due to existence // of theoretical races. - Lock lock_; + base::Lock lock_; // Used as a barrier at shutdown to ensure the SyncerThread terminates before // we allow the UI thread to return from Stop(). This gets signalled whenever // one of two events occur: a new pending_work_ task was scheduled, or the // SyncerThread has terminated. We only care about (1) when we are in Stop(), // because we have to manually Run() the task. - ConditionVariable syncapi_event_; + base::ConditionVariable syncapi_event_; DISALLOW_COPY_AND_ASSIGN(UIModelWorker); }; diff --git a/chrome/browser/sync/glue/ui_model_worker_unittest.cc b/chrome/browser/sync/glue/ui_model_worker_unittest.cc index e71a254..a2a1dde 100644 --- a/chrome/browser/sync/glue/ui_model_worker_unittest.cc +++ b/chrome/browser/sync/glue/ui_model_worker_unittest.cc @@ -5,8 +5,8 @@ #include "base/callback.h" #include "base/message_loop.h" #include "base/ref_counted.h" -#include "base/thread.h" -#include "base/waitable_event.h" +#include "base/threading/thread.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/ui_model_worker.h" #include "testing/gtest/include/gtest/gtest.h" diff --git a/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h b/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h index ac2d0c4..a879836 100644 --- a/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h +++ b/chrome/browser/sync/notifier/cache_invalidation_packet_handler.h @@ -13,8 +13,8 @@ #include "base/basictypes.h" #include "base/gtest_prod_util.h" -#include "base/non_thread_safe.h" #include "base/scoped_callback_factory.h" +#include "base/threading/non_thread_safe.h" #include "base/weak_ptr.h" namespace invalidation { @@ -52,7 +52,7 @@ class CacheInvalidationPacketHandler { void HandleInboundPacket(const std::string& packet); - NonThreadSafe non_thread_safe_; + base::NonThreadSafe non_thread_safe_; base::ScopedCallbackFactory<CacheInvalidationPacketHandler> scoped_callback_factory_; diff --git a/chrome/browser/sync/notifier/chrome_invalidation_client.h b/chrome/browser/sync/notifier/chrome_invalidation_client.h index e408085..99af02c 100644 --- a/chrome/browser/sync/notifier/chrome_invalidation_client.h +++ b/chrome/browser/sync/notifier/chrome_invalidation_client.h @@ -12,9 +12,9 @@ #include <string> #include "base/basictypes.h" -#include "base/non_thread_safe.h" #include "base/scoped_callback_factory.h" #include "base/scoped_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" @@ -99,7 +99,7 @@ class ChromeInvalidationClient void HandleOutboundPacket( invalidation::NetworkEndpoint* const& network_endpoint); - NonThreadSafe non_thread_safe_; + base::NonThreadSafe non_thread_safe_; ChromeSystemResources chrome_system_resources_; base::ScopedCallbackFactory<ChromeInvalidationClient> scoped_callback_factory_; diff --git a/chrome/browser/sync/notifier/chrome_system_resources.h b/chrome/browser/sync/notifier/chrome_system_resources.h index b6e6256..06b8001 100644 --- a/chrome/browser/sync/notifier/chrome_system_resources.h +++ b/chrome/browser/sync/notifier/chrome_system_resources.h @@ -14,9 +14,9 @@ #include <string> #include "base/message_loop.h" -#include "base/non_thread_safe.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" #include "google/cacheinvalidation/invalidation-client.h" @@ -52,7 +52,7 @@ class ChromeSystemResources : public invalidation::SystemResources { invalidation::StorageCallback* callback); private: - NonThreadSafe non_thread_safe_; + base::NonThreadSafe non_thread_safe_; scoped_ptr<ScopedRunnableMethodFactory<ChromeSystemResources> > scoped_runnable_method_factory_; // Holds all posted tasks that have not yet been run. diff --git a/chrome/browser/sync/notifier/registration_manager.h b/chrome/browser/sync/notifier/registration_manager.h index 1b1a534..2694373 100644 --- a/chrome/browser/sync/notifier/registration_manager.h +++ b/chrome/browser/sync/notifier/registration_manager.h @@ -12,7 +12,7 @@ #include <map> #include "base/basictypes.h" -#include "base/non_thread_safe.h" +#include "base/threading/non_thread_safe.h" #include "chrome/browser/sync/syncable/model_type.h" #include "google/cacheinvalidation/invalidation-client.h" @@ -54,7 +54,7 @@ class RegistrationManager { void OnRegister(const invalidation::RegistrationUpdateResult& result); - NonThreadSafe non_thread_safe_; + base::NonThreadSafe non_thread_safe_; // Weak pointer. invalidation::InvalidationClient* invalidation_client_; RegistrationStatusMap registration_status_; diff --git a/chrome/browser/sync/profile_sync_factory_mock.cc b/chrome/browser/sync/profile_sync_factory_mock.cc index 68fc045..2d92100 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.cc +++ b/chrome/browser/sync/profile_sync_factory_mock.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. @@ -11,6 +11,8 @@ using browser_sync::ChangeProcessor; using testing::_; using testing::InvokeWithoutArgs; +ProfileSyncFactoryMock::ProfileSyncFactoryMock() {} + ProfileSyncFactoryMock::ProfileSyncFactoryMock( AssociatorInterface* bookmark_model_associator, ChangeProcessor* bookmark_change_processor) @@ -23,6 +25,8 @@ ProfileSyncFactoryMock::ProfileSyncFactoryMock( &ProfileSyncFactoryMock::MakeBookmarkSyncComponents)); } +ProfileSyncFactoryMock::~ProfileSyncFactoryMock() {} + ProfileSyncFactory::SyncComponents ProfileSyncFactoryMock::MakeBookmarkSyncComponents() { return SyncComponents(bookmark_model_associator_.release(), diff --git a/chrome/browser/sync/profile_sync_factory_mock.h b/chrome/browser/sync/profile_sync_factory_mock.h index f5210a0..a1d1a15 100644 --- a/chrome/browser/sync/profile_sync_factory_mock.h +++ b/chrome/browser/sync/profile_sync_factory_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. @@ -18,10 +18,11 @@ class ChangeProcessor; class ProfileSyncFactoryMock : public ProfileSyncFactory { public: - ProfileSyncFactoryMock() {} + ProfileSyncFactoryMock(); ProfileSyncFactoryMock( browser_sync::AssociatorInterface* bookmark_model_associator, browser_sync::ChangeProcessor* bookmark_change_processor); + virtual ~ProfileSyncFactoryMock(); MOCK_METHOD1(CreateProfileSyncService, ProfileSyncService*(const std::string&)); diff --git a/chrome/browser/sync/profile_sync_service.cc b/chrome/browser/sync/profile_sync_service.cc index a3da689..852ed4b 100644 --- a/chrome/browser/sync/profile_sync_service.cc +++ b/chrome/browser/sync/profile_sync_service.cc @@ -19,6 +19,7 @@ #include "base/string_util.h" #include "base/task.h" #include "base/utf_string_conversions.h" +#include "chrome/browser/browser_signin.h" #include "chrome/browser/history/history_types.h" #include "chrome/browser/platform_util.h" #include "chrome/browser/prefs/pref_service.h" @@ -31,6 +32,7 @@ #include "chrome/browser/sync/glue/session_data_type_controller.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/signin_manager.h" +#include "chrome/browser/sync/sync_ui_util.h" #include "chrome/browser/sync/token_migrator.h" #include "chrome/common/chrome_switches.h" #include "chrome/common/net/gaia/gaia_constants.h" @@ -39,6 +41,7 @@ #include "chrome/common/notification_type.h" #include "chrome/common/pref_names.h" #include "chrome/common/time_format.h" +#include "grit/chromium_strings.h" #include "grit/generated_resources.h" #include "jingle/notifier/communicator/const_communicator.h" #include "net/base/cookie_monster.h" @@ -716,11 +719,7 @@ void ProfileSyncService::ShowLoginDialog(gfx::NativeWindow parent_window) { } wizard_.SetParent(parent_window); - // This method will also be called if a passphrase is needed. - if (observed_passphrase_required_) - wizard_.Step(SyncSetupWizard::ENTER_PASSPHRASE); - else - wizard_.Step(SyncSetupWizard::GAIA_LOGIN); + wizard_.Step(SyncSetupWizard::GAIA_LOGIN); FOR_EACH_OBSERVER(Observer, observers_, OnStateChanged()); } @@ -734,6 +733,31 @@ void ProfileSyncService::ShowConfigure(gfx::NativeWindow parent_window) { wizard_.Step(SyncSetupWizard::CONFIGURE); } +void ProfileSyncService::PromptForExistingPassphrase( + gfx::NativeWindow parent_window) { + if (WizardIsVisible()) { + wizard_.Focus(); + return; + } + wizard_.SetParent(parent_window); + wizard_.Step(SyncSetupWizard::ENTER_PASSPHRASE); +} + +void ProfileSyncService::ShowPassphraseMigration( + gfx::NativeWindow parent_window) { + wizard_.SetParent(parent_window); + wizard_.Step(SyncSetupWizard::PASSPHRASE_MIGRATION); +} + +void ProfileSyncService::SigninForPassphrase(TabContents* container) { + string16 prefilled_username = GetAuthenticatedUsername(); + string16 login_message = sync_ui_util::GetLoginMessageForEncryption(); + profile_->GetBrowserSignin()->RequestSignin(container, + prefilled_username, + login_message, + this); +} + SyncBackendHost::StatusSummary ProfileSyncService::QuerySyncStatusSummary() { if (backend_.get() && backend_initialized_) return backend_->GetStatusSummary(); @@ -1138,6 +1162,27 @@ void ProfileSyncService::Observe(NotificationType type, } } +// This is the delegate callback from BrowserSigin. +void ProfileSyncService::OnLoginSuccess() { + // The reason for the browser signin was a non-explicit passphrase + // required signal. If this is the first time through the passphrase + // process, we want to show the migration UI and offer an explicit + // passphrase. Otherwise, we're done because the implicit is enough. + + if (passphrase_required_for_decryption_) { + // NOT first time (decrypting something encrypted elsewhere). + // Do nothing. + } else { + ShowPassphraseMigration(NULL); + } +} + +// This is the delegate callback from BrowserSigin. +void ProfileSyncService::OnLoginFailure(const GoogleServiceAuthError& error) { + // Do nothing. The UI will already reflect the fact that the + // user is not signed in. +} + void ProfileSyncService::AddObserver(Observer* observer) { observers_.AddObserver(observer); } diff --git a/chrome/browser/sync/profile_sync_service.h b/chrome/browser/sync/profile_sync_service.h index 19583b6..62a8e07 100644 --- a/chrome/browser/sync/profile_sync_service.h +++ b/chrome/browser/sync/profile_sync_service.h @@ -15,6 +15,7 @@ #include "base/string16.h" #include "base/time.h" #include "base/timer.h" +#include "chrome/browser/browser_signin.h" #include "chrome/browser/prefs/pref_member.h" #include "chrome/browser/sync/engine/syncapi.h" #include "chrome/browser/sync/glue/data_type_controller.h" @@ -37,6 +38,7 @@ class NotificationSource; class NotificationType; class Profile; class ProfileSyncFactory; +class TabContents; class TokenMigrator; // ProfileSyncService is the layer between browser subsystems like bookmarks, @@ -83,6 +85,7 @@ class TokenMigrator; // class ProfileSyncService : public browser_sync::SyncFrontend, public browser_sync::UnrecoverableErrorHandler, + public BrowserSignin::SigninDelegate, public NotificationObserver { public: typedef ProfileSyncServiceObserver Observer; @@ -223,6 +226,9 @@ class ProfileSyncService : public browser_sync::SyncFrontend, } virtual void ShowLoginDialog(gfx::NativeWindow parent_window); void ShowConfigure(gfx::NativeWindow parent_window); + void PromptForExistingPassphrase(gfx::NativeWindow parent_window); + void SigninForPassphrase(TabContents* container); + void ShowPassphraseMigration(gfx::NativeWindow parent_window); // Pretty-printed strings for a given StatusSummary. static std::string BuildSyncStatusSummaryText( @@ -230,6 +236,10 @@ class ProfileSyncService : public browser_sync::SyncFrontend, // Returns true if the SyncBackendHost has told us it's ready to accept // changes. + // [REMARK] - it is safe to call this function only from the ui thread. + // because the variable is not thread safe and should only be accessed from + // single thread. If we want multiple threads to access this(and there is + // currently no need to do so) we need to protect this with a lock. // TODO(timsteele): What happens if the bookmark model is loaded, a change // takes place, and the backend isn't initialized yet? bool sync_initialized() const { return backend_initialized_; } @@ -315,6 +325,10 @@ class ProfileSyncService : public browser_sync::SyncFrontend, const NotificationSource& source, const NotificationDetails& details); + // BrowserSignin::SigninDelegate interface. + virtual void OnLoginSuccess(); + virtual void OnLoginFailure(const GoogleServiceAuthError& error); + // Changes which data types we're going to be syncing to |preferred_types|. // If it is running, the DataTypeManager will be instructed to reconfigure // the sync backend so that exactly these datatypes are actively synced. See diff --git a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc index a7b929a..025c573 100644 --- a/chrome/browser/sync/profile_sync_service_autofill_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_autofill_unittest.cc @@ -16,7 +16,7 @@ #include "base/task.h" #include "base/time.h" #include "base/utf_string_conversions.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "chrome/browser/autofill/autofill_common_test.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/sync/abstract_profile_sync_service_test.h" @@ -28,10 +28,15 @@ #include "chrome/browser/sync/glue/autofill_data_type_controller.h" #include "chrome/browser/sync/glue/autofill_model_associator.h" #include "chrome/browser/sync/glue/autofill_model_associator2.h" +#include "chrome/browser/sync/glue/autofill_profile_change_processor.h" +#include "chrome/browser/sync/glue/autofill_profile_data_type_controller.h" +#include "chrome/browser/sync/glue/autofill_profile_model_associator.h" +#include "chrome/browser/sync/glue/data_type_controller.h" #include "chrome/browser/sync/profile_sync_factory.h" #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/sync/profile_sync_test_util.h" #include "chrome/browser/sync/protocol/autofill_specifics.pb.h" +#include "chrome/browser/sync/syncable/autofill_migration.h" #include "chrome/browser/sync/syncable/syncable.h" #include "chrome/browser/sync/syncable/model_type.h" #include "chrome/browser/sync/test_profile_sync_service.h" @@ -47,9 +52,15 @@ using base::Time; using base::WaitableEvent; +using browser_sync::AutofillChangeProcessor; using browser_sync::AutofillChangeProcessor2; using browser_sync::AutofillDataTypeController; +using browser_sync::AutofillModelAssociator; using browser_sync::AutofillModelAssociator2; +using browser_sync::AutofillProfileChangeProcessor; +using browser_sync::AutofillProfileDataTypeController; +using browser_sync::AutofillProfileModelAssociator; +using browser_sync::DataTypeController; using browser_sync::GROUP_DB; using browser_sync::kAutofillTag; using browser_sync::SyncBackendHostForProfileSyncTest; @@ -99,9 +110,26 @@ class WebDatabaseMock : public WebDatabase { MOCK_METHOD1(AddAutoFillProfile, bool(const AutoFillProfile&)); // NOLINT MOCK_METHOD1(RemoveAutoFillProfile, - bool(int)); // NOLINT + bool(const string&)); // NOLINT }; +class ProfileSyncServiceAutofillTest; + +template<class AutoFillProfile> +syncable::ModelType GetModelType() { + return syncable::UNSPECIFIED; +} + +template<> +syncable::ModelType GetModelType<AutofillEntry>() { + return syncable::AUTOFILL; +} + +template<> +syncable::ModelType GetModelType<AutoFillProfile>() { + return syncable::AUTOFILL_PROFILE; +} + class WebDataServiceFake : public WebDataService { public: explicit WebDataServiceFake(WebDatabase* web_database) @@ -118,31 +146,113 @@ class WebDataServiceFake : public WebDataService { WebDatabase* web_database_; }; -class PersonalDataManagerMock: public PersonalDataManager { - public: - MOCK_CONST_METHOD0(IsDataLoaded, bool()); - MOCK_METHOD0(LoadProfiles, void()); - MOCK_METHOD0(LoadCreditCards, void()); - MOCK_METHOD0(Refresh, void()); -}; - ACTION_P4(MakeAutofillSyncComponents, service, wd, pdm, dtc) { EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::DB)); if (!BrowserThread::CurrentlyOn(BrowserThread::DB)) return ProfileSyncFactory::SyncComponents(NULL, NULL); - AutofillModelAssociator2* model_associator = - new AutofillModelAssociator2(service, wd, pdm); - AutofillChangeProcessor2* change_processor = - new AutofillChangeProcessor2(model_associator, wd, pdm, dtc); + AutofillModelAssociator* model_associator = + new AutofillModelAssociator(service, wd, pdm); + AutofillChangeProcessor* change_processor = + new AutofillChangeProcessor(model_associator, wd, pdm, dtc); return ProfileSyncFactory::SyncComponents(model_associator, change_processor); } +ACTION_P4(MakeAutofillProfileSyncComponents, service, wd, pdm, dtc) { + EXPECT_TRUE(BrowserThread::CurrentlyOn(BrowserThread::DB)); + if (!BrowserThread::CurrentlyOn(BrowserThread::DB)) + return ProfileSyncFactory::SyncComponents(NULL, NULL); + AutofillProfileModelAssociator* model_associator = + new AutofillProfileModelAssociator(service, wd, pdm); + AutofillProfileChangeProcessor* change_processor = + new AutofillProfileChangeProcessor(model_associator, wd, pdm, dtc); + return ProfileSyncFactory::SyncComponents(model_associator, + change_processor); +} + +class AbstractAutofillFactory { + public: + virtual AutofillDataTypeController* CreateDataTypeController( + ProfileSyncFactory *factory, + ProfileMock* profile, + ProfileSyncService* service) = 0; + virtual void SetExpectation(ProfileSyncFactoryMock* factory, + ProfileSyncService* service, + WebDatabase* wd, + PersonalDataManager* pdm, + DataTypeController* dtc) = 0; + virtual ~AbstractAutofillFactory() {} +}; + +class AutofillEntryFactory : public AbstractAutofillFactory { + public: + browser_sync::AutofillDataTypeController* CreateDataTypeController( + ProfileSyncFactory *factory, + ProfileMock* profile, + ProfileSyncService* service) { + return new AutofillDataTypeController(factory, + profile, + service); + } + + void SetExpectation(ProfileSyncFactoryMock* factory, + ProfileSyncService* service, + WebDatabase* wd, + PersonalDataManager* pdm, + DataTypeController* dtc) { + EXPECT_CALL(*factory, CreateAutofillSyncComponents(_,_,_,_)). + WillOnce(MakeAutofillSyncComponents(service, wd, pdm, dtc)); + } +}; + +class AutofillProfileFactory : public AbstractAutofillFactory { + public: + browser_sync::AutofillDataTypeController* CreateDataTypeController( + ProfileSyncFactory *factory, + ProfileMock* profile, + ProfileSyncService* service) { + return new AutofillProfileDataTypeController(factory, + profile, + service); + } + + void SetExpectation(ProfileSyncFactoryMock* factory, + ProfileSyncService* service, + WebDatabase* wd, + PersonalDataManager* pdm, + DataTypeController* dtc) { + EXPECT_CALL(*factory, CreateAutofillProfileSyncComponents(_,_,_,_)). + WillOnce(MakeAutofillProfileSyncComponents(service, wd, pdm, dtc)); + } +}; + +class PersonalDataManagerMock: public PersonalDataManager { + public: + MOCK_CONST_METHOD0(IsDataLoaded, bool()); + MOCK_METHOD0(LoadProfiles, void()); + MOCK_METHOD0(LoadCreditCards, void()); + MOCK_METHOD0(Refresh, void()); +}; +template <class T> class AddAutofillTask; + class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { protected: ProfileSyncServiceAutofillTest() : db_thread_(BrowserThread::DB) { } + AutofillProfileFactory profile_factory_; + AutofillEntryFactory entry_factory_; + + AbstractAutofillFactory* GetFactory(syncable::ModelType type) { + if (type == syncable::AUTOFILL) { + return &entry_factory_; + } else if (type == syncable::AUTOFILL_PROFILE) { + return &profile_factory_; + } else { + NOTREACHED(); + return NULL; + } + } virtual void SetUp() { web_data_service_ = new WebDataServiceFake(&web_database_); personal_data_manager_ = new PersonalDataManagerMock(); @@ -162,48 +272,49 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { MessageLoop::current()->RunAllPending(); } - void StartSyncService(Task* task, bool will_fail_association) { - if (!service_.get()) { - service_.reset( - new TestProfileSyncService(&factory_, &profile_, "test_user", false, - task)); - AutofillDataTypeController* data_type_controller = - new AutofillDataTypeController(&factory_, - &profile_, - service_.get()); - - SyncBackendHostForProfileSyncTest:: - SetDefaultExpectationsForWorkerCreation(&profile_); - - EXPECT_CALL(factory_, CreateAutofillSyncComponents(_, _, _, _)). - WillOnce(MakeAutofillSyncComponents(service_.get(), - &web_database_, - personal_data_manager_.get(), - data_type_controller)); - EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). - WillOnce(ReturnNewDataTypeManager()); - - EXPECT_CALL(profile_, GetWebDataService(_)). - WillOnce(Return(web_data_service_.get())); - - EXPECT_CALL(profile_, GetPersonalDataManager()). - WillRepeatedly(Return(personal_data_manager_.get())); - - EXPECT_CALL(*personal_data_manager_, IsDataLoaded()). - WillRepeatedly(Return(true)); - - // We need tokens to get the tests going - token_service_.IssueAuthTokenForTest( - GaiaConstants::kSyncService, "token"); - - 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(); - } + void StartSyncService(Task* task, + bool will_fail_association, + syncable::ModelType type) { + AbstractAutofillFactory* factory = GetFactory(type); + service_.reset( + new TestProfileSyncService(&factory_, &profile_, "test_user", false, + task)); + AutofillDataTypeController* data_type_controller = + factory->CreateDataTypeController(&factory_, + &profile_, + service_.get()); + SyncBackendHostForProfileSyncTest:: + SetDefaultExpectationsForWorkerCreation(&profile_); + + factory->SetExpectation(&factory_, + service_.get(), + &web_database_, + personal_data_manager_.get(), + data_type_controller); + + EXPECT_CALL(factory_, CreateDataTypeManager(_, _)). + WillOnce(ReturnNewDataTypeManager()); + + EXPECT_CALL(profile_, GetWebDataService(_)). + WillOnce(Return(web_data_service_.get())); + + EXPECT_CALL(profile_, GetPersonalDataManager()). + WillRepeatedly(Return(personal_data_manager_.get())); + + EXPECT_CALL(*personal_data_manager_, IsDataLoaded()). + WillRepeatedly(Return(true)); + + // We need tokens to get the tests going + token_service_.IssueAuthTokenForTest( + GaiaConstants::kSyncService, "token"); + + 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(); } bool AddAutofillSyncNode(const AutofillEntry& entry) { @@ -214,30 +325,27 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { return false; sync_api::WriteNode node(&trans); - std::string tag = AutofillModelAssociator2::KeyToTag(entry.key().name(), + std::string tag = AutofillModelAssociator::KeyToTag(entry.key().name(), entry.key().value()); if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) return false; - AutofillChangeProcessor2::WriteAutofillEntry(entry, &node); + AutofillChangeProcessor::WriteAutofillEntry(entry, &node); return true; } - bool AddAutofillProfileSyncNode(const AutoFillProfile& profile) { + bool AddAutofillSyncNode(const AutoFillProfile& profile) { sync_api::WriteTransaction trans( service_->backend()->GetUserShareHandle()); sync_api::ReadNode autofill_root(&trans); - if (!autofill_root.InitByTagLookup(browser_sync::kAutofillTag)) + if (!autofill_root.InitByTagLookup(browser_sync::kAutofillProfileTag)) return false; sync_api::WriteNode node(&trans); - std::string tag = browser_sync::AutofillModelAssociator2::ProfileLabelToTag( - profile.Label()); - if (!node.InitUniqueByCreation(syncable::AUTOFILL, autofill_root, tag)) + std::string tag = profile.guid(); + if (!node.InitUniqueByCreation(syncable::AUTOFILL_PROFILE, + autofill_root, tag)) return false; - AutofillChangeProcessor2::WriteAutofillProfile(profile, &node); - sync_pb::AutofillSpecifics s(node.GetAutofillSpecifics()); - s.mutable_profile()->set_label(UTF16ToUTF8(profile.Label())); - node.SetAutofillSpecifics(s); + AutofillProfileChangeProcessor::WriteAutofillProfile(profile, &node); return true; } @@ -268,8 +376,8 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { entries->push_back(AutofillEntry(key, timestamps)); } else if (autofill.has_profile()) { AutoFillProfile p; - p.set_label(UTF8ToUTF16(autofill.profile().label())); - AutofillModelAssociator2::OverwriteProfileWithServerData(&p, + p.set_guid(autofill.profile().guid()); + AutofillProfileModelAssociator::OverwriteProfileWithServerData(&p, autofill.profile()); profiles->push_back(p); } @@ -278,6 +386,31 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { return true; } + bool GetAutofillProfilesFromSyncDBUnderProfileNode( + std::vector<AutoFillProfile>* profiles) { + sync_api::ReadTransaction trans(service_->backend()->GetUserShareHandle()); + sync_api::ReadNode autofill_root(&trans); + if (!autofill_root.InitByTagLookup(browser_sync::kAutofillProfileTag)) + return false; + + int64 child_id = autofill_root.GetFirstChildId(); + while (child_id != sync_api::kInvalidId) { + sync_api::ReadNode child_node(&trans); + if (!child_node.InitByIdLookup(child_id)) + return false; + + const sync_pb::AutofillProfileSpecifics& autofill( + child_node.GetAutofillProfileSpecifics()); + AutoFillProfile p; + p.set_guid(autofill.guid()); + AutofillProfileModelAssociator::OverwriteProfileWithServerData(&p, + autofill); + profiles->push_back(p); + child_id = child_node.GetSuccessorId(); + } + return true; + } + void SetIdleChangeProcessorExpectations() { EXPECT_CALL(web_database_, RemoveFormElement(_, _)).Times(0); EXPECT_CALL(web_database_, GetAutofillTimestamps(_, _, _)).Times(0); @@ -303,7 +436,8 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { return MakeAutofillEntry(name, value, timestamp, -1); } - friend class AddAutofillEntriesTask; + friend class AddAutofillTask<AutofillEntry>; + friend class AddAutofillTask<AutoFillProfile>; friend class FakeServerUpdater; BrowserThread db_thread_; @@ -315,34 +449,28 @@ class ProfileSyncServiceAutofillTest : public AbstractProfileSyncServiceTest { scoped_refptr<PersonalDataManagerMock> personal_data_manager_; }; -class AddAutofillEntriesTask : public Task { +template <class T> +class AddAutofillTask : public Task { public: - AddAutofillEntriesTask(ProfileSyncServiceAutofillTest* test, - const std::vector<AutofillEntry>& entries, - const std::vector<AutoFillProfile>& profiles) - : test_(test), entries_(entries), profiles_(profiles), success_(false) { + AddAutofillTask(ProfileSyncServiceAutofillTest* test, + const std::vector<T>& entries) + : test_(test), entries_(entries), success_(false) { } virtual void Run() { - if (!test_->CreateRoot(syncable::AUTOFILL)) + if (!test_->CreateRoot(GetModelType<T>())) return; for (size_t i = 0; i < entries_.size(); ++i) { if (!test_->AddAutofillSyncNode(entries_[i])) return; } - for (size_t i = 0; i < profiles_.size(); ++i) { - if (!test_->AddAutofillProfileSyncNode(profiles_[i])) - return; - } success_ = true; } - bool success() { return success_; } private: ProfileSyncServiceAutofillTest* test_; - const std::vector<AutofillEntry>& entries_; - const std::vector<AutoFillProfile>& profiles_; + const std::vector<T>& entries_; bool success_; }; @@ -392,7 +520,7 @@ class FakeServerUpdater: public base::RefCountedThreadSafe<FakeServerUpdater> { ASSERT_TRUE(dir.good()); // Create autofill protobuf - std::string tag = AutofillModelAssociator2::KeyToTag(entry_.key().name(), + std::string tag = AutofillModelAssociator::KeyToTag(entry_.key().name(), entry_.key().value()); sync_pb::AutofillSpecifics new_autofill; new_autofill.set_name(UTF16ToUTF8(entry_.key().name())); @@ -485,7 +613,7 @@ class FakeServerUpdater: public base::RefCountedThreadSafe<FakeServerUpdater> { // waiting for the PersonalDataManager. TEST_F(ProfileSyncServiceAutofillTest, FailModelAssociation) { // Don't create the root autofill node so startup fails. - StartSyncService(NULL, true); + StartSyncService(NULL, true, syncable::AUTOFILL); EXPECT_TRUE(service_->unrecoverable_error_detected()); } @@ -495,7 +623,7 @@ TEST_F(ProfileSyncServiceAutofillTest, EmptyNativeEmptySync) { SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); EXPECT_CALL(*personal_data_manager_, Refresh()); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> sync_entries; std::vector<AutoFillProfile> sync_profiles; @@ -513,7 +641,7 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeEntriesEmptySync) { SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); EXPECT_CALL(*personal_data_manager_, Refresh()); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> sync_entries; std::vector<AutoFillProfile> sync_profiles; @@ -523,18 +651,15 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeEntriesEmptySync) { EXPECT_EQ(0U, sync_profiles.size()); } -TEST_F(ProfileSyncServiceAutofillTest, HasMixedNativeEmptySync) { - std::vector<AutofillEntry> entries; - entries.push_back(MakeAutofillEntry("foo", "bar", 1)); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). - WillOnce(DoAll(SetArgumentPointee<0>(entries), Return(true))); +TEST_F(ProfileSyncServiceAutofillTest, HasProfileEmptySync) { std::vector<AutoFillProfile*> profiles; std::vector<AutoFillProfile> expected_profiles; // Owned by GetAutoFillProfiles caller. AutoFillProfile* profile0 = new AutoFillProfile; - autofill_test::SetProfileInfo(profile0, - "Billing", "Marion", "Mitchell", "Morrison", + autofill_test::SetProfileInfoWithGuid(profile0, + "{54B3F9AA-335E-4f71-A27D-719C41564230}", "Billing", + "Mitchell", "Morrison", "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", "91601", "US", "12345678910", "01987654321"); profiles.push_back(profile0); @@ -543,88 +668,15 @@ TEST_F(ProfileSyncServiceAutofillTest, HasMixedNativeEmptySync) { WillOnce(DoAll(SetArgumentPointee<0>(profiles), Return(true))); EXPECT_CALL(*personal_data_manager_, Refresh()); SetIdleChangeProcessorExpectations(); - CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); + CreateRootTask task(this, syncable::AUTOFILL_PROFILE); + StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); - std::vector<AutofillEntry> sync_entries; std::vector<AutoFillProfile> sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles)); - ASSERT_EQ(1U, entries.size()); - EXPECT_TRUE(entries[0] == sync_entries[0]); + ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode(&sync_profiles)); EXPECT_EQ(1U, sync_profiles.size()); EXPECT_EQ(0, expected_profiles[0].Compare(sync_profiles[0])); } -bool ProfilesMatchExceptLabelImpl(AutoFillProfile p1, AutoFillProfile p2) { - const AutoFillFieldType types[] = { NAME_FIRST, - NAME_MIDDLE, - NAME_LAST, - EMAIL_ADDRESS, - COMPANY_NAME, - ADDRESS_HOME_LINE1, - ADDRESS_HOME_LINE2, - ADDRESS_HOME_CITY, - ADDRESS_HOME_STATE, - ADDRESS_HOME_ZIP, - ADDRESS_HOME_COUNTRY, - PHONE_HOME_NUMBER, - PHONE_FAX_NUMBER }; - if (p1.Label() == p2.Label()) - return false; - - for (size_t index = 0; index < arraysize(types); ++index) { - if (p1.GetFieldText(AutoFillType(types[index])) != - p2.GetFieldText(AutoFillType(types[index]))) - return false; - } - return true; -} - -MATCHER_P(ProfileMatchesExceptLabel, profile, "") { - return ProfilesMatchExceptLabelImpl(arg, profile); -} - -TEST_F(ProfileSyncServiceAutofillTest, HasDuplicateProfileLabelsEmptySync) { - std::vector<AutoFillProfile> expected_profiles; - std::vector<AutoFillProfile*> profiles; - AutoFillProfile* profile0 = new AutoFillProfile; - autofill_test::SetProfileInfo(profile0, - "Billing", "Marion", "Mitchell", "Morrison", - "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", - "91601", "US", "12345678910", "01987654321"); - AutoFillProfile* profile1 = new AutoFillProfile; - autofill_test::SetProfileInfo(profile1, - "Billing", "Same", "Label", "Morrison", - "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", - "91601", "US", "12345678910", "01987654321"); - profiles.push_back(profile0); - profiles.push_back(profile1); - expected_profiles.push_back(*profile0); - expected_profiles.push_back(*profile1); - AutoFillProfile relabelled_profile; - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); - EXPECT_CALL(*personal_data_manager_, Refresh()); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). - WillOnce(DoAll(SetArgumentPointee<0>(profiles), Return(true))); - EXPECT_CALL(web_database_, UpdateAutoFillProfile( - ProfileMatchesExceptLabel(expected_profiles[1]))). - WillOnce(DoAll(SaveArg<0>(&relabelled_profile), Return(true))); - - SetIdleChangeProcessorExpectations(); - CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); - ASSERT_TRUE(task.success()); - std::vector<AutofillEntry> sync_entries; - std::vector<AutoFillProfile> sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&sync_entries, &sync_profiles)); - EXPECT_EQ(0U, sync_entries.size()); - EXPECT_EQ(2U, sync_profiles.size()); - EXPECT_EQ(0, expected_profiles[0].Compare(sync_profiles[1])); - EXPECT_TRUE(ProfilesMatchExceptLabelImpl(expected_profiles[1], - sync_profiles[0])); - EXPECT_EQ(sync_profiles[0].Label(), relabelled_profile.Label()); -} - TEST_F(ProfileSyncServiceAutofillTest, HasNativeWithDuplicatesEmptySync) { // There is buggy autofill code that allows duplicate name/value // pairs to exist in the database with separate pair_ids. @@ -638,7 +690,7 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeWithDuplicatesEmptySync) { SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); EXPECT_CALL(*personal_data_manager_, Refresh()); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> sync_entries; std::vector<AutoFillProfile> sync_profiles; @@ -649,46 +701,25 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeWithDuplicatesEmptySync) { TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncNoMerge) { AutofillEntry native_entry(MakeAutofillEntry("native", "entry", 1)); AutofillEntry sync_entry(MakeAutofillEntry("sync", "entry", 2)); - AutoFillProfile sync_profile; - autofill_test::SetProfileInfo(&sync_profile, - "Billing", "Marion", "Mitchell", "Morrison", - "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", - "91601", "US", "12345678910", "01987654321"); - - AutoFillProfile* native_profile = new AutoFillProfile; - autofill_test::SetProfileInfo(native_profile, - "Work", "Josephine", "Alicia", "Saenz", - "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", - "32801", "US", "19482937549", "13502849239"); std::vector<AutofillEntry> native_entries; native_entries.push_back(native_entry); - std::vector<AutoFillProfile*> native_profiles; - native_profiles.push_back(native_profile); - - std::vector<AutoFillProfile> expected_profiles; - expected_profiles.push_back(*native_profile); - expected_profiles.push_back(sync_profile); EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_entries), Return(true))); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). - WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); + + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); + std::vector<AutofillEntry> sync_entries; sync_entries.push_back(sync_entry); - std::vector<AutoFillProfile> sync_profiles; - sync_profiles.push_back(sync_profile); - AddAutofillEntriesTask task(this, sync_entries, sync_profiles); - AutoFillProfile to_be_added(sync_profile); + AddAutofillTask<AutofillEntry> task(this, sync_entries); + EXPECT_CALL(web_database_, UpdateAutofillEntries(ElementsAre(sync_entry))). WillOnce(Return(true)); - // TODO(dhollowa): Duplicate removal when contents match but GUIDs don't. - // http://crbug.com/58813 - EXPECT_CALL(web_database_, AddAutoFillProfile(_)). - WillOnce(Return(true)); + EXPECT_CALL(*personal_data_manager_, Refresh()); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::set<AutofillEntry> expected_entries; @@ -703,9 +734,6 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncNoMerge) { new_sync_entries.end()); EXPECT_TRUE(expected_entries == new_sync_entries_set); - EXPECT_EQ(2U, new_sync_profiles.size()); - EXPECT_EQ(0, expected_profiles[0].Compare(new_sync_profiles[0])); - EXPECT_EQ(0, expected_profiles[1].Compare(new_sync_profiles[1])); } TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeEntry) { @@ -720,14 +748,13 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeEntry) { EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); std::vector<AutofillEntry> sync_entries; - std::vector<AutoFillProfile> sync_profiles; sync_entries.push_back(sync_entry); - AddAutofillEntriesTask task(this, sync_entries, sync_profiles); + AddAutofillTask<AutofillEntry> task(this, sync_entries); EXPECT_CALL(web_database_, UpdateAutofillEntries(ElementsAre(merged_entry))). WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); std::vector<AutofillEntry> new_sync_entries; @@ -740,51 +767,89 @@ TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeEntry) { TEST_F(ProfileSyncServiceAutofillTest, HasNativeHasSyncMergeProfile) { AutoFillProfile sync_profile; - autofill_test::SetProfileInfo(&sync_profile, - "Billing", "Marion", "Mitchell", "Morrison", + 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; - autofill_test::SetProfileInfo(native_profile, - "Billing", "Josephine", "Alicia", "Saenz", + 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; native_profiles.push_back(native_profile); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - std::vector<AutofillEntry> sync_entries; std::vector<AutoFillProfile> sync_profiles; sync_profiles.push_back(sync_profile); - AddAutofillEntriesTask task(this, sync_entries, sync_profiles); + AddAutofillTask<AutoFillProfile> task(this, sync_profiles); - // TODO(dhollowa): Duplicate removal when contents match but GUIDs don't. - // http://crbug.com/58813 EXPECT_CALL(web_database_, UpdateAutoFillProfile(_)). WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); - std::vector<AutofillEntry> new_sync_entries; std::vector<AutoFillProfile> new_sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, - &new_sync_profiles)); + ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( + &new_sync_profiles)); ASSERT_EQ(1U, new_sync_profiles.size()); EXPECT_EQ(0, sync_profile.Compare(new_sync_profiles[0])); } +TEST_F(ProfileSyncServiceAutofillTest, MergeProfileWithDifferentGuid) { + 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"); + + string native_guid = "{EDC609ED-7EEE-4f27-B00C-423242A9C44B}"; + 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; + native_profiles.push_back(native_profile); + EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). + WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); + + std::vector<AutoFillProfile> sync_profiles; + sync_profiles.push_back(sync_profile); + AddAutofillTask<AutoFillProfile> task(this, sync_profiles); + + EXPECT_CALL(web_database_, AddAutoFillProfile(_)). + WillOnce(Return(true)); + EXPECT_CALL(web_database_, 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; + ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( + &new_sync_profiles)); + ASSERT_EQ(1U, new_sync_profiles.size()); + EXPECT_EQ(0, sync_profile.Compare(new_sync_profiles[0])); + EXPECT_EQ(sync_profile.guid(), new_sync_profiles[0].guid()); +} + TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddEntry) { EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); SetIdleChangeProcessorExpectations(); CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); AutofillEntry added_entry(MakeAutofillEntry("added", "entry", 1)); @@ -809,89 +874,33 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddEntry) { } TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddProfile) { - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); SetIdleChangeProcessorExpectations(); - CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); + CreateRootTask task(this, syncable::AUTOFILL_PROFILE); + StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); AutoFillProfile added_profile; - autofill_test::SetProfileInfo(&added_profile, - "Billing", "Josephine", "Alicia", "Saenz", + autofill_test::SetProfileInfoWithGuid(&added_profile, + "{D6ADA912-D374-4c0a-917D-F5C8EBE43011}", "Josephine", "Alicia", "Saenz", "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", "32801", "US", "19482937549", "13502849239"); - AutofillProfileChange change(AutofillProfileChange::ADD, - added_profile.Label(), &added_profile, string16()); + AutofillProfileChangeGUID change(AutofillProfileChangeGUID::ADD, + added_profile.guid(), &added_profile); scoped_refptr<ThreadNotifier> notifier(new ThreadNotifier(&db_thread_)); - notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED_GUID, Source<WebDataService>(web_data_service_.get()), - Details<AutofillProfileChange>(&change)); + Details<AutofillProfileChangeGUID>(&change)); - std::vector<AutofillEntry> new_sync_entries; std::vector<AutoFillProfile> new_sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, - &new_sync_profiles)); + ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( + &new_sync_profiles)); ASSERT_EQ(1U, new_sync_profiles.size()); EXPECT_EQ(0, added_profile.Compare(new_sync_profiles[0])); } -TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeAddProfileConflict) { - AutoFillProfile sync_profile; - autofill_test::SetProfileInfo(&sync_profile, - "Billing", "Marion", "Mitchell", "Morrison", - "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", - "91601", "US", "12345678910", "01987654321"); - - std::vector<AutofillEntry> sync_entries; - std::vector<AutoFillProfile> sync_profiles; - sync_profiles.push_back(sync_profile); - AddAutofillEntriesTask task(this, sync_entries, sync_profiles); - - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)).WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); - // TODO(dhollowa): Duplicate removal when contents match but GUIDs don't. - // http://crbug.com/58813 - EXPECT_CALL(web_database_, AddAutoFillProfile(_)). - WillOnce(Return(true)); - EXPECT_CALL(*personal_data_manager_, Refresh()); - SetIdleChangeProcessorExpectations(); - StartSyncService(&task, false); - ASSERT_TRUE(task.success()); - - AutoFillProfile added_profile; - autofill_test::SetProfileInfo(&added_profile, - "Billing", "Josephine", "Alicia", "Saenz", - "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", - "32801", "US", "19482937549", "13502849239"); - - AutofillProfileChange change(AutofillProfileChange::ADD, - added_profile.Label(), &added_profile, string16()); - - AutoFillProfile relabelled_profile; - EXPECT_CALL(web_database_, UpdateAutoFillProfile( - ProfileMatchesExceptLabel(added_profile))). - WillOnce(DoAll(SaveArg<0>(&relabelled_profile), Return(true))); - EXPECT_CALL(*personal_data_manager_, Refresh()); - - scoped_refptr<ThreadNotifier> notifier(new ThreadNotifier(&db_thread_)); - notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, - Source<WebDataService>(web_data_service_.get()), - Details<AutofillProfileChange>(&change)); - - std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, - &new_sync_profiles)); - ASSERT_EQ(2U, new_sync_profiles.size()); - EXPECT_EQ(0, sync_profile.Compare(new_sync_profiles[1])); - EXPECT_TRUE(ProfilesMatchExceptLabelImpl(added_profile, - new_sync_profiles[0])); - EXPECT_EQ(new_sync_profiles[0].Label(), relabelled_profile.Label()); -} - TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateEntry) { AutofillEntry original_entry(MakeAutofillEntry("my", "entry", 1)); std::vector<AutofillEntry> original_entries; @@ -902,7 +911,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateEntry) { EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); AutofillEntry updated_entry(MakeAutofillEntry("my", "entry", 1, 2)); @@ -928,147 +937,6 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateEntry) { } -TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateProfile) { - AutoFillProfile* native_profile = new AutoFillProfile; - autofill_test::SetProfileInfo(native_profile, - "Billing", "Josephine", "Alicia", "Saenz", - "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", - "32801", "US", "19482937549", "13502849239"); - std::vector<AutoFillProfile*> native_profiles; - native_profiles.push_back(native_profile); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). - WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - EXPECT_CALL(*personal_data_manager_, Refresh()); - CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); - ASSERT_TRUE(task.success()); - - AutoFillProfile update_profile; - autofill_test::SetProfileInfo(&update_profile, - "Billing", "Changin'", "Mah", "Namez", - "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", - "32801", "US", "19482937549", "13502849239"); - - AutofillProfileChange change(AutofillProfileChange::UPDATE, - update_profile.Label(), &update_profile, - ASCIIToUTF16("Billing")); - scoped_refptr<ThreadNotifier> notifier(new ThreadNotifier(&db_thread_)); - notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, - Source<WebDataService>(web_data_service_.get()), - Details<AutofillProfileChange>(&change)); - - std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, - &new_sync_profiles)); - ASSERT_EQ(1U, new_sync_profiles.size()); - EXPECT_EQ(0, update_profile.Compare(new_sync_profiles[0])); -} - -TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeUpdateProfileRelabel) { - AutoFillProfile* native_profile = new AutoFillProfile; - autofill_test::SetProfileInfo(native_profile, - "Billing", "Josephine", "Alicia", "Saenz", - "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", - "32801", "US", "19482937549", "13502849239"); - std::vector<AutoFillProfile*> native_profiles; - native_profiles.push_back(native_profile); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). - WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - EXPECT_CALL(*personal_data_manager_, Refresh()); - CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); - ASSERT_TRUE(task.success()); - - AutoFillProfile update_profile; - autofill_test::SetProfileInfo(&update_profile, - "TRYIN 2 FOOL U", "Josephine", "Alicia", "Saenz", - "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", - "32801", "US", "19482937549", "13502849239"); - - AutofillProfileChange change(AutofillProfileChange::UPDATE, - update_profile.Label(), &update_profile, - ASCIIToUTF16("Billing")); - scoped_refptr<ThreadNotifier> notifier(new ThreadNotifier(&db_thread_)); - notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, - Source<WebDataService>(web_data_service_.get()), - Details<AutofillProfileChange>(&change)); - - std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, - &new_sync_profiles)); - ASSERT_EQ(1U, new_sync_profiles.size()); - EXPECT_EQ(0, update_profile.Compare(new_sync_profiles[0])); -} - -TEST_F(ProfileSyncServiceAutofillTest, - ProcessUserChangeUpdateProfileRelabelConflict) { - std::vector<AutoFillProfile*> native_profiles; - native_profiles.push_back(new AutoFillProfile); - native_profiles.push_back(new AutoFillProfile); - autofill_test::SetProfileInfo(native_profiles[0], - "Billing", "Josephine", "Alicia", "Saenz", - "joewayne@me.xyz", "Fox", "1212 Center.", "Bld. 5", "Orlando", "FL", - "32801", "US", "19482937549", "13502849239"); - autofill_test::SetProfileInfo(native_profiles[1], - "ExistingLabel", "Marion", "Mitchell", "Morrison", - "johnwayne@me.xyz", "Fox", "123 Zoo St.", "unit 5", "Hollywood", "CA", - "91601", "US", "12345678910", "01987654321"); - AutoFillProfile marion(*native_profiles[1]); - AutoFillProfile josephine(*native_profiles[0]); - - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); - EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). - WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - EXPECT_CALL(*personal_data_manager_, Refresh()); - CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); - ASSERT_TRUE(task.success()); - MessageLoop::current()->RunAllPending(); - Mock::VerifyAndClearExpectations(&web_database_); - native_profiles.clear(); // Contents freed. - - // Update josephine twice with marion's label. The second time ought to be - // idempotent, settling on the same name and not triggering a sync upload. - for (int pass = 0; pass < 2; ++pass) { - AutoFillProfile josephine_update(josephine); - // TODO(dhollowa): Replace with |AutoFillProfile::set_guid|. - // http://crbug.com/58813 - josephine_update.set_label(ASCIIToUTF16("ExistingLabel")); - - AutoFillProfile relabelled_profile; - EXPECT_CALL(web_database_, UpdateAutoFillProfile( - ProfileMatchesExceptLabel(josephine_update))). - WillOnce(DoAll(SaveArg<0>(&relabelled_profile), Return(true))); - EXPECT_CALL(*personal_data_manager_, Refresh()); - - AutofillProfileChange change(AutofillProfileChange::UPDATE, - josephine_update.Label(), &josephine_update, - josephine.Label()); - scoped_refptr<ThreadNotifier> notifier(new ThreadNotifier(&db_thread_)); - notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, - Source<WebDataService>(web_data_service_.get()), - Details<AutofillProfileChange>(&change)); - MessageLoop::current()->RunAllPending(); // Run the Refresh task. - Mock::VerifyAndClearExpectations(&web_database_); - - std::vector<AutofillEntry> new_sync_entries; - std::vector<AutoFillProfile> new_sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, - &new_sync_profiles)); - ASSERT_EQ(2U, new_sync_profiles.size()); - EXPECT_EQ(0, marion.Compare(new_sync_profiles[1])); - EXPECT_TRUE(ProfilesMatchExceptLabelImpl(josephine_update, - new_sync_profiles[0])); - EXPECT_EQ(ASCIIToUTF16("ExistingLabel2"), new_sync_profiles[0].Label()); - EXPECT_EQ(ASCIIToUTF16("ExistingLabel2"), relabelled_profile.Label()); - josephine = relabelled_profile; - } -} - TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveEntry) { AutofillEntry original_entry(MakeAutofillEntry("my", "entry", 1)); std::vector<AutofillEntry> original_entries; @@ -1079,7 +947,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveEntry) { EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); AutofillChangeList changes; @@ -1099,42 +967,39 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveEntry) { TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeRemoveProfile) { AutoFillProfile sync_profile; - autofill_test::SetProfileInfo(&sync_profile, - "Billing", "Josephine", "Alicia", "Saenz", + 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; - autofill_test::SetProfileInfo(native_profile, - "Billing", "Josephine", "Alicia", "Saenz", + 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; native_profiles.push_back(native_profile); - EXPECT_CALL(web_database_, GetAllAutofillEntries(_)). WillOnce(Return(true)); EXPECT_CALL(web_database_, GetAutoFillProfiles(_)). WillOnce(DoAll(SetArgumentPointee<0>(native_profiles), Return(true))); - std::vector<AutofillEntry> sync_entries; std::vector<AutoFillProfile> sync_profiles; sync_profiles.push_back(sync_profile); - AddAutofillEntriesTask task(this, sync_entries, sync_profiles); + AddAutofillTask<AutoFillProfile> task(this, sync_profiles); EXPECT_CALL(*personal_data_manager_, Refresh()); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL_PROFILE); ASSERT_TRUE(task.success()); - AutofillProfileChange change(AutofillProfileChange::REMOVE, - sync_profile.Label(), NULL, string16()); + AutofillProfileChangeGUID change(AutofillProfileChangeGUID::REMOVE, + sync_profile.guid(), NULL); scoped_refptr<ThreadNotifier> notifier(new ThreadNotifier(&db_thread_)); - notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED, + notifier->Notify(NotificationType::AUTOFILL_PROFILE_CHANGED_GUID, Source<WebDataService>(web_data_service_.get()), - Details<AutofillProfileChange>(&change)); + Details<AutofillProfileChangeGUID>(&change)); - std::vector<AutofillEntry> new_sync_entries; std::vector<AutoFillProfile> new_sync_profiles; - ASSERT_TRUE(GetAutofillEntriesFromSyncDB(&new_sync_entries, - &new_sync_profiles)); - ASSERT_EQ(0U, new_sync_entries.size()); + ASSERT_TRUE(GetAutofillProfilesFromSyncDBUnderProfileNode( + &new_sync_profiles)); + ASSERT_EQ(0U, new_sync_profiles.size()); } TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeError) { @@ -1142,7 +1007,7 @@ TEST_F(ProfileSyncServiceAutofillTest, ProcessUserChangeError) { EXPECT_CALL(web_database_, GetAutoFillProfiles(_)).WillOnce(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()); CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); // Inject an evil entry into the sync db to conflict with the same @@ -1179,7 +1044,7 @@ TEST_F(ProfileSyncServiceAutofillTest, DISABLED_ServerChangeRace) { WillRepeatedly(Return(true)); EXPECT_CALL(*personal_data_manager_, Refresh()).Times(3); CreateRootTask task(this, syncable::AUTOFILL); - StartSyncService(&task, false); + StartSyncService(&task, false, syncable::AUTOFILL); ASSERT_TRUE(task.success()); // (true, false) means we have to reset after |Signal|, init to unsignaled. diff --git a/chrome/browser/sync/profile_sync_service_harness.cc b/chrome/browser/sync/profile_sync_service_harness.cc index d275147..14fba6e 100644 --- a/chrome/browser/sync/profile_sync_service_harness.cc +++ b/chrome/browser/sync/profile_sync_service_harness.cc @@ -2,12 +2,16 @@ // 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 <algorithm> +#include <vector> + #include "base/message_loop.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/sync_backend_host.h" -#include "chrome/browser/sync/profile_sync_service_harness.h" #include "chrome/browser/sync/sessions/session_state.h" #include "chrome/browser/ui/browser.h" #include "chrome/common/net/gaia/gaia_constants.h" @@ -90,8 +94,7 @@ ProfileSyncServiceHarness::ProfileSyncServiceHarness( : wait_state_(INITIAL_WAIT_STATE), profile_(profile), service_(NULL), - last_timestamp_(0), - min_timestamp_needed_(kMinTimestampNeededNone), + timestamp_match_partner_(NULL), username_(username), password_(password), id_(id) { @@ -263,11 +266,14 @@ bool ProfileSyncServiceHarness::RunStateChangeMachine() { } case WAITING_FOR_UPDATES: { LogClientInfo("WAITING_FOR_UPDATES"); - if (!IsSynced() || GetUpdatedTimestamp() < min_timestamp_needed_) { - // The client is not yet fully synced. Continue waiting until the client - // is at the required minimum timestamp. + DCHECK(timestamp_match_partner_); + if (!MatchesOtherClient(timestamp_match_partner_)) { + // The client is not yet fully synced; keep waiting until we converge. break; } + timestamp_match_partner_->service()->RemoveObserver(this); + timestamp_match_partner_ = NULL; + SignalStateCompleteWithNextState(FULLY_SYNCED); break; } @@ -353,7 +359,7 @@ bool ProfileSyncServiceHarness::AwaitMutualSyncCycleCompletion( LogClientInfo("AwaitMutualSyncCycleCompletion"); if (!AwaitSyncCycleCompletion("Sync cycle completion on active client.")) return false; - return partner->WaitUntilTimestampIsAtLeast(last_timestamp_, + return partner->WaitUntilTimestampMatches(this, "Sync cycle completion on passive client."); } @@ -367,7 +373,7 @@ bool ProfileSyncServiceHarness::AwaitGroupSyncCycleCompletion( partners.begin(); it != partners.end(); ++it) { if ((this != *it) && ((*it)->wait_state_ != SYNC_DISABLED)) { return_value = return_value && - (*it)->WaitUntilTimestampIsAtLeast(last_timestamp_, + (*it)->WaitUntilTimestampMatches(this, "Sync cycle completion on partner client."); } } @@ -388,20 +394,21 @@ bool ProfileSyncServiceHarness::AwaitQuiescence( return return_value; } -bool ProfileSyncServiceHarness::WaitUntilTimestampIsAtLeast( - int64 timestamp, const std::string& reason) { - LogClientInfo("WaitUntilTimestampIsAtLeast"); +bool ProfileSyncServiceHarness::WaitUntilTimestampMatches( + ProfileSyncServiceHarness* partner, const std::string& reason) { + LogClientInfo("WaitUntilTimestampMatches"); if (wait_state_ == SYNC_DISABLED) { LOG(ERROR) << "Sync disabled for Client " << id_ << "."; return false; } - min_timestamp_needed_ = timestamp; - if (GetUpdatedTimestamp() < min_timestamp_needed_) { - wait_state_ = WAITING_FOR_UPDATES; - return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); - } else { + DCHECK(!timestamp_match_partner_); + if (MatchesOtherClient(partner)) return true; - } + + timestamp_match_partner_ = partner; + partner->service()->AddObserver(this); + wait_state_ = WAITING_FOR_UPDATES; + return AwaitStatusChangeWithTimeout(kLiveSyncOperationTimeoutMs, reason); } bool ProfileSyncServiceHarness::AwaitStatusChangeWithTimeout( @@ -424,8 +431,13 @@ bool ProfileSyncServiceHarness::AwaitStatusChangeWithTimeout( timeout_milliseconds); loop->Run(); loop->SetNestableTasksAllowed(did_allow_nestable_tasks); - LogClientInfo("AwaitStatusChangeWithTimeout succeeded"); - return timeout_signal->Abort(); + if (timeout_signal->Abort()) { + LogClientInfo("AwaitStatusChangeWithTimeout succeeded"); + return true; + } else { + LogClientInfo("AwaitStatusChangeWithTimeout timed out"); + return false; + } } ProfileSyncService::Status ProfileSyncServiceHarness::GetStatus() { @@ -449,6 +461,27 @@ bool ProfileSyncServiceHarness::IsSynced() { snap->unsynced_count == 0); } +bool ProfileSyncServiceHarness::MatchesOtherClient( + ProfileSyncServiceHarness* partner) { + if (!IsSynced()) + return false; + + // Only look for a match if we have at least one enabled datatype in + // common with the partner client. + syncable::ModelTypeSet types, other_types, intersection_types; + service()->GetPreferredDataTypes(&types); + partner->service()->GetPreferredDataTypes(&other_types); + std::set_intersection(types.begin(), types.end(), other_types.begin(), + other_types.end(), + inserter(intersection_types, + intersection_types.begin())); + if (intersection_types.empty()) { + return true; + } + return partner->IsSynced() && + partner->GetUpdatedTimestamp() == GetUpdatedTimestamp(); +} + const SyncSessionSnapshot* ProfileSyncServiceHarness::GetLastSessionSnapshot() const { DCHECK(service_ != NULL) << "Sync service has not yet been set up."; @@ -539,9 +572,7 @@ void ProfileSyncServiceHarness::DisableSyncForAllDatatypes() { int64 ProfileSyncServiceHarness::GetUpdatedTimestamp() { const SyncSessionSnapshot* snap = GetLastSessionSnapshot(); DCHECK(snap != NULL) << "GetUpdatedTimestamp(): Sync snapshot is NULL."; - DCHECK_LE(last_timestamp_, snap->max_local_timestamp); - last_timestamp_ = snap->max_local_timestamp; - return last_timestamp_; + return snap->max_local_timestamp; } void ProfileSyncServiceHarness::LogClientInfo(std::string message) { diff --git a/chrome/browser/sync/profile_sync_service_harness.h b/chrome/browser/sync/profile_sync_service_harness.h index e3047af..064afe1 100644 --- a/chrome/browser/sync/profile_sync_service_harness.h +++ b/chrome/browser/sync/profile_sync_service_harness.h @@ -59,8 +59,9 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { bool AwaitSyncCycleCompletion(const std::string& reason); // Blocks the caller until this harness has observed that the sync engine - // has "synced" up to at least the specified local timestamp. - bool WaitUntilTimestampIsAtLeast(int64 timestamp, const std::string& reason); + // has downloaded all the changes seen by the |partner| harness's client. + bool WaitUntilTimestampMatches( + ProfileSyncServiceHarness* partner, const std::string& reason); // Calling this acts as a barrier and blocks the caller until |this| and // |partner| have both completed a sync cycle. When calling this method, @@ -72,10 +73,9 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { bool AwaitMutualSyncCycleCompletion(ProfileSyncServiceHarness* partner); // Blocks the caller until |this| completes its ongoing sync cycle and every - // other client in |partners| has a timestamp that is greater than or equal to - // the timestamp of |this|. Note: Use this method when exactly one client - // makes local change(s), and more than one client is waiting to receive those - // changes. + // other client in |partners| have achieved identical download progresses. + // Note: Use this method when exactly one client makes local change(s), + // and more than one client is waiting to receive those changes. bool AwaitGroupSyncCycleCompletion( std::vector<ProfileSyncServiceHarness*>& partners); @@ -168,11 +168,14 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { // Returns true if the sync client has no unsynced items. bool IsSynced(); + // Returns true if this client has downloaded all the items that the + // other client has. + bool MatchesOtherClient(ProfileSyncServiceHarness* partner); + // Logs message with relevant info about client's sync state (if available). void LogClientInfo(std::string message); - // Updates |last_timestamp_| with the timestamp of the current sync session. - // Returns the new value of |last_timestamp_|. + // Gets the current progress indicator of the current sync session. int64 GetUpdatedTimestamp(); WaitState wait_state_; @@ -180,15 +183,9 @@ class ProfileSyncServiceHarness : public ProfileSyncServiceObserver { Profile* profile_; ProfileSyncService* service_; - // This value tracks the max sync timestamp (e.g. synced-to revision) inside - // the sync engine. It gets updated when a sync cycle ends and the session - // snapshot implies syncing is "done". - int64 last_timestamp_; - - // The minimum value of the 'max_local_timestamp' member of a - // SyncSessionSnapshot we need to wait to observe in OnStateChanged when told - // to WaitUntilTimestampIsAtLeast(...). - int64 min_timestamp_needed_; + // The harness of the client whose update progress marker we're expecting + // eventually match. + ProfileSyncServiceHarness* timestamp_match_partner_; // Credentials used for GAIA authentication. std::string username_; diff --git a/chrome/browser/sync/profile_sync_service_mock.cc b/chrome/browser/sync/profile_sync_service_mock.cc new file mode 100644 index 0000000..8bfdb10 --- /dev/null +++ b/chrome/browser/sync/profile_sync_service_mock.cc @@ -0,0 +1,9 @@ +// 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_mock.h" + +ProfileSyncServiceMock::ProfileSyncServiceMock() {} + +ProfileSyncServiceMock::~ProfileSyncServiceMock() {} diff --git a/chrome/browser/sync/profile_sync_service_mock.h b/chrome/browser/sync/profile_sync_service_mock.h index a338631..32bff66 100644 --- a/chrome/browser/sync/profile_sync_service_mock.h +++ b/chrome/browser/sync/profile_sync_service_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. @@ -17,8 +17,8 @@ class ProfileSyncServiceMock : public ProfileSyncService { public: - ProfileSyncServiceMock() {} - virtual ~ProfileSyncServiceMock() {} + ProfileSyncServiceMock(); + virtual ~ProfileSyncServiceMock(); MOCK_METHOD0(DisableForUser, void()); MOCK_METHOD0(OnBackendInitialized, void()); diff --git a/chrome/browser/sync/profile_sync_service_session_unittest.cc b/chrome/browser/sync/profile_sync_service_session_unittest.cc index 6242f0e..7f90cd6 100644 --- a/chrome/browser/sync/profile_sync_service_session_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_session_unittest.cc @@ -170,16 +170,9 @@ TEST_F(ProfileSyncServiceSessionTest, WriteSessionToNode) { bool has_nodes; ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); ASSERT_TRUE(has_nodes); - ASSERT_TRUE(model_associator_->ChromeModelHasUserCreatedNodes(&has_nodes)); - ASSERT_TRUE(has_nodes); std::string machine_tag = model_associator_->GetCurrentMachineTag(); - int64 sync_id; - ASSERT_TRUE(model_associator_->GetSyncIdForTaggedNode(&machine_tag, - &sync_id)); - ASSERT_EQ(model_associator_->GetSyncIdFromChromeId(machine_tag), sync_id); - scoped_ptr<const sync_pb::SessionSpecifics> sync_specifics( - model_associator_->GetChromeNodeFromSyncId(sync_id)); - ASSERT_TRUE(sync_specifics != NULL); + int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag); + ASSERT_NE(sync_api::kInvalidId, sync_id); // Check that we can get the correct session specifics back from the node. sync_api::ReadTransaction trans(sync_service_-> @@ -188,8 +181,10 @@ TEST_F(ProfileSyncServiceSessionTest, WriteSessionToNode) { ASSERT_TRUE(node.InitByClientTagLookup(syncable::SESSIONS, machine_tag)); const sync_pb::SessionSpecifics& specifics(node.GetSessionSpecifics()); - ASSERT_EQ(sync_specifics->session_tag(), specifics.session_tag()); ASSERT_EQ(machine_tag, specifics.session_tag()); + ASSERT_TRUE(specifics.has_header()); + const sync_pb::SessionHeader& header_s = specifics.header(); + ASSERT_EQ(0, header_s.window_size()); } // Test that we can fill this machine's session, write it to a node, @@ -208,51 +203,35 @@ TEST_F(ProfileSyncServiceSessionTest, WriteFilledSessionToNode) { AddTab(browser(), GURL("http://bar/1")); NavigateAndCommitActiveTab(GURL("http://bar/2")); - // Report a saved session, thus causing the ChangeProcessor to write to a - // node. - NotificationService::current()->Notify( - NotificationType::SESSION_SERVICE_SAVED, - Source<Profile>(profile()), - NotificationService::NoDetails()); + ASSERT_TRUE(model_associator_->SyncModelHasUserCreatedNodes(&has_nodes)); + ASSERT_TRUE(has_nodes); std::string machine_tag = model_associator_->GetCurrentMachineTag(); - int64 sync_id; - ASSERT_TRUE(model_associator_->GetSyncIdForTaggedNode(&machine_tag, - &sync_id)); - ASSERT_EQ(model_associator_->GetSyncIdFromChromeId(machine_tag), sync_id); - scoped_ptr<const sync_pb::SessionSpecifics> sync_specifics( - model_associator_->GetChromeNodeFromSyncId(sync_id)); - ASSERT_TRUE(sync_specifics != NULL); + int64 sync_id = model_associator_->GetSyncIdFromSessionTag(machine_tag); + ASSERT_NE(sync_api::kInvalidId, sync_id); // Check that this machine's data is not included in the foreign windows. - ScopedVector<ForeignSession> foreign_sessions; - model_associator_->GetSessionData(&foreign_sessions.get()); + std::vector<const ForeignSession*> foreign_sessions; + model_associator_->GetAllForeignSessions(&foreign_sessions); ASSERT_EQ(foreign_sessions.size(), 0U); - // Get the windows for this machine from the node and check that they were + // Get the tabs for this machine from the node and check that they were // filled. - sync_api::ReadTransaction trans(sync_service_-> - backend()->GetUserShareHandle()); - sync_api::ReadNode node(&trans); - ASSERT_TRUE(node.InitByClientTagLookup(syncable::SESSIONS, - machine_tag)); - model_associator_->AppendForeignSessionWithID(sync_id, - &foreign_sessions.get(), &trans); - ASSERT_EQ(foreign_sessions.size(), 1U); - ASSERT_EQ(1U, foreign_sessions[0]->windows.size()); - ASSERT_EQ(2U, foreign_sessions[0]->windows[0]->tabs.size()); - ASSERT_EQ(2U, foreign_sessions[0]->windows[0]->tabs[0]->navigations.size()); - ASSERT_EQ(GURL("http://bar/1"), - foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].virtual_url()); - ASSERT_EQ(GURL("http://bar/2"), - foreign_sessions[0]->windows[0]->tabs[0]->navigations[1].virtual_url()); - ASSERT_EQ(2U, foreign_sessions[0]->windows[0]->tabs[1]->navigations.size()); - ASSERT_EQ(GURL("http://foo/1"), - foreign_sessions[0]->windows[0]->tabs[1]->navigations[0].virtual_url()); - ASSERT_EQ(GURL("http://foo/2"), - foreign_sessions[0]->windows[0]->tabs[1]->navigations[1].virtual_url()); - const sync_pb::SessionSpecifics& specifics(node.GetSessionSpecifics()); - ASSERT_EQ(sync_specifics->session_tag(), specifics.session_tag()); - ASSERT_EQ(machine_tag, specifics.session_tag()); + SessionModelAssociator::TabLinksMap tab_map = model_associator_->tab_map_; + ASSERT_EQ(2U, tab_map.size()); + // Tabs are ordered by sessionid in tab_map, so should be able to traverse + // the tree based on order of tabs created + SessionModelAssociator::TabLinksMap::iterator iter = tab_map.begin(); + ASSERT_EQ(2, iter->second.tab()->controller().entry_count()); + ASSERT_EQ(GURL("http://foo/1"), iter->second.tab()->controller(). + GetEntryAtIndex(0)->virtual_url()); + ASSERT_EQ(GURL("http://foo/2"), iter->second.tab()->controller(). + GetEntryAtIndex(1)->virtual_url()); + iter++; + ASSERT_EQ(2, iter->second.tab()->controller().entry_count()); + ASSERT_EQ(GURL("http://bar/1"), iter->second.tab()->controller(). + GetEntryAtIndex(0)->virtual_url()); + ASSERT_EQ(GURL("http://bar/2"), iter->second.tab()->controller(). + GetEntryAtIndex(1)->virtual_url()); } // Test that we fail on a failed model association. @@ -273,13 +252,18 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) { ASSERT_TRUE(has_nodes); // Fill an instance of session specifics with a foreign session's data. - sync_pb::SessionSpecifics specifics; + sync_pb::SessionSpecifics meta_specifics; std::string machine_tag = "session_sync123"; - specifics.set_session_tag(machine_tag); - sync_pb::SessionWindow* window = specifics.add_session_window(); - window->set_selected_tab_index(1); - window->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_NORMAL); - sync_pb::SessionTab* tab = window->add_session_tab(); + meta_specifics.set_session_tag(machine_tag); + sync_pb::SessionHeader* header_s = meta_specifics.mutable_header(); + sync_pb::SessionWindow* window_s = header_s->add_window(); + window_s->add_tab(0); + window_s->set_browser_type(sync_pb::SessionWindow_BrowserType_TYPE_NORMAL); + window_s->set_selected_tab_index(1); + + sync_pb::SessionSpecifics tab_specifics; + tab_specifics.set_session_tag(machine_tag); + sync_pb::SessionTab* tab = tab_specifics.mutable_tab(); tab->set_tab_visual_index(13); tab->set_current_navigation_index(3); tab->set_pinned(true); @@ -293,25 +277,15 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) { // Update the server with the session specifics. { - sync_api::WriteTransaction trans(sync_service_-> - backend()->GetUserShareHandle()); - sync_api::ReadNode root(&trans); - ASSERT_TRUE(root.InitByTagLookup(kSessionsTag)); - model_associator_->UpdateSyncModel(&specifics, &trans, &root); - model_associator_->UpdateFromSyncModel(&trans); + model_associator_->AssociateForeignSpecifics(meta_specifics, 0); + model_associator_->AssociateForeignSpecifics(tab_specifics, 0); } - // Check that the foreign session was written to a node and retrieve the data. - int64 sync_id; - ASSERT_TRUE(model_associator_->GetSyncIdForTaggedNode(&machine_tag, - &sync_id)); - ASSERT_EQ(model_associator_->GetSyncIdFromChromeId(machine_tag), sync_id); - scoped_ptr<const sync_pb::SessionSpecifics> sync_specifics( - model_associator_->GetChromeNodeFromSyncId(sync_id)); - ASSERT_TRUE(sync_specifics != NULL); - ScopedVector<ForeignSession> foreign_sessions; - model_associator_->GetSessionData(&foreign_sessions.get()); + // Check that the foreign session was associated and retrieve the data. + std::vector<const ForeignSession*> foreign_sessions; + model_associator_->GetAllForeignSessions(&foreign_sessions); ASSERT_EQ(1U, foreign_sessions.size()); + ASSERT_EQ(machine_tag, foreign_sessions[0]->foreign_session_tag); ASSERT_EQ(1U, foreign_sessions[0]->windows.size()); ASSERT_EQ(1U, foreign_sessions[0]->windows[0]->tabs.size()); ASSERT_EQ(1U, foreign_sessions[0]->windows[0]->tabs[0]->navigations.size()); @@ -334,14 +308,6 @@ TEST_F(ProfileSyncServiceSessionTest, WriteForeignSessionToNode) { foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].transition()); ASSERT_EQ(GURL("http://foo/1"), foreign_sessions[0]->windows[0]->tabs[0]->navigations[0].virtual_url()); - sync_api::WriteTransaction trans(sync_service_-> - backend()->GetUserShareHandle()); - sync_api::ReadNode node(&trans); - ASSERT_TRUE(node.InitByClientTagLookup(syncable::SESSIONS, - machine_tag)); - const sync_pb::SessionSpecifics& specifics_(node.GetSessionSpecifics()); - ASSERT_EQ(sync_specifics->session_tag(), specifics_.session_tag()); - ASSERT_EQ(machine_tag, specifics_.session_tag()); } // Test the DataTypeController on update. @@ -349,7 +315,7 @@ TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionUpdate) { CreateRootTask task(this); ASSERT_TRUE(StartSyncService(&task, false)); ASSERT_TRUE(task.success()); - int64 node_id = model_associator_->GetSyncIdFromChromeId( + int64 node_id = model_associator_->GetSyncIdFromSessionTag( model_associator_->GetCurrentMachineTag()); scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord); record->action = SyncManager::ChangeRecord::ACTION_UPDATE; @@ -368,7 +334,7 @@ TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionAdd) { ASSERT_TRUE(StartSyncService(&task, false)); ASSERT_TRUE(task.success()); - int64 node_id = model_associator_->GetSyncIdFromChromeId( + int64 node_id = model_associator_->GetSyncIdFromSessionTag( model_associator_->GetCurrentMachineTag()); scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord); record->action = SyncManager::ChangeRecord::ACTION_ADD; @@ -387,7 +353,7 @@ TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionDelete) { ASSERT_TRUE(StartSyncService(&task, false)); ASSERT_TRUE(task.success()); - int64 node_id = model_associator_->GetSyncIdFromChromeId( + int64 node_id = model_associator_->GetSyncIdFromSessionTag( model_associator_->GetCurrentMachineTag()); scoped_ptr<SyncManager::ChangeRecord> record(new SyncManager::ChangeRecord); record->action = SyncManager::ChangeRecord::ACTION_DELETE; @@ -399,6 +365,64 @@ TEST_F(ProfileSyncServiceSessionTest, UpdatedSyncNodeActionDelete) { } ASSERT_TRUE(notified_of_update_); } +// Test the TabNodePool when it starts off empty. +TEST_F(ProfileSyncServiceSessionTest, TabNodePoolEmpty) { + CreateRootTask task(this); + ASSERT_TRUE(StartSyncService(&task, false)); + ASSERT_TRUE(task.success()); + + std::vector<int64> node_ids; + ASSERT_EQ(0U, model_associator_->tab_pool_.capacity()); + ASSERT_TRUE(model_associator_->tab_pool_.empty()); + ASSERT_TRUE(model_associator_->tab_pool_.full()); + const size_t num_ids = 10; + for (size_t i = 0; i < num_ids; ++i) { + int64 id = model_associator_->tab_pool_.GetFreeTabNode(); + ASSERT_GT(id, -1); + node_ids.push_back(id); + } + ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); + ASSERT_TRUE(model_associator_->tab_pool_.empty()); + ASSERT_FALSE(model_associator_->tab_pool_.full()); + for (size_t i = 0; i < num_ids; ++i) { + model_associator_->tab_pool_.FreeTabNode(node_ids[i]); + } + ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); + ASSERT_FALSE(model_associator_->tab_pool_.empty()); + ASSERT_TRUE(model_associator_->tab_pool_.full()); +} + +// Test the TabNodePool when it starts off with nodes +TEST_F(ProfileSyncServiceSessionTest, TabNodePoolNonEmpty) { + CreateRootTask task(this); + ASSERT_TRUE(StartSyncService(&task, false)); + ASSERT_TRUE(task.success()); + + const size_t num_starting_nodes = 3; + for (size_t i = 0; i < num_starting_nodes; ++i) { + model_associator_->tab_pool_.AddTabNode(i); + } + + std::vector<int64> node_ids; + ASSERT_EQ(num_starting_nodes, model_associator_->tab_pool_.capacity()); + ASSERT_FALSE(model_associator_->tab_pool_.empty()); + ASSERT_TRUE(model_associator_->tab_pool_.full()); + const size_t num_ids = 10; + for (size_t i = 0; i < num_ids; ++i) { + int64 id = model_associator_->tab_pool_.GetFreeTabNode(); + ASSERT_GT(id, -1); + node_ids.push_back(id); + } + ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); + ASSERT_TRUE(model_associator_->tab_pool_.empty()); + ASSERT_FALSE(model_associator_->tab_pool_.full()); + for (size_t i = 0; i < num_ids; ++i) { + model_associator_->tab_pool_.FreeTabNode(node_ids[i]); + } + ASSERT_EQ(num_ids, model_associator_->tab_pool_.capacity()); + ASSERT_FALSE(model_associator_->tab_pool_.empty()); + ASSERT_TRUE(model_associator_->tab_pool_.full()); +} } // namespace browser_sync 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 17795c6..98b70c0 100644 --- a/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc +++ b/chrome/browser/sync/profile_sync_service_typed_url_unittest.cc @@ -8,7 +8,7 @@ #include "base/ref_counted.h" #include "base/string16.h" -#include "base/thread.h" +#include "base/threading/thread.h" #include "base/time.h" #include "chrome/browser/history/history_backend.h" #include "chrome/browser/history/history_notifications.h" diff --git a/chrome/browser/sync/profile_sync_test_util.h b/chrome/browser/sync/profile_sync_test_util.h index f6e66ac..4e28efa 100644 --- a/chrome/browser/sync/profile_sync_test_util.h +++ b/chrome/browser/sync/profile_sync_test_util.h @@ -13,8 +13,8 @@ #include "base/ref_counted.h" #include "base/scoped_ptr.h" #include "base/task.h" -#include "base/thread.h" -#include "base/waitable_event.h" +#include "base/threading/thread.h" +#include "base/synchronization/waitable_event.h" #include "base/utf_string_conversions.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/webdata/web_database.h" diff --git a/chrome/browser/sync/protocol/session_specifics.proto b/chrome/browser/sync/protocol/session_specifics.proto index 050687d..5b00b35 100644 --- a/chrome/browser/sync/protocol/session_specifics.proto +++ b/chrome/browser/sync/protocol/session_specifics.proto @@ -13,14 +13,20 @@ package sync_pb; import "sync.proto"; -// Properties of session sync objects. message SessionSpecifics { - // Unique id for the session. + // Unique id for the client. optional string session_tag = 1; + optional SessionHeader header = 2; + optional SessionTab tab = 3; +} +// Properties of session sync objects. +message SessionHeader { // Each session is composed of windows. - repeated SessionWindow session_window = 2; + repeated SessionWindow window = 2; } message SessionWindow { + // Unique (to the owner) id for this window. + optional int32 window_id = 1; // Index of the selected tab in tabs; -1 if no tab is selected. optional int32 selected_tab_index = 2 [default = -1]; // Type of the browser. Currently we only store browsers of type @@ -30,22 +36,26 @@ message SessionWindow { TYPE_POPUP = 2; } optional BrowserType browser_type = 3 [default = TYPE_NORMAL]; - // The tabs that compose a window. - repeated SessionTab session_tab= 4; + // The tabs that compose a window (correspond to tab id's). + repeated int32 tab = 4; } message SessionTab { + // Unique (to the owner) id for this tab. + optional int32 tab_id = 1; + // The unique id for the window this tab belongs to. + optional int32 window_id = 2; // Visual index of the tab within its window. There may be gaps in these // values. - optional int32 tab_visual_index = 2 [default = -1]; + optional int32 tab_visual_index = 3 [default = -1]; // Identifies the index of the current navigation in navigations. For // example, if this is 2 it means the current navigation is navigations[2]. - optional int32 current_navigation_index = 3 [default = -1]; + optional int32 current_navigation_index = 4 [default = -1]; // True if the tab is pinned. - optional bool pinned = 4 [default = false]; + optional bool pinned = 5 [default = false]; // If non-empty, this tab is an app tab and this is the id of the extension. - optional string extension_app_id = 5; + optional string extension_app_id = 6; // Tabs are navigated, and the navigation data is here. - repeated TabNavigation navigation = 6; + repeated TabNavigation navigation = 7; } message TabNavigation { // The index in the NavigationController. If this is -1, it means this diff --git a/chrome/browser/sync/resources/configure.html b/chrome/browser/sync/resources/configure.html index 90a3491..b087169 100644 --- a/chrome/browser/sync/resources/configure.html +++ b/chrome/browser/sync/resources/configure.html @@ -83,13 +83,11 @@ form { } #sync-encryption-instructions { - margin-bottom: 10px; - line-height: 1.8em; + margin-bottom: 5px; } #sync-passphrase-warning { - font-style: italic; - line-height: 1.8em; + margin-bottom: 5px; } #encryption-tab-contents > .sync_item_show { @@ -145,6 +143,26 @@ form { margin-right: 10px; margin-bottom: 10px; } +.sync-section { + background: #EEE; + margin: 5px 0px; + padding: 6px; +} + +#explicit-message { + margin-bottom: 5px; +} + +#change-passphrase { + margin: 10px 0; + background: #EEE; + padding: 8px; +} + +#clear-data-button { + margin-top: 10px; +} + html[dir='rtl'] .sync-footer { text-align: left; left: 0px; @@ -254,9 +272,9 @@ html[os='mac'] input[type='submit'] { args.syncApps; document.getElementById("appsItem").className = "sync-item-show"; } else { - document.getElementById("appsItem").className = "sync-item-hide";
- }
-
+ document.getElementById("appsItem").className = "sync-item-hide"; + } + setCheckboxesToKeepEverythingSynced(args.keepEverythingSynced); if (args.sessionsRegistered) { document.getElementById("sessionsCheckbox").checked = args.syncSessions; @@ -267,13 +285,18 @@ html[os='mac'] input[type='submit'] { } function setEncryptionCheckboxes(args) { - document.getElementById("usePassphraseCheckbox").checked = - args["usePassphrase"]; - - // The passphrase, once set, cannot be unset. if (args["usePassphrase"]) { - document.getElementById("usePassphraseCheckbox").disabled = true; + document.getElementById("explicit-option").checked = true; + + // 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"; + } else { + document.getElementById("google-option").checked = true; + document.getElementById("change-passphrase").style.display = "none"; } + switchToMode(""); } function setErrorState(args) { @@ -298,6 +321,8 @@ 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.) @@ -328,6 +353,10 @@ html[os='mac'] input[type='submit'] { } var f = document.getElementById("chooseDataTypesForm"); + if (!checkPassphraseMatch()) { + return false; + } + var syncAll = f.keepEverythingSyncedRadio.checked; // These values need to be kept in sync with where they are read in // SyncSetupFlow::GetDataTypeChoiceData(). @@ -342,7 +371,8 @@ html[os='mac'] input[type='submit'] { "syncTypedUrls": syncAll || f.typedUrlsCheckbox.checked, "syncApps": syncAll || f.appsCheckbox.checked, "syncSessions": syncAll || f.sessionsCheckbox.checked, - "usePassphrase": document.getElementById("usePassphraseCheckbox").checked + "usePassphrase": (getRadioCheckedValue() == 'explicit'), + "passphrase": f.passphrase.value }); chrome.send("Configure", [result]); } @@ -354,15 +384,73 @@ html[os='mac'] input[type='submit'] { document.getElementById(currentTab + "-tab-contents").className = "sync-config-tab-contents-inactive"; } - + 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"; + } + } + + function getRadioCheckedValue() { + var f = document.getElementById("chooseDataTypesForm"); + for (var i = 0; i < f.option.length; ++i) { + if (f.option[i].checked) { + return f.option[i].value; + } + } + return undefined; + } + + function onRadioChange() { + switchToMode(getRadioCheckedValue()); + } + + function checkPassphraseMatch() { + var emptyError = document.getElementById("emptyerror"); + var mismatchError = document.getElementById("mismatcherror"); + emptyError.style.display = "none"; + mismatchError.style.display = "none"; + + if (getRadioCheckedValue() != "explicit") { + return true; + } + var f = document.getElementById("chooseDataTypesForm"); + if (f.passphrase.value.length == 0) { + emptyError.style.display = "block"; + return false; + } + if (f.confirmpassphrase.value.length > 0 && + f.confirmpassphrase.value != f.passphrase.value) { + mismatchError.style.display = "block"; + return false; + } + return true; + } + + function sendValuesAndClose() { + var result = JSON.stringify({"option": getRadioCheckedValue(), + "passphrase": f.passphrase.value}); + chrome.send("FirstPassphrase", [result]); + } + + function goToDashboard() { + chrome.send("GoToDashboard", [""]); + chrome.send("DialogClose", [""]); + } + </script> </head> <body i18n-values=".style.fontFamily:fontfamily" @@ -381,9 +469,9 @@ html[os='mac'] input[type='submit'] { </div> <div id="data-type-tab-contents" class="sync-config-tab-contents-inactive"> - <div class="sync-header" + <div class="sync-header" i18n-content="choosedatatypesheader"></div> - <div class="sync-choose_data_types_instructions" + <div class="sync-choose_data_types_instructions" i18n-content="choosedatatypesinstructions"></div> <div class="sync-select-customization"> <div class="sync-choice_radio"> @@ -395,7 +483,7 @@ html[os='mac'] input[type='submit'] { </label> </div> <div id="chooseDataTypes" class="sync-choice_radio"> - <input id="chooseDataTypesRadio" type="radio" name="syncChooseDataTypes" + <input id="chooseDataTypesRadio" type="radio" name="syncChooseDataTypes" onclick="setCheckboxesToKeepEverythingSynced(false)"> <label for="chooseDataTypesRadio" i18n-content="choosedatatypes" ></label> <div id="chooseDataTypesBody"> @@ -403,49 +491,49 @@ html[os='mac'] input[type='submit'] { <!-- Apps --> <div class="sync-item-show" id="appsItem"> <input id="appsCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="appsCheckboxLabel" name="dataTypeLabel" + <label id="appsCheckboxLabel" name="dataTypeLabel" for="appsCheckbox" i18n-content="apps" i18n-values="title:apps"></label> </div> <!-- Autofill --> <div class="sync-item-show" id="autofillItem"> <input id="autofillCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="autofillCheckboxLabel" name="dataTypeLabel" + <label id="autofillCheckboxLabel" name="dataTypeLabel" for="autofillCheckbox" i18n-content="autofill" i18n-values="title:autofill"></label> </div> <!-- Bookmarks --> <div class="sync-item-show" id="bookmarksItem"> <input id="bookmarksCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="bookmarksCheckboxLabel" name="dataTypeLabel" + <label id="bookmarksCheckboxLabel" name="dataTypeLabel" for="bookmarksCheckbox" i18n-content="bookmarks" i18n-values="title:bookmarks"></label> </div> <!-- Extensions --> <div class="sync-item-show" id="extensionsItem"> <input id="extensionsCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="extensionsCheckboxLabel" name="dataTypeLabel" + <label id="extensionsCheckboxLabel" name="dataTypeLabel" for="extensionsCheckbox" i18n-content="extensions" i18n-values="title:extensions"></label> </div> <!-- Omnibox --> <div class="sync-item-show" id="omniboxItem"> <input id="typedUrlsCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="typedUrlsCheckboxLabel" name="dataTypeLabel" + <label id="typedUrlsCheckboxLabel" name="dataTypeLabel" for="typedUrlsCheckbox" i18n-content="typedurls" i18n-values="title:typedurls"></label> </div> <!-- Passwords --> <div class="sync-item-show" id="passwordsItem"> <input id="passwordsCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="passwordsCheckboxLabel" name="dataTypeLabel" + <label id="passwordsCheckboxLabel" name="dataTypeLabel" for="passwordsCheckbox" i18n-content="passwords" i18n-values="title:passwords"></label> </div> <!-- Preferences --> <div class="sync-item-show" id="preferencesItem"> <input id="preferencesCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="preferencesCheckboxLabel" name="dataTypeLabel" + <label id="preferencesCheckboxLabel" name="dataTypeLabel" for="preferencesCheckbox" i18n-content="preferences" i18n-values="title:preferences"></label> </div> @@ -458,7 +546,7 @@ html[os='mac'] input[type='submit'] { <!-- Sessions --> <div class="sync-item-show" id="sessionsItem"> <input id="sessionsCheckbox" name="dataTypeCheckbox" type="checkbox"> - <label id="sessionsCheckboxLabel" name="dataTypeLabel" + <label id="sessionsCheckboxLabel" name="dataTypeLabel" for="sessionsCheckbox" i18n-content="foreignsessions" il8n-values="title:sessions"></label> </div> @@ -475,21 +563,58 @@ html[os='mac'] input[type='submit'] { </div> <div id="encryption-tab-contents" class="sync-config-tab-contents-inactive"> - <div id="sync-encryption-instructions" + <div id="sync-encryption-instructions" i18n-content="encryptionInstructions"></div> - - <div class="sync-item-show" id="usePassphrase"> - <input id="usePassphraseCheckbox" name="usePassphraseCheckbox" - type="checkbox" /> - <label id="usePassphraseLabel" name="usePassphraseLabel" - for="usePassphraseCheckbox" i18n-content="usePassphraseLabel"> - </label> + + <div> + <input id="google-option" name="option" type="radio" + value="google" onchange="onRadioChange();"> + <span i18n-content="googleOption"></span> + </input> + </div> + <div> + <input id="explicit-option" name="option" type="radio" + value="explicit" onchange="onRadioChange();"> + <span i18n-content="explicitOption"></span> + </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" onkeyup="checkPassphraseMatch();" + onchange="checkPassphraseMatch();"/> + </div> + <div> + <div i18n-content="confirmLabel" id="confirmPassphraseLabel"> + </div> + <input id="confirmpassphrase" name="confirmpassphrase" type="password" + label="confirmPassphraseLabel" + onkeyup="checkPassphraseMatch();" + onchange="checkPassphraseMatch();" /> + </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="sync-passphrase-warning" i18n-content="passphraseWarning"> + + <div id="change-passphrase"> + <div id="sync-passphrase-warning" i18n-content="passphraseWarning"> + </div> + <div id="sync-reset-confirmation" i18n-content="cleardataconfirmation" + style="display: none;"> + </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" diff --git a/chrome/browser/sync/resources/encryption_login.html b/chrome/browser/sync/resources/encryption_login.html new file mode 100644 index 0000000..7883cca --- /dev/null +++ b/chrome/browser/sync/resources/encryption_login.html @@ -0,0 +1,6 @@ +<!-- This is intentionally an HTML fragment to be embedded + in the browser sign in HTML --> +<div id="login_message"> +<h1>$1</h1> +<p>$2</p> +</div> diff --git a/chrome/browser/sync/resources/firstpassphrase.html b/chrome/browser/sync/resources/firstpassphrase.html new file mode 100644 index 0000000..4f82ef9 --- /dev/null +++ b/chrome/browser/sync/resources/firstpassphrase.html @@ -0,0 +1,204 @@ +<html i18n-values="dir:textdirection;"> +<head> +<title></title> +<style type="text/css"> +body { + line-height: 1.5em; + background: #FFFFFF; + font-size: 11pt; +} +html[os='mac'] body { + line-height: 1.5em; + background: #FFFFFF; +} +form { + -webkit-user-select: none; +} +.error { + color: red; + font-size: 10pt; + } +.sync-header { + font-size: 1.2em; + font-weight: bold; + margin-bottom: 10px; +} +.sync-instructions { + margin-top: 10px; + margin-bottom: 10px; +} +.sync-footer { + position: fixed; + right: 0px; + bottom: 0px; + margin-right: 10px; + margin-bottom: 10px; +} +.sync-section { + background: #EEE; + margin: 5px; + padding: 10px; +} +html[dir='rtl'] .sync-footer { + text-align: left; + left: 0px; + bottom: 0px; + margin-left: 20px; +} +input[type='button'], +input[type='submit'] { + min-width: 87px; + min-height: 26px; +} +html[os='mac'] input[type='button'], +html[os='mac'] input[type='submit'] { + font-size: 12pt; +} + +#passphrase { + margin-top: 5px; +} + +#passphraseLabel, +#confirmPassphraseLabel { + width: 145px; + float: left; + text-align: right; + margin-right: 4px; + padding-top: 3px; +} + +</style> +<script src="chrome://resources/js/cr.js"></script> +<script> + var currentMode; + + // Called once, when this html/js is loaded. + function setupDialog(args) { + // Allow platform specific rules + if (cr.isMac) { + document.documentElement.setAttribute('os', 'mac'); + } else if (!cr.isWindows) { + document.documentElement.setAttribute('os', 'linux'); + } + + switchToMode(""); + } + + function switchToMode(mode) { + document.getElementById("section-explicit").style.display = "none"; + document.getElementById("section-nothanks").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"; + } else if (mode == "nothanks") { + document.getElementById("section-nothanks").style.display = "block"; + } + } + + function getRadioCheckedValue() { + var f = document.getElementById("form"); + for (var i = 0; i < f.option.length; ++i) { + if (f.option[i].checked) { + return f.option[i].value; + } + } + return undefined; + } + + function onRadioChange() { + switchToMode(getRadioCheckedValue()); + } + + function checkPassphraseMatch() { + var emptyError = document.getElementById("emptyerror"); + var mismatchError = document.getElementById("mismatcherror"); + emptyError.style.display = "none"; + mismatchError.style.display = "none"; + + if (getRadioCheckedValue() != "explicit") { + return true; + } + var f = document.getElementById("form"); + if (f.passphrase.value.length == 0) { + emptyError.style.display = "block"; + return false; + } + if (f.confirmpassphrase.value.length > 0 && + f.confirmpassphrase.value != f.passphrase.value) { + mismatchError.style.display = "block"; + return false; + } + return true; + } + + function sendValuesAndClose() { + var f = document.getElementById("form"); + if (!checkPassphraseMatch()) { + return false; + } + + var result = JSON.stringify({"option": getRadioCheckedValue(), + "passphrase": f.passphrase.value}); + chrome.send("FirstPassphrase", [result]); + } +</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" onchange="onRadioChange();"> + <span i18n-content="googleOption"></span> + </input> + </div> + <div> + <input name="option" type="radio" value="explicit" onchange="onRadioChange();"> + <span i18n-content="explicitOption"></span> + </input> + </div> + <div> + <input name="option" type="radio" value="nothanks" onchange="onRadioChange();"> + <span i18n-content="nothanksOption"></span> + </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"></div> + <div> + <div i18n-content="passphraseLabel" id="passphraseLabel"></div> + <input id="passphrase" name="passphrase" label="passphraseLabel" + type="password" onkeyup="checkPassphraseMatch();" + onchange="checkPassphraseMatch();"/> + </div> + <div> + <div i18n-content="confirmLabel" id="confirmPassphraseLabel"> + </div> + <input id="confirmpassphrase" name="confirmpassphrase" type="password" + label="confirmPassphraseLabel" + onkeyup="checkPassphraseMatch();" + onchange="checkPassphraseMatch();" /> + </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-section" id="section-nothanks"> + <div i18n-content="sectionNothanksMessage"></div> + </div> + + <div class="sync-footer"> + <input id="okButton" type="submit" i18n-values="value:ok" /> + </div> +</form> +</body> +</html> diff --git a/chrome/browser/sync/resources/setup_flow.html b/chrome/browser/sync/resources/setup_flow.html index a3de363..495828f 100644 --- a/chrome/browser/sync/resources/setup_flow.html +++ b/chrome/browser/sync/resources/setup_flow.html @@ -3,7 +3,8 @@ <title></title> <script type="text/javascript"> function hideAllPages() { - var pages = ['login', 'configure', 'passphrase', 'settingup', 'done']; + var pages = ['login', 'configure', 'passphrase', + 'settingup', 'done', 'firstpassphrase']; for (var i = 0; i < pages.length; ++i) { document.getElementById(pages[i]).style.display = 'none'; document.getElementById(pages[i]).tabIndex = -1; @@ -21,6 +22,7 @@ function showPassphrase() { showPage('passphrase'); } function showSettingUp() { showPage('settingup'); } function showSetupDone() { showPage('done'); } + function showFirstPassphrase() { showPage('firstpassphrase'); } // Called once, when this html/js is loaded. function showTheRightIframe() { @@ -45,5 +47,8 @@ <iframe id="done" frameborder="0" width="100%" scrolling="no" height="100%" src="chrome://syncresources/setupdone" style="display:none" tabindex="-1"></iframe> + <iframe id="firstpassphrase" frameborder="0" width="100%" scrolling="no" + height="100%" src="chrome://syncresources/firstpassphrase" + style="display:none" tabindex="-1"></iframe> </body> </html> diff --git a/chrome/browser/sync/sync_setup_flow.cc b/chrome/browser/sync/sync_setup_flow.cc index a6dd5ec..adb06ea 100644 --- a/chrome/browser/sync/sync_setup_flow.cc +++ b/chrome/browser/sync/sync_setup_flow.cc @@ -44,6 +44,10 @@ void FlowHandler::RegisterMessages() { NewCallback(this, &FlowHandler::HandleConfigure)); dom_ui_->RegisterMessageCallback("Passphrase", NewCallback(this, &FlowHandler::HandlePassphraseEntry)); + dom_ui_->RegisterMessageCallback("FirstPassphrase", + NewCallback(this, &FlowHandler::HandleFirstPassphrase)); + dom_ui_->RegisterMessageCallback("GoToDashboard", + NewCallback(this, &FlowHandler::HandleGoToDashboard)); } static bool GetAuthData(const std::string& json, @@ -76,6 +80,18 @@ bool GetPassphrase(const std::string& json, std::string* passphrase, result->GetString("mode", mode); } +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)); @@ -145,6 +161,9 @@ static bool GetConfiguration(const std::string& json, // 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; } @@ -204,6 +223,28 @@ void FlowHandler::HandlePassphraseEntry(const ListValue* args) { flow_->OnPassphraseEntry(passphrase, mode); } +void FlowHandler::HandleFirstPassphrase(const ListValue* args) { + std::string json(dom_ui_util::GetJsonResponseFromFirstArgumentInList(args)); + + 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; + } + + 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 @@ -258,6 +299,17 @@ void FlowHandler::ShowPassphraseEntry(const DictionaryValue& args) { ExecuteJavascriptInIFrame(kPassphraseIFrameXPath, script); } +void FlowHandler::ShowFirstPassphrase(const DictionaryValue& args) { + if (dom_ui_) + dom_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); +} + void FlowHandler::ShowSettingUp() { if (dom_ui_) dom_ui_->CallJavascriptFunction(L"showSettingUp"); @@ -306,7 +358,6 @@ SyncSetupFlow::SyncSetupFlow(SyncSetupWizard::State start_state, login_start_time_(base::TimeTicks::Now()), flow_handler_(new FlowHandler()), owns_flow_handler_(true), - configuration_pending_(false), service_(service), html_dialog_window_(NULL) { flow_handler_->set_flow(this); @@ -390,7 +441,8 @@ void SyncSetupFlow::OnDialogClosed(const std::string& json_retval) { } std::wstring SyncSetupFlow::GetDialogTitle() const { - return l10n_util::GetString(IDS_SYNC_MY_BOOKMARKS_LABEL); + return UTF16ToWideHack( + l10n_util::GetStringUTF16(IDS_SYNC_MY_BOOKMARKS_LABEL)); } bool SyncSetupFlow::IsDialogModal() const { @@ -502,8 +554,6 @@ bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) { return current_state_ == SyncSetupWizard::GAIA_LOGIN; case SyncSetupWizard::CONFIGURE: return current_state_ == SyncSetupWizard::GAIA_SUCCESS; - case SyncSetupWizard::CREATE_PASSPHRASE: - return current_state_ == SyncSetupWizard::CONFIGURE; case SyncSetupWizard::ENTER_PASSPHRASE: return current_state_ == SyncSetupWizard::CONFIGURE || current_state_ == SyncSetupWizard::SETTING_UP; @@ -511,8 +561,8 @@ bool SyncSetupFlow::ShouldAdvance(SyncSetupWizard::State state) { return current_state_ == SyncSetupWizard::CONFIGURE; case SyncSetupWizard::SETTING_UP: return current_state_ == SyncSetupWizard::CONFIGURE || - current_state_ == SyncSetupWizard::CREATE_PASSPHRASE || - current_state_ == SyncSetupWizard::ENTER_PASSPHRASE; + current_state_ == SyncSetupWizard::ENTER_PASSPHRASE || + current_state_ == SyncSetupWizard::PASSPHRASE_MIGRATION; case SyncSetupWizard::FATAL_ERROR: return true; // You can always hit the panic button. case SyncSetupWizard::DONE_FIRST_TIME: @@ -553,16 +603,16 @@ void SyncSetupFlow::Advance(SyncSetupWizard::State advance_state) { flow_handler_->ShowConfigure(args); break; } - case SyncSetupWizard::CREATE_PASSPHRASE: { + case SyncSetupWizard::ENTER_PASSPHRASE: { DictionaryValue args; - args.SetString("mode", "new"); + SyncSetupFlow::GetArgsForEnterPassphrase(service_, &args); flow_handler_->ShowPassphraseEntry(args); break; } - case SyncSetupWizard::ENTER_PASSPHRASE: { + case SyncSetupWizard::PASSPHRASE_MIGRATION: { DictionaryValue args; - SyncSetupFlow::GetArgsForEnterPassphrase(service_, &args); - flow_handler_->ShowPassphraseEntry(args); + args.SetString("iframeToShow", "firstpassphrase"); + flow_handler_->ShowFirstPassphrase(args); break; } case SyncSetupWizard::SETUP_ABORTED_BY_PENDING_CLEAR: { @@ -629,6 +679,9 @@ SyncSetupFlow* SyncSetupFlow::Run(ProfileSyncService* service, 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); @@ -663,56 +716,47 @@ void SyncSetupFlow::OnUserSubmittedAuth(const std::string& username, } void SyncSetupFlow::OnUserConfigured(const SyncConfiguration& configuration) { - // Store the configuration in case we need more information. - configuration_ = configuration; - configuration_pending_ = true; - - // If the user is activating secondary passphrase for the first time, - // we need to prompt them to enter one. - if (configuration.use_secondary_passphrase && - !service_->IsUsingSecondaryPassphrase()) { - // TODO(tim): If we could download the Nigori node first before any other - // types, we could do that prior to showing the configure page so that we - // could pre-populate the 'Use an encryption passphrase' checkbox. - // http://crbug.com/60182 - Advance(SyncSetupWizard::CREATE_PASSPHRASE); - return; - } - - OnConfigurationComplete(); -} - -void SyncSetupFlow::OnConfigurationComplete() { - if (!configuration_pending_) - return; - // 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); + !configuration.use_secondary_passphrase || + configuration.secondary_passphrase.length() > 0); - if (configuration_.use_secondary_passphrase && + if (configuration.use_secondary_passphrase && !service_->IsUsingSecondaryPassphrase()) { - service_->SetPassphrase(configuration_.secondary_passphrase, true); + service_->SetPassphrase(configuration.secondary_passphrase, true); } - service_->OnUserChoseDatatypes(configuration_.sync_everything, - configuration_.data_types); - - configuration_pending_ = false; + service_->OnUserChoseDatatypes(configuration.sync_everything, + configuration.data_types); } void SyncSetupFlow::OnPassphraseEntry(const std::string& passphrase, const std::string& mode) { - if (current_state_ == SyncSetupWizard::ENTER_PASSPHRASE) { - service_->SetPassphrase(passphrase, mode == std::string("enter")); - Advance(SyncSetupWizard::SETTING_UP); - } else if (configuration_pending_) { - DCHECK_EQ(SyncSetupWizard::CREATE_PASSPHRASE, current_state_); - configuration_.secondary_passphrase = passphrase; - OnConfigurationComplete(); + Advance(SyncSetupWizard::SETTING_UP); + service_->SetPassphrase(passphrase, true); +} + +void SyncSetupFlow::OnFirstPassphraseEntry(const std::string& option, + const std::string& passphrase) { + Advance(SyncSetupWizard::SETTING_UP); + if (option == "explicit") { + service_->SetPassphrase(passphrase, true); + } else if (option == "nothanks") { + // User opted out of encrypted sync, need to turn off encrypted + // data types. + syncable::ModelTypeSet registered_types; + service_->GetRegisteredDataTypes(®istered_types); + registered_types.erase(syncable::PASSWORDS); + service_->OnUserChoseDatatypes(false, registered_types); + } else if (option == "google") { + // Implicit passphrase already set up. + Advance(SyncSetupWizard::DONE); } } + +void SyncSetupFlow::OnGoToDashboard() { + BrowserList::GetLastActive()->OpenPrivacyDashboardTabAndActivate(); +} diff --git a/chrome/browser/sync/sync_setup_flow.h b/chrome/browser/sync/sync_setup_flow.h index 8bfc443..f6c0233 100644 --- a/chrome/browser/sync/sync_setup_flow.h +++ b/chrome/browser/sync/sync_setup_flow.h @@ -110,7 +110,10 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { void OnPassphraseEntry(const std::string& passphrase, const std::string& mode); - void OnConfigurationComplete(); + void OnFirstPassphraseEntry(const std::string& option, + const std::string& passphrase); + + void OnGoToDashboard(); private: FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, InitialStepLogin); @@ -124,6 +127,7 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, DiscreteRunChooseDataTypesAbortedByPendingClear); FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, EnterPassphraseRequired); + FRIEND_TEST_ALL_PREFIXES(SyncSetupWizardTest, PassphraseMigration); // Use static Run method to get an instance. SyncSetupFlow(SyncSetupWizard::State start_state, @@ -149,11 +153,6 @@ class SyncSetupFlow : public HtmlDialogUIDelegate { FlowHandler* flow_handler_; mutable bool owns_flow_handler_; - // The current configuration, held pending until all the information has - // been populated (possibly using multiple dialog states). - SyncConfiguration configuration_; - bool configuration_pending_; - // We need this to write the sentinel "setup completed" pref. ProfileSyncService* service_; @@ -198,6 +197,8 @@ class FlowHandler : public DOMMessageHandler { void HandleSubmitAuth(const ListValue* args); void HandleConfigure(const ListValue* args); void HandlePassphraseEntry(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); @@ -205,6 +206,7 @@ class FlowHandler : public DOMMessageHandler { 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); diff --git a/chrome/browser/sync/sync_setup_wizard.cc b/chrome/browser/sync/sync_setup_wizard.cc index e80afe0..6e9d254 100644 --- a/chrome/browser/sync/sync_setup_wizard.cc +++ b/chrome/browser/sync/sync_setup_wizard.cc @@ -79,6 +79,7 @@ void SyncResourcesSource::StartDataRequest(const std::string& path_raw, const char kSyncGaiaLoginPath[] = "gaialogin"; const char kSyncConfigurePath[] = "configure"; const char kSyncPassphrasePath[] = "passphrase"; + const char kSyncFirstPassphrasePath[] = "firstpassphrase"; const char kSyncSettingUpPath[] = "settingup"; const char kSyncSetupDonePath[] = "setupdone"; @@ -149,23 +150,48 @@ void SyncResourcesSource::StartDataRequest(const std::string& path_raw, GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, GetStringUTF16(IDS_PRODUCT_NAME))); AddString(dict, "encryptAllLabel", IDS_SYNC_ENCRYPT_ALL_LABEL); - AddString(dict, "usePassphraseLabel", IDS_SYNC_PASSPHRASE_CHECKBOX_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); // 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, "newPassphraseTitle", IDS_SYNC_NEW_PASSPHRASE_TITLE); - AddString(dict, "newPassphraseBody", IDS_SYNC_NEW_PASSPHRASE_BODY); AddString(dict, "enterPassphraseTitle", IDS_SYNC_ENTER_PASSPHRASE_TITLE); AddString(dict, "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY); - AddString(dict, "gaiaPassphraseTitle", IDS_SYNC_GAIA_PASSPHRASE_TITLE); - AddString(dict, "gaiaPassphraseBody", IDS_SYNC_GAIA_PASSPHRASE_BODY); AddString(dict, "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL); 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, "nothanksOption", IDS_SYNC_PASSPHRASE_OPT_CANCEL); + AddString(dict, "sectionGoogleMessage", IDS_SYNC_PASSPHRASE_MSG_GOOGLE); + AddString(dict, "sectionExplicitMessage", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT); + AddString(dict, "sectionNothanksMessage", IDS_SYNC_PASSPHRASE_MSG_CANCEL); + 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, "ok", IDS_OK); + AddString(dict, "cancel", IDS_CANCEL); } else if (path_raw == kSyncSettingUpPath) { html_resource_id = IDR_SYNC_SETTING_UP_HTML; @@ -279,9 +305,9 @@ SyncSetupWizard::State SyncSetupWizard::GetEndStateForDiscreteRun( State result = FATAL_ERROR; if (start_state == GAIA_LOGIN) { result = GAIA_SUCCESS; - } else if (start_state == ENTER_PASSPHRASE) { - result = DONE; - } else if (start_state == CONFIGURE) { + } else if (start_state == ENTER_PASSPHRASE || + start_state == CONFIGURE || + start_state == PASSPHRASE_MIGRATION) { result = DONE; } DCHECK_NE(FATAL_ERROR, result) << diff --git a/chrome/browser/sync/sync_setup_wizard.h b/chrome/browser/sync/sync_setup_wizard.h index dad3fa9..76b6fae 100644 --- a/chrome/browser/sync/sync_setup_wizard.h +++ b/chrome/browser/sync/sync_setup_wizard.h @@ -29,10 +29,11 @@ class SyncSetupWizard { // Encryption -- // Choose what to encrypt and whether to use a passphrase. CONFIGURE, - // Show the screen that lets you enter a new passphrase - CREATE_PASSPHRASE, // Show the screen that prompts for your passphrase ENTER_PASSPHRASE, + // Show the passphrase "first time" screen for migrating users, where all + // is explained and they choose between google password/custom passphrase. + PASSPHRASE_MIGRATION, // The panic switch. Something went terribly wrong during setup and we // can't recover. FATAL_ERROR, diff --git a/chrome/browser/sync/sync_setup_wizard_unittest.cc b/chrome/browser/sync/sync_setup_wizard_unittest.cc index 46a0d69..b0da0d7 100644 --- a/chrome/browser/sync/sync_setup_wizard_unittest.cc +++ b/chrome/browser/sync/sync_setup_wizard_unittest.cc @@ -312,6 +312,7 @@ TEST_F(SyncSetupWizardTest, InitialStepLogin) { TEST_F(SyncSetupWizardTest, ChooseDataTypesSetsPrefs) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); + wizard_->Step(SyncSetupWizard::GAIA_SUCCESS); wizard_->Step(SyncSetupWizard::CONFIGURE); ListValue data_type_choices_value; @@ -359,6 +360,22 @@ TEST_F(SyncSetupWizardTest, EnterPassphraseRequired) { EXPECT_EQ("myPassphrase", service_->passphrase_); } +TEST_F(SyncSetupWizardTest, PassphraseMigration) { + SKIP_TEST_ON_MACOSX(); + wizard_->Step(SyncSetupWizard::PASSPHRASE_MIGRATION); + ListValue value; + value.Append(new StringValue("{\"option\":\"explicit\"," + "\"passphrase\":\"myPassphrase\"}")); + test_window_->flow()->flow_handler_->HandleFirstPassphrase(&value); + EXPECT_EQ("myPassphrase", service_->passphrase_); + + ListValue value2; + value2.Append(new StringValue("{\"option\":\"nothanks\"," + "\"passphrase\":\"myPassphrase\"}")); + test_window_->flow()->flow_handler_->HandleFirstPassphrase(&value2); + EXPECT_EQ(service_->chosen_data_types_.count(syncable::PASSWORDS), 0U); +} + TEST_F(SyncSetupWizardTest, DialogCancelled) { SKIP_TEST_ON_MACOSX(); wizard_->Step(SyncSetupWizard::GAIA_LOGIN); diff --git a/chrome/browser/sync/sync_ui_util.cc b/chrome/browser/sync/sync_ui_util.cc index 27fdb9a..d49ada1 100644 --- a/chrome/browser/sync/sync_ui_util.cc +++ b/chrome/browser/sync/sync_ui_util.cc @@ -5,6 +5,7 @@ #include "chrome/browser/sync/sync_ui_util.h" #include "app/l10n_util.h" +#include "app/resource_bundle.h" #include "base/i18n/number_formatting.h" #include "base/i18n/time_formatting.h" #include "base/string_util.h" @@ -13,6 +14,7 @@ #include "chrome/browser/sync/profile_sync_service.h" #include "chrome/browser/ui/options/options_window.h" #include "chrome/common/net/gaia/google_service_auth_error.h" +#include "grit/browser_resources.h" #include "grit/chromium_strings.h" #include "grit/generated_resources.h" @@ -102,8 +104,28 @@ MessageType GetStatusInfo(ProfileSyncService* service, l10n_util::GetStringUTF16(IDS_SYNC_AUTHENTICATING_LABEL)); } result_type = PRE_SYNCED; - } else if (auth_error.state() != AuthError::NONE || - service->observed_passphrase_required()) { + } else if (service->observed_passphrase_required()) { + if (service->passphrase_required_for_decryption()) { + // NOT first machine. + // Show a link and present as an error ("needs attention"). + if (status_label && link_label) { + status_label->assign(string16()); + link_label->assign( + l10n_util::GetStringUTF16(IDS_SYNC_CONFIGURE_ENCRYPTION)); + } + result_type = SYNC_ERROR; + } else { + // First machine. Show as a promotion. + if (status_label && link_label) { + status_label->assign( + l10n_util::GetStringFUTF16(IDS_SYNC_NTP_PASSWORD_PROMO, + l10n_util::GetStringUTF16(IDS_PRODUCT_NAME))); + link_label->assign( + l10n_util::GetStringUTF16(IDS_SYNC_NTP_PASSWORD_ENABLE)); + } + result_type = SYNC_PROMO; + } + } else if (auth_error.state() != AuthError::NONE) { if (status_label && link_label) { GetStatusLabelsForAuthError(auth_error, service, status_label, link_label); @@ -156,6 +178,20 @@ MessageType GetStatusInfo(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) { @@ -322,6 +358,12 @@ void ConstructAboutInformation(ProfileSyncService* service, std::string location_str; loc.Write(true, true, &location_str); strings->SetString("unrecoverable_error_location", location_str); + } else if (!service->sync_initialized()) { + strings->SetString("summary", "Sync not yet initialized"); + } else if (service->backend() == NULL) { + strings->SetString("summary", + "Unrecoverable error detected. Backend is null when it shouldnt be"); + NOTREACHED(); } else { browser_sync::ModelSafeRoutingInfo routes; service->backend()->GetModelSafeRoutingInfo(&routes); diff --git a/chrome/browser/sync/sync_ui_util.h b/chrome/browser/sync/sync_ui_util.h index 8bcd85a..5a168c4 100644 --- a/chrome/browser/sync/sync_ui_util.h +++ b/chrome/browser/sync/sync_ui_util.h @@ -24,11 +24,15 @@ enum MessageType { PRE_SYNCED, // User has not set up sync. SYNCED, // We are synced and authenticated to a gmail account. SYNC_ERROR, // A sync error (such as invalid credentials) has occurred. + SYNC_PROMO, // A situation has occurred which should be brought to the user's + // attention, but not as an error. }; // 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, @@ -60,4 +64,3 @@ void AddIntSyncDetail(ListValue* details, int64 stat_value); } // namespace sync_ui_util #endif // CHROME_BROWSER_SYNC_SYNC_UI_UTIL_H_ - diff --git a/chrome/browser/sync/syncable/syncable_unittest.cc b/chrome/browser/sync/syncable/syncable_unittest.cc index 32a4cc3..6c887b2 100644 --- a/chrome/browser/sync/syncable/syncable_unittest.cc +++ b/chrome/browser/sync/syncable/syncable_unittest.cc @@ -23,10 +23,10 @@ #include "base/file_path.h" #include "base/file_util.h" #include "base/logging.h" -#include "base/platform_thread.h" #include "base/scoped_ptr.h" #include "base/scoped_temp_dir.h" #include "base/string_util.h" +#include "base/threading/platform_thread.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" @@ -386,7 +386,7 @@ TEST_F(SyncableDirectoryTest, TakeSnapshotGetsMetahandlesToPurge) { dir_->PurgeEntriesWithTypeIn(to_purge); Directory::SaveChangesSnapshot snapshot1; - AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); + base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); dir_->TakeSnapshotForSaveChanges(&snapshot1); EXPECT_TRUE(expected_purges == snapshot1.metahandles_to_purge); @@ -415,7 +415,7 @@ TEST_F(SyncableDirectoryTest, TakeSnapshotGetsAllDirtyHandlesTest) { // Fake SaveChanges() and make sure we got what we expected. { Directory::SaveChangesSnapshot snapshot; - AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); + base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); dir_->TakeSnapshotForSaveChanges(&snapshot); // Make sure there's an entry for each new metahandle. Make sure all // entries are marked dirty. @@ -448,7 +448,7 @@ TEST_F(SyncableDirectoryTest, TakeSnapshotGetsAllDirtyHandlesTest) { // Fake SaveChanges() and make sure we got what we expected. { Directory::SaveChangesSnapshot snapshot; - AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); + base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); dir_->TakeSnapshotForSaveChanges(&snapshot); // Make sure there's an entry for each new metahandle. Make sure all // entries are marked dirty. @@ -588,7 +588,7 @@ TEST_F(SyncableDirectoryTest, TakeSnapshotGetsOnlyDirtyHandlesTest) { // Fake SaveChanges() and make sure we got what we expected. { Directory::SaveChangesSnapshot snapshot; - AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); + base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); dir_->TakeSnapshotForSaveChanges(&snapshot); // Make sure there are no dirty_metahandles. EXPECT_EQ(0u, snapshot.dirty_metas.size()); @@ -614,7 +614,7 @@ TEST_F(SyncableDirectoryTest, TakeSnapshotGetsOnlyDirtyHandlesTest) { // Fake SaveChanges() and make sure we got what we expected. { Directory::SaveChangesSnapshot snapshot; - AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); + base::AutoLock scoped_lock(dir_->kernel_->save_changes_mutex); dir_->TakeSnapshotForSaveChanges(&snapshot); // Make sure there's an entry for each changed metahandle. Make sure all // entries are marked dirty. @@ -1356,7 +1356,7 @@ TEST(SyncableDirectoryManager, TestFileRelease) { ASSERT_TRUE(file_util::Delete(dm.GetSyncDataDatabasePath(), true)); } -class ThreadOpenTestDelegate : public PlatformThread::Delegate { +class ThreadOpenTestDelegate : public base::PlatformThread::Delegate { public: explicit ThreadOpenTestDelegate(DirectoryManager* dm) : directory_manager_(dm) {} @@ -1373,10 +1373,10 @@ class ThreadOpenTestDelegate : public PlatformThread::Delegate { TEST(SyncableDirectoryManager, ThreadOpenTest) { DirectoryManager dm(FilePath(FILE_PATH_LITERAL("."))); - PlatformThreadHandle thread_handle; + base::PlatformThreadHandle thread_handle; ThreadOpenTestDelegate test_delegate(&dm); - ASSERT_TRUE(PlatformThread::Create(0, &test_delegate, &thread_handle)); - PlatformThread::Join(thread_handle); + ASSERT_TRUE(base::PlatformThread::Create(0, &test_delegate, &thread_handle)); + base::PlatformThread::Join(thread_handle); { ScopedDirLookup dir(&dm, "Open"); ASSERT_TRUE(dir.good()); @@ -1389,13 +1389,13 @@ TEST(SyncableDirectoryManager, ThreadOpenTest) { struct Step { Step() : condvar(&mutex), number(0) {} - Lock mutex; - ConditionVariable condvar; + base::Lock mutex; + base::ConditionVariable condvar; int number; int64 metahandle; }; -class ThreadBugDelegate : public PlatformThread::Delegate { +class ThreadBugDelegate : public base::PlatformThread::Delegate { public: // a role is 0 or 1, meaning this thread does the odd or event steps. ThreadBugDelegate(int role, Step* step, DirectoryManager* dirman) @@ -1409,7 +1409,7 @@ class ThreadBugDelegate : public PlatformThread::Delegate { // PlatformThread::Delegate methods: virtual void ThreadMain() { const std::string dirname = "ThreadBug1"; - AutoLock scoped_lock(step_->mutex); + base::AutoLock scoped_lock(step_->mutex); while (step_->number < 3) { while (step_->number % 2 != role_) { @@ -1457,14 +1457,16 @@ TEST(SyncableDirectoryManager, ThreadBug1) { ThreadBugDelegate thread_delegate_1(0, &step, &dirman); ThreadBugDelegate thread_delegate_2(1, &step, &dirman); - PlatformThreadHandle thread_handle_1; - PlatformThreadHandle thread_handle_2; + base::PlatformThreadHandle thread_handle_1; + base::PlatformThreadHandle thread_handle_2; - ASSERT_TRUE(PlatformThread::Create(0, &thread_delegate_1, &thread_handle_1)); - ASSERT_TRUE(PlatformThread::Create(0, &thread_delegate_2, &thread_handle_2)); + ASSERT_TRUE( + base::PlatformThread::Create(0, &thread_delegate_1, &thread_handle_1)); + ASSERT_TRUE( + base::PlatformThread::Create(0, &thread_delegate_2, &thread_handle_2)); - PlatformThread::Join(thread_handle_1); - PlatformThread::Join(thread_handle_2); + base::PlatformThread::Join(thread_handle_1); + base::PlatformThread::Join(thread_handle_2); } @@ -1480,7 +1482,7 @@ class DirectoryKernelStalenessBugDelegate : public ThreadBugDelegate { virtual void ThreadMain() { const char test_bytes[] = "test data"; const std::string dirname = "DirectoryKernelStalenessBug"; - AutoLock scoped_lock(step_->mutex); + base::AutoLock scoped_lock(step_->mutex); const Id jeff_id = TestIdFactory::FromNumber(100); while (step_->number < 4) { @@ -1552,17 +1554,19 @@ TEST(SyncableDirectoryManager, DirectoryKernelStalenessBug) { DirectoryKernelStalenessBugDelegate thread_delegate_1(0, &step, &dirman); DirectoryKernelStalenessBugDelegate thread_delegate_2(1, &step, &dirman); - PlatformThreadHandle thread_handle_1; - PlatformThreadHandle thread_handle_2; + base::PlatformThreadHandle thread_handle_1; + base::PlatformThreadHandle thread_handle_2; - ASSERT_TRUE(PlatformThread::Create(0, &thread_delegate_1, &thread_handle_1)); - ASSERT_TRUE(PlatformThread::Create(0, &thread_delegate_2, &thread_handle_2)); + ASSERT_TRUE( + base::PlatformThread::Create(0, &thread_delegate_1, &thread_handle_1)); + ASSERT_TRUE( + base::PlatformThread::Create(0, &thread_delegate_2, &thread_handle_2)); - PlatformThread::Join(thread_handle_1); - PlatformThread::Join(thread_handle_2); + base::PlatformThread::Join(thread_handle_1); + base::PlatformThread::Join(thread_handle_2); } -class StressTransactionsDelegate : public PlatformThread::Delegate { +class StressTransactionsDelegate : public base::PlatformThread::Delegate { public: StressTransactionsDelegate(DirectoryManager* dm, const std::string& dirname, @@ -1588,7 +1592,7 @@ class StressTransactionsDelegate : public PlatformThread::Delegate { if (rand_action < 4 && !path_name.empty()) { ReadTransaction trans(dir, __FILE__, __LINE__); CHECK(1 == CountEntriesWithName(&trans, trans.root_id(), path_name)); - PlatformThread::Sleep(rand() % 10); + base::PlatformThread::Sleep(rand() % 10); } else { std::string unique_name = StringPrintf("%d.%d", thread_number_, entry_count++); @@ -1596,7 +1600,7 @@ class StressTransactionsDelegate : public PlatformThread::Delegate { WriteTransaction trans(dir, UNITTEST, __FILE__, __LINE__); MutableEntry e(&trans, CREATE, trans.root_id(), path_name); CHECK(e.good()); - PlatformThread::Sleep(rand() % 20); + base::PlatformThread::Sleep(rand() % 20); e.Put(IS_UNSYNCED, true); if (e.Put(ID, TestIdFactory::FromNumber(rand())) && e.Get(ID).ServerKnows() && !e.Get(ID).IsRoot()) { @@ -1616,18 +1620,18 @@ TEST(SyncableDirectory, StressTransactions) { dirman.Open(dirname); const int kThreadCount = 7; - PlatformThreadHandle threads[kThreadCount]; + base::PlatformThreadHandle threads[kThreadCount]; scoped_ptr<StressTransactionsDelegate> thread_delegates[kThreadCount]; for (int i = 0; i < kThreadCount; ++i) { thread_delegates[i].reset( new StressTransactionsDelegate(&dirman, dirname, i)); - ASSERT_TRUE( - PlatformThread::Create(0, thread_delegates[i].get(), &threads[i])); + ASSERT_TRUE(base::PlatformThread::Create( + 0, thread_delegates[i].get(), &threads[i])); } for (int i = 0; i < kThreadCount; ++i) { - PlatformThread::Join(threads[i]); + base::PlatformThread::Join(threads[i]); } dirman.Close(dirname); diff --git a/chrome/browser/sync/test_profile_sync_service.h b/chrome/browser/sync/test_profile_sync_service.h index f5da3d6..2615b24 100644 --- a/chrome/browser/sync/test_profile_sync_service.h +++ b/chrome/browser/sync/test_profile_sync_service.h @@ -112,6 +112,12 @@ class SyncBackendHostForProfileSyncTest : public SyncBackendHost { } } + virtual void ConfigureDataTypes(const syncable::ModelTypeSet& types, + CancelableTask* ready_task) { + SetAutofillMigrationState(syncable::MIGRATED); + SyncBackendHost::ConfigureDataTypes(types, ready_task); + } + virtual void HandleInitializationCompletedOnFrontendLoop() { set_syncapi_initialized(); // Need to do this asap so task below works. diff --git a/chrome/browser/sync/tools/sync_listen_notifications.cc b/chrome/browser/sync/tools/sync_listen_notifications.cc index d15ded3..0f66c27 100644 --- a/chrome/browser/sync/tools/sync_listen_notifications.cc +++ b/chrome/browser/sync/tools/sync_listen_notifications.cc @@ -299,7 +299,6 @@ int main(int argc, char* argv[]) { xmpp_client_settings.set_server(addr); net::CertVerifier cert_verifier; - MessageLoopForIO message_loop; // Connect and listen. diff --git a/chrome/browser/sync/util/channel.h b/chrome/browser/sync/util/channel.h index a21f6ea..88ddfc4 100644 --- a/chrome/browser/sync/util/channel.h +++ b/chrome/browser/sync/util/channel.h @@ -52,7 +52,7 @@ #include "base/lock.h" #include "base/observer_list.h" -#include "base/platform_thread.h" +#include "base/threading/platform_thread.h" namespace browser_sync { @@ -106,7 +106,7 @@ class Channel { 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_ != PlatformThread::CurrentId()); + bool need_lock = (locking_thread_ != base::PlatformThread::CurrentId()); if (need_lock) event_handlers_mutex_.Acquire(); @@ -121,7 +121,7 @@ class Channel { // This may result in an observer trying to remove itself, so keep track // of the thread we're locked on. - locking_thread_ = PlatformThread::CurrentId(); + locking_thread_ = base::PlatformThread::CurrentId(); ChannelObserverList::Iterator it(event_handlers_); EventHandler* obs; @@ -136,7 +136,7 @@ class Channel { private: Lock event_handlers_mutex_; - PlatformThreadId locking_thread_; + base::PlatformThreadId locking_thread_; ObserverList<EventHandler> event_handlers_; }; diff --git a/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc index d5f88d4..b85256e 100644 --- a/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc +++ b/chrome/browser/sync/util/extensions_activity_monitor_unittest.cc @@ -6,7 +6,7 @@ #include "base/file_path.h" #include "base/string_util.h" -#include "base/waitable_event.h" +#include "base/synchronization/waitable_event.h" #include "base/values.h" #include "chrome/browser/browser_thread.h" #include "chrome/browser/extensions/extension_bookmarks_module.h" |