普通文本  |  467行  |  17.96 KB

// Copyright 2013 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 "chrome/browser/prefs/pref_metrics_service.h"

#include "base/bind.h"
#include "base/command_line.h"
#include "base/json/json_string_value_serializer.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_registry_simple.h"
#include "base/prefs/pref_service.h"
#include "base/prefs/scoped_user_pref_update.h"
#include "base/strings/string_number_conversions.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/browser_shutdown.h"
// Accessing the Device ID API here is a layering violation.
// TODO(bbudge) Move the API so it's usable here.
// http://crbug.com/276485
#include "chrome/browser/extensions/api/music_manager_private/device_id.h"
#include "chrome/browser/prefs/pref_service_syncable.h"
#include "chrome/browser/prefs/session_startup_pref.h"
#include "chrome/browser/prefs/synced_pref_change_registrar.h"
#include "chrome/browser/profiles/incognito_helpers.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_prepopulate_data.h"
#include "chrome/browser/ui/tabs/pinned_tab_codec.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/common/pref_names.h"
#include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
#include "crypto/hmac.h"
#include "grit/browser_resources.h"
#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
#include "ui/base/resource/resource_bundle.h"

namespace {

const int kSessionStartupPrefValueMax = SessionStartupPref::kPrefValueMax;

// An unregistered preference to fill in indices in kTrackedPrefs below for
// preferences that aren't defined on every platform. This is fine as the code
// below (e.g. CheckTrackedPreferences()) skips unregistered preferences and
// should thus never report any data about that index on the platforms where
// that preference is unimplemented.
const char kUnregisteredPreference[] = "_";

// These preferences must be kept in sync with the TrackedPreference enum in
// tools/metrics/histograms/histograms.xml. To add a new preference, append it
// to the array and add a corresponding value to the histogram enum.
const char* kTrackedPrefs[] = {
  prefs::kShowHomeButton,
  prefs::kHomePageIsNewTabPage,
  prefs::kHomePage,
  prefs::kRestoreOnStartup,
  prefs::kURLsToRestoreOnStartup,
  prefs::kExtensionsPref,
  prefs::kGoogleServicesLastUsername,
  prefs::kSearchProviderOverrides,
  prefs::kDefaultSearchProviderSearchURL,
  prefs::kDefaultSearchProviderKeyword,
  prefs::kDefaultSearchProviderName,
#if !defined(OS_ANDROID)
  prefs::kPinnedTabs,
#else
  kUnregisteredPreference,
#endif
  prefs::kExtensionKnownDisabled,
  prefs::kProfileResetPromptMemento,
};

const size_t kSHA256DigestSize = 32;

}  // namespace

PrefMetricsService::PrefMetricsService(Profile* profile)
    : profile_(profile),
      prefs_(profile_->GetPrefs()),
      local_state_(g_browser_process->local_state()),
      profile_name_(profile_->GetPath().AsUTF8Unsafe()),
      tracked_pref_paths_(kTrackedPrefs),
      tracked_pref_path_count_(arraysize(kTrackedPrefs)),
      checked_tracked_prefs_(false),
      weak_factory_(this) {
  pref_hash_seed_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
      IDR_PREF_HASH_SEED_BIN).as_string();

  RecordLaunchPrefs();

  PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
  synced_pref_change_registrar_.reset(new SyncedPrefChangeRegistrar(prefs));

  RegisterSyncedPrefObservers();

  // The following code might cause callbacks into this instance before we exit
  // the constructor. This instance should be initialized at this point.
#if defined(OS_WIN) || defined(OS_MACOSX)
  // We need the machine id to compute pref value hashes. Fetch that, and then
  // call CheckTrackedPreferences in the callback.
  extensions::api::DeviceId::GetDeviceId(
      "PrefMetricsService",  // non-empty string to obfuscate the device id.
      Bind(&PrefMetricsService::GetDeviceIdCallback,
           weak_factory_.GetWeakPtr()));
#endif  // defined(OS_WIN) || defined(OS_MACOSX)
}

// For unit testing only.
PrefMetricsService::PrefMetricsService(Profile* profile,
                                       PrefService* local_state,
                                       const std::string& device_id,
                                       const char** tracked_pref_paths,
                                       int tracked_pref_path_count)
    : profile_(profile),
      prefs_(profile->GetPrefs()),
      local_state_(local_state),
      profile_name_(profile_->GetPath().AsUTF8Unsafe()),
      pref_hash_seed_(kSHA256DigestSize, 0),
      device_id_(device_id),
      tracked_pref_paths_(tracked_pref_paths),
      tracked_pref_path_count_(tracked_pref_path_count),
      checked_tracked_prefs_(false),
      weak_factory_(this) {
  CheckTrackedPreferences();
}

