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.
5 #include "components/sync_driver/about_sync_util.h"
9 #include "base/location.h"
10 #include "base/strings/string16.h"
11 #include "base/strings/stringprintf.h"
12 #include "base/values.h"
13 #include "components/signin/core/browser/signin_manager_base.h"
14 #include "components/sync_driver/sync_service.h"
15 #include "components/version_info/version_info.h"
16 #include "sync/api/time.h"
17 #include "sync/internal_api/public/engine/sync_status.h"
18 #include "sync/internal_api/public/sessions/sync_session_snapshot.h"
19 #include "sync/internal_api/public/util/sync_string_conversions.h"
20 #include "sync/protocol/proto_enum_conversions.h"
22 using base::DictionaryValue
;
23 using base::ListValue
;
25 namespace sync_driver
{
27 namespace sync_ui_util
{
29 const char kIdentityTitle
[] = "Identity";
30 const char kDetailsKey
[] = "details";
33 const char kAboutJS
[] = "about.js";
34 const char kChromeSyncJS
[] = "chrome_sync.js";
35 const char kDataJS
[] = "data.js";
36 const char kEventsJS
[] = "events.js";
37 const char kSearchJS
[] = "search.js";
38 const char kSyncIndexJS
[] = "sync_index.js";
39 const char kSyncLogJS
[] = "sync_log.js";
40 const char kSyncNodeBrowserJS
[] = "sync_node_browser.js";
41 const char kSyncSearchJS
[] = "sync_search.js";
42 const char kTypesJS
[] = "types.js";
45 const char kDispatchEvent
[] = "chrome.sync.dispatchEvent";
46 const char kGetAllNodes
[] = "getAllNodes";
47 const char kGetAllNodesCallback
[] = "chrome.sync.getAllNodesCallback";
48 const char kRegisterForEvents
[] = "registerForEvents";
49 const char kRegisterForPerTypeCounters
[] = "registerForPerTypeCounters";
50 const char kRequestListOfTypes
[] = "requestListOfTypes";
51 const char kRequestUpdatedAboutInfo
[] = "requestUpdatedAboutInfo";
54 const char kCommit
[] = "commit";
55 const char kCounters
[] = "counters";
56 const char kCounterType
[] = "counterType";
57 const char kModelType
[] = "modelType";
58 const char kOnAboutInfoUpdated
[] = "onAboutInfoUpdated";
59 const char kOnCountersUpdated
[] = "onCountersUpdated";
60 const char kOnProtocolEvent
[] = "onProtocolEvent";
61 const char kOnReceivedListOfTypes
[] = "onReceivedListOfTypes";
62 const char kStatus
[] = "status";
63 const char kTypes
[] = "types";
64 const char kUpdate
[] = "update";
68 // Creates a 'section' for display on about:sync, consisting of a title and a
69 // list of fields. Returns a pointer to the new section. Note that
70 // |parent_list|, not the caller, owns the newly added section.
71 base::ListValue
* AddSection(base::ListValue
* parent_list
,
72 const std::string
& title
) {
73 base::DictionaryValue
* section
= new base::DictionaryValue();
74 base::ListValue
* section_contents
= new base::ListValue();
75 section
->SetString("title", title
);
76 section
->Set("data", section_contents
);
77 section
->SetBoolean("is_sensitive", false);
78 parent_list
->Append(section
);
79 return section_contents
;
82 // Same as AddSection, but for data that should be elided when dumped into text
83 // form and posted in a public forum (e.g. unique identifiers).
84 base::ListValue
* AddSensitiveSection(base::ListValue
* parent_list
,
85 const std::string
& title
) {
86 base::DictionaryValue
* section
= new base::DictionaryValue();
87 base::ListValue
* section_contents
= new base::ListValue();
88 section
->SetString("title", title
);
89 section
->Set("data", section_contents
);
90 section
->SetBoolean("is_sensitive", true);
91 parent_list
->Append(section
);
92 return section_contents
;
95 // The following helper classes help manage the about:sync fields which will be
96 // populated in method in ConstructAboutInformation.
98 // Each instance of one of thse classes indicates a field in about:sync. Each
99 // field will be serialized to a DictionaryValue with entries for 'stat_name',
100 // 'stat_value' and 'is_valid'.
102 class StringSyncStat
{
104 StringSyncStat(base::ListValue
* section
, const std::string
& key
);
105 void SetValue(const std::string
& value
);
106 void SetValue(const base::string16
& value
);
109 // Owned by the |section| passed in during construction.
110 base::DictionaryValue
* stat_
;
113 StringSyncStat::StringSyncStat(base::ListValue
* section
,
114 const std::string
& key
) {
115 stat_
= new base::DictionaryValue();
116 stat_
->SetString("stat_name", key
);
117 stat_
->SetString("stat_value", "Uninitialized");
118 stat_
->SetBoolean("is_valid", false);
119 section
->Append(stat_
);
122 void StringSyncStat::SetValue(const std::string
& value
) {
123 stat_
->SetString("stat_value", value
);
124 stat_
->SetBoolean("is_valid", true);
127 void StringSyncStat::SetValue(const base::string16
& value
) {
128 stat_
->SetString("stat_value", value
);
129 stat_
->SetBoolean("is_valid", true);
134 BoolSyncStat(base::ListValue
* section
, const std::string
& key
);
135 void SetValue(bool value
);
138 // Owned by the |section| passed in during construction.
139 base::DictionaryValue
* stat_
;
142 BoolSyncStat::BoolSyncStat(base::ListValue
* section
, const std::string
& key
) {
143 stat_
= new base::DictionaryValue();
144 stat_
->SetString("stat_name", key
);
145 stat_
->SetBoolean("stat_value", false);
146 stat_
->SetBoolean("is_valid", false);
147 section
->Append(stat_
);
150 void BoolSyncStat::SetValue(bool value
) {
151 stat_
->SetBoolean("stat_value", value
);
152 stat_
->SetBoolean("is_valid", true);
157 IntSyncStat(base::ListValue
* section
, const std::string
& key
);
158 void SetValue(int value
);
161 // Owned by the |section| passed in during construction.
162 base::DictionaryValue
* stat_
;
165 IntSyncStat::IntSyncStat(base::ListValue
* section
, const std::string
& key
) {
166 stat_
= new base::DictionaryValue();
167 stat_
->SetString("stat_name", key
);
168 stat_
->SetInteger("stat_value", 0);
169 stat_
->SetBoolean("is_valid", false);
170 section
->Append(stat_
);
173 void IntSyncStat::SetValue(int value
) {
174 stat_
->SetInteger("stat_value", value
);
175 stat_
->SetBoolean("is_valid", true);
178 // Returns a string describing the chrome version environment. Version format:
179 // <Build Info> <OS> <Version number> (<Last change>)<channel or "-devel">
180 // If version information is unavailable, returns "invalid."
181 // TODO(zea): this approximately matches MakeUserAgentForSyncApi in
182 // sync_backend_host.cc. Unify the two if possible.
183 std::string
GetVersionString(version_info::Channel channel
) {
184 // Build a version string that matches MakeUserAgentForSyncApi with the
185 // addition of channel info and proper OS names.
186 // chrome::GetChannelString() returns empty string for stable channel or
187 // unofficial builds, the channel string otherwise. We want to have "-devel"
188 // for unofficial builds only.
189 std::string version_modifier
= version_info::GetChannelString(channel
);
190 if (version_modifier
.empty()) {
191 if (channel
!= version_info::Channel::STABLE
) {
192 version_modifier
= "-devel";
195 version_modifier
= " " + version_modifier
;
197 return version_info::GetProductName() + " " + version_info::GetOSType() +
198 " " + version_info::GetVersionNumber() + " (" +
199 version_info::GetLastChange() + ")" + version_modifier
;
202 std::string
GetTimeStr(base::Time time
, const std::string
& default_msg
) {
203 std::string time_str
;
205 time_str
= default_msg
;
207 time_str
= syncer::GetTimeDebugString(time
);
211 std::string
GetConnectionStatus(
212 const sync_driver::SyncService::SyncTokenStatus
& status
) {
214 switch (status
.connection_status
) {
215 case syncer::CONNECTION_NOT_ATTEMPTED
:
216 base::StringAppendF(&message
, "not attempted");
218 case syncer::CONNECTION_OK
:
220 &message
, "OK since %s",
221 GetTimeStr(status
.connection_status_update_time
, "n/a").c_str());
223 case syncer::CONNECTION_AUTH_ERROR
:
225 &message
, "auth error since %s",
226 GetTimeStr(status
.connection_status_update_time
, "n/a").c_str());
228 case syncer::CONNECTION_SERVER_ERROR
:
230 &message
, "server error since %s",
231 GetTimeStr(status
.connection_status_update_time
, "n/a").c_str());
241 // This function both defines the structure of the message to be returned and
242 // its contents. Most of the message consists of simple fields in about:sync
243 // which are grouped into sections and populated with the help of the SyncStat
244 // classes defined above.
245 scoped_ptr
<base::DictionaryValue
> ConstructAboutInformation(
246 sync_driver::SyncService
* service
,
247 SigninManagerBase
* signin
,
248 version_info::Channel channel
) {
249 scoped_ptr
<base::DictionaryValue
> about_info(new base::DictionaryValue());
251 // 'details': A list of sections.
252 base::ListValue
* stats_list
= new base::ListValue();
254 // The following lines define the sections and their fields. For each field,
255 // a class is instantiated, which allows us to reference the fields in
256 // 'setter' code later on in this function.
257 base::ListValue
* section_summary
= AddSection(stats_list
, "Summary");
258 StringSyncStat
summary_string(section_summary
, "Summary");
260 base::ListValue
* section_version
= AddSection(stats_list
, "Version Info");
261 StringSyncStat
client_version(section_version
, "Client Version");
262 StringSyncStat
server_url(section_version
, "Server URL");
264 base::ListValue
* section_identity
=
265 AddSensitiveSection(stats_list
, kIdentityTitle
);
266 StringSyncStat
sync_id(section_identity
, "Sync Client ID");
267 StringSyncStat
invalidator_id(section_identity
, "Invalidator Client ID");
268 StringSyncStat
username(section_identity
, "Username");
270 base::ListValue
* section_credentials
= AddSection(stats_list
, "Credentials");
271 StringSyncStat
request_token_time(section_credentials
, "Requested Token");
272 StringSyncStat
receive_token_time(section_credentials
, "Received Token");
273 StringSyncStat
token_request_status(section_credentials
,
274 "Token Request Status");
275 StringSyncStat
next_token_request(section_credentials
,
276 "Next Token Request");
278 base::ListValue
* section_local
= AddSection(stats_list
, "Local State");
279 StringSyncStat
server_connection(section_local
,
280 "Server Connection");
281 StringSyncStat
last_synced(section_local
, "Last Synced");
282 BoolSyncStat
is_setup_complete(section_local
,
283 "Sync First-Time Setup Complete");
284 StringSyncStat
backend_initialization(section_local
,
285 "Sync Backend Initialization");
286 BoolSyncStat
is_syncing(section_local
, "Syncing");
288 base::ListValue
* section_network
= AddSection(stats_list
, "Network");
289 BoolSyncStat
is_throttled(section_network
, "Throttled");
290 StringSyncStat
retry_time(section_network
, "Retry time (maybe stale)");
291 BoolSyncStat
are_notifications_enabled(section_network
,
292 "Notifications Enabled");
294 base::ListValue
* section_encryption
= AddSection(stats_list
, "Encryption");
295 BoolSyncStat
is_using_explicit_passphrase(section_encryption
,
296 "Explicit Passphrase");
297 BoolSyncStat
is_passphrase_required(section_encryption
,
298 "Passphrase Required");
299 BoolSyncStat
is_cryptographer_ready(section_encryption
,
300 "Cryptographer Ready");
301 BoolSyncStat
has_pending_keys(section_encryption
,
302 "Cryptographer Has Pending Keys");
303 StringSyncStat
encrypted_types(section_encryption
, "Encrypted Types");
304 BoolSyncStat
has_keystore_key(section_encryption
, "Has Keystore Key");
305 StringSyncStat
keystore_migration_time(section_encryption
,
306 "Keystore Migration Time");
307 StringSyncStat
passphrase_type(section_encryption
,
309 StringSyncStat
passphrase_time(section_encryption
,
312 base::ListValue
* section_last_session
= AddSection(
313 stats_list
, "Status from Last Completed Session");
314 StringSyncStat
session_source(section_last_session
, "Sync Source");
315 StringSyncStat
get_key_result(section_last_session
, "GetKey Step Result");
316 StringSyncStat
download_result(section_last_session
, "Download Step Result");
317 StringSyncStat
commit_result(section_last_session
, "Commit Step Result");
319 base::ListValue
* section_counters
= AddSection(stats_list
, "Running Totals");
320 IntSyncStat
notifications_received(section_counters
,
321 "Notifications Received");
322 IntSyncStat
updates_received(section_counters
, "Updates Downloaded");
323 IntSyncStat
tombstone_updates(section_counters
, "Tombstone Updates");
324 IntSyncStat
reflected_updates(section_counters
, "Reflected Updates");
325 IntSyncStat
successful_commits(section_counters
, "Successful Commits");
326 IntSyncStat
conflicts_resolved_local_wins(section_counters
,
327 "Conflicts Resolved: Client Wins");
328 IntSyncStat
conflicts_resolved_server_wins(section_counters
,
329 "Conflicts Resolved: Server Wins");
331 base::ListValue
*section_this_cycle
= AddSection(stats_list
,
332 "Transient Counters (this cycle)");
333 IntSyncStat
encryption_conflicts(section_this_cycle
, "Encryption Conflicts");
334 IntSyncStat
hierarchy_conflicts(section_this_cycle
, "Hierarchy Conflicts");
335 IntSyncStat
server_conflicts(section_this_cycle
, "Server Conflicts");
336 IntSyncStat
committed_items(section_this_cycle
, "Committed Items");
338 base::ListValue
* section_that_cycle
= AddSection(
339 stats_list
, "Transient Counters (last cycle of last completed session)");
340 IntSyncStat
updates_downloaded(section_that_cycle
, "Updates Downloaded");
341 IntSyncStat
committed_count(section_that_cycle
, "Committed Count");
342 IntSyncStat
entries(section_that_cycle
, "Entries");
344 base::ListValue
* section_nudge_info
= AddSection(
345 stats_list
, "Nudge Source Counters");
346 IntSyncStat
nudge_source_notification(
347 section_nudge_info
, "Server Invalidations");
348 IntSyncStat
nudge_source_local(section_nudge_info
, "Local Changes");
349 IntSyncStat
nudge_source_local_refresh(section_nudge_info
, "Local Refreshes");
351 // This list of sections belongs in the 'details' field of the returned
353 about_info
->Set(kDetailsKey
, stats_list
);
355 // Populate all the fields we declared above.
356 client_version
.SetValue(GetVersionString(channel
));
359 summary_string
.SetValue("Sync service does not exist");
360 return about_info
.Pass();
363 syncer::SyncStatus full_status
;
364 bool is_status_valid
= service
->QueryDetailedSyncStatus(&full_status
);
365 bool sync_active
= service
->IsSyncActive();
366 const syncer::sessions::SyncSessionSnapshot
& snapshot
=
367 service
->GetLastSessionSnapshot();
370 summary_string
.SetValue(service
->QuerySyncStatusSummaryString());
372 server_url
.SetValue(service
->sync_service_url().spec());
374 if (is_status_valid
&& !full_status
.sync_id
.empty())
375 sync_id
.SetValue(full_status
.sync_id
);
376 if (is_status_valid
&& !full_status
.invalidator_client_id
.empty())
377 invalidator_id
.SetValue(full_status
.invalidator_client_id
);
379 username
.SetValue(signin
->GetAuthenticatedAccountInfo().email
);
381 const sync_driver::SyncService::SyncTokenStatus
& token_status
=
382 service
->GetSyncTokenStatus();
383 server_connection
.SetValue(GetConnectionStatus(token_status
));
384 request_token_time
.SetValue(GetTimeStr(token_status
.token_request_time
,
386 receive_token_time
.SetValue(GetTimeStr(token_status
.token_receive_time
,
388 std::string err
= token_status
.last_get_token_error
.error_message();
389 token_request_status
.SetValue(err
.empty() ? "OK" : err
);
390 next_token_request
.SetValue(
391 GetTimeStr(token_status
.next_token_request_time
, "not scheduled"));
393 last_synced
.SetValue(service
->GetLastSyncedTimeString());
394 is_setup_complete
.SetValue(service
->HasSyncSetupCompleted());
395 backend_initialization
.SetValue(
396 service
->GetBackendInitializationStateString());
397 if (is_status_valid
) {
398 is_syncing
.SetValue(full_status
.syncing
);
399 retry_time
.SetValue(GetTimeStr(full_status
.retry_time
,
400 "Scheduler is not in backoff or throttled"));
403 if (snapshot
.is_initialized())
404 is_throttled
.SetValue(snapshot
.is_silenced());
405 if (is_status_valid
) {
406 are_notifications_enabled
.SetValue(
407 full_status
.notifications_enabled
);
411 is_using_explicit_passphrase
.SetValue(
412 service
->IsUsingSecondaryPassphrase());
413 is_passphrase_required
.SetValue(service
->IsPassphraseRequired());
414 passphrase_time
.SetValue(
415 GetTimeStr(service
->GetExplicitPassphraseTime(), "No Passphrase Time"));
417 if (is_status_valid
) {
418 is_cryptographer_ready
.SetValue(full_status
.cryptographer_ready
);
419 has_pending_keys
.SetValue(full_status
.crypto_has_pending_keys
);
420 encrypted_types
.SetValue(
421 ModelTypeSetToString(full_status
.encrypted_types
));
422 has_keystore_key
.SetValue(full_status
.has_keystore_key
);
423 keystore_migration_time
.SetValue(
424 GetTimeStr(full_status
.keystore_migration_time
, "Not Migrated"));
425 passphrase_type
.SetValue(
426 PassphraseTypeToString(full_status
.passphrase_type
));
429 if (snapshot
.is_initialized()) {
430 if (snapshot
.legacy_updates_source() !=
431 sync_pb::GetUpdatesCallerInfo::UNKNOWN
) {
432 session_source
.SetValue(
433 syncer::GetUpdatesSourceString(snapshot
.legacy_updates_source()));
435 get_key_result
.SetValue(
436 GetSyncerErrorString(
437 snapshot
.model_neutral_state().last_get_key_result
));
438 download_result
.SetValue(
439 GetSyncerErrorString(
440 snapshot
.model_neutral_state().last_download_updates_result
));
441 commit_result
.SetValue(
442 GetSyncerErrorString(
443 snapshot
.model_neutral_state().commit_result
));
446 if (is_status_valid
) {
447 notifications_received
.SetValue(full_status
.notifications_received
);
448 updates_received
.SetValue(full_status
.updates_received
);
449 tombstone_updates
.SetValue(full_status
.tombstone_updates_received
);
450 reflected_updates
.SetValue(full_status
.reflected_updates_received
);
451 successful_commits
.SetValue(full_status
.num_commits_total
);
452 conflicts_resolved_local_wins
.SetValue(
453 full_status
.num_local_overwrites_total
);
454 conflicts_resolved_server_wins
.SetValue(
455 full_status
.num_server_overwrites_total
);
458 if (is_status_valid
) {
459 encryption_conflicts
.SetValue(full_status
.encryption_conflicts
);
460 hierarchy_conflicts
.SetValue(full_status
.hierarchy_conflicts
);
461 server_conflicts
.SetValue(full_status
.server_conflicts
);
462 committed_items
.SetValue(full_status
.committed_count
);
465 if (is_status_valid
) {
466 nudge_source_notification
.SetValue(full_status
.nudge_source_notification
);
467 nudge_source_local
.SetValue(full_status
.nudge_source_local
);
468 nudge_source_local_refresh
.SetValue(full_status
.nudge_source_local_refresh
);
471 if (snapshot
.is_initialized()) {
472 updates_downloaded
.SetValue(
473 snapshot
.model_neutral_state().num_updates_downloaded_total
);
474 committed_count
.SetValue(
475 snapshot
.model_neutral_state().num_successful_commits
);
476 entries
.SetValue(snapshot
.num_entries());
479 // The values set from this point onwards do not belong in the
482 // We don't need to check is_status_valid here.
483 // full_status.sync_protocol_error is exported directly from the
484 // ProfileSyncService, even if the backend doesn't exist.
485 const bool actionable_error_detected
=
486 full_status
.sync_protocol_error
.error_type
!= syncer::UNKNOWN_ERROR
&&
487 full_status
.sync_protocol_error
.error_type
!= syncer::SYNC_SUCCESS
;
489 about_info
->SetBoolean("actionable_error_detected",
490 actionable_error_detected
);
492 // NOTE: We won't bother showing any of the following values unless
493 // actionable_error_detected is set.
495 base::ListValue
* actionable_error
= new base::ListValue();
496 about_info
->Set("actionable_error", actionable_error
);
498 StringSyncStat
error_type(actionable_error
, "Error Type");
499 StringSyncStat
action(actionable_error
, "Action");
500 StringSyncStat
url(actionable_error
, "URL");
501 StringSyncStat
description(actionable_error
, "Error Description");
503 if (actionable_error_detected
) {
504 error_type
.SetValue(syncer::GetSyncErrorTypeString(
505 full_status
.sync_protocol_error
.error_type
));
506 action
.SetValue(syncer::GetClientActionString(
507 full_status
.sync_protocol_error
.action
));
508 url
.SetValue(full_status
.sync_protocol_error
.url
);
509 description
.SetValue(full_status
.sync_protocol_error
.error_description
);
512 about_info
->SetBoolean("unrecoverable_error_detected",
513 service
->HasUnrecoverableError());
515 if (service
->HasUnrecoverableError()) {
516 tracked_objects::Location
loc(service
->unrecoverable_error_location());
517 std::string location_str
;
518 loc
.Write(true, true, &location_str
);
519 std::string unrecoverable_error_message
=
520 "Unrecoverable error detected at " + location_str
+
521 ": " + service
->unrecoverable_error_message();
522 about_info
->SetString("unrecoverable_error_message",
523 unrecoverable_error_message
);
526 about_info
->Set("type_status", service
->GetTypeStatusMap());
528 return about_info
.Pass();
531 } // namespace sync_ui_util
533 } // namespace sync_driver