| // Copyright (c) 2012 The Chromium Authors. All rights reserved. | 
 | // Use of this source code is governed by a BSD-style license that can be | 
 | // found in the LICENSE file. | 
 |  | 
 | #include "components/sync/engine_impl/sync_manager_impl.h" | 
 |  | 
 | #include <stddef.h> | 
 |  | 
 | #include <utility> | 
 |  | 
 | #include "base/base64.h" | 
 | #include "base/bind.h" | 
 | #include "base/callback.h" | 
 | #include "base/compiler_specific.h" | 
 | #include "base/json/json_writer.h" | 
 | #include "base/memory/ptr_util.h" | 
 | #include "base/memory/ref_counted.h" | 
 | #include "base/metrics/histogram_macros.h" | 
 | #include "base/observer_list.h" | 
 | #include "base/strings/string_number_conversions.h" | 
 | #include "base/threading/thread_task_runner_handle.h" | 
 | #include "base/values.h" | 
 | #include "components/sync/base/cancelation_signal.h" | 
 | #include "components/sync/base/experiments.h" | 
 | #include "components/sync/base/invalidation_interface.h" | 
 | #include "components/sync/base/model_type.h" | 
 | #include "components/sync/engine/configure_reason.h" | 
 | #include "components/sync/engine/engine_components_factory.h" | 
 | #include "components/sync/engine/engine_util.h" | 
 | #include "components/sync/engine/net/http_post_provider_factory.h" | 
 | #include "components/sync/engine/polling_constants.h" | 
 | #include "components/sync/engine_impl/cycle/directory_type_debug_info_emitter.h" | 
 | #include "components/sync/engine_impl/loopback_server/loopback_connection_manager.h" | 
 | #include "components/sync/engine_impl/model_type_connector_proxy.h" | 
 | #include "components/sync/engine_impl/net/sync_server_connection_manager.h" | 
 | #include "components/sync/engine_impl/sync_scheduler.h" | 
 | #include "components/sync/engine_impl/syncer_types.h" | 
 | #include "components/sync/engine_impl/uss_migrator.h" | 
 | #include "components/sync/protocol/proto_value_conversions.h" | 
 | #include "components/sync/protocol/sync.pb.h" | 
 | #include "components/sync/syncable/base_node.h" | 
 | #include "components/sync/syncable/directory.h" | 
 | #include "components/sync/syncable/entry.h" | 
 | #include "components/sync/syncable/in_memory_directory_backing_store.h" | 
 | #include "components/sync/syncable/on_disk_directory_backing_store.h" | 
 | #include "components/sync/syncable/read_node.h" | 
 | #include "components/sync/syncable/read_transaction.h" | 
 | #include "components/sync/syncable/write_node.h" | 
 | #include "components/sync/syncable/write_transaction.h" | 
 |  | 
 |  | 
 | using base::TimeDelta; | 
 | using sync_pb::GetUpdatesCallerInfo; | 
 |  | 
 | class GURL; | 
 |  | 
 | namespace syncer { | 
 |  | 
 | using syncable::ImmutableWriteTransactionInfo; | 
 | using syncable::SPECIFICS; | 
 | using syncable::UNIQUE_POSITION; | 
 |  | 
 | namespace { | 
 |  | 
 | GetUpdatesCallerInfo::GetUpdatesSource GetSourceFromReason( | 
 |     ConfigureReason reason) { | 
 |   switch (reason) { | 
 |     case CONFIGURE_REASON_RECONFIGURATION: | 
 |       return GetUpdatesCallerInfo::RECONFIGURATION; | 
 |     case CONFIGURE_REASON_MIGRATION: | 
 |       return GetUpdatesCallerInfo::MIGRATION; | 
 |     case CONFIGURE_REASON_NEW_CLIENT: | 
 |       return GetUpdatesCallerInfo::NEW_CLIENT; | 
 |     case CONFIGURE_REASON_NEWLY_ENABLED_DATA_TYPE: | 
 |     case CONFIGURE_REASON_CRYPTO: | 
 |     case CONFIGURE_REASON_CATCH_UP: | 
 |       return GetUpdatesCallerInfo::NEWLY_SUPPORTED_DATATYPE; | 
 |     case CONFIGURE_REASON_PROGRAMMATIC: | 
 |       return GetUpdatesCallerInfo::PROGRAMMATIC; | 
 |     case CONFIGURE_REASON_UNKNOWN: | 
 |       NOTREACHED(); | 
 |   } | 
 |   return GetUpdatesCallerInfo::UNKNOWN; | 
 | } | 
 |  | 
 | }  // namespace | 
 |  | 
 | SyncManagerImpl::SyncManagerImpl(const std::string& name) | 
 |     : name_(name), | 
 |       change_delegate_(nullptr), | 
 |       initialized_(false), | 
 |       observing_network_connectivity_changes_(false), | 
 |       weak_ptr_factory_(this) { | 
 |   // Pre-fill |notification_info_map_|. | 
 |   for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { | 
 |     notification_info_map_.insert( | 
 |         std::make_pair(ModelTypeFromInt(i), NotificationInfo())); | 
 |   } | 
 | } | 
 |  | 
 | SyncManagerImpl::~SyncManagerImpl() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   CHECK(!initialized_); | 
 | } | 
 |  | 
 | SyncManagerImpl::NotificationInfo::NotificationInfo() : total_count(0) {} | 
 | SyncManagerImpl::NotificationInfo::~NotificationInfo() {} | 
 |  | 
 | base::DictionaryValue* SyncManagerImpl::NotificationInfo::ToValue() const { | 
 |   base::DictionaryValue* value = new base::DictionaryValue(); | 
 |   value->SetInteger("totalCount", total_count); | 
 |   value->SetString("payload", payload); | 
 |   return value; | 
 | } | 
 |  | 
 | bool SyncManagerImpl::VisiblePositionsDiffer( | 
 |     const syncable::EntryKernelMutation& mutation) const { | 
 |   const syncable::EntryKernel& a = mutation.original; | 
 |   const syncable::EntryKernel& b = mutation.mutated; | 
 |   if (!b.ShouldMaintainPosition()) | 
 |     return false; | 
 |   if (!a.ref(UNIQUE_POSITION).Equals(b.ref(UNIQUE_POSITION))) | 
 |     return true; | 
 |   if (a.ref(syncable::PARENT_ID) != b.ref(syncable::PARENT_ID)) | 
 |     return true; | 
 |   return false; | 
 | } | 
 |  | 
 | bool SyncManagerImpl::VisiblePropertiesDiffer( | 
 |     const syncable::EntryKernelMutation& mutation, | 
 |     Cryptographer* cryptographer) const { | 
 |   const syncable::EntryKernel& a = mutation.original; | 
 |   const syncable::EntryKernel& b = mutation.mutated; | 
 |   const sync_pb::EntitySpecifics& a_specifics = a.ref(SPECIFICS); | 
 |   const sync_pb::EntitySpecifics& b_specifics = b.ref(SPECIFICS); | 
 |   DCHECK_EQ(GetModelTypeFromSpecifics(a_specifics), | 
 |             GetModelTypeFromSpecifics(b_specifics)); | 
 |   ModelType model_type = GetModelTypeFromSpecifics(b_specifics); | 
 |   // Suppress updates to items that aren't tracked by any browser model. | 
 |   if (model_type < FIRST_REAL_MODEL_TYPE || | 
 |       !a.ref(syncable::UNIQUE_SERVER_TAG).empty()) { | 
 |     return false; | 
 |   } | 
 |   if (a.ref(syncable::IS_DIR) != b.ref(syncable::IS_DIR)) | 
 |     return true; | 
 |   if (!AreSpecificsEqual(cryptographer, a.ref(syncable::SPECIFICS), | 
 |                          b.ref(syncable::SPECIFICS))) { | 
 |     return true; | 
 |   } | 
 |   if (!AreAttachmentMetadataEqual(a.ref(syncable::ATTACHMENT_METADATA), | 
 |                                   b.ref(syncable::ATTACHMENT_METADATA))) { | 
 |     return true; | 
 |   } | 
 |   // We only care if the name has changed if neither specifics is encrypted | 
 |   // (encrypted nodes blow away the NON_UNIQUE_NAME). | 
 |   if (!a_specifics.has_encrypted() && !b_specifics.has_encrypted() && | 
 |       a.ref(syncable::NON_UNIQUE_NAME) != b.ref(syncable::NON_UNIQUE_NAME)) | 
 |     return true; | 
 |   if (VisiblePositionsDiffer(mutation)) | 
 |     return true; | 
 |   return false; | 
 | } | 
 |  | 
 | ModelTypeSet SyncManagerImpl::InitialSyncEndedTypes() { | 
 |   DCHECK(initialized_); | 
 |   return model_type_registry_->GetInitialSyncEndedTypes(); | 
 | } | 
 |  | 
 | ModelTypeSet SyncManagerImpl::GetTypesWithEmptyProgressMarkerToken( | 
 |     ModelTypeSet types) { | 
 |   ModelTypeSet result; | 
 |   for (ModelTypeSet::Iterator i = types.First(); i.Good(); i.Inc()) { | 
 |     sync_pb::DataTypeProgressMarker marker; | 
 |     directory()->GetDownloadProgress(i.Get(), &marker); | 
 |  | 
 |     if (marker.token().empty()) | 
 |       result.Put(i.Get()); | 
 |   } | 
 |   return result; | 
 | } | 
 |  | 
 | void SyncManagerImpl::ConfigureSyncer( | 
 |     ConfigureReason reason, | 
 |     ModelTypeSet to_download, | 
 |     const base::Closure& ready_task, | 
 |     const base::Closure& retry_task) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   DCHECK(!ready_task.is_null()); | 
 |   DCHECK(initialized_); | 
 |  | 
 |   // Don't download non-blocking types that have already completed initial sync. | 
 |   to_download.RemoveAll( | 
 |       model_type_registry_->GetInitialSyncDoneNonBlockingTypes()); | 
 |  | 
 |   DVLOG(1) << "Configuring -" | 
 |            << "\n\t" | 
 |            << "types to download: " << ModelTypeSetToString(to_download); | 
 |   ConfigurationParams params(GetSourceFromReason(reason), to_download, | 
 |                              ready_task, retry_task); | 
 |  | 
 |   scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time()); | 
 |   scheduler_->ScheduleConfiguration(params); | 
 | } | 
 |  | 
 | void SyncManagerImpl::Init(InitArgs* args) { | 
 |   CHECK(!initialized_); | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   DCHECK(args->post_factory.get()); | 
 |   if (!args->enable_local_sync_backend) { | 
 |     DCHECK(!args->credentials.account_id.empty()); | 
 |     DCHECK(!args->credentials.sync_token.empty()); | 
 |     DCHECK(!args->credentials.scope_set.empty()); | 
 |   } | 
 |   DCHECK(args->cancelation_signal); | 
 |   DVLOG(1) << "SyncManager starting Init..."; | 
 |  | 
 |   weak_handle_this_ = MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()); | 
 |  | 
 |   change_delegate_ = args->change_delegate; | 
 |  | 
 |   AddObserver(&js_sync_manager_observer_); | 
 |   SetJsEventHandler(args->event_handler); | 
 |  | 
 |   AddObserver(&debug_info_event_listener_); | 
 |  | 
 |   database_path_ = args->database_location.Append( | 
 |       syncable::Directory::kSyncDatabaseFilename); | 
 |   report_unrecoverable_error_function_ = | 
 |       args->report_unrecoverable_error_function; | 
 |  | 
 |   allstatus_.SetHasKeystoreKey( | 
 |       !args->restored_keystore_key_for_bootstrapping.empty()); | 
 |   sync_encryption_handler_ = base::MakeUnique<SyncEncryptionHandlerImpl>( | 
 |       &share_, args->encryptor, args->restored_key_for_bootstrapping, | 
 |       args->restored_keystore_key_for_bootstrapping); | 
 |   sync_encryption_handler_->AddObserver(this); | 
 |   sync_encryption_handler_->AddObserver(&debug_info_event_listener_); | 
 |   sync_encryption_handler_->AddObserver(&js_sync_encryption_handler_observer_); | 
 |  | 
 |   base::FilePath absolute_db_path = database_path_; | 
 |   DCHECK(absolute_db_path.IsAbsolute()); | 
 |  | 
 |   std::unique_ptr<syncable::DirectoryBackingStore> backing_store = | 
 |       args->engine_components_factory->BuildDirectoryBackingStore( | 
 |           EngineComponentsFactory::STORAGE_ON_DISK, | 
 |           args->credentials.account_id, absolute_db_path); | 
 |  | 
 |   DCHECK(backing_store.get()); | 
 |   share_.directory = base::MakeUnique<syncable::Directory>( | 
 |       backing_store.release(), args->unrecoverable_error_handler, | 
 |       report_unrecoverable_error_function_, sync_encryption_handler_.get(), | 
 |       sync_encryption_handler_->GetCryptographerUnsafe()); | 
 |   share_.sync_credentials = args->credentials; | 
 |  | 
 |   // UserShare is accessible to a lot of code that doesn't need access to the | 
 |   // sync token so clear sync_token from the UserShare. | 
 |   share_.sync_credentials.sync_token = ""; | 
 |  | 
 |   DVLOG(1) << "Username: " << args->credentials.email; | 
 |   DVLOG(1) << "AccountId: " << args->credentials.account_id; | 
 |   if (!OpenDirectory(args->credentials.account_id)) { | 
 |     NotifyInitializationFailure(); | 
 |     LOG(ERROR) << "Sync manager initialization failed!"; | 
 |     return; | 
 |   } | 
 |  | 
 |   // Now that we have opened the Directory we can restore any previously saved | 
 |   // nigori specifics. | 
 |   if (args->saved_nigori_state) { | 
 |     sync_encryption_handler_->RestoreNigori(*args->saved_nigori_state); | 
 |     args->saved_nigori_state.reset(); | 
 |   } | 
 |  | 
 |   if (args->enable_local_sync_backend) { | 
 |     VLOG(1) << "Running against local sync backend."; | 
 |     allstatus_.SetLocalBackendFolder( | 
 |         args->local_sync_backend_folder.AsUTF8Unsafe()); | 
 |     connection_manager_ = base::MakeUnique<LoopbackConnectionManager>( | 
 |         args->cancelation_signal, args->local_sync_backend_folder); | 
 |   } else { | 
 |     connection_manager_ = base::MakeUnique<SyncServerConnectionManager>( | 
 |         args->service_url.host() + args->service_url.path(), | 
 |         args->service_url.EffectiveIntPort(), | 
 |         args->service_url.SchemeIsCryptographic(), args->post_factory.release(), | 
 |         args->cancelation_signal); | 
 |   } | 
 |   connection_manager_->set_client_id(directory()->cache_guid()); | 
 |   connection_manager_->AddListener(this); | 
 |  | 
 |   std::string sync_id = directory()->cache_guid(); | 
 |  | 
 |   DVLOG(1) << "Setting sync client ID: " << sync_id; | 
 |   allstatus_.SetSyncId(sync_id); | 
 |   DVLOG(1) << "Setting invalidator client ID: " << args->invalidator_client_id; | 
 |   allstatus_.SetInvalidatorClientId(args->invalidator_client_id); | 
 |  | 
 |   model_type_registry_ = base::MakeUnique<ModelTypeRegistry>( | 
 |       args->workers, &share_, this, base::Bind(&MigrateDirectoryData)); | 
 |   sync_encryption_handler_->AddObserver(model_type_registry_.get()); | 
 |  | 
 |   // Build a SyncCycleContext and store the worker in it. | 
 |   DVLOG(1) << "Sync is bringing up SyncCycleContext."; | 
 |   std::vector<SyncEngineEventListener*> listeners; | 
 |   listeners.push_back(&allstatus_); | 
 |   listeners.push_back(this); | 
 |   cycle_context_ = args->engine_components_factory->BuildContext( | 
 |       connection_manager_.get(), directory(), args->extensions_activity, | 
 |       listeners, &debug_info_event_listener_, model_type_registry_.get(), | 
 |       args->invalidator_client_id); | 
 |   scheduler_ = args->engine_components_factory->BuildScheduler( | 
 |       name_, cycle_context_.get(), args->cancelation_signal, | 
 |       args->enable_local_sync_backend); | 
 |  | 
 |   scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time()); | 
 |  | 
 |   initialized_ = true; | 
 |  | 
 |   if (!args->enable_local_sync_backend) { | 
 |     net::NetworkChangeNotifier::AddIPAddressObserver(this); | 
 |     net::NetworkChangeNotifier::AddConnectionTypeObserver(this); | 
 |     observing_network_connectivity_changes_ = true; | 
 |  | 
 |     UpdateCredentials(args->credentials); | 
 |   } else { | 
 |     scheduler_->OnCredentialsUpdated(); | 
 |   } | 
 |  | 
 |   NotifyInitializationSuccess(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::NotifyInitializationSuccess() { | 
 |   for (auto& observer : observers_) { | 
 |     observer.OnInitializationComplete( | 
 |         MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), | 
 |         MakeWeakHandle(debug_info_event_listener_.GetWeakPtr()), true, | 
 |         InitialSyncEndedTypes()); | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::NotifyInitializationFailure() { | 
 |   for (auto& observer : observers_) { | 
 |     observer.OnInitializationComplete( | 
 |         MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), | 
 |         MakeWeakHandle(debug_info_event_listener_.GetWeakPtr()), false, | 
 |         ModelTypeSet()); | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnPassphraseRequired( | 
 |     PassphraseRequiredReason reason, | 
 |     const sync_pb::EncryptedData& pending_keys) { | 
 |   // Does nothing. | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnPassphraseAccepted() { | 
 |   // Does nothing. | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnBootstrapTokenUpdated( | 
 |     const std::string& bootstrap_token, | 
 |     BootstrapTokenType type) { | 
 |   if (type == KEYSTORE_BOOTSTRAP_TOKEN) | 
 |     allstatus_.SetHasKeystoreKey(true); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnEncryptedTypesChanged(ModelTypeSet encrypted_types, | 
 |                                               bool encrypt_everything) { | 
 |   allstatus_.SetEncryptedTypes(encrypted_types); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnEncryptionComplete() { | 
 |   // Does nothing. | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnCryptographerStateChanged( | 
 |     Cryptographer* cryptographer) { | 
 |   allstatus_.SetCryptographerReady(cryptographer->is_ready()); | 
 |   allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys()); | 
 |   allstatus_.SetKeystoreMigrationTime( | 
 |       sync_encryption_handler_->migration_time()); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnPassphraseTypeChanged( | 
 |     PassphraseType type, | 
 |     base::Time explicit_passphrase_time) { | 
 |   allstatus_.SetPassphraseType(type); | 
 |   allstatus_.SetKeystoreMigrationTime( | 
 |       sync_encryption_handler_->migration_time()); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnLocalSetPassphraseEncryption( | 
 |     const SyncEncryptionHandler::NigoriState& nigori_state) {} | 
 |  | 
 | void SyncManagerImpl::StartSyncingNormally( | 
 |     base::Time last_poll_time) { | 
 |   // Start the sync scheduler. | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   scheduler_->Start(SyncScheduler::NORMAL_MODE, last_poll_time); | 
 | } | 
 |  | 
 | void SyncManagerImpl::StartConfiguration() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time()); | 
 | } | 
 |  | 
 | syncable::Directory* SyncManagerImpl::directory() { | 
 |   return share_.directory.get(); | 
 | } | 
 |  | 
 | const SyncScheduler* SyncManagerImpl::scheduler() const { | 
 |   return scheduler_.get(); | 
 | } | 
 |  | 
 | bool SyncManagerImpl::GetHasInvalidAuthTokenForTest() const { | 
 |   return connection_manager_->HasInvalidAuthToken(); | 
 | } | 
 |  | 
 | bool SyncManagerImpl::OpenDirectory(const std::string& username) { | 
 |   DCHECK(!initialized_) << "Should only happen once"; | 
 |  | 
 |   // Set before Open(). | 
 |   change_observer_ = MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr()); | 
 |   WeakHandle<syncable::TransactionObserver> transaction_observer( | 
 |       MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr())); | 
 |  | 
 |   syncable::DirOpenResult open_result = syncable::NOT_INITIALIZED; | 
 |   open_result = directory()->Open(username, this, transaction_observer); | 
 |   if (open_result != syncable::OPENED) { | 
 |     LOG(ERROR) << "Could not open share for:" << username; | 
 |     return false; | 
 |   } | 
 |  | 
 |   // Unapplied datatypes (those that do not have initial sync ended set) get | 
 |   // re-downloaded during any configuration. But, it's possible for a datatype | 
 |   // to have a progress marker but not have initial sync ended yet, making | 
 |   // it a candidate for migration. This is a problem, as the DataTypeManager | 
 |   // does not support a migration while it's already in the middle of a | 
 |   // configuration. As a result, any partially synced datatype can stall the | 
 |   // DTM, waiting for the configuration to complete, which it never will due | 
 |   // to the migration error. In addition, a partially synced nigori will | 
 |   // trigger the migration logic before the backend is initialized, resulting | 
 |   // in crashes. We therefore detect and purge any partially synced types as | 
 |   // part of initialization. | 
 |   PurgePartiallySyncedTypes(); | 
 |   return true; | 
 | } | 
 |  | 
 | void SyncManagerImpl::PurgePartiallySyncedTypes() { | 
 |   ModelTypeSet partially_synced_types = ModelTypeSet::All(); | 
 |   partially_synced_types.RemoveAll(directory()->InitialSyncEndedTypes()); | 
 |   partially_synced_types.RemoveAll( | 
 |       GetTypesWithEmptyProgressMarkerToken(ModelTypeSet::All())); | 
 |  | 
 |   DVLOG(1) << "Purging partially synced types " | 
 |            << ModelTypeSetToString(partially_synced_types); | 
 |   UMA_HISTOGRAM_COUNTS("Sync.PartiallySyncedTypes", | 
 |                        partially_synced_types.Size()); | 
 |   directory()->PurgeEntriesWithTypeIn(partially_synced_types, ModelTypeSet(), | 
 |                                       ModelTypeSet()); | 
 | } | 
 |  | 
 | void SyncManagerImpl::PurgeDisabledTypes(ModelTypeSet to_purge, | 
 |                                          ModelTypeSet to_journal, | 
 |                                          ModelTypeSet to_unapply) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   DCHECK(initialized_); | 
 |   DVLOG(1) << "Purging disabled types:\n\t" | 
 |            << "types to purge: " << ModelTypeSetToString(to_purge) << "\n\t" | 
 |            << "types to journal: " << ModelTypeSetToString(to_journal) << "\n\t" | 
 |            << "types to unapply: " << ModelTypeSetToString(to_unapply); | 
 |   DCHECK(to_purge.HasAll(to_journal)); | 
 |   DCHECK(to_purge.HasAll(to_unapply)); | 
 |   directory()->PurgeEntriesWithTypeIn(to_purge, to_journal, to_unapply); | 
 | } | 
 |  | 
 | void SyncManagerImpl::UpdateCredentials(const SyncCredentials& credentials) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   DCHECK(initialized_); | 
 |   DCHECK(!credentials.account_id.empty()); | 
 |   DCHECK(!credentials.sync_token.empty()); | 
 |   DCHECK(!credentials.scope_set.empty()); | 
 |   cycle_context_->set_account_name(credentials.email); | 
 |  | 
 |   observing_network_connectivity_changes_ = true; | 
 |   if (!connection_manager_->SetAuthToken(credentials.sync_token)) | 
 |     return;  // Auth token is known to be invalid, so exit early. | 
 |  | 
 |   scheduler_->OnCredentialsUpdated(); | 
 |  | 
 |   // TODO(zea): pass the credential age to the debug info event listener. | 
 | } | 
 |  | 
 | void SyncManagerImpl::AddObserver(SyncManager::Observer* observer) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   observers_.AddObserver(observer); | 
 | } | 
 |  | 
 | void SyncManagerImpl::RemoveObserver(SyncManager::Observer* observer) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   observers_.RemoveObserver(observer); | 
 | } | 
 |  | 
 | void SyncManagerImpl::ShutdownOnSyncThread(ShutdownReason reason) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |  | 
 |   // Prevent any in-flight method calls from running.  Also | 
 |   // invalidates |weak_handle_this_| and |change_observer_|. | 
 |   weak_ptr_factory_.InvalidateWeakPtrs(); | 
 |   js_mutation_event_observer_.InvalidateWeakPtrs(); | 
 |  | 
 |   scheduler_.reset(); | 
 |   cycle_context_.reset(); | 
 |  | 
 |   if (model_type_registry_) | 
 |     sync_encryption_handler_->RemoveObserver(model_type_registry_.get()); | 
 |  | 
 |   model_type_registry_.reset(); | 
 |  | 
 |   if (sync_encryption_handler_) { | 
 |     sync_encryption_handler_->RemoveObserver(&debug_info_event_listener_); | 
 |     sync_encryption_handler_->RemoveObserver(this); | 
 |   } | 
 |  | 
 |   SetJsEventHandler(WeakHandle<JsEventHandler>()); | 
 |   RemoveObserver(&js_sync_manager_observer_); | 
 |  | 
 |   RemoveObserver(&debug_info_event_listener_); | 
 |  | 
 |   // |connection_manager_| may end up being null here in tests (in synchronous | 
 |   // initialization mode). | 
 |   // | 
 |   // TODO(akalin): Fix this behavior. | 
 |   if (connection_manager_) | 
 |     connection_manager_->RemoveListener(this); | 
 |   connection_manager_.reset(); | 
 |  | 
 |   net::NetworkChangeNotifier::RemoveIPAddressObserver(this); | 
 |   net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); | 
 |   observing_network_connectivity_changes_ = false; | 
 |  | 
 |   if (initialized_ && directory()) { | 
 |     directory()->SaveChanges(); | 
 |   } | 
 |  | 
 |   share_.directory.reset(); | 
 |  | 
 |   change_delegate_ = nullptr; | 
 |  | 
 |   initialized_ = false; | 
 |  | 
 |   // We reset these here, since only now we know they will not be | 
 |   // accessed from other threads (since we shut down everything). | 
 |   change_observer_.Reset(); | 
 |   weak_handle_this_.Reset(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnIPAddressChanged() { | 
 |   if (!observing_network_connectivity_changes_) { | 
 |     DVLOG(1) << "IP address change dropped."; | 
 |     return; | 
 |   } | 
 |   DVLOG(1) << "IP address change detected."; | 
 |   OnNetworkConnectivityChangedImpl(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnConnectionTypeChanged( | 
 |     net::NetworkChangeNotifier::ConnectionType) { | 
 |   if (!observing_network_connectivity_changes_) { | 
 |     DVLOG(1) << "Connection type change dropped."; | 
 |     return; | 
 |   } | 
 |   DVLOG(1) << "Connection type change detected."; | 
 |   OnNetworkConnectivityChangedImpl(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnNetworkConnectivityChangedImpl() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   scheduler_->OnConnectionStatusChange(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnServerConnectionEvent( | 
 |     const ServerConnectionEvent& event) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   if (event.connection_code == HttpResponse::SERVER_CONNECTION_OK) { | 
 |     for (auto& observer : observers_) { | 
 |       observer.OnConnectionStatusChange(CONNECTION_OK); | 
 |     } | 
 |   } | 
 |  | 
 |   if (event.connection_code == HttpResponse::SYNC_AUTH_ERROR) { | 
 |     observing_network_connectivity_changes_ = false; | 
 |     for (auto& observer : observers_) { | 
 |       observer.OnConnectionStatusChange(CONNECTION_AUTH_ERROR); | 
 |     } | 
 |   } | 
 |  | 
 |   if (event.connection_code == HttpResponse::SYNC_SERVER_ERROR) { | 
 |     for (auto& observer : observers_) { | 
 |       observer.OnConnectionStatusChange(CONNECTION_SERVER_ERROR); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::HandleTransactionCompleteChangeEvent( | 
 |     ModelTypeSet models_with_changes) { | 
 |   // This notification happens immediately after the transaction mutex is | 
 |   // released. This allows work to be performed without blocking other threads | 
 |   // from acquiring a transaction. | 
 |   if (!change_delegate_) | 
 |     return; | 
 |  | 
 |   // Call commit. | 
 |   for (ModelTypeSet::Iterator it = models_with_changes.First(); it.Good(); | 
 |        it.Inc()) { | 
 |     change_delegate_->OnChangesComplete(it.Get()); | 
 |     change_observer_.Call( | 
 |         FROM_HERE, &SyncManager::ChangeObserver::OnChangesComplete, it.Get()); | 
 |   } | 
 | } | 
 |  | 
 | ModelTypeSet SyncManagerImpl::HandleTransactionEndingChangeEvent( | 
 |     const ImmutableWriteTransactionInfo& write_transaction_info, | 
 |     syncable::BaseTransaction* trans) { | 
 |   // This notification happens immediately before a syncable WriteTransaction | 
 |   // falls out of scope. It happens while the channel mutex is still held, | 
 |   // and while the transaction mutex is held, so it cannot be re-entrant. | 
 |   if (!change_delegate_ || change_records_.empty()) | 
 |     return ModelTypeSet(); | 
 |  | 
 |   // This will continue the WriteTransaction using a read only wrapper. | 
 |   // This is the last chance for read to occur in the WriteTransaction | 
 |   // that's closing. This special ReadTransaction will not close the | 
 |   // underlying transaction. | 
 |   ReadTransaction read_trans(GetUserShare(), trans); | 
 |  | 
 |   ModelTypeSet models_with_changes; | 
 |   for (ChangeRecordMap::const_iterator it = change_records_.begin(); | 
 |        it != change_records_.end(); ++it) { | 
 |     DCHECK(!it->second.Get().empty()); | 
 |     ModelType type = ModelTypeFromInt(it->first); | 
 |     change_delegate_->OnChangesApplied( | 
 |         type, trans->directory()->GetTransactionVersion(type), &read_trans, | 
 |         it->second); | 
 |     change_observer_.Call(FROM_HERE, | 
 |                           &SyncManager::ChangeObserver::OnChangesApplied, type, | 
 |                           write_transaction_info.Get().id, it->second); | 
 |     models_with_changes.Put(type); | 
 |   } | 
 |   change_records_.clear(); | 
 |   return models_with_changes; | 
 | } | 
 |  | 
 | void SyncManagerImpl::HandleCalculateChangesChangeEventFromSyncApi( | 
 |     const ImmutableWriteTransactionInfo& write_transaction_info, | 
 |     syncable::BaseTransaction* trans, | 
 |     std::vector<int64_t>* entries_changed) { | 
 |   // We have been notified about a user action changing a sync model. | 
 |   LOG_IF(WARNING, !change_records_.empty()) | 
 |       << "CALCULATE_CHANGES called with unapplied old changes."; | 
 |  | 
 |   // The mutated model type, or UNSPECIFIED if nothing was mutated. | 
 |   ModelTypeSet mutated_model_types; | 
 |  | 
 |   const syncable::ImmutableEntryKernelMutationMap& mutations = | 
 |       write_transaction_info.Get().mutations; | 
 |   for (syncable::EntryKernelMutationMap::const_iterator it = | 
 |            mutations.Get().begin(); | 
 |        it != mutations.Get().end(); ++it) { | 
 |     if (!it->second.mutated.ref(syncable::IS_UNSYNCED)) { | 
 |       continue; | 
 |     } | 
 |  | 
 |     ModelType model_type = | 
 |         GetModelTypeFromSpecifics(it->second.mutated.ref(SPECIFICS)); | 
 |     if (model_type < FIRST_REAL_MODEL_TYPE) { | 
 |       NOTREACHED() << "Permanent or underspecified item changed via syncapi."; | 
 |       continue; | 
 |     } | 
 |  | 
 |     // Found real mutation. | 
 |     if (model_type != UNSPECIFIED) { | 
 |       mutated_model_types.Put(model_type); | 
 |       entries_changed->push_back(it->second.mutated.ref(syncable::META_HANDLE)); | 
 |     } | 
 |   } | 
 |  | 
 |   // Nudge if necessary. | 
 |   if (!mutated_model_types.Empty()) { | 
 |     if (weak_handle_this_.IsInitialized()) { | 
 |       weak_handle_this_.Call(FROM_HERE, | 
 |                              &SyncManagerImpl::RequestNudgeForDataTypes, | 
 |                              FROM_HERE, mutated_model_types); | 
 |     } else { | 
 |       NOTREACHED(); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::SetExtraChangeRecordData( | 
 |     int64_t id, | 
 |     ModelType type, | 
 |     ChangeReorderBuffer* buffer, | 
 |     Cryptographer* cryptographer, | 
 |     const syncable::EntryKernel& original, | 
 |     bool existed_before, | 
 |     bool exists_now) { | 
 |   // If this is a deletion and the datatype was encrypted, we need to decrypt it | 
 |   // and attach it to the buffer. | 
 |   if (!exists_now && existed_before) { | 
 |     sync_pb::EntitySpecifics original_specifics(original.ref(SPECIFICS)); | 
 |     if (type == PASSWORDS) { | 
 |       // Passwords must use their own legacy ExtraPasswordChangeRecordData. | 
 |       std::unique_ptr<sync_pb::PasswordSpecificsData> data( | 
 |           DecryptPasswordSpecifics(original_specifics, cryptographer)); | 
 |       if (!data) { | 
 |         NOTREACHED(); | 
 |         return; | 
 |       } | 
 |       buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data)); | 
 |     } else if (original_specifics.has_encrypted()) { | 
 |       // All other datatypes can just create a new unencrypted specifics and | 
 |       // attach it. | 
 |       const sync_pb::EncryptedData& encrypted = original_specifics.encrypted(); | 
 |       if (!cryptographer->Decrypt(encrypted, &original_specifics)) { | 
 |         NOTREACHED(); | 
 |         return; | 
 |       } | 
 |     } | 
 |     buffer->SetSpecificsForId(id, original_specifics); | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::HandleCalculateChangesChangeEventFromSyncer( | 
 |     const ImmutableWriteTransactionInfo& write_transaction_info, | 
 |     syncable::BaseTransaction* trans, | 
 |     std::vector<int64_t>* entries_changed) { | 
 |   // We only expect one notification per sync step, so change_buffers_ should | 
 |   // contain no pending entries. | 
 |   LOG_IF(WARNING, !change_records_.empty()) | 
 |       << "CALCULATE_CHANGES called with unapplied old changes."; | 
 |  | 
 |   ChangeReorderBuffer change_buffers[MODEL_TYPE_COUNT]; | 
 |  | 
 |   Cryptographer* crypto = directory()->GetCryptographer(trans); | 
 |   const syncable::ImmutableEntryKernelMutationMap& mutations = | 
 |       write_transaction_info.Get().mutations; | 
 |   for (syncable::EntryKernelMutationMap::const_iterator it = | 
 |            mutations.Get().begin(); | 
 |        it != mutations.Get().end(); ++it) { | 
 |     bool existed_before = !it->second.original.ref(syncable::IS_DEL); | 
 |     bool exists_now = !it->second.mutated.ref(syncable::IS_DEL); | 
 |  | 
 |     // Omit items that aren't associated with a model. | 
 |     ModelType type = | 
 |         GetModelTypeFromSpecifics(it->second.mutated.ref(SPECIFICS)); | 
 |     if (type < FIRST_REAL_MODEL_TYPE) | 
 |       continue; | 
 |  | 
 |     int64_t handle = it->first; | 
 |     if (exists_now && !existed_before) | 
 |       change_buffers[type].PushAddedItem(handle); | 
 |     else if (!exists_now && existed_before) | 
 |       change_buffers[type].PushDeletedItem(handle); | 
 |     else if (exists_now && existed_before && | 
 |              VisiblePropertiesDiffer(it->second, crypto)) | 
 |       change_buffers[type].PushUpdatedItem(handle); | 
 |  | 
 |     SetExtraChangeRecordData(handle, type, &change_buffers[type], crypto, | 
 |                              it->second.original, existed_before, exists_now); | 
 |   } | 
 |  | 
 |   ReadTransaction read_trans(GetUserShare(), trans); | 
 |   for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { | 
 |     if (!change_buffers[i].IsEmpty()) { | 
 |       if (change_buffers[i].GetAllChangesInTreeOrder(&read_trans, | 
 |                                                      &(change_records_[i]))) { | 
 |         for (size_t j = 0; j < change_records_[i].Get().size(); ++j) | 
 |           entries_changed->push_back((change_records_[i].Get())[j].id); | 
 |       } | 
 |       if (change_records_[i].Get().empty()) | 
 |         change_records_.erase(i); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::RequestNudgeForDataTypes( | 
 |     const tracked_objects::Location& nudge_location, | 
 |     ModelTypeSet types) { | 
 |   debug_info_event_listener_.OnNudgeFromDatatype(types.First().Get()); | 
 |  | 
 |   scheduler_->ScheduleLocalNudge(types, nudge_location); | 
 | } | 
 |  | 
 | void SyncManagerImpl::NudgeForInitialDownload(ModelType type) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   scheduler_->ScheduleInitialSyncNudge(type); | 
 | } | 
 |  | 
 | void SyncManagerImpl::NudgeForCommit(ModelType type) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   RequestNudgeForDataTypes(FROM_HERE, ModelTypeSet(type)); | 
 | } | 
 |  | 
 | void SyncManagerImpl::NudgeForRefresh(ModelType type) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   RefreshTypes(ModelTypeSet(type)); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnSyncCycleEvent(const SyncCycleEvent& event) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   // Only send an event if this is due to a cycle ending and this cycle | 
 |   // concludes a canonical "sync" process; that is, based on what is known | 
 |   // locally we are "all happy" and up to date.  There may be new changes on | 
 |   // the server, but we'll get them on a subsequent sync. | 
 |   // | 
 |   // Notifications are sent at the end of every sync cycle, regardless of | 
 |   // whether we should sync again. | 
 |   if (event.what_happened == SyncCycleEvent::SYNC_CYCLE_ENDED) { | 
 |     if (!initialized_) { | 
 |       DVLOG(1) << "OnSyncCycleCompleted not sent because sync api is not " | 
 |                << "initialized"; | 
 |       return; | 
 |     } | 
 |  | 
 |     DVLOG(1) << "Sending OnSyncCycleCompleted"; | 
 |     for (auto& observer : observers_) { | 
 |       observer.OnSyncCycleCompleted(event.snapshot); | 
 |     } | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnActionableError(const SyncProtocolError& error) { | 
 |   for (auto& observer : observers_) { | 
 |     observer.OnActionableError(error); | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnRetryTimeChanged(base::Time) {} | 
 |  | 
 | void SyncManagerImpl::OnThrottledTypesChanged(ModelTypeSet) {} | 
 |  | 
 | void SyncManagerImpl::OnBackedOffTypesChanged(ModelTypeSet) {} | 
 |  | 
 | void SyncManagerImpl::OnMigrationRequested(ModelTypeSet types) { | 
 |   for (auto& observer : observers_) { | 
 |     observer.OnMigrationRequested(types); | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnProtocolEvent(const ProtocolEvent& event) { | 
 |   protocol_event_buffer_.RecordProtocolEvent(event); | 
 |   for (auto& observer : observers_) { | 
 |     observer.OnProtocolEvent(event); | 
 |   } | 
 | } | 
 |  | 
 | void SyncManagerImpl::SetJsEventHandler( | 
 |     const WeakHandle<JsEventHandler>& event_handler) { | 
 |   js_sync_manager_observer_.SetJsEventHandler(event_handler); | 
 |   js_mutation_event_observer_.SetJsEventHandler(event_handler); | 
 |   js_sync_encryption_handler_observer_.SetJsEventHandler(event_handler); | 
 | } | 
 |  | 
 | void SyncManagerImpl::SetInvalidatorEnabled(bool invalidator_enabled) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |  | 
 |   DVLOG(1) << "Invalidator enabled state is now: " << invalidator_enabled; | 
 |   allstatus_.SetNotificationsEnabled(invalidator_enabled); | 
 |   scheduler_->SetNotificationsEnabled(invalidator_enabled); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnIncomingInvalidation( | 
 |     ModelType type, | 
 |     std::unique_ptr<InvalidationInterface> invalidation) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |  | 
 |   allstatus_.IncrementNotificationsReceived(); | 
 |   scheduler_->ScheduleInvalidationNudge(type, std::move(invalidation), | 
 |                                         FROM_HERE); | 
 | } | 
 |  | 
 | void SyncManagerImpl::RefreshTypes(ModelTypeSet types) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   if (types.Empty()) { | 
 |     LOG(WARNING) << "Sync received refresh request with no types specified."; | 
 |   } else { | 
 |     scheduler_->ScheduleLocalRefreshRequest(types, FROM_HERE); | 
 |   } | 
 | } | 
 |  | 
 | SyncStatus SyncManagerImpl::GetDetailedStatus() const { | 
 |   return allstatus_.status(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::SaveChanges() { | 
 |   directory()->SaveChanges(); | 
 | } | 
 |  | 
 | UserShare* SyncManagerImpl::GetUserShare() { | 
 |   DCHECK(initialized_); | 
 |   return &share_; | 
 | } | 
 |  | 
 | ModelTypeConnector* SyncManagerImpl::GetModelTypeConnector() { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   return model_type_registry_.get(); | 
 | } | 
 |  | 
 | std::unique_ptr<ModelTypeConnector> | 
 | SyncManagerImpl::GetModelTypeConnectorProxy() { | 
 |   DCHECK(initialized_); | 
 |   return base::MakeUnique<ModelTypeConnectorProxy>( | 
 |       base::ThreadTaskRunnerHandle::Get(), model_type_registry_->AsWeakPtr()); | 
 | } | 
 |  | 
 | const std::string SyncManagerImpl::cache_guid() { | 
 |   DCHECK(initialized_); | 
 |   return directory()->cache_guid(); | 
 | } | 
 |  | 
 | bool SyncManagerImpl::ReceivedExperiment(Experiments* experiments) { | 
 |   ReadTransaction trans(FROM_HERE, GetUserShare()); | 
 |   ReadNode nigori_node(&trans); | 
 |   if (nigori_node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK) { | 
 |     DVLOG(1) << "Couldn't find Nigori node."; | 
 |     return false; | 
 |   } | 
 |   bool found_experiment = false; | 
 |  | 
 |   ReadNode favicon_sync_node(&trans); | 
 |   if (favicon_sync_node.InitByClientTagLookup(EXPERIMENTS, kFaviconSyncTag) == | 
 |       BaseNode::INIT_OK) { | 
 |     experiments->favicon_sync_limit = | 
 |         favicon_sync_node.GetExperimentsSpecifics() | 
 |             .favicon_sync() | 
 |             .favicon_sync_limit(); | 
 |     found_experiment = true; | 
 |   } | 
 |  | 
 |   ReadNode pre_commit_update_avoidance_node(&trans); | 
 |   if (pre_commit_update_avoidance_node.InitByClientTagLookup( | 
 |           EXPERIMENTS, kPreCommitUpdateAvoidanceTag) == BaseNode::INIT_OK) { | 
 |     cycle_context_->set_server_enabled_pre_commit_update_avoidance( | 
 |         pre_commit_update_avoidance_node.GetExperimentsSpecifics() | 
 |             .pre_commit_update_avoidance() | 
 |             .enabled()); | 
 |     // We don't bother setting found_experiment.  The frontend doesn't need to | 
 |     // know about this. | 
 |   } | 
 |  | 
 |   ReadNode gcm_invalidations_node(&trans); | 
 |   if (gcm_invalidations_node.InitByClientTagLookup( | 
 |           EXPERIMENTS, kGCMInvalidationsTag) == BaseNode::INIT_OK) { | 
 |     const sync_pb::GcmInvalidationsFlags& gcm_invalidations = | 
 |         gcm_invalidations_node.GetExperimentsSpecifics().gcm_invalidations(); | 
 |     if (gcm_invalidations.has_enabled()) { | 
 |       experiments->gcm_invalidations_enabled = gcm_invalidations.enabled(); | 
 |       found_experiment = true; | 
 |     } | 
 |   } | 
 |  | 
 |   return found_experiment; | 
 | } | 
 |  | 
 | bool SyncManagerImpl::HasUnsyncedItems() { | 
 |   ReadTransaction trans(FROM_HERE, GetUserShare()); | 
 |   return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); | 
 | } | 
 |  | 
 | SyncEncryptionHandler* SyncManagerImpl::GetEncryptionHandler() { | 
 |   return sync_encryption_handler_.get(); | 
 | } | 
 |  | 
 | std::vector<std::unique_ptr<ProtocolEvent>> | 
 | SyncManagerImpl::GetBufferedProtocolEvents() { | 
 |   return protocol_event_buffer_.GetBufferedProtocolEvents(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::RegisterDirectoryTypeDebugInfoObserver( | 
 |     TypeDebugInfoObserver* observer) { | 
 |   model_type_registry_->RegisterDirectoryTypeDebugInfoObserver(observer); | 
 | } | 
 |  | 
 | void SyncManagerImpl::UnregisterDirectoryTypeDebugInfoObserver( | 
 |     TypeDebugInfoObserver* observer) { | 
 |   model_type_registry_->UnregisterDirectoryTypeDebugInfoObserver(observer); | 
 | } | 
 |  | 
 | bool SyncManagerImpl::HasDirectoryTypeDebugInfoObserver( | 
 |     TypeDebugInfoObserver* observer) { | 
 |   return model_type_registry_->HasDirectoryTypeDebugInfoObserver(observer); | 
 | } | 
 |  | 
 | void SyncManagerImpl::RequestEmitDebugInfo() { | 
 |   model_type_registry_->RequestEmitDebugInfo(); | 
 | } | 
 |  | 
 | void SyncManagerImpl::ClearServerData(const ClearServerDataCallback& callback) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   scheduler_->Start(SyncScheduler::CLEAR_SERVER_DATA_MODE, base::Time()); | 
 |   ClearParams params(callback); | 
 |   scheduler_->ScheduleClearServerData(params); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnCookieJarChanged(bool account_mismatch, | 
 |                                          bool empty_jar) { | 
 |   DCHECK(thread_checker_.CalledOnValidThread()); | 
 |   cycle_context_->set_cookie_jar_mismatch(account_mismatch); | 
 |   cycle_context_->set_cookie_jar_empty(empty_jar); | 
 | } | 
 |  | 
 | void SyncManagerImpl::OnMemoryDump(base::trace_event::ProcessMemoryDump* pmd) { | 
 |   directory()->OnMemoryDump(pmd); | 
 | } | 
 |  | 
 | }  // namespace syncer |