blob: de3aefcb4a77a037c531dfa123a031d63a89b857 [file] [log] [blame]
// 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 <vector>
#include "base/i18n/time_formatting.h"
#include "base/logging.h"
#include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "components/sync/driver/sync_service.h"
#include "components/sync/driver/sync_token_status.h"
#include "components/sync/driver/sync_user_settings.h"
#include "components/sync/engine/cycle/sync_cycle_snapshot.h"
#include "components/sync/engine/sync_status.h"
#include "components/sync/engine/sync_string_conversions.h"
#include "components/sync/model/time.h"
#include "components/sync/protocol/proto_enum_conversions.h"
#include "url/gurl.h"
namespace syncer {
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";
const char kUserEventsJS[] = "user_events.js";
const char kTrafficLogJS[] = "traffic_log.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 kRequestIncludeSpecificsInitialState[] =
"requestIncludeSpecificsInitialState";
const char kRequestListOfTypes[] = "requestListOfTypes";
const char kRequestStart[] = "requestStart";
const char kRequestStopKeepData[] = "requestStopKeepData";
const char kRequestStopClearData[] = "requestStopClearData";
const char kRequestUpdatedAboutInfo[] = "requestUpdatedAboutInfo";
const char kRequestUserEventsVisibility[] = "requestUserEventsVisibility";
const char kSetIncludeSpecifics[] = "setIncludeSpecifics";
const char kTriggerRefresh[] = "triggerRefresh";
const char kUserEventsVisibilityCallback[] =
"chrome.sync.userEventsVisibilityCallback";
const char kWriteUserEvent[] = "writeUserEvent";
// Other strings.
const char kCommit[] = "commit";
const char kCounters[] = "counters";
const char kCounterType[] = "counterType";
const char kIncludeSpecifics[] = "includeSpecifics";
const char kModelType[] = "modelType";
const char kOnAboutInfoUpdated[] = "onAboutInfoUpdated";
const char kOnCountersUpdated[] = "onCountersUpdated";
const char kOnProtocolEvent[] = "onProtocolEvent";
const char kOnReceivedIncludeSpecificsInitialState[] =
"onReceivedIncludeSpecificsInitialState";
const char kOnReceivedListOfTypes[] = "onReceivedListOfTypes";
const char kStatus[] = "status";
const char kTypes[] = "types";
const char kUpdate[] = "update";
namespace {
const char kUninitialized[] = "Uninitialized";
// This class represents one field in about:sync. It gets serialized into a
// dictionary with entries for 'stat_name', 'stat_value' and 'is_valid'.
class StatBase {
public:
base::Value ToValue() const {
base::Value result(base::Value::Type::DICTIONARY);
result.SetKey("stat_name", base::Value(key_));
result.SetKey("stat_value", value_.Clone());
result.SetKey("is_valid", base::Value(is_valid_));
return result;
}
protected:
StatBase(const std::string& key, base::Value default_value)
: key_(key), value_(std::move(default_value)) {}
void SetFromValue(base::Value value) {
value_ = std::move(value);
is_valid_ = true;
}
private:
std::string key_;
base::Value value_;
bool is_valid_ = false;
};
template <typename T>
class Stat : public StatBase {
public:
Stat(const std::string& key, const T& default_value)
: StatBase(key, base::Value(default_value)) {}
void Set(const T& value) { SetFromValue(base::Value(value)); }
};
// A section for display on about:sync, consisting of a title and a list of
// fields.
class Section {
public:
explicit Section(const std::string& title) : title_(title) {}
void MarkSensitive() { is_sensitive_ = true; }
Stat<bool>* AddBoolStat(const std::string& key) {
return AddStat(key, false);
}
Stat<int>* AddIntStat(const std::string& key) { return AddStat(key, 0); }
Stat<std::string>* AddStringStat(const std::string& key) {
return AddStat(key, std::string(kUninitialized));
}
base::Value ToValue() const {
base::Value result(base::Value::Type::DICTIONARY);
result.SetKey("title", base::Value(title_));
base::Value stats(base::Value::Type::LIST);
for (const std::unique_ptr<StatBase>& stat : stats_)
stats.GetList().push_back(stat->ToValue());
result.SetKey("data", std::move(stats));
result.SetKey("is_sensitive", base::Value(is_sensitive_));
return result;
}
private:
template <typename T>
Stat<T>* AddStat(const std::string& key, const T& default_value) {
auto stat = std::make_unique<Stat<T>>(key, default_value);
Stat<T>* result = stat.get();
stats_.push_back(std::move(stat));
return result;
}
std::string title_;
std::vector<std::unique_ptr<StatBase>> stats_;
bool is_sensitive_ = false;
};
class SectionList {
public:
SectionList() = default;
Section* AddSection(const std::string& title) {
sections_.push_back(std::make_unique<Section>(title));
return sections_.back().get();
}
base::Value ToValue() const {
base::Value result(base::Value::Type::LIST);
for (const std::unique_ptr<Section>& section : sections_)
result.GetList().push_back(section->ToValue());
return result;
}
private:
std::vector<std::unique_ptr<Section>> sections_;
};
std::string GetDisableReasonsString(int disable_reasons) {
if (disable_reasons == syncer::SyncService::DISABLE_REASON_NONE) {
return "None";
}
std::vector<std::string> reason_strings;
if (disable_reasons & syncer::SyncService::DISABLE_REASON_PLATFORM_OVERRIDE)
reason_strings.push_back("Platform override");
if (disable_reasons & syncer::SyncService::DISABLE_REASON_ENTERPRISE_POLICY)
reason_strings.push_back("Enterprise policy");
if (disable_reasons & syncer::SyncService::DISABLE_REASON_NOT_SIGNED_IN)
reason_strings.push_back("Not signed in");
if (disable_reasons & syncer::SyncService::DISABLE_REASON_USER_CHOICE)
reason_strings.push_back("User choice");
if (disable_reasons & syncer::SyncService::DISABLE_REASON_UNRECOVERABLE_ERROR)
reason_strings.push_back("Unrecoverable error");
return base::JoinString(reason_strings, ", ");
}
std::string GetTransportStateString(syncer::SyncService::TransportState state) {
switch (state) {
case syncer::SyncService::TransportState::DISABLED:
return "Disabled";
case syncer::SyncService::TransportState::WAITING_FOR_START_REQUEST:
return "Waiting for start request";
case syncer::SyncService::TransportState::START_DEFERRED:
return "Start deferred";
case syncer::SyncService::TransportState::INITIALIZING:
return "Initializing";
case syncer::SyncService::TransportState::PENDING_DESIRED_CONFIGURATION:
return "Pending desired configuration";
case syncer::SyncService::TransportState::CONFIGURING:
return "Configuring data types";
case syncer::SyncService::TransportState::ACTIVE:
return "Active";
}
NOTREACHED();
return std::string();
}
// 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 syncer::MakeUserAgentForSync in
// sync_util.h. Unify the two if possible.
std::string GetVersionString(version_info::Channel channel) {
// Build a version string that matches syncer::MakeUserAgentForSync with the
// addition of channel info and proper OS names.
// chrome::GetChannelName() 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) {
if (time.is_null())
return default_msg;
return GetTimeDebugString(time);
}
// Analogous to GetTimeDebugString from components/sync/base/time.h. Consider
// moving it there if more places need this.
std::string GetTimeDeltaDebugString(base::TimeDelta t) {
base::string16 result;
if (!base::TimeDurationFormat(t, base::DURATION_WIDTH_WIDE, &result)) {
return "Invalid TimeDelta?!";
}
return base::UTF16ToUTF8(result);
}
std::string GetLastSyncedTimeString(base::Time last_synced_time) {
if (last_synced_time.is_null())
return "Never";
base::TimeDelta time_since_last_sync = base::Time::Now() - last_synced_time;
if (time_since_last_sync < base::TimeDelta::FromMinutes(1))
return "Just now";
return GetTimeDeltaDebugString(time_since_last_sync) + " ago";
}
std::string GetConnectionStatus(const SyncTokenStatus& status) {
switch (status.connection_status) {
case CONNECTION_NOT_ATTEMPTED:
return "not attempted";
case CONNECTION_OK:
return base::StringPrintf(
"OK since %s",
GetTimeStr(status.connection_status_update_time, "n/a").c_str());
case CONNECTION_AUTH_ERROR:
return base::StringPrintf(
"auth error since %s",
GetTimeStr(status.connection_status_update_time, "n/a").c_str());
case CONNECTION_SERVER_ERROR:
return base::StringPrintf(
"server error since %s",
GetTimeStr(status.connection_status_update_time, "n/a").c_str());
}
NOTREACHED();
return std::string();
}
} // 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(
SyncService* service,
version_info::Channel channel) {
auto about_info = std::make_unique<base::DictionaryValue>();
SectionList section_list;
Section* section_summary = section_list.AddSection("Summary");
Stat<std::string>* transport_state =
section_summary->AddStringStat("Transport State");
Stat<std::string>* disable_reasons =
section_summary->AddStringStat("Disable Reasons");
Stat<bool>* feature_enabled =
section_summary->AddBoolStat("Sync Feature Enabled");
Stat<bool>* setup_in_progress =
section_summary->AddBoolStat("Setup In Progress");
Section* section_version = section_list.AddSection("Version Info");
Stat<std::string>* client_version =
section_version->AddStringStat("Client Version");
Stat<std::string>* server_url = section_version->AddStringStat("Server URL");
Section* section_identity = section_list.AddSection(kIdentityTitle);
section_identity->MarkSensitive();
Stat<std::string>* sync_client_id =
section_identity->AddStringStat("Sync Client ID");
Stat<std::string>* invalidator_id =
section_identity->AddStringStat("Invalidator Client ID");
Stat<std::string>* username = section_identity->AddStringStat("Username");
Stat<bool>* user_is_primary = section_identity->AddBoolStat("Is Primary");
Stat<std::string>* auth_error = section_identity->AddStringStat("Auth Error");
// TODO(treib): Add the *time* of the auth error?
Section* section_credentials = section_list.AddSection("Credentials");
Stat<std::string>* request_token_time =
section_credentials->AddStringStat("Requested Token");
Stat<std::string>* receive_token_time =
section_credentials->AddStringStat("Received Token");
Stat<std::string>* last_token_request_result =
section_credentials->AddStringStat("Last Token Request Result");
Stat<bool>* has_token = section_credentials->AddBoolStat("Has Token");
Stat<std::string>* next_token_request =
section_credentials->AddStringStat("Next Token Request");
Section* section_local = section_list.AddSection("Local State");
Stat<std::string>* server_connection =
section_local->AddStringStat("Server Connection");
Stat<std::string>* last_synced = section_local->AddStringStat("Last Synced");
Stat<bool>* is_setup_complete =
section_local->AddBoolStat("Sync First-Time Setup Complete");
Stat<bool>* is_syncing = section_local->AddBoolStat("Sync Cycle Ongoing");
Stat<bool>* is_local_sync_enabled =
section_local->AddBoolStat("Local Sync Backend Enabled");
Stat<std::string>* local_backend_path =
section_local->AddStringStat("Local Backend Path");
Section* section_network = section_list.AddSection("Network");
Stat<bool>* is_any_throttled_or_backoff =
section_network->AddBoolStat("Throttled or Backoff");
Stat<std::string>* retry_time = section_network->AddStringStat("Retry Time");
Stat<bool>* are_notifications_enabled =
section_network->AddBoolStat("Notifications Enabled");
Section* section_encryption = section_list.AddSection("Encryption");
Stat<bool>* is_using_explicit_passphrase =
section_encryption->AddBoolStat("Explicit Passphrase");
Stat<bool>* is_passphrase_required =
section_encryption->AddBoolStat("Passphrase Required");
Stat<bool>* is_cryptographer_ready =
section_encryption->AddBoolStat("Cryptographer Ready");
Stat<bool>* has_pending_keys =
section_encryption->AddBoolStat("Cryptographer Has Pending Keys");
Stat<std::string>* encrypted_types =
section_encryption->AddStringStat("Encrypted Types");
Stat<bool>* has_keystore_key =
section_encryption->AddBoolStat("Has Keystore Key");
Stat<std::string>* keystore_migration_time =
section_encryption->AddStringStat("Keystore Migration Time");
Stat<std::string>* passphrase_type =
section_encryption->AddStringStat("Passphrase Type");
Stat<std::string>* passphrase_time =
section_encryption->AddStringStat("Passphrase Time");
Section* section_last_session =
section_list.AddSection("Status from Last Completed Session");
Stat<std::string>* session_source =
section_last_session->AddStringStat("Sync Source");
Stat<std::string>* get_key_result =
section_last_session->AddStringStat("GetKey Step Result");
Stat<std::string>* download_result =
section_last_session->AddStringStat("Download Step Result");
Stat<std::string>* commit_result =
section_last_session->AddStringStat("Commit Step Result");
Section* section_counters = section_list.AddSection("Running Totals");
Stat<int>* notifications_received =
section_counters->AddIntStat("Notifications Received");
Stat<int>* updates_received =
section_counters->AddIntStat("Updates Downloaded");
Stat<int>* tombstone_updates =
section_counters->AddIntStat("Tombstone Updates");
Stat<int>* reflected_updates =
section_counters->AddIntStat("Reflected Updates");
Stat<int>* successful_commits =
section_counters->AddIntStat("Successful Commits");
Stat<int>* conflicts_resolved_local_wins =
section_counters->AddIntStat("Conflicts Resolved: Client Wins");
Stat<int>* conflicts_resolved_server_wins =
section_counters->AddIntStat("Conflicts Resolved: Server Wins");
Section* section_this_cycle =
section_list.AddSection("Transient Counters (this cycle)");
Stat<int>* encryption_conflicts =
section_this_cycle->AddIntStat("Encryption Conflicts");
Stat<int>* hierarchy_conflicts =
section_this_cycle->AddIntStat("Hierarchy Conflicts");
Stat<int>* server_conflicts =
section_this_cycle->AddIntStat("Server Conflicts");
Stat<int>* committed_items =
section_this_cycle->AddIntStat("Committed Items");
Section* section_that_cycle = section_list.AddSection(
"Transient Counters (last cycle of last completed session)");
Stat<int>* updates_downloaded =
section_that_cycle->AddIntStat("Updates Downloaded");
Stat<int>* committed_count =
section_that_cycle->AddIntStat("Committed Count");
Stat<int>* entries = section_that_cycle->AddIntStat("Entries");
Section* section_nudge_info =
section_list.AddSection("Nudge Source Counters");
Stat<int>* nudge_source_notification =
section_nudge_info->AddIntStat("Server Invalidations");
Stat<int>* nudge_source_local =
section_nudge_info->AddIntStat("Local Changes");
Stat<int>* nudge_source_local_refresh =
section_nudge_info->AddIntStat("Local Refreshes");
// Populate all the fields we declared above.
client_version->Set(GetVersionString(channel));
if (!service) {
transport_state->Set("Sync service does not exist");
about_info->SetKey(kDetailsKey, section_list.ToValue());
return about_info;
}
// Summary.
transport_state->Set(GetTransportStateString(service->GetTransportState()));
disable_reasons->Set(GetDisableReasonsString(service->GetDisableReasons()));
feature_enabled->Set(service->IsSyncFeatureEnabled());
setup_in_progress->Set(service->IsSetupInProgress());
SyncStatus full_status;
bool is_status_valid = service->QueryDetailedSyncStatus(&full_status);
const SyncCycleSnapshot& snapshot = service->GetLastCycleSnapshot();
const SyncTokenStatus& token_status = service->GetSyncTokenStatus();
// Version Info.
// |client_version| was already set above.
server_url->Set(service->sync_service_url().spec());
// Identity.
if (is_status_valid && !full_status.sync_id.empty())
sync_client_id->Set(full_status.sync_id);
if (is_status_valid && !full_status.invalidator_client_id.empty())
invalidator_id->Set(full_status.invalidator_client_id);
username->Set(service->GetAuthenticatedAccountInfo().email);
user_is_primary->Set(service->IsAuthenticatedAccountPrimary());
std::string auth_error_str = service->GetAuthError().ToString();
auth_error->Set(auth_error_str.empty() ? "None" : auth_error_str);
// Credentials.
request_token_time->Set(GetTimeStr(token_status.token_request_time, "n/a"));
receive_token_time->Set(GetTimeStr(token_status.token_receive_time, "n/a"));
std::string err = token_status.last_get_token_error.error_message();
last_token_request_result->Set(err.empty() ? "OK" : err);
has_token->Set(token_status.has_token);
next_token_request->Set(
GetTimeStr(token_status.next_token_request_time, "not scheduled"));
// Local State.
server_connection->Set(GetConnectionStatus(token_status));
last_synced->Set(GetLastSyncedTimeString(service->GetLastSyncedTime()));
is_setup_complete->Set(service->GetUserSettings()->IsFirstSetupComplete());
if (is_status_valid)
is_syncing->Set(full_status.syncing);
is_local_sync_enabled->Set(service->IsLocalSyncEnabled());
if (service->IsLocalSyncEnabled() && is_status_valid)
local_backend_path->Set(full_status.local_sync_folder);
// Network.
if (snapshot.is_initialized())
is_any_throttled_or_backoff->Set(snapshot.is_silenced());
if (is_status_valid) {
retry_time->Set(GetTimeStr(full_status.retry_time,
"Scheduler is not in backoff or throttled"));
}
if (is_status_valid)
are_notifications_enabled->Set(full_status.notifications_enabled);
// Encryption.
if (service->IsSyncFeatureActive()) {
is_using_explicit_passphrase->Set(service->IsUsingSecondaryPassphrase());
is_passphrase_required->Set(service->IsPassphraseRequired());
passphrase_time->Set(
GetTimeStr(service->GetUserSettings()->GetExplicitPassphraseTime(),
"No Passphrase Time"));
}
if (is_status_valid) {
is_cryptographer_ready->Set(full_status.cryptographer_ready);
has_pending_keys->Set(full_status.crypto_has_pending_keys);
encrypted_types->Set(ModelTypeSetToString(full_status.encrypted_types));
has_keystore_key->Set(full_status.has_keystore_key);
keystore_migration_time->Set(
GetTimeStr(full_status.keystore_migration_time, "Not Migrated"));
passphrase_type->Set(PassphraseTypeToString(full_status.passphrase_type));
}
// Status from Last Completed Session.
if (snapshot.is_initialized()) {
if (snapshot.get_updates_origin() != sync_pb::SyncEnums::UNKNOWN_ORIGIN) {
session_source->Set(ProtoEnumToString(snapshot.get_updates_origin()));
}
get_key_result->Set(
snapshot.model_neutral_state().last_get_key_result.ToString());
download_result->Set(
snapshot.model_neutral_state().last_download_updates_result.ToString());
commit_result->Set(snapshot.model_neutral_state().commit_result.ToString());
}
// Running Totals.
if (is_status_valid) {
notifications_received->Set(full_status.notifications_received);
updates_received->Set(full_status.updates_received);
tombstone_updates->Set(full_status.tombstone_updates_received);
reflected_updates->Set(full_status.reflected_updates_received);
successful_commits->Set(full_status.num_commits_total);
conflicts_resolved_local_wins->Set(full_status.num_local_overwrites_total);
conflicts_resolved_server_wins->Set(
full_status.num_server_overwrites_total);
}
// Transient Counters (this cycle).
if (is_status_valid) {
encryption_conflicts->Set(full_status.encryption_conflicts);
hierarchy_conflicts->Set(full_status.hierarchy_conflicts);
server_conflicts->Set(full_status.server_conflicts);
committed_items->Set(full_status.committed_count);
}
// Transient Counters (last cycle of last completed session).
if (snapshot.is_initialized()) {
updates_downloaded->Set(
snapshot.model_neutral_state().num_updates_downloaded_total);
committed_count->Set(snapshot.model_neutral_state().num_successful_commits);
entries->Set(static_cast<int>(snapshot.num_entries()));
}
// Nudge Source Counters.
if (is_status_valid) {
nudge_source_notification->Set(full_status.nudge_source_notification);
nudge_source_local->Set(full_status.nudge_source_local);
nudge_source_local_refresh->Set(full_status.nudge_source_local_refresh);
}
// This list of sections belongs in the 'details' field of the returned
// message.
about_info->SetKey(kDetailsKey, section_list.ToValue());
// 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 != UNKNOWN_ERROR &&
full_status.sync_protocol_error.error_type != SYNC_SUCCESS;
about_info->SetKey("actionable_error_detected",
base::Value(actionable_error_detected));
// NOTE: We won't bother showing any of the following values unless
// actionable_error_detected is set.
base::Value actionable_error(base::Value::Type::LIST);
Stat<std::string> error_type("Error Type", kUninitialized);
Stat<std::string> action("Action", kUninitialized);
Stat<std::string> url("URL", kUninitialized);
Stat<std::string> description("Error Description", kUninitialized);
if (actionable_error_detected) {
error_type.Set(
GetSyncErrorTypeString(full_status.sync_protocol_error.error_type));
action.Set(GetClientActionString(full_status.sync_protocol_error.action));
url.Set(full_status.sync_protocol_error.url);
description.Set(full_status.sync_protocol_error.error_description);
}
actionable_error.GetList().push_back(error_type.ToValue());
actionable_error.GetList().push_back(action.ToValue());
actionable_error.GetList().push_back(url.ToValue());
actionable_error.GetList().push_back(description.ToValue());
about_info->SetKey("actionable_error", std::move(actionable_error));
about_info->SetKey("unrecoverable_error_detected",
base::Value(service->HasUnrecoverableError()));
if (service->HasUnrecoverableError()) {
std::string unrecoverable_error_message =
"Unrecoverable error detected at " +
service->unrecoverable_error_location().ToString() + ": " +
service->unrecoverable_error_message();
about_info->SetKey("unrecoverable_error_message",
base::Value(unrecoverable_error_message));
}
about_info->SetKey("type_status", base::Value::FromUniquePtrValue(
service->GetTypeStatusMap()));
return about_info;
}
} // namespace sync_ui_util
} // namespace syncer