OLD | NEW |
| (Empty) |
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "sync/internal_api/sync_manager_impl.h" | |
6 | |
7 #include <stddef.h> | |
8 #include <stdint.h> | |
9 #include <string> | |
10 #include <utility> | |
11 | |
12 #include "base/base64.h" | |
13 #include "base/bind.h" | |
14 #include "base/callback.h" | |
15 #include "base/compiler_specific.h" | |
16 #include "base/json/json_writer.h" | |
17 #include "base/memory/ptr_util.h" | |
18 #include "base/memory/ref_counted.h" | |
19 #include "base/metrics/histogram.h" | |
20 #include "base/observer_list.h" | |
21 #include "base/strings/string_number_conversions.h" | |
22 #include "base/threading/thread_task_runner_handle.h" | |
23 #include "base/values.h" | |
24 #include "sync/engine/sync_scheduler.h" | |
25 #include "sync/engine/syncer_types.h" | |
26 #include "sync/internal_api/change_reorder_buffer.h" | |
27 #include "sync/internal_api/model_type_connector_proxy.h" | |
28 #include "sync/internal_api/public/base/cancelation_signal.h" | |
29 #include "sync/internal_api/public/base/invalidation_interface.h" | |
30 #include "sync/internal_api/public/base/model_type.h" | |
31 #include "sync/internal_api/public/base_node.h" | |
32 #include "sync/internal_api/public/configure_reason.h" | |
33 #include "sync/internal_api/public/engine/polling_constants.h" | |
34 #include "sync/internal_api/public/http_post_provider_factory.h" | |
35 #include "sync/internal_api/public/internal_components_factory.h" | |
36 #include "sync/internal_api/public/read_node.h" | |
37 #include "sync/internal_api/public/read_transaction.h" | |
38 #include "sync/internal_api/public/user_share.h" | |
39 #include "sync/internal_api/public/util/experiments.h" | |
40 #include "sync/internal_api/public/write_node.h" | |
41 #include "sync/internal_api/public/write_transaction.h" | |
42 #include "sync/internal_api/syncapi_internal.h" | |
43 #include "sync/internal_api/syncapi_server_connection_manager.h" | |
44 #include "sync/protocol/proto_value_conversions.h" | |
45 #include "sync/protocol/sync.pb.h" | |
46 #include "sync/sessions/directory_type_debug_info_emitter.h" | |
47 #include "sync/syncable/directory.h" | |
48 #include "sync/syncable/entry.h" | |
49 #include "sync/syncable/in_memory_directory_backing_store.h" | |
50 #include "sync/syncable/on_disk_directory_backing_store.h" | |
51 | |
52 using base::TimeDelta; | |
53 using sync_pb::GetUpdatesCallerInfo; | |
54 | |
55 class GURL; | |
56 | |
57 namespace syncer { | |
58 | |
59 using sessions::SyncSessionContext; | |
60 using syncable::ImmutableWriteTransactionInfo; | |
61 using syncable::SPECIFICS; | |
62 using syncable::UNIQUE_POSITION; | |
63 | |
64 namespace { | |
65 | |
66 GetUpdatesCallerInfo::GetUpdatesSource GetSourceFromReason( | |
67 ConfigureReason reason) { | |
68 switch (reason) { | |
69 case CONFIGURE_REASON_RECONFIGURATION: | |
70 return GetUpdatesCallerInfo::RECONFIGURATION; | |
71 case CONFIGURE_REASON_MIGRATION: | |
72 return GetUpdatesCallerInfo::MIGRATION; | |
73 case CONFIGURE_REASON_NEW_CLIENT: | |
74 return GetUpdatesCallerInfo::NEW_CLIENT; | |
75 case CONFIGURE_REASON_NEWLY_ENABLED_DATA_TYPE: | |
76 case CONFIGURE_REASON_CRYPTO: | |
77 case CONFIGURE_REASON_CATCH_UP: | |
78 return GetUpdatesCallerInfo::NEWLY_SUPPORTED_DATATYPE; | |
79 case CONFIGURE_REASON_PROGRAMMATIC: | |
80 return GetUpdatesCallerInfo::PROGRAMMATIC; | |
81 case CONFIGURE_REASON_UNKNOWN: | |
82 NOTREACHED(); | |
83 } | |
84 return GetUpdatesCallerInfo::UNKNOWN; | |
85 } | |
86 | |
87 } // namespace | |
88 | |
89 SyncManagerImpl::SyncManagerImpl(const std::string& name) | |
90 : name_(name), | |
91 change_delegate_(NULL), | |
92 initialized_(false), | |
93 observing_network_connectivity_changes_(false), | |
94 weak_ptr_factory_(this) { | |
95 // Pre-fill |notification_info_map_|. | |
96 for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { | |
97 notification_info_map_.insert( | |
98 std::make_pair(ModelTypeFromInt(i), NotificationInfo())); | |
99 } | |
100 } | |
101 | |
102 SyncManagerImpl::~SyncManagerImpl() { | |
103 DCHECK(thread_checker_.CalledOnValidThread()); | |
104 CHECK(!initialized_); | |
105 } | |
106 | |
107 SyncManagerImpl::NotificationInfo::NotificationInfo() : total_count(0) {} | |
108 SyncManagerImpl::NotificationInfo::~NotificationInfo() {} | |
109 | |
110 base::DictionaryValue* SyncManagerImpl::NotificationInfo::ToValue() const { | |
111 base::DictionaryValue* value = new base::DictionaryValue(); | |
112 value->SetInteger("totalCount", total_count); | |
113 value->SetString("payload", payload); | |
114 return value; | |
115 } | |
116 | |
117 bool SyncManagerImpl::VisiblePositionsDiffer( | |
118 const syncable::EntryKernelMutation& mutation) const { | |
119 const syncable::EntryKernel& a = mutation.original; | |
120 const syncable::EntryKernel& b = mutation.mutated; | |
121 if (!b.ShouldMaintainPosition()) | |
122 return false; | |
123 if (!a.ref(UNIQUE_POSITION).Equals(b.ref(UNIQUE_POSITION))) | |
124 return true; | |
125 if (a.ref(syncable::PARENT_ID) != b.ref(syncable::PARENT_ID)) | |
126 return true; | |
127 return false; | |
128 } | |
129 | |
130 bool SyncManagerImpl::VisiblePropertiesDiffer( | |
131 const syncable::EntryKernelMutation& mutation, | |
132 Cryptographer* cryptographer) const { | |
133 const syncable::EntryKernel& a = mutation.original; | |
134 const syncable::EntryKernel& b = mutation.mutated; | |
135 const sync_pb::EntitySpecifics& a_specifics = a.ref(SPECIFICS); | |
136 const sync_pb::EntitySpecifics& b_specifics = b.ref(SPECIFICS); | |
137 DCHECK_EQ(GetModelTypeFromSpecifics(a_specifics), | |
138 GetModelTypeFromSpecifics(b_specifics)); | |
139 ModelType model_type = GetModelTypeFromSpecifics(b_specifics); | |
140 // Suppress updates to items that aren't tracked by any browser model. | |
141 if (model_type < FIRST_REAL_MODEL_TYPE || | |
142 !a.ref(syncable::UNIQUE_SERVER_TAG).empty()) { | |
143 return false; | |
144 } | |
145 if (a.ref(syncable::IS_DIR) != b.ref(syncable::IS_DIR)) | |
146 return true; | |
147 if (!AreSpecificsEqual(cryptographer, | |
148 a.ref(syncable::SPECIFICS), | |
149 b.ref(syncable::SPECIFICS))) { | |
150 return true; | |
151 } | |
152 if (!AreAttachmentMetadataEqual(a.ref(syncable::ATTACHMENT_METADATA), | |
153 b.ref(syncable::ATTACHMENT_METADATA))) { | |
154 return true; | |
155 } | |
156 // We only care if the name has changed if neither specifics is encrypted | |
157 // (encrypted nodes blow away the NON_UNIQUE_NAME). | |
158 if (!a_specifics.has_encrypted() && !b_specifics.has_encrypted() && | |
159 a.ref(syncable::NON_UNIQUE_NAME) != b.ref(syncable::NON_UNIQUE_NAME)) | |
160 return true; | |
161 if (VisiblePositionsDiffer(mutation)) | |
162 return true; | |
163 return false; | |
164 } | |
165 | |
166 ModelTypeSet SyncManagerImpl::InitialSyncEndedTypes() { | |
167 DCHECK(initialized_); | |
168 return model_type_registry_->GetInitialSyncEndedTypes(); | |
169 } | |
170 | |
171 ModelTypeSet SyncManagerImpl::GetTypesWithEmptyProgressMarkerToken( | |
172 ModelTypeSet types) { | |
173 ModelTypeSet result; | |
174 for (ModelTypeSet::Iterator i = types.First(); i.Good(); i.Inc()) { | |
175 sync_pb::DataTypeProgressMarker marker; | |
176 directory()->GetDownloadProgress(i.Get(), &marker); | |
177 | |
178 if (marker.token().empty()) | |
179 result.Put(i.Get()); | |
180 } | |
181 return result; | |
182 } | |
183 | |
184 void SyncManagerImpl::ConfigureSyncer( | |
185 ConfigureReason reason, | |
186 ModelTypeSet to_download, | |
187 ModelTypeSet to_purge, | |
188 ModelTypeSet to_journal, | |
189 ModelTypeSet to_unapply, | |
190 const ModelSafeRoutingInfo& new_routing_info, | |
191 const base::Closure& ready_task, | |
192 const base::Closure& retry_task) { | |
193 DCHECK(thread_checker_.CalledOnValidThread()); | |
194 DCHECK(!ready_task.is_null()); | |
195 DCHECK(initialized_); | |
196 | |
197 DVLOG(1) << "Configuring -" | |
198 << "\n\t" << "current types: " | |
199 << ModelTypeSetToString(GetRoutingInfoTypes(new_routing_info)) | |
200 << "\n\t" << "types to download: " | |
201 << ModelTypeSetToString(to_download) | |
202 << "\n\t" << "types to purge: " | |
203 << ModelTypeSetToString(to_purge) | |
204 << "\n\t" << "types to journal: " | |
205 << ModelTypeSetToString(to_journal) | |
206 << "\n\t" << "types to unapply: " | |
207 << ModelTypeSetToString(to_unapply); | |
208 if (!PurgeDisabledTypes(to_purge, | |
209 to_journal, | |
210 to_unapply)) { | |
211 // We failed to cleanup the types. Invoke the ready task without actually | |
212 // configuring any types. The caller should detect this as a configuration | |
213 // failure and act appropriately. | |
214 ready_task.Run(); | |
215 return; | |
216 } | |
217 | |
218 ConfigurationParams params(GetSourceFromReason(reason), | |
219 to_download, | |
220 new_routing_info, | |
221 ready_task, | |
222 retry_task); | |
223 | |
224 scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time()); | |
225 scheduler_->ScheduleConfiguration(params); | |
226 } | |
227 | |
228 void SyncManagerImpl::Init(InitArgs* args) { | |
229 CHECK(!initialized_); | |
230 DCHECK(thread_checker_.CalledOnValidThread()); | |
231 DCHECK(args->post_factory.get()); | |
232 DCHECK(!args->credentials.account_id.empty()); | |
233 DCHECK(!args->credentials.sync_token.empty()); | |
234 DCHECK(!args->credentials.scope_set.empty()); | |
235 DCHECK(args->cancelation_signal); | |
236 DVLOG(1) << "SyncManager starting Init..."; | |
237 | |
238 weak_handle_this_ = MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()); | |
239 | |
240 change_delegate_ = args->change_delegate; | |
241 | |
242 AddObserver(&js_sync_manager_observer_); | |
243 SetJsEventHandler(args->event_handler); | |
244 | |
245 AddObserver(&debug_info_event_listener_); | |
246 | |
247 database_path_ = args->database_location.Append( | |
248 syncable::Directory::kSyncDatabaseFilename); | |
249 report_unrecoverable_error_function_ = | |
250 args->report_unrecoverable_error_function; | |
251 | |
252 allstatus_.SetHasKeystoreKey( | |
253 !args->restored_keystore_key_for_bootstrapping.empty()); | |
254 sync_encryption_handler_.reset(new SyncEncryptionHandlerImpl( | |
255 &share_, args->encryptor, args->restored_key_for_bootstrapping, | |
256 args->restored_keystore_key_for_bootstrapping)); | |
257 sync_encryption_handler_->AddObserver(this); | |
258 sync_encryption_handler_->AddObserver(&debug_info_event_listener_); | |
259 sync_encryption_handler_->AddObserver(&js_sync_encryption_handler_observer_); | |
260 | |
261 base::FilePath absolute_db_path = database_path_; | |
262 DCHECK(absolute_db_path.IsAbsolute()); | |
263 | |
264 std::unique_ptr<syncable::DirectoryBackingStore> backing_store = | |
265 args->internal_components_factory->BuildDirectoryBackingStore( | |
266 InternalComponentsFactory::STORAGE_ON_DISK, | |
267 args->credentials.account_id, absolute_db_path); | |
268 | |
269 DCHECK(backing_store.get()); | |
270 share_.directory.reset( | |
271 new syncable::Directory( | |
272 backing_store.release(), | |
273 args->unrecoverable_error_handler, | |
274 report_unrecoverable_error_function_, | |
275 sync_encryption_handler_.get(), | |
276 sync_encryption_handler_->GetCryptographerUnsafe())); | |
277 share_.sync_credentials = args->credentials; | |
278 | |
279 // UserShare is accessible to a lot of code that doesn't need access to the | |
280 // sync token so clear sync_token from the UserShare. | |
281 share_.sync_credentials.sync_token = ""; | |
282 | |
283 DVLOG(1) << "Username: " << args->credentials.email; | |
284 DVLOG(1) << "AccountId: " << args->credentials.account_id; | |
285 if (!OpenDirectory(args->credentials.account_id)) { | |
286 NotifyInitializationFailure(); | |
287 LOG(ERROR) << "Sync manager initialization failed!"; | |
288 return; | |
289 } | |
290 | |
291 // Now that we have opened the Directory we can restore any previously saved | |
292 // nigori specifics. | |
293 if (args->saved_nigori_state) { | |
294 sync_encryption_handler_->RestoreNigori(*args->saved_nigori_state); | |
295 args->saved_nigori_state.reset(); | |
296 } | |
297 | |
298 connection_manager_.reset(new SyncAPIServerConnectionManager( | |
299 args->service_url.host() + args->service_url.path(), | |
300 args->service_url.EffectiveIntPort(), | |
301 args->service_url.SchemeIsCryptographic(), args->post_factory.release(), | |
302 args->cancelation_signal)); | |
303 connection_manager_->set_client_id(directory()->cache_guid()); | |
304 connection_manager_->AddListener(this); | |
305 | |
306 std::string sync_id = directory()->cache_guid(); | |
307 | |
308 DVLOG(1) << "Setting sync client ID: " << sync_id; | |
309 allstatus_.SetSyncId(sync_id); | |
310 DVLOG(1) << "Setting invalidator client ID: " << args->invalidator_client_id; | |
311 allstatus_.SetInvalidatorClientId(args->invalidator_client_id); | |
312 | |
313 model_type_registry_.reset( | |
314 new ModelTypeRegistry(args->workers, directory(), this)); | |
315 sync_encryption_handler_->AddObserver(model_type_registry_.get()); | |
316 | |
317 // Build a SyncSessionContext and store the worker in it. | |
318 DVLOG(1) << "Sync is bringing up SyncSessionContext."; | |
319 std::vector<SyncEngineEventListener*> listeners; | |
320 listeners.push_back(&allstatus_); | |
321 listeners.push_back(this); | |
322 session_context_ = args->internal_components_factory->BuildContext( | |
323 connection_manager_.get(), directory(), args->extensions_activity, | |
324 listeners, &debug_info_event_listener_, model_type_registry_.get(), | |
325 args->invalidator_client_id); | |
326 scheduler_ = args->internal_components_factory->BuildScheduler( | |
327 name_, session_context_.get(), args->cancelation_signal); | |
328 | |
329 scheduler_->Start(SyncScheduler::CONFIGURATION_MODE, base::Time()); | |
330 | |
331 initialized_ = true; | |
332 | |
333 net::NetworkChangeNotifier::AddIPAddressObserver(this); | |
334 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); | |
335 observing_network_connectivity_changes_ = true; | |
336 | |
337 UpdateCredentials(args->credentials); | |
338 | |
339 NotifyInitializationSuccess(); | |
340 } | |
341 | |
342 void SyncManagerImpl::NotifyInitializationSuccess() { | |
343 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | |
344 OnInitializationComplete( | |
345 MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), | |
346 MakeWeakHandle(debug_info_event_listener_.GetWeakPtr()), | |
347 true, InitialSyncEndedTypes())); | |
348 } | |
349 | |
350 void SyncManagerImpl::NotifyInitializationFailure() { | |
351 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | |
352 OnInitializationComplete( | |
353 MakeWeakHandle(weak_ptr_factory_.GetWeakPtr()), | |
354 MakeWeakHandle(debug_info_event_listener_.GetWeakPtr()), | |
355 false, ModelTypeSet())); | |
356 } | |
357 | |
358 void SyncManagerImpl::OnPassphraseRequired( | |
359 PassphraseRequiredReason reason, | |
360 const sync_pb::EncryptedData& pending_keys) { | |
361 // Does nothing. | |
362 } | |
363 | |
364 void SyncManagerImpl::OnPassphraseAccepted() { | |
365 // Does nothing. | |
366 } | |
367 | |
368 void SyncManagerImpl::OnBootstrapTokenUpdated( | |
369 const std::string& bootstrap_token, | |
370 BootstrapTokenType type) { | |
371 if (type == KEYSTORE_BOOTSTRAP_TOKEN) | |
372 allstatus_.SetHasKeystoreKey(true); | |
373 } | |
374 | |
375 void SyncManagerImpl::OnEncryptedTypesChanged(ModelTypeSet encrypted_types, | |
376 bool encrypt_everything) { | |
377 allstatus_.SetEncryptedTypes(encrypted_types); | |
378 } | |
379 | |
380 void SyncManagerImpl::OnEncryptionComplete() { | |
381 // Does nothing. | |
382 } | |
383 | |
384 void SyncManagerImpl::OnCryptographerStateChanged( | |
385 Cryptographer* cryptographer) { | |
386 allstatus_.SetCryptographerReady(cryptographer->is_ready()); | |
387 allstatus_.SetCryptoHasPendingKeys(cryptographer->has_pending_keys()); | |
388 allstatus_.SetKeystoreMigrationTime( | |
389 sync_encryption_handler_->migration_time()); | |
390 } | |
391 | |
392 void SyncManagerImpl::OnPassphraseTypeChanged( | |
393 PassphraseType type, | |
394 base::Time explicit_passphrase_time) { | |
395 allstatus_.SetPassphraseType(type); | |
396 allstatus_.SetKeystoreMigrationTime( | |
397 sync_encryption_handler_->migration_time()); | |
398 } | |
399 | |
400 void SyncManagerImpl::OnLocalSetPassphraseEncryption( | |
401 const SyncEncryptionHandler::NigoriState& nigori_state) { | |
402 } | |
403 | |
404 void SyncManagerImpl::StartSyncingNormally( | |
405 const ModelSafeRoutingInfo& routing_info, | |
406 base::Time last_poll_time) { | |
407 // Start the sync scheduler. | |
408 // TODO(sync): We always want the newest set of routes when we switch back | |
409 // to normal mode. Figure out how to enforce set_routing_info is always | |
410 // appropriately set and that it's only modified when switching to normal | |
411 // mode. | |
412 DCHECK(thread_checker_.CalledOnValidThread()); | |
413 session_context_->SetRoutingInfo(routing_info); | |
414 scheduler_->Start(SyncScheduler::NORMAL_MODE, | |
415 last_poll_time); | |
416 } | |
417 | |
418 syncable::Directory* SyncManagerImpl::directory() { | |
419 return share_.directory.get(); | |
420 } | |
421 | |
422 const SyncScheduler* SyncManagerImpl::scheduler() const { | |
423 return scheduler_.get(); | |
424 } | |
425 | |
426 bool SyncManagerImpl::GetHasInvalidAuthTokenForTest() const { | |
427 return connection_manager_->HasInvalidAuthToken(); | |
428 } | |
429 | |
430 bool SyncManagerImpl::OpenDirectory(const std::string& username) { | |
431 DCHECK(!initialized_) << "Should only happen once"; | |
432 | |
433 // Set before Open(). | |
434 change_observer_ = MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr()); | |
435 WeakHandle<syncable::TransactionObserver> transaction_observer( | |
436 MakeWeakHandle(js_mutation_event_observer_.AsWeakPtr())); | |
437 | |
438 syncable::DirOpenResult open_result = syncable::NOT_INITIALIZED; | |
439 open_result = directory()->Open(username, this, transaction_observer); | |
440 if (open_result != syncable::OPENED) { | |
441 LOG(ERROR) << "Could not open share for:" << username; | |
442 return false; | |
443 } | |
444 | |
445 // Unapplied datatypes (those that do not have initial sync ended set) get | |
446 // re-downloaded during any configuration. But, it's possible for a datatype | |
447 // to have a progress marker but not have initial sync ended yet, making | |
448 // it a candidate for migration. This is a problem, as the DataTypeManager | |
449 // does not support a migration while it's already in the middle of a | |
450 // configuration. As a result, any partially synced datatype can stall the | |
451 // DTM, waiting for the configuration to complete, which it never will due | |
452 // to the migration error. In addition, a partially synced nigori will | |
453 // trigger the migration logic before the backend is initialized, resulting | |
454 // in crashes. We therefore detect and purge any partially synced types as | |
455 // part of initialization. | |
456 if (!PurgePartiallySyncedTypes()) | |
457 return false; | |
458 | |
459 return true; | |
460 } | |
461 | |
462 bool SyncManagerImpl::PurgePartiallySyncedTypes() { | |
463 ModelTypeSet partially_synced_types = ModelTypeSet::All(); | |
464 partially_synced_types.RemoveAll(directory()->InitialSyncEndedTypes()); | |
465 partially_synced_types.RemoveAll(GetTypesWithEmptyProgressMarkerToken( | |
466 ModelTypeSet::All())); | |
467 | |
468 DVLOG(1) << "Purging partially synced types " | |
469 << ModelTypeSetToString(partially_synced_types); | |
470 UMA_HISTOGRAM_COUNTS("Sync.PartiallySyncedTypes", | |
471 partially_synced_types.Size()); | |
472 if (partially_synced_types.Empty()) | |
473 return true; | |
474 return directory()->PurgeEntriesWithTypeIn(partially_synced_types, | |
475 ModelTypeSet(), | |
476 ModelTypeSet()); | |
477 } | |
478 | |
479 bool SyncManagerImpl::PurgeDisabledTypes( | |
480 ModelTypeSet to_purge, | |
481 ModelTypeSet to_journal, | |
482 ModelTypeSet to_unapply) { | |
483 if (to_purge.Empty()) | |
484 return true; | |
485 DVLOG(1) << "Purging disabled types " << ModelTypeSetToString(to_purge); | |
486 DCHECK(to_purge.HasAll(to_journal)); | |
487 DCHECK(to_purge.HasAll(to_unapply)); | |
488 return directory()->PurgeEntriesWithTypeIn(to_purge, to_journal, to_unapply); | |
489 } | |
490 | |
491 void SyncManagerImpl::UpdateCredentials(const SyncCredentials& credentials) { | |
492 DCHECK(thread_checker_.CalledOnValidThread()); | |
493 DCHECK(initialized_); | |
494 DCHECK(!credentials.account_id.empty()); | |
495 DCHECK(!credentials.sync_token.empty()); | |
496 DCHECK(!credentials.scope_set.empty()); | |
497 session_context_->set_account_name(credentials.email); | |
498 | |
499 observing_network_connectivity_changes_ = true; | |
500 if (!connection_manager_->SetAuthToken(credentials.sync_token)) | |
501 return; // Auth token is known to be invalid, so exit early. | |
502 | |
503 scheduler_->OnCredentialsUpdated(); | |
504 | |
505 // TODO(zea): pass the credential age to the debug info event listener. | |
506 } | |
507 | |
508 void SyncManagerImpl::AddObserver(SyncManager::Observer* observer) { | |
509 DCHECK(thread_checker_.CalledOnValidThread()); | |
510 observers_.AddObserver(observer); | |
511 } | |
512 | |
513 void SyncManagerImpl::RemoveObserver(SyncManager::Observer* observer) { | |
514 DCHECK(thread_checker_.CalledOnValidThread()); | |
515 observers_.RemoveObserver(observer); | |
516 } | |
517 | |
518 void SyncManagerImpl::ShutdownOnSyncThread(ShutdownReason reason) { | |
519 DCHECK(thread_checker_.CalledOnValidThread()); | |
520 | |
521 // Prevent any in-flight method calls from running. Also | |
522 // invalidates |weak_handle_this_| and |change_observer_|. | |
523 weak_ptr_factory_.InvalidateWeakPtrs(); | |
524 js_mutation_event_observer_.InvalidateWeakPtrs(); | |
525 | |
526 scheduler_.reset(); | |
527 session_context_.reset(); | |
528 | |
529 if (model_type_registry_) | |
530 sync_encryption_handler_->RemoveObserver(model_type_registry_.get()); | |
531 | |
532 model_type_registry_.reset(); | |
533 | |
534 if (sync_encryption_handler_) { | |
535 sync_encryption_handler_->RemoveObserver(&debug_info_event_listener_); | |
536 sync_encryption_handler_->RemoveObserver(this); | |
537 } | |
538 | |
539 SetJsEventHandler(WeakHandle<JsEventHandler>()); | |
540 RemoveObserver(&js_sync_manager_observer_); | |
541 | |
542 RemoveObserver(&debug_info_event_listener_); | |
543 | |
544 // |connection_manager_| may end up being NULL here in tests (in synchronous | |
545 // initialization mode). | |
546 // | |
547 // TODO(akalin): Fix this behavior. | |
548 if (connection_manager_) | |
549 connection_manager_->RemoveListener(this); | |
550 connection_manager_.reset(); | |
551 | |
552 net::NetworkChangeNotifier::RemoveIPAddressObserver(this); | |
553 net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); | |
554 observing_network_connectivity_changes_ = false; | |
555 | |
556 if (initialized_ && directory()) { | |
557 directory()->SaveChanges(); | |
558 } | |
559 | |
560 share_.directory.reset(); | |
561 | |
562 change_delegate_ = NULL; | |
563 | |
564 initialized_ = false; | |
565 | |
566 // We reset these here, since only now we know they will not be | |
567 // accessed from other threads (since we shut down everything). | |
568 change_observer_.Reset(); | |
569 weak_handle_this_.Reset(); | |
570 } | |
571 | |
572 void SyncManagerImpl::OnIPAddressChanged() { | |
573 if (!observing_network_connectivity_changes_) { | |
574 DVLOG(1) << "IP address change dropped."; | |
575 return; | |
576 } | |
577 DVLOG(1) << "IP address change detected."; | |
578 OnNetworkConnectivityChangedImpl(); | |
579 } | |
580 | |
581 void SyncManagerImpl::OnConnectionTypeChanged( | |
582 net::NetworkChangeNotifier::ConnectionType) { | |
583 if (!observing_network_connectivity_changes_) { | |
584 DVLOG(1) << "Connection type change dropped."; | |
585 return; | |
586 } | |
587 DVLOG(1) << "Connection type change detected."; | |
588 OnNetworkConnectivityChangedImpl(); | |
589 } | |
590 | |
591 void SyncManagerImpl::OnNetworkConnectivityChangedImpl() { | |
592 DCHECK(thread_checker_.CalledOnValidThread()); | |
593 scheduler_->OnConnectionStatusChange(); | |
594 } | |
595 | |
596 void SyncManagerImpl::OnServerConnectionEvent( | |
597 const ServerConnectionEvent& event) { | |
598 DCHECK(thread_checker_.CalledOnValidThread()); | |
599 if (event.connection_code == | |
600 HttpResponse::SERVER_CONNECTION_OK) { | |
601 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | |
602 OnConnectionStatusChange(CONNECTION_OK)); | |
603 } | |
604 | |
605 if (event.connection_code == HttpResponse::SYNC_AUTH_ERROR) { | |
606 observing_network_connectivity_changes_ = false; | |
607 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | |
608 OnConnectionStatusChange(CONNECTION_AUTH_ERROR)); | |
609 } | |
610 | |
611 if (event.connection_code == HttpResponse::SYNC_SERVER_ERROR) { | |
612 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | |
613 OnConnectionStatusChange(CONNECTION_SERVER_ERROR)); | |
614 } | |
615 } | |
616 | |
617 void SyncManagerImpl::HandleTransactionCompleteChangeEvent( | |
618 ModelTypeSet models_with_changes) { | |
619 // This notification happens immediately after the transaction mutex is | |
620 // released. This allows work to be performed without blocking other threads | |
621 // from acquiring a transaction. | |
622 if (!change_delegate_) | |
623 return; | |
624 | |
625 // Call commit. | |
626 for (ModelTypeSet::Iterator it = models_with_changes.First(); | |
627 it.Good(); it.Inc()) { | |
628 change_delegate_->OnChangesComplete(it.Get()); | |
629 change_observer_.Call( | |
630 FROM_HERE, | |
631 &SyncManager::ChangeObserver::OnChangesComplete, | |
632 it.Get()); | |
633 } | |
634 } | |
635 | |
636 ModelTypeSet | |
637 SyncManagerImpl::HandleTransactionEndingChangeEvent( | |
638 const ImmutableWriteTransactionInfo& write_transaction_info, | |
639 syncable::BaseTransaction* trans) { | |
640 // This notification happens immediately before a syncable WriteTransaction | |
641 // falls out of scope. It happens while the channel mutex is still held, | |
642 // and while the transaction mutex is held, so it cannot be re-entrant. | |
643 if (!change_delegate_ || change_records_.empty()) | |
644 return ModelTypeSet(); | |
645 | |
646 // This will continue the WriteTransaction using a read only wrapper. | |
647 // This is the last chance for read to occur in the WriteTransaction | |
648 // that's closing. This special ReadTransaction will not close the | |
649 // underlying transaction. | |
650 ReadTransaction read_trans(GetUserShare(), trans); | |
651 | |
652 ModelTypeSet models_with_changes; | |
653 for (ChangeRecordMap::const_iterator it = change_records_.begin(); | |
654 it != change_records_.end(); ++it) { | |
655 DCHECK(!it->second.Get().empty()); | |
656 ModelType type = ModelTypeFromInt(it->first); | |
657 change_delegate_-> | |
658 OnChangesApplied(type, trans->directory()->GetTransactionVersion(type), | |
659 &read_trans, it->second); | |
660 change_observer_.Call(FROM_HERE, | |
661 &SyncManager::ChangeObserver::OnChangesApplied, | |
662 type, write_transaction_info.Get().id, it->second); | |
663 models_with_changes.Put(type); | |
664 } | |
665 change_records_.clear(); | |
666 return models_with_changes; | |
667 } | |
668 | |
669 void SyncManagerImpl::HandleCalculateChangesChangeEventFromSyncApi( | |
670 const ImmutableWriteTransactionInfo& write_transaction_info, | |
671 syncable::BaseTransaction* trans, | |
672 std::vector<int64_t>* entries_changed) { | |
673 // We have been notified about a user action changing a sync model. | |
674 LOG_IF(WARNING, !change_records_.empty()) << | |
675 "CALCULATE_CHANGES called with unapplied old changes."; | |
676 | |
677 // The mutated model type, or UNSPECIFIED if nothing was mutated. | |
678 ModelTypeSet mutated_model_types; | |
679 | |
680 const syncable::ImmutableEntryKernelMutationMap& mutations = | |
681 write_transaction_info.Get().mutations; | |
682 for (syncable::EntryKernelMutationMap::const_iterator it = | |
683 mutations.Get().begin(); it != mutations.Get().end(); ++it) { | |
684 if (!it->second.mutated.ref(syncable::IS_UNSYNCED)) { | |
685 continue; | |
686 } | |
687 | |
688 ModelType model_type = | |
689 GetModelTypeFromSpecifics(it->second.mutated.ref(SPECIFICS)); | |
690 if (model_type < FIRST_REAL_MODEL_TYPE) { | |
691 NOTREACHED() << "Permanent or underspecified item changed via syncapi."; | |
692 continue; | |
693 } | |
694 | |
695 // Found real mutation. | |
696 if (model_type != UNSPECIFIED) { | |
697 mutated_model_types.Put(model_type); | |
698 entries_changed->push_back(it->second.mutated.ref(syncable::META_HANDLE)); | |
699 } | |
700 } | |
701 | |
702 // Nudge if necessary. | |
703 if (!mutated_model_types.Empty()) { | |
704 if (weak_handle_this_.IsInitialized()) { | |
705 weak_handle_this_.Call(FROM_HERE, | |
706 &SyncManagerImpl::RequestNudgeForDataTypes, | |
707 FROM_HERE, | |
708 mutated_model_types); | |
709 } else { | |
710 NOTREACHED(); | |
711 } | |
712 } | |
713 } | |
714 | |
715 void SyncManagerImpl::SetExtraChangeRecordData( | |
716 int64_t id, | |
717 ModelType type, | |
718 ChangeReorderBuffer* buffer, | |
719 Cryptographer* cryptographer, | |
720 const syncable::EntryKernel& original, | |
721 bool existed_before, | |
722 bool exists_now) { | |
723 // If this is a deletion and the datatype was encrypted, we need to decrypt it | |
724 // and attach it to the buffer. | |
725 if (!exists_now && existed_before) { | |
726 sync_pb::EntitySpecifics original_specifics(original.ref(SPECIFICS)); | |
727 if (type == PASSWORDS) { | |
728 // Passwords must use their own legacy ExtraPasswordChangeRecordData. | |
729 std::unique_ptr<sync_pb::PasswordSpecificsData> data( | |
730 DecryptPasswordSpecifics(original_specifics, cryptographer)); | |
731 if (!data) { | |
732 NOTREACHED(); | |
733 return; | |
734 } | |
735 buffer->SetExtraDataForId(id, new ExtraPasswordChangeRecordData(*data)); | |
736 } else if (original_specifics.has_encrypted()) { | |
737 // All other datatypes can just create a new unencrypted specifics and | |
738 // attach it. | |
739 const sync_pb::EncryptedData& encrypted = original_specifics.encrypted(); | |
740 if (!cryptographer->Decrypt(encrypted, &original_specifics)) { | |
741 NOTREACHED(); | |
742 return; | |
743 } | |
744 } | |
745 buffer->SetSpecificsForId(id, original_specifics); | |
746 } | |
747 } | |
748 | |
749 void SyncManagerImpl::HandleCalculateChangesChangeEventFromSyncer( | |
750 const ImmutableWriteTransactionInfo& write_transaction_info, | |
751 syncable::BaseTransaction* trans, | |
752 std::vector<int64_t>* entries_changed) { | |
753 // We only expect one notification per sync step, so change_buffers_ should | |
754 // contain no pending entries. | |
755 LOG_IF(WARNING, !change_records_.empty()) << | |
756 "CALCULATE_CHANGES called with unapplied old changes."; | |
757 | |
758 ChangeReorderBuffer change_buffers[MODEL_TYPE_COUNT]; | |
759 | |
760 Cryptographer* crypto = directory()->GetCryptographer(trans); | |
761 const syncable::ImmutableEntryKernelMutationMap& mutations = | |
762 write_transaction_info.Get().mutations; | |
763 for (syncable::EntryKernelMutationMap::const_iterator it = | |
764 mutations.Get().begin(); it != mutations.Get().end(); ++it) { | |
765 bool existed_before = !it->second.original.ref(syncable::IS_DEL); | |
766 bool exists_now = !it->second.mutated.ref(syncable::IS_DEL); | |
767 | |
768 // Omit items that aren't associated with a model. | |
769 ModelType type = | |
770 GetModelTypeFromSpecifics(it->second.mutated.ref(SPECIFICS)); | |
771 if (type < FIRST_REAL_MODEL_TYPE) | |
772 continue; | |
773 | |
774 int64_t handle = it->first; | |
775 if (exists_now && !existed_before) | |
776 change_buffers[type].PushAddedItem(handle); | |
777 else if (!exists_now && existed_before) | |
778 change_buffers[type].PushDeletedItem(handle); | |
779 else if (exists_now && existed_before && | |
780 VisiblePropertiesDiffer(it->second, crypto)) | |
781 change_buffers[type].PushUpdatedItem(handle); | |
782 | |
783 SetExtraChangeRecordData(handle, type, &change_buffers[type], crypto, | |
784 it->second.original, existed_before, exists_now); | |
785 } | |
786 | |
787 ReadTransaction read_trans(GetUserShare(), trans); | |
788 for (int i = FIRST_REAL_MODEL_TYPE; i < MODEL_TYPE_COUNT; ++i) { | |
789 if (!change_buffers[i].IsEmpty()) { | |
790 if (change_buffers[i].GetAllChangesInTreeOrder(&read_trans, | |
791 &(change_records_[i]))) { | |
792 for (size_t j = 0; j < change_records_[i].Get().size(); ++j) | |
793 entries_changed->push_back((change_records_[i].Get())[j].id); | |
794 } | |
795 if (change_records_[i].Get().empty()) | |
796 change_records_.erase(i); | |
797 } | |
798 } | |
799 } | |
800 | |
801 void SyncManagerImpl::RequestNudgeForDataTypes( | |
802 const tracked_objects::Location& nudge_location, | |
803 ModelTypeSet types) { | |
804 debug_info_event_listener_.OnNudgeFromDatatype(types.First().Get()); | |
805 | |
806 scheduler_->ScheduleLocalNudge(types, nudge_location); | |
807 } | |
808 | |
809 void SyncManagerImpl::NudgeForInitialDownload(syncer::ModelType type) { | |
810 DCHECK(thread_checker_.CalledOnValidThread()); | |
811 scheduler_->ScheduleInitialSyncNudge(type); | |
812 } | |
813 | |
814 void SyncManagerImpl::NudgeForCommit(syncer::ModelType type) { | |
815 DCHECK(thread_checker_.CalledOnValidThread()); | |
816 RequestNudgeForDataTypes(FROM_HERE, ModelTypeSet(type)); | |
817 } | |
818 | |
819 void SyncManagerImpl::NudgeForRefresh(syncer::ModelType type) { | |
820 DCHECK(thread_checker_.CalledOnValidThread()); | |
821 RefreshTypes(ModelTypeSet(type)); | |
822 } | |
823 | |
824 void SyncManagerImpl::OnSyncCycleEvent(const SyncCycleEvent& event) { | |
825 DCHECK(thread_checker_.CalledOnValidThread()); | |
826 // Only send an event if this is due to a cycle ending and this cycle | |
827 // concludes a canonical "sync" process; that is, based on what is known | |
828 // locally we are "all happy" and up to date. There may be new changes on | |
829 // the server, but we'll get them on a subsequent sync. | |
830 // | |
831 // Notifications are sent at the end of every sync cycle, regardless of | |
832 // whether we should sync again. | |
833 if (event.what_happened == SyncCycleEvent::SYNC_CYCLE_ENDED) { | |
834 if (!initialized_) { | |
835 DVLOG(1) << "OnSyncCycleCompleted not sent because sync api is not " | |
836 << "initialized"; | |
837 return; | |
838 } | |
839 | |
840 DVLOG(1) << "Sending OnSyncCycleCompleted"; | |
841 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | |
842 OnSyncCycleCompleted(event.snapshot)); | |
843 } | |
844 } | |
845 | |
846 void SyncManagerImpl::OnActionableError(const SyncProtocolError& error) { | |
847 FOR_EACH_OBSERVER( | |
848 SyncManager::Observer, observers_, | |
849 OnActionableError(error)); | |
850 } | |
851 | |
852 void SyncManagerImpl::OnRetryTimeChanged(base::Time) {} | |
853 | |
854 void SyncManagerImpl::OnThrottledTypesChanged(ModelTypeSet) {} | |
855 | |
856 void SyncManagerImpl::OnMigrationRequested(ModelTypeSet types) { | |
857 FOR_EACH_OBSERVER( | |
858 SyncManager::Observer, observers_, | |
859 OnMigrationRequested(types)); | |
860 } | |
861 | |
862 void SyncManagerImpl::OnProtocolEvent(const ProtocolEvent& event) { | |
863 protocol_event_buffer_.RecordProtocolEvent(event); | |
864 FOR_EACH_OBSERVER(SyncManager::Observer, observers_, | |
865 OnProtocolEvent(event)); | |
866 } | |
867 | |
868 void SyncManagerImpl::SetJsEventHandler( | |
869 const WeakHandle<JsEventHandler>& event_handler) { | |
870 js_sync_manager_observer_.SetJsEventHandler(event_handler); | |
871 js_mutation_event_observer_.SetJsEventHandler(event_handler); | |
872 js_sync_encryption_handler_observer_.SetJsEventHandler(event_handler); | |
873 } | |
874 | |
875 std::unique_ptr<base::ListValue> SyncManagerImpl::GetAllNodesForType( | |
876 syncer::ModelType type) { | |
877 DirectoryTypeDebugInfoEmitterMap* emitter_map = | |
878 model_type_registry_->directory_type_debug_info_emitter_map(); | |
879 DirectoryTypeDebugInfoEmitterMap::iterator it = emitter_map->find(type); | |
880 | |
881 if (it == emitter_map->end()) { | |
882 // This can happen in some cases. The UI thread makes requests of us | |
883 // when it doesn't really know which types are enabled or disabled. | |
884 DLOG(WARNING) << "Asked to return debug info for invalid type " | |
885 << ModelTypeToString(type); | |
886 return std::unique_ptr<base::ListValue>(new base::ListValue()); | |
887 } | |
888 | |
889 return it->second->GetAllNodes(); | |
890 } | |
891 | |
892 void SyncManagerImpl::SetInvalidatorEnabled(bool invalidator_enabled) { | |
893 DCHECK(thread_checker_.CalledOnValidThread()); | |
894 | |
895 DVLOG(1) << "Invalidator enabled state is now: " << invalidator_enabled; | |
896 allstatus_.SetNotificationsEnabled(invalidator_enabled); | |
897 scheduler_->SetNotificationsEnabled(invalidator_enabled); | |
898 } | |
899 | |
900 void SyncManagerImpl::OnIncomingInvalidation( | |
901 syncer::ModelType type, | |
902 std::unique_ptr<InvalidationInterface> invalidation) { | |
903 DCHECK(thread_checker_.CalledOnValidThread()); | |
904 | |
905 allstatus_.IncrementNotificationsReceived(); | |
906 scheduler_->ScheduleInvalidationNudge(type, std::move(invalidation), | |
907 FROM_HERE); | |
908 } | |
909 | |
910 void SyncManagerImpl::RefreshTypes(ModelTypeSet types) { | |
911 DCHECK(thread_checker_.CalledOnValidThread()); | |
912 if (types.Empty()) { | |
913 LOG(WARNING) << "Sync received refresh request with no types specified."; | |
914 } else { | |
915 scheduler_->ScheduleLocalRefreshRequest( | |
916 types, FROM_HERE); | |
917 } | |
918 } | |
919 | |
920 SyncStatus SyncManagerImpl::GetDetailedStatus() const { | |
921 return allstatus_.status(); | |
922 } | |
923 | |
924 void SyncManagerImpl::SaveChanges() { | |
925 directory()->SaveChanges(); | |
926 } | |
927 | |
928 UserShare* SyncManagerImpl::GetUserShare() { | |
929 DCHECK(initialized_); | |
930 return &share_; | |
931 } | |
932 | |
933 std::unique_ptr<syncer_v2::ModelTypeConnector> | |
934 SyncManagerImpl::GetModelTypeConnectorProxy() { | |
935 DCHECK(initialized_); | |
936 return base::WrapUnique(new syncer_v2::ModelTypeConnectorProxy( | |
937 base::ThreadTaskRunnerHandle::Get(), model_type_registry_->AsWeakPtr())); | |
938 } | |
939 | |
940 const std::string SyncManagerImpl::cache_guid() { | |
941 DCHECK(initialized_); | |
942 return directory()->cache_guid(); | |
943 } | |
944 | |
945 bool SyncManagerImpl::ReceivedExperiment(Experiments* experiments) { | |
946 ReadTransaction trans(FROM_HERE, GetUserShare()); | |
947 ReadNode nigori_node(&trans); | |
948 if (nigori_node.InitTypeRoot(NIGORI) != BaseNode::INIT_OK) { | |
949 DVLOG(1) << "Couldn't find Nigori node."; | |
950 return false; | |
951 } | |
952 bool found_experiment = false; | |
953 | |
954 ReadNode favicon_sync_node(&trans); | |
955 if (favicon_sync_node.InitByClientTagLookup( | |
956 syncer::EXPERIMENTS, | |
957 syncer::kFaviconSyncTag) == BaseNode::INIT_OK) { | |
958 experiments->favicon_sync_limit = | |
959 favicon_sync_node.GetExperimentsSpecifics().favicon_sync(). | |
960 favicon_sync_limit(); | |
961 found_experiment = true; | |
962 } | |
963 | |
964 ReadNode pre_commit_update_avoidance_node(&trans); | |
965 if (pre_commit_update_avoidance_node.InitByClientTagLookup( | |
966 syncer::EXPERIMENTS, | |
967 syncer::kPreCommitUpdateAvoidanceTag) == BaseNode::INIT_OK) { | |
968 session_context_->set_server_enabled_pre_commit_update_avoidance( | |
969 pre_commit_update_avoidance_node.GetExperimentsSpecifics(). | |
970 pre_commit_update_avoidance().enabled()); | |
971 // We don't bother setting found_experiment. The frontend doesn't need to | |
972 // know about this. | |
973 } | |
974 | |
975 ReadNode gcm_invalidations_node(&trans); | |
976 if (gcm_invalidations_node.InitByClientTagLookup( | |
977 syncer::EXPERIMENTS, syncer::kGCMInvalidationsTag) == | |
978 BaseNode::INIT_OK) { | |
979 const sync_pb::GcmInvalidationsFlags& gcm_invalidations = | |
980 gcm_invalidations_node.GetExperimentsSpecifics().gcm_invalidations(); | |
981 if (gcm_invalidations.has_enabled()) { | |
982 experiments->gcm_invalidations_enabled = gcm_invalidations.enabled(); | |
983 found_experiment = true; | |
984 } | |
985 } | |
986 | |
987 return found_experiment; | |
988 } | |
989 | |
990 bool SyncManagerImpl::HasUnsyncedItems() { | |
991 ReadTransaction trans(FROM_HERE, GetUserShare()); | |
992 return (trans.GetWrappedTrans()->directory()->unsynced_entity_count() != 0); | |
993 } | |
994 | |
995 SyncEncryptionHandler* SyncManagerImpl::GetEncryptionHandler() { | |
996 return sync_encryption_handler_.get(); | |
997 } | |
998 | |
999 ScopedVector<syncer::ProtocolEvent> | |
1000 SyncManagerImpl::GetBufferedProtocolEvents() { | |
1001 return protocol_event_buffer_.GetBufferedProtocolEvents(); | |
1002 } | |
1003 | |
1004 void SyncManagerImpl::RegisterDirectoryTypeDebugInfoObserver( | |
1005 syncer::TypeDebugInfoObserver* observer) { | |
1006 model_type_registry_->RegisterDirectoryTypeDebugInfoObserver(observer); | |
1007 } | |
1008 | |
1009 void SyncManagerImpl::UnregisterDirectoryTypeDebugInfoObserver( | |
1010 syncer::TypeDebugInfoObserver* observer) { | |
1011 model_type_registry_->UnregisterDirectoryTypeDebugInfoObserver(observer); | |
1012 } | |
1013 | |
1014 bool SyncManagerImpl::HasDirectoryTypeDebugInfoObserver( | |
1015 syncer::TypeDebugInfoObserver* observer) { | |
1016 return model_type_registry_->HasDirectoryTypeDebugInfoObserver(observer); | |
1017 } | |
1018 | |
1019 void SyncManagerImpl::RequestEmitDebugInfo() { | |
1020 model_type_registry_->RequestEmitDebugInfo(); | |
1021 } | |
1022 | |
1023 void SyncManagerImpl::ClearServerData(const ClearServerDataCallback& callback) { | |
1024 DCHECK(thread_checker_.CalledOnValidThread()); | |
1025 scheduler_->Start(SyncScheduler::CLEAR_SERVER_DATA_MODE, base::Time()); | |
1026 ClearParams params(callback); | |
1027 scheduler_->ScheduleClearServerData(params); | |
1028 } | |
1029 | |
1030 void SyncManagerImpl::OnCookieJarChanged(bool account_mismatch, | |
1031 bool empty_jar) { | |
1032 DCHECK(thread_checker_.CalledOnValidThread()); | |
1033 session_context_->set_cookie_jar_mismatch(account_mismatch); | |
1034 session_context_->set_cookie_jar_empty(empty_jar); | |
1035 } | |
1036 | |
1037 } // namespace syncer | |
OLD | NEW |