PrefMetricsService::~PrefMetricsService() {
}

void PrefMetricsService::RecordLaunchPrefs() {
  bool show_home_button = prefs_->GetBoolean(prefs::kShowHomeButton);
  bool home_page_is_ntp = prefs_->GetBoolean(prefs::kHomePageIsNewTabPage);
  UMA_HISTOGRAM_BOOLEAN("Settings.ShowHomeButton", show_home_button);
  if (show_home_button) {
    UMA_HISTOGRAM_BOOLEAN("Settings.GivenShowHomeButton_HomePageIsNewTabPage",
                          home_page_is_ntp);
  }

  // For non-NTP homepages, see if the URL comes from the same TLD+1 as a known
  // search engine.  Note that this is only an approximation of search engine
  // use, due to both false negatives (pages that come from unknown TLD+1 X but
  // consist of a search box that sends to known TLD+1 Y) and false positives
  // (pages that share a TLD+1 with a known engine but aren't actually search
  // pages, e.g. plus.google.com).
  if (!home_page_is_ntp) {
    GURL homepage_url(prefs_->GetString(prefs::kHomePage));
    if (homepage_url.is_valid()) {
      UMA_HISTOGRAM_ENUMERATION(
          "Settings.HomePageEngineType",
          TemplateURLPrepopulateData::GetEngineType(homepage_url),
          SEARCH_ENGINE_MAX);
    }
  }

  int restore_on_startup = prefs_->GetInteger(prefs::kRestoreOnStartup);
  UMA_HISTOGRAM_ENUMERATION("Settings.StartupPageLoadSettings",
                            restore_on_startup, kSessionStartupPrefValueMax);
  if (restore_on_startup == SessionStartupPref::kPrefValueURLs) {
    const ListValue* url_list = prefs_->GetList(prefs::kURLsToRestoreOnStartup);
    UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.StartupPageLoadURLs",
                                url_list->GetSize(), 1, 50, 20);
    // Similarly, check startup pages for known search engine TLD+1s.
    std::string url_text;
    for (size_t i = 0; i < url_list->GetSize(); ++i) {
      if (url_list->GetString(i, &url_text)) {
        GURL start_url(url_text);
        if (start_url.is_valid()) {
          UMA_HISTOGRAM_ENUMERATION(
              "Settings.StartupPageEngineTypes",
              TemplateURLPrepopulateData::GetEngineType(start_url),
              SEARCH_ENGINE_MAX);
        }
      }
    }
  }

#if !defined(OS_ANDROID)
  StartupTabs startup_tabs = PinnedTabCodec::ReadPinnedTabs(profile_);
  UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.PinnedTabs",
                              startup_tabs.size(), 1, 50, 20);
  for (size_t i = 0; i < startup_tabs.size(); ++i) {
    GURL start_url(startup_tabs.at(i).url);
    if (start_url.is_valid()) {
      UMA_HISTOGRAM_ENUMERATION(
          "Settings.PinnedTabEngineTypes",
          TemplateURLPrepopulateData::GetEngineType(start_url),
          SEARCH_ENGINE_MAX);
    }
  }
#endif
}

// static
void PrefMetricsService::RegisterPrefs(PrefRegistrySimple* registry) {
  // Register the top level dictionary to map profile names to dictionaries of
  // tracked preferences.
  registry->RegisterDictionaryPref(prefs::kProfilePreferenceHashes);
}

void PrefMetricsService::RegisterSyncedPrefObservers() {
  LogHistogramValueCallback booleanHandler = base::Bind(
      &PrefMetricsService::LogBooleanPrefChange, base::Unretained(this));

  AddPrefObserver(prefs::kShowHomeButton, "ShowHomeButton", booleanHandler);
  AddPrefObserver(prefs::kHomePageIsNewTabPage, "HomePageIsNewTabPage",
                  booleanHandler);

  AddPrefObserver(prefs::kRestoreOnStartup, "StartupPageLoadSettings",
                  base::Bind(&PrefMetricsService::LogIntegerPrefChange,
                             base::Unretained(this),
                             kSessionStartupPrefValueMax));
}

void PrefMetricsService::AddPrefObserver(
    const std::string& path,
    const std::string& histogram_name_prefix,
    const LogHistogramValueCallback& callback) {
  synced_pref_change_registrar_->Add(path.c_str(),
      base::Bind(&PrefMetricsService::OnPrefChanged,
                 base::Unretained(this),
                 histogram_name_prefix, callback));
}

