| // 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_driver/about_sync_util.h" |
| |
| #include <string> |
| #include <utility> |
| |
| #include "base/location.h" |
| #include "base/strings/string16.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/values.h" |
| #include "components/signin/core/browser/signin_manager_base.h" |
| #include "components/sync_driver/sync_service.h" |
| #include "components/version_info/version_info.h" |
| #include "sync/api/time.h" |
| #include "sync/internal_api/public/engine/sync_status.h" |
| #include "sync/internal_api/public/sessions/sync_session_snapshot.h" |
| #include "sync/internal_api/public/util/sync_string_conversions.h" |
| #include "sync/protocol/proto_enum_conversions.h" |
| |
| using base::DictionaryValue; |
| using base::ListValue; |
| |
| namespace sync_driver { |
| |
| namespace sync_ui_util { |
| |
| const char kIdentityTitle[] = "Identity"; |
| const char kDetailsKey[] = "details"; |
| |
| // Resource paths. |
| const char kAboutJS[] = "about.js"; |
| const char kChromeSyncJS[] = "chrome_sync.js"; |
| const char kDataJS[] = "data.js"; |
| const char kEventsJS[] = "events.js"; |
| const char kSearchJS[] = "search.js"; |
| const char kSyncIndexJS[] = "sync_index.js"; |
| const char kSyncLogJS[] = "sync_log.js"; |
| const char kSyncNodeBrowserJS[] = "sync_node_browser.js"; |
| const char kSyncSearchJS[] = "sync_search.js"; |
| const char kTypesJS[] = "types.js"; |
| |
| // Message handlers. |
| const char kDispatchEvent[] = "chrome.sync.dispatchEvent"; |
| const char kGetAllNodes[] = "getAllNodes"; |
| const char kGetAllNodesCallback[] = "chrome.sync.getAllNodesCallback"; |
| const char kRegisterForEvents[] = "registerForEvents"; |
| const char kRegisterForPerTypeCounters[] = "registerForPerTypeCounters"; |
| const char kRequestListOfTypes[] = "requestListOfTypes"; |
| const char kRequestUpdatedAboutInfo[] = "requestUpdatedAboutInfo"; |
| |
| // Other strings. |
| const char kCommit[] = "commit"; |
| const char kCounters[] = "counters"; |
| const char kCounterType[] = "counterType"; |
| const char kModelType[] = "modelType"; |
| const char kOnAboutInfoUpdated[] = "onAboutInfoUpdated"; |
| const char kOnCountersUpdated[] = "onCountersUpdated"; |
| const char kOnProtocolEvent[] = "onProtocolEvent"; |
| const char kOnReceivedListOfTypes[] = "onReceivedListOfTypes"; |
| const char kStatus[] = "status"; |
| const char kTypes[] = "types"; |
| const char kUpdate[] = "update"; |
| |
| namespace { |
| |
| // Creates a 'section' for display on about:sync, consisting of a title and a |
| // list of fields. Returns a pointer to the new section. Note that |
| // |parent_list|, not the caller, owns the newly added section. |
| base::ListValue* AddSection(base::ListValue* parent_list, |
| const std::string& title) { |
| std::unique_ptr<base::DictionaryValue> section(new base::DictionaryValue()); |
| base::ListValue* section_contents = new base::ListValue(); |
| section->SetString("title", title); |
| section->Set("data", section_contents); |
| section->SetBoolean("is_sensitive", false); |
| parent_list->Append(std::move(section)); |
| return section_contents; |
| } |
| |
| // Same as AddSection, but for data that should be elided when dumped into text |
| // form and posted in a public forum (e.g. unique identifiers). |
| base::ListValue* AddSensitiveSection(base::ListValue* parent_list, |
| const std::string& title) { |
| std::unique_ptr<base::DictionaryValue> section(new base::DictionaryValue()); |
| base::ListValue* section_contents = new base::ListValue(); |
| section->SetString("title", title); |
| section->Set("data", section_contents); |
| section->SetBoolean("is_sensitive", true); |
| parent_list->Append(std::move(section)); |
| return section_contents; |
| } |
| |
| // The following helper classes help manage the about:sync fields which will be |
| // populated in method in ConstructAboutInformation. |
| // |
| // Each instance of one of thse classes indicates a field in about:sync. Each |
| // field will be serialized to a DictionaryValue with entries for 'stat_name', |
| // 'stat_value' and 'is_valid'. |
| |
| class StringSyncStat { |
| public: |
| StringSyncStat(base::ListValue* section, const std::string& key); |
| void SetValue(const std::string& value); |
| void SetValue(const base::string16& value); |
| |
| private: |
| // Owned by the |section| passed in during construction. |
| base::DictionaryValue* stat_; |
| }; |
| |
| StringSyncStat::StringSyncStat(base::ListValue* section, |
| const std::string& key) { |
| stat_ = new base::DictionaryValue(); |
| stat_->SetString("stat_name", key); |
| stat_->SetString("stat_value", "Uninitialized"); |
| stat_->SetBoolean("is_valid", false); |
| section->Append(stat_); |
| } |
| |
| void StringSyncStat::SetValue(const std::string& value) { |
| stat_->SetString("stat_value", value); |
| stat_->SetBoolean("is_valid", true); |
| } |
| |
| void StringSyncStat::SetValue(const base::string16& value) { |
| stat_->SetString("stat_value", value); |
| stat_->SetBoolean("is_valid", true); |
| } |
| |
| class BoolSyncStat { |
| public: |
| BoolSyncStat(base::ListValue* section, const std::string& key); |
| void SetValue(bool value); |
| |
| private: |
| // Owned by the |section| passed in during construction. |
| base::DictionaryValue* stat_; |
| }; |
| |
| BoolSyncStat::BoolSyncStat(base::ListValue* section, const std::string& key) { |
| stat_ = new base::DictionaryValue(); |
| stat_->SetString("stat_name", key); |
| stat_->SetBoolean("stat_value", false); |
| stat_->SetBoolean("is_valid", false); |
| section->Append(stat_); |
| } |
| |
| void BoolSyncStat::SetValue(bool value) { |
| stat_->SetBoolean("stat_value", value); |
| stat_->SetBoolean("is_valid", true); |
| } |
| |
| class IntSyncStat { |
| public: |
| IntSyncStat(base::ListValue* section, const std::string& key); |
| void SetValue(int value); |
| |
| private: |
| // Owned by the |section| passed in during construction. |
| base::DictionaryValue* stat_; |
| }; |
| |
| IntSyncStat::IntSyncStat(base::ListValue* section, const std::string& key) { |
| stat_ = new base::DictionaryValue(); |
| stat_->SetString("stat_name", key); |
| stat_->SetInteger("stat_value", 0); |
| stat_->SetBoolean("is_valid", false); |
| section->Append(stat_); |
| } |
| |
| void IntSyncStat::SetValue(int value) { |
| stat_->SetInteger("stat_value", value); |
| stat_->SetBoolean("is_valid", true); |
| } |
| |
| // Returns a string describing the chrome version environment. Version format: |
| // <Build Info> <OS> <Version number> (<Last change>)<channel or "-devel"> |
| // If version information is unavailable, returns "invalid." |
| // TODO(zea): this approximately matches MakeUserAgentForSyncApi in |
| // sync_backend_host.cc. Unify the two if possible. |
| std::string GetVersionString(version_info::Channel channel) { |
| // Build a version string that matches MakeUserAgentForSyncApi with the |
| // addition of channel info and proper OS names. |
| // chrome::GetChannelString() returns empty string for stable channel or |
| // unofficial builds, the channel string otherwise. We want to have "-devel" |
| // for unofficial builds only. |
| std::string version_modifier = version_info::GetChannelString(channel); |
| if (version_modifier.empty()) { |
| if (channel != version_info::Channel::STABLE) { |
| version_modifier = "-devel"; |
| } |
| } else { |
| version_modifier = " " + version_modifier; |
| } |
| return version_info::GetProductName() + " " + version_info::GetOSType() + |
| " " + version_info::GetVersionNumber() + " (" + |
| version_info::GetLastChange() + ")" + version_modifier; |
| } |
| |
| std::string GetTimeStr(base::Time time, const std::string& default_msg) { |
| std::string time_str; |
| if (time.is_null()) |
| time_str = default_msg; |
| else |
| time_str = syncer::GetTimeDebugString(time); |
| return time_str; |
| } |
| |
| std::string GetConnectionStatus( |
| const sync_driver::SyncService::SyncTokenStatus& status) { |
| std::string message; |
| switch (status.connection_status) { |
| case syncer::CONNECTION_NOT_ATTEMPTED: |
| base::StringAppendF(&message, "not attempted"); |
| break; |
| case syncer::CONNECTION_OK: |
| base::StringAppendF( |
| &message, "OK since %s", |
| GetTimeStr(status.connection_status_update_time, "n/a").c_str()); |
| break; |
| case syncer::CONNECTION_AUTH_ERROR: |
| base::StringAppendF( |
| &message, "auth error since %s", |
| GetTimeStr(status.connection_status_update_time, "n/a").c_str()); |
| break; |
| case syncer::CONNECTION_SERVER_ERROR: |
| base::StringAppendF( |
| &message, "server error since %s", |
| GetTimeStr(status.connection_status_update_time, "n/a").c_str()); |
| break; |
| default: |
| NOTREACHED(); |
| } |
| return message; |
| } |
| |
| } // namespace |
| |
| // This function both defines the structure of the message to be returned and |
| // its contents. Most of the message consists of simple fields in about:sync |
| // which are grouped into sections and populated with the help of the SyncStat |
| // classes defined above. |
| std::unique_ptr<base::DictionaryValue> ConstructAboutInformation( |
| sync_driver::SyncService* service, |
| SigninManagerBase* signin, |
| version_info::Channel channel) { |
| std::unique_ptr<base::DictionaryValue> about_info( |
| new base::DictionaryValue()); |
| |
| // 'details': A list of sections. |
| base::ListValue* stats_list = new base::ListValue(); |
| |
| // The following lines define the sections and their fields. For each field, |
| // a class is instantiated, which allows us to reference the fields in |
| // 'setter' code later on in this function. |
| base::ListValue* section_summary = AddSection(stats_list, "Summary"); |
| StringSyncStat summary_string(section_summary, "Summary"); |
| |
| base::ListValue* section_version = AddSection(stats_list, "Version Info"); |
| StringSyncStat client_version(section_version, "Client Version"); |
| StringSyncStat server_url(section_version, "Server URL"); |
| |
| base::ListValue* section_identity = |
| AddSensitiveSection(stats_list, kIdentityTitle); |
| StringSyncStat sync_id(section_identity, "Sync Client ID"); |
| StringSyncStat invalidator_id(section_identity, "Invalidator Client ID"); |
| StringSyncStat username(section_identity, "Username"); |
| |
| base::ListValue* section_credentials = AddSection(stats_list, "Credentials"); |
| StringSyncStat request_token_time(section_credentials, "Requested Token"); |
| StringSyncStat receive_token_time(section_credentials, "Received Token"); |
| StringSyncStat token_request_status(section_credentials, |
| "Token Request Status"); |
| StringSyncStat next_token_request(section_credentials, |
| "Next Token Request"); |
| |
| base::ListValue* section_local = AddSection(stats_list, "Local State"); |
| StringSyncStat server_connection(section_local, |
| "Server Connection"); |
| StringSyncStat last_synced(section_local, "Last Synced"); |
| BoolSyncStat is_setup_complete(section_local, |
| "Sync First-Time Setup Complete"); |
| StringSyncStat backend_initialization(section_local, |
| "Sync Backend Initialization"); |
| BoolSyncStat is_syncing(section_local, "Syncing"); |
| |
| base::ListValue* section_network = AddSection(stats_list, "Network"); |
| BoolSyncStat is_throttled(section_network, "Throttled"); |
| StringSyncStat retry_time(section_network, "Retry time (maybe stale)"); |
| BoolSyncStat are_notifications_enabled(section_network, |
| "Notifications Enabled"); |
| |
| base::ListValue* section_encryption = AddSection(stats_list, "Encryption"); |
| BoolSyncStat is_using_explicit_passphrase(section_encryption, |
| "Explicit Passphrase"); |
| BoolSyncStat is_passphrase_required(section_encryption, |
| "Passphrase Required"); |
| BoolSyncStat is_cryptographer_ready(section_encryption, |
| "Cryptographer Ready"); |
| BoolSyncStat has_pending_keys(section_encryption, |
| "Cryptographer Has Pending Keys"); |
| StringSyncStat encrypted_types(section_encryption, "Encrypted Types"); |
| BoolSyncStat has_keystore_key(section_encryption, "Has Keystore Key"); |
| StringSyncStat keystore_migration_time(section_encryption, |
| "Keystore Migration Time"); |
| StringSyncStat passphrase_type(section_encryption, |
| "Passphrase Type"); |
| StringSyncStat passphrase_time(section_encryption, |
| "Passphrase Time"); |
| |
| base::ListValue* section_last_session = AddSection( |
| stats_list, "Status from Last Completed Session"); |
| StringSyncStat session_source(section_last_session, "Sync Source"); |
| StringSyncStat get_key_result(section_last_session, "GetKey Step Result"); |
| StringSyncStat download_result(section_last_session, "Download Step Result"); |
| StringSyncStat commit_result(section_last_session, "Commit Step Result"); |
| |
| base::ListValue* section_counters = AddSection(stats_list, "Running Totals"); |
| IntSyncStat notifications_received(section_counters, |
| "Notifications Received"); |
| IntSyncStat updates_received(section_counters, "Updates Downloaded"); |
| IntSyncStat tombstone_updates(section_counters, "Tombstone Updates"); |
| IntSyncStat reflected_updates(section_counters, "Reflected Updates"); |
| IntSyncStat successful_commits(section_counters, "Successful Commits"); |
| IntSyncStat conflicts_resolved_local_wins(section_counters, |
| "Conflicts Resolved: Client Wins"); |
| IntSyncStat conflicts_resolved_server_wins(section_counters, |
| "Conflicts Resolved: Server Wins"); |
| |
| base::ListValue *section_this_cycle = AddSection(stats_list, |
| "Transient Counters (this cycle)"); |
| IntSyncStat encryption_conflicts(section_this_cycle, "Encryption Conflicts"); |
| IntSyncStat hierarchy_conflicts(section_this_cycle, "Hierarchy Conflicts"); |
| IntSyncStat server_conflicts(section_this_cycle, "Server Conflicts"); |
| IntSyncStat committed_items(section_this_cycle, "Committed Items"); |
| |
| base::ListValue* section_that_cycle = AddSection( |
| stats_list, "Transient Counters (last cycle of last completed session)"); |
| IntSyncStat updates_downloaded(section_that_cycle, "Updates Downloaded"); |
| IntSyncStat committed_count(section_that_cycle, "Committed Count"); |
| IntSyncStat entries(section_that_cycle, "Entries"); |
| |
| base::ListValue* section_nudge_info = AddSection( |
| stats_list, "Nudge Source Counters"); |
| IntSyncStat nudge_source_notification( |
| section_nudge_info, "Server Invalidations"); |
| IntSyncStat nudge_source_local(section_nudge_info, "Local Changes"); |
| IntSyncStat nudge_source_local_refresh(section_nudge_info, "Local Refreshes"); |
| |
| // This list of sections belongs in the 'details' field of the returned |
| // message. |
| about_info->Set(kDetailsKey, stats_list); |
| |
| // Populate all the fields we declared above. |
| client_version.SetValue(GetVersionString(channel)); |
| |
| if (!service) { |
| summary_string.SetValue("Sync service does not exist"); |
| return about_info; |
| } |
| |
| syncer::SyncStatus full_status; |
| bool is_status_valid = service->QueryDetailedSyncStatus(&full_status); |
| bool sync_active = service->IsSyncActive(); |
| const syncer::sessions::SyncSessionSnapshot& snapshot = |
| service->GetLastSessionSnapshot(); |
| |
| if (is_status_valid) |
| summary_string.SetValue(service->QuerySyncStatusSummaryString()); |
| |
| server_url.SetValue(service->sync_service_url().spec()); |
| |
| if (is_status_valid && !full_status.sync_id.empty()) |
| sync_id.SetValue(full_status.sync_id); |
| if (is_status_valid && !full_status.invalidator_client_id.empty()) |
| invalidator_id.SetValue(full_status.invalidator_client_id); |
| if (signin) |
| username.SetValue(signin->GetAuthenticatedAccountInfo().email); |
| |
| const sync_driver::SyncService::SyncTokenStatus& token_status = |
| service->GetSyncTokenStatus(); |
| server_connection.SetValue(GetConnectionStatus(token_status)); |
| request_token_time.SetValue(GetTimeStr(token_status.token_request_time, |
| "n/a")); |
| receive_token_time.SetValue(GetTimeStr(token_status.token_receive_time, |
| "n/a")); |
| std::string err = token_status.last_get_token_error.error_message(); |
| token_request_status.SetValue(err.empty() ? "OK" : err); |
| next_token_request.SetValue( |
| GetTimeStr(token_status.next_token_request_time, "not scheduled")); |
| |
| last_synced.SetValue(service->GetLastSyncedTimeString()); |
| is_setup_complete.SetValue(service->IsFirstSetupComplete()); |
| backend_initialization.SetValue( |
| service->GetBackendInitializationStateString()); |
| if (is_status_valid) { |
| is_syncing.SetValue(full_status.syncing); |
| retry_time.SetValue(GetTimeStr(full_status.retry_time, |
| "Scheduler is not in backoff or throttled")); |
| } |
| |
| if (snapshot.is_initialized()) |
| is_throttled.SetValue(snapshot.is_silenced()); |
| if (is_status_valid) { |
| are_notifications_enabled.SetValue( |
| full_status.notifications_enabled); |
| } |
| |
| if (sync_active) { |
| is_using_explicit_passphrase.SetValue( |
| service->IsUsingSecondaryPassphrase()); |
| is_passphrase_required.SetValue(service->IsPassphraseRequired()); |
| passphrase_time.SetValue( |
| GetTimeStr(service->GetExplicitPassphraseTime(), "No Passphrase Time")); |
| } |
| if (is_status_valid) { |
| is_cryptographer_ready.SetValue(full_status.cryptographer_ready); |
| has_pending_keys.SetValue(full_status.crypto_has_pending_keys); |
| encrypted_types.SetValue( |
| ModelTypeSetToString(full_status.encrypted_types)); |
| has_keystore_key.SetValue(full_status.has_keystore_key); |
| keystore_migration_time.SetValue( |
| GetTimeStr(full_status.keystore_migration_time, "Not Migrated")); |
| passphrase_type.SetValue( |
| PassphraseTypeToString(full_status.passphrase_type)); |
| } |
| |
| if (snapshot.is_initialized()) { |
| if (snapshot.legacy_updates_source() != |
| sync_pb::GetUpdatesCallerInfo::UNKNOWN) { |
| session_source.SetValue( |
| syncer::GetUpdatesSourceString(snapshot.legacy_updates_source())); |
| } |
| get_key_result.SetValue( |
| GetSyncerErrorString( |
| snapshot.model_neutral_state().last_get_key_result)); |
| download_result.SetValue( |
| GetSyncerErrorString( |
| snapshot.model_neutral_state().last_download_updates_result)); |
| commit_result.SetValue( |
| GetSyncerErrorString( |
| snapshot.model_neutral_state().commit_result)); |
| } |
| |
| if (is_status_valid) { |
| notifications_received.SetValue(full_status.notifications_received); |
| updates_received.SetValue(full_status.updates_received); |
| tombstone_updates.SetValue(full_status.tombstone_updates_received); |
| reflected_updates.SetValue(full_status.reflected_updates_received); |
| successful_commits.SetValue(full_status.num_commits_total); |
| conflicts_resolved_local_wins.SetValue( |
| full_status.num_local_overwrites_total); |
| conflicts_resolved_server_wins.SetValue( |
| full_status.num_server_overwrites_total); |
| } |
| |
| if (is_status_valid) { |
| encryption_conflicts.SetValue(full_status.encryption_conflicts); |
| hierarchy_conflicts.SetValue(full_status.hierarchy_conflicts); |
| server_conflicts.SetValue(full_status.server_conflicts); |
| committed_items.SetValue(full_status.committed_count); |
| } |
| |
| if (is_status_valid) { |
| nudge_source_notification.SetValue(full_status.nudge_source_notification); |
| nudge_source_local.SetValue(full_status.nudge_source_local); |
| nudge_source_local_refresh.SetValue(full_status.nudge_source_local_refresh); |
| } |
| |
| if (snapshot.is_initialized()) { |
| updates_downloaded.SetValue( |
| snapshot.model_neutral_state().num_updates_downloaded_total); |
| committed_count.SetValue( |
| snapshot.model_neutral_state().num_successful_commits); |
| entries.SetValue(snapshot.num_entries()); |
| } |
| |
| // The values set from this point onwards do not belong in the |
| // details list. |
| |
| // We don't need to check is_status_valid here. |
| // full_status.sync_protocol_error is exported directly from the |
| // ProfileSyncService, even if the backend doesn't exist. |
| const bool actionable_error_detected = |
| full_status.sync_protocol_error.error_type != syncer::UNKNOWN_ERROR && |
| full_status.sync_protocol_error.error_type != syncer::SYNC_SUCCESS; |
| |
| about_info->SetBoolean("actionable_error_detected", |
| actionable_error_detected); |
| |
| // NOTE: We won't bother showing any of the following values unless |
| // actionable_error_detected is set. |
| |
| base::ListValue* actionable_error = new base::ListValue(); |
| about_info->Set("actionable_error", actionable_error); |
| |
| StringSyncStat error_type(actionable_error, "Error Type"); |
| StringSyncStat action(actionable_error, "Action"); |
| StringSyncStat url(actionable_error, "URL"); |
| StringSyncStat description(actionable_error, "Error Description"); |
| |
| if (actionable_error_detected) { |
| error_type.SetValue(syncer::GetSyncErrorTypeString( |
| full_status.sync_protocol_error.error_type)); |
| action.SetValue(syncer::GetClientActionString( |
| full_status.sync_protocol_error.action)); |
| url.SetValue(full_status.sync_protocol_error.url); |
| description.SetValue(full_status.sync_protocol_error.error_description); |
| } |
| |
| about_info->SetBoolean("unrecoverable_error_detected", |
| service->HasUnrecoverableError()); |
| |
| if (service->HasUnrecoverableError()) { |
| tracked_objects::Location loc(service->unrecoverable_error_location()); |
| std::string location_str; |
| loc.Write(true, true, &location_str); |
| std::string unrecoverable_error_message = |
| "Unrecoverable error detected at " + location_str + |
| ": " + service->unrecoverable_error_message(); |
| about_info->SetString("unrecoverable_error_message", |
| unrecoverable_error_message); |
| } |
| |
| about_info->Set("type_status", service->GetTypeStatusMap()); |
| |
| return about_info; |
| } |
| |
| } // namespace sync_ui_util |
| |
| } // namespace sync_driver |