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