void PrefMetricsService::OnPrefChanged(
    const std::string& histogram_name_prefix,
    const LogHistogramValueCallback& callback,
    const std::string& path,
    bool from_sync) {
  PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
  const PrefService::Preference* pref = prefs->FindPreference(path.c_str());
  DCHECK(pref);
  std::string source_name(
      from_sync ? ".PulledFromSync" : ".PushedToSync");
  std::string histogram_name("Settings." + histogram_name_prefix + source_name);
  callback.Run(histogram_name, pref->GetValue());
};

void PrefMetricsService::LogBooleanPrefChange(const std::string& histogram_name,
                                              const Value* value) {
  bool boolean_value = false;
  if (!value->GetAsBoolean(&boolean_value))
    return;
  base::HistogramBase* histogram = base::BooleanHistogram::FactoryGet(
      histogram_name, base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram->Add(boolean_value);
}

void PrefMetricsService::LogIntegerPrefChange(int boundary_value,
                                              const std::string& histogram_name,
                                              const Value* value) {
  int integer_value = 0;
  if (!value->GetAsInteger(&integer_value))
    return;
  base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
      histogram_name,
      1,
      boundary_value,
      boundary_value + 1,
      base::HistogramBase::kUmaTargetedHistogramFlag);
  histogram->Add(integer_value);
}

void PrefMetricsService::GetDeviceIdCallback(const std::string& device_id) {
#if !defined(OS_WIN) || defined(ENABLE_RLZ)
  // A device_id is expected in all scenarios except when RLZ is disabled on
  // Windows.
  DCHECK(!device_id.empty());
#endif

  device_id_ = device_id;
  CheckTrackedPreferences();
}

// To detect changes to Preferences that happen outside of Chrome, we hash
// selected pref values and save them in local state. CheckTrackedPreferences
// compares the saved values to the values observed in the profile's prefs. A
// dictionary of dictionaries in local state holds the hashed values, grouped by
// profile. To make the system more resistant to spoofing, pref values are
// hashed with the pref path and the device id.
void PrefMetricsService::CheckTrackedPreferences() {
  // Make sure this is only called once per instance of this service.
  DCHECK(!checked_tracked_prefs_);
  checked_tracked_prefs_ = true;

  const base::DictionaryValue* pref_hash_dicts =
      local_state_->GetDictionary(prefs::kProfilePreferenceHashes);
  // Get the hashed prefs dictionary if it exists. If it doesn't, it will be
  // created if we set preference values below.
  const base::DictionaryValue* hashed_prefs = NULL;
  pref_hash_dicts->GetDictionaryWithoutPathExpansion(profile_name_,
                                                     &hashed_prefs);
  for (int i = 0; i < tracked_pref_path_count_; ++i) {
    if (!prefs_->FindPreference(tracked_pref_paths_[i])) {
      // All tracked preferences need to have been registered already.
      DCHECK_EQ(kUnregisteredPreference, tracked_pref_paths_[i]);
      continue;
    }

    const base::Value* value = prefs_->GetUserPrefValue(tracked_pref_paths_[i]);
    std::string last_hash;
    // First try to get the stored expected hash...
    if (hashed_prefs &&
        hashed_prefs->GetString(tracked_pref_paths_[i], &last_hash)) {
      // ... if we have one get the hash of the current value...
      const std::string value_hash =
          GetHashedPrefValue(tracked_pref_paths_[i], value,
                             HASHED_PREF_STYLE_NEW);
      // ... and check that it matches...
      if (value_hash == last_hash) {
        UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceUnchanged",
            i, tracked_pref_path_count_);
      } else {
        // ... if it doesn't: was the value simply cleared?
        if (!value) {
          UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceCleared",
              i, tracked_pref_path_count_);
        } else {
          // ... or does it match the old style hash?
          std::string old_style_hash =
              GetHashedPrefValue(tracked_pref_paths_[i], value,
                                 HASHED_PREF_STYLE_DEPRECATED);
          if (old_style_hash == last_hash) {
            UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceMigrated",
                i, tracked_pref_path_count_);
          } else {
            // ... or was it simply changed to something else?
            UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceChanged",
                i, tracked_pref_path_count_);
          }
        }

        // ... either way update the expected hash to match the new value.
        UpdateTrackedPreference(tracked_pref_paths_[i]);
      }
    } else {
      // Record that we haven't tracked this preference yet, or the hash in
      // local state was removed.
      UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceInitialized",
          i, tracked_pref_path_count_);
      UpdateTrackedPreference(tracked_pref_paths_[i]);
    }
  }

  // Now that we've checked the incoming preferences, register for change
  // notifications, unless this is test code.
  // TODO(bbudge) Fix failing browser_tests and so we can remove this test.
  // Several tests fail when they shutdown before they can write local state.
  if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType) &&
      !CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeFrame)) {
    InitializePrefObservers();
  }
}

void PrefMetricsService::UpdateTrackedPreference(const char* path) {
  const base::Value* value = prefs_->GetUserPrefValue(path);
  DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes);
  DictionaryValue* child_dictionary = NULL;

  // Get the dictionary corresponding to the profile name,
  // which may have a '.'
  if (!update->GetDictionaryWithoutPathExpansion(profile_name_,
                                                 &child_dictionary)) {
    child_dictionary = new DictionaryValue;
    update->SetWithoutPathExpansion(profile_name_, child_dictionary);
  }

  child_dictionary->SetString(path,
                              GetHashedPrefValue(path, value,
                                                 HASHED_PREF_STYLE_NEW));
}

std::string PrefMetricsService::GetHashedPrefValue(
    const char* path,
    const base::Value* value,
    HashedPrefStyle desired_style) {
  // Dictionary values may contain empty lists and sub-dictionaries. Make a
  // deep copy with those removed to make the hash more stable.
  const DictionaryValue* dict_value;
  scoped_ptr<DictionaryValue> canonical_dict_value;
  if (value &&
      value->GetAsDictionary(&dict_value)) {
    canonical_dict_value.reset(dict_value->DeepCopyWithoutEmptyChildren());
    value = canonical_dict_value.get();
  }

  std::string value_as_string;
  // If |value| is NULL, we will still build a unique hash based on |device_id_|
  // and |path| below.
  if (value) {
    JSONStringValueSerializer serializer(&value_as_string);
    serializer.Serialize(*value);
  }

  std::string string_to_hash;
  // TODO(gab): Remove this as the old style is phased out.
  if (desired_style == HASHED_PREF_STYLE_NEW) {
    string_to_hash.append(device_id_);
    string_to_hash.append(path);
  }
  string_to_hash.append(value_as_string);

  crypto::HMAC hmac(crypto::HMAC::SHA256);
  unsigned char digest[kSHA256DigestSize];
  if (!hmac.Init(pref_hash_seed_) ||
      !hmac.Sign(string_to_hash, digest, kSHA256DigestSize)) {
    NOTREACHED();
    return std::string();
  }

  return base::HexEncode(digest, kSHA256DigestSize);
}

void PrefMetricsService::InitializePrefObservers() {
  pref_registrar_.Init(prefs_);
  for (int i = 0; i < tracked_pref_path_count_; ++i) {
    if (!prefs_->FindPreference(tracked_pref_paths_[i])) {
      // All tracked preferences need to have been registered already.
      DCHECK_EQ(kUnregisteredPreference, tracked_pref_paths_[i]);
      continue;
    }

    pref_registrar_.Add(
        tracked_pref_paths_[i],
        base::Bind(&PrefMetricsService::UpdateTrackedPreference,
                   weak_factory_.GetWeakPtr(),
                   tracked_pref_paths_[i]));
  }
}

// static
PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() {
  return Singleton<PrefMetricsService::Factory>::get();
}

// static
PrefMetricsService* PrefMetricsService::Factory::GetForProfile(
    Profile* profile) {
  return static_cast<PrefMetricsService*>(
      GetInstance()->GetServiceForBrowserContext(profile, true));
}

PrefMetricsService::Factory::Factory()
    : BrowserContextKeyedServiceFactory(
        "PrefMetricsService",
        BrowserContextDependencyManager::GetInstance()) {
}

PrefMetricsService::Factory::~Factory() {
}

BrowserContextKeyedService*
PrefMetricsService::Factory::BuildServiceInstanceFor(
    content::BrowserContext* profile) const {
  return new PrefMetricsService(static_cast<Profile*>(profile));
}

bool PrefMetricsService::Factory::ServiceIsCreatedWithBrowserContext() const {
  return true;
}

bool PrefMetricsService::Factory::ServiceIsNULLWhileTesting() const {
  return false;
}

content::BrowserContext* PrefMetricsService::Factory::GetBrowserContextToUse(
    content::BrowserContext* context) const {
  return chrome::GetBrowserContextRedirectedInIncognito(context);
}