Home | History | Annotate | Download | only in prefs
      1 // Copyright 2013 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 "chrome/browser/prefs/pref_metrics_service.h"
      6 
      7 #include "base/bind.h"
      8 #include "base/command_line.h"
      9 #include "base/json/json_string_value_serializer.h"
     10 #include "base/metrics/histogram.h"
     11 #include "base/prefs/pref_registry_simple.h"
     12 #include "base/prefs/pref_service.h"
     13 #include "base/strings/string_number_conversions.h"
     14 #include "chrome/browser/browser_process.h"
     15 #include "chrome/browser/browser_shutdown.h"
     16 // Accessing the Device ID API here is a layering violation.
     17 // TODO(bbudge) Move the API so it's usable here.
     18 // http://crbug.com/276485
     19 #include "chrome/browser/extensions/api/music_manager_private/device_id.h"
     20 #include "chrome/browser/extensions/extension_prefs.h"
     21 #include "chrome/browser/prefs/pref_service_syncable.h"
     22 #include "chrome/browser/prefs/scoped_user_pref_update.h"
     23 #include "chrome/browser/prefs/session_startup_pref.h"
     24 #include "chrome/browser/prefs/synced_pref_change_registrar.h"
     25 #include "chrome/browser/profiles/incognito_helpers.h"
     26 #include "chrome/browser/profiles/profile.h"
     27 #include "chrome/browser/search_engines/template_url_prepopulate_data.h"
     28 #include "chrome/common/chrome_switches.h"
     29 #include "chrome/common/pref_names.h"
     30 #include "components/browser_context_keyed_service/browser_context_dependency_manager.h"
     31 #include "crypto/hmac.h"
     32 #include "grit/browser_resources.h"
     33 #include "net/base/registry_controlled_domains/registry_controlled_domain.h"
     34 #include "ui/base/resource/resource_bundle.h"
     35 
     36 namespace {
     37 
     38 const int kSessionStartupPrefValueMax = SessionStartupPref::kPrefValueMax;
     39 
     40 // These preferences must be kept in sync with the TrackedPreference enum in
     41 // tools/metrics/histograms/histograms.xml. To add a new preference, append it
     42 // to the array and add a corresponding value to the histogram enum.
     43 const char* kTrackedPrefs[] = {
     44   prefs::kShowHomeButton,
     45   prefs::kHomePageIsNewTabPage,
     46   prefs::kHomePage,
     47   prefs::kRestoreOnStartup,
     48   prefs::kURLsToRestoreOnStartup,
     49   "place holder",  // Reserved for prefs::kExtensionsPref.
     50   prefs::kGoogleServicesLastUsername,
     51   prefs::kSearchProviderOverrides,
     52   prefs::kDefaultSearchProviderSearchURL,
     53   prefs::kDefaultSearchProviderKeyword,
     54   prefs::kDefaultSearchProviderName,
     55 #if !defined(OS_ANDROID)
     56   prefs::kPinnedTabs,
     57 #endif
     58 };
     59 
     60 static const size_t kSHA256DigestSize = 32;
     61 
     62 }  // namespace
     63 
     64 PrefMetricsService::PrefMetricsService(Profile* profile)
     65     : profile_(profile),
     66       prefs_(profile_->GetPrefs()),
     67       local_state_(g_browser_process->local_state()),
     68       profile_name_(profile_->GetPath().AsUTF8Unsafe()),
     69       tracked_pref_paths_(kTrackedPrefs),
     70       tracked_pref_path_count_(arraysize(kTrackedPrefs)),
     71       checked_tracked_prefs_(false),
     72       weak_factory_(this) {
     73   pref_hash_seed_ = ResourceBundle::GetSharedInstance().GetRawDataResource(
     74       IDR_PREF_HASH_SEED_BIN).as_string();
     75 
     76   RecordLaunchPrefs();
     77 
     78   PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
     79   synced_pref_change_registrar_.reset(new SyncedPrefChangeRegistrar(prefs));
     80 
     81   RegisterSyncedPrefObservers();
     82 
     83   // The following code might cause callbacks into this instance before we exit
     84   // the constructor. This instance should be initialized at this point.
     85 #if defined(OS_WIN) || defined(OS_MACOSX)
     86   // We need the machine id to compute pref value hashes. Fetch that, and then
     87   // call CheckTrackedPreferences in the callback.
     88   extensions::api::DeviceId::GetDeviceId(
     89       "PrefMetricsService",  // non-empty string to obfuscate the device id.
     90       Bind(&PrefMetricsService::GetDeviceIdCallback,
     91            weak_factory_.GetWeakPtr()));
     92 #endif  // defined(OS_WIN) || defined(OS_MACOSX)
     93 }
     94 
     95 // For unit testing only.
     96 PrefMetricsService::PrefMetricsService(Profile* profile,
     97                                        PrefService* local_state,
     98                                        const std::string& device_id,
     99                                        const char** tracked_pref_paths,
    100                                        int tracked_pref_path_count)
    101     : profile_(profile),
    102       prefs_(profile->GetPrefs()),
    103       local_state_(local_state),
    104       pref_hash_seed_(kSHA256DigestSize, 0),
    105       device_id_(device_id),
    106       tracked_pref_paths_(tracked_pref_paths),
    107       tracked_pref_path_count_(tracked_pref_path_count),
    108       checked_tracked_prefs_(false),
    109       weak_factory_(this) {
    110   CheckTrackedPreferences();
    111 }
    112 
    113 PrefMetricsService::~PrefMetricsService() {
    114 }
    115 
    116 void PrefMetricsService::RecordLaunchPrefs() {
    117   bool show_home_button = prefs_->GetBoolean(prefs::kShowHomeButton);
    118   bool home_page_is_ntp = prefs_->GetBoolean(prefs::kHomePageIsNewTabPage);
    119   UMA_HISTOGRAM_BOOLEAN("Settings.ShowHomeButton", show_home_button);
    120   if (show_home_button) {
    121     UMA_HISTOGRAM_BOOLEAN("Settings.GivenShowHomeButton_HomePageIsNewTabPage",
    122                           home_page_is_ntp);
    123   }
    124 
    125   // For non-NTP homepages, see if the URL comes from the same TLD+1 as a known
    126   // search engine.  Note that this is only an approximation of search engine
    127   // use, due to both false negatives (pages that come from unknown TLD+1 X but
    128   // consist of a search box that sends to known TLD+1 Y) and false positives
    129   // (pages that share a TLD+1 with a known engine but aren't actually search
    130   // pages, e.g. plus.google.com).
    131   if (!home_page_is_ntp) {
    132     GURL homepage_url(prefs_->GetString(prefs::kHomePage));
    133     if (homepage_url.is_valid()) {
    134       UMA_HISTOGRAM_ENUMERATION(
    135           "Settings.HomePageEngineType",
    136           TemplateURLPrepopulateData::GetEngineType(homepage_url),
    137           SEARCH_ENGINE_MAX);
    138     }
    139   }
    140 
    141   int restore_on_startup = prefs_->GetInteger(prefs::kRestoreOnStartup);
    142   UMA_HISTOGRAM_ENUMERATION("Settings.StartupPageLoadSettings",
    143                             restore_on_startup, kSessionStartupPrefValueMax);
    144   if (restore_on_startup == SessionStartupPref::kPrefValueURLs) {
    145     const ListValue* url_list = prefs_->GetList(prefs::kURLsToRestoreOnStartup);
    146     UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.StartupPageLoadURLs",
    147                                 url_list->GetSize(), 1, 50, 20);
    148     // Similarly, check startup pages for known search engine TLD+1s.
    149     std::string url_text;
    150     for (size_t i = 0; i < url_list->GetSize(); i++) {
    151       if (url_list->GetString(i, &url_text)) {
    152         GURL start_url(url_text);
    153         if (start_url.is_valid()) {
    154           UMA_HISTOGRAM_ENUMERATION(
    155               "Settings.StartupPageEngineTypes",
    156               TemplateURLPrepopulateData::GetEngineType(start_url),
    157               SEARCH_ENGINE_MAX);
    158         }
    159       }
    160     }
    161   }
    162 }
    163 
    164 // static
    165 void PrefMetricsService::RegisterPrefs(PrefRegistrySimple* registry) {
    166   // Register the top level dictionary to map profile names to dictionaries of
    167   // tracked preferences.
    168   registry->RegisterDictionaryPref(prefs::kProfilePreferenceHashes);
    169 }
    170 
    171 void PrefMetricsService::RegisterSyncedPrefObservers() {
    172   LogHistogramValueCallback booleanHandler = base::Bind(
    173       &PrefMetricsService::LogBooleanPrefChange, base::Unretained(this));
    174 
    175   AddPrefObserver(prefs::kShowHomeButton, "ShowHomeButton", booleanHandler);
    176   AddPrefObserver(prefs::kHomePageIsNewTabPage, "HomePageIsNewTabPage",
    177                   booleanHandler);
    178 
    179   AddPrefObserver(prefs::kRestoreOnStartup, "StartupPageLoadSettings",
    180                   base::Bind(&PrefMetricsService::LogIntegerPrefChange,
    181                              base::Unretained(this),
    182                              kSessionStartupPrefValueMax));
    183 }
    184 
    185 void PrefMetricsService::AddPrefObserver(
    186     const std::string& path,
    187     const std::string& histogram_name_prefix,
    188     const LogHistogramValueCallback& callback) {
    189   synced_pref_change_registrar_->Add(path.c_str(),
    190       base::Bind(&PrefMetricsService::OnPrefChanged,
    191                  base::Unretained(this),
    192                  histogram_name_prefix, callback));
    193 }
    194 
    195 void PrefMetricsService::OnPrefChanged(
    196     const std::string& histogram_name_prefix,
    197     const LogHistogramValueCallback& callback,
    198     const std::string& path,
    199     bool from_sync) {
    200   PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_);
    201   const PrefService::Preference* pref = prefs->FindPreference(path.c_str());
    202   DCHECK(pref);
    203   std::string source_name(
    204       from_sync ? ".PulledFromSync" : ".PushedToSync");
    205   std::string histogram_name("Settings." + histogram_name_prefix + source_name);
    206   callback.Run(histogram_name, pref->GetValue());
    207 };
    208 
    209 void PrefMetricsService::LogBooleanPrefChange(const std::string& histogram_name,
    210                                               const Value* value) {
    211   bool boolean_value = false;
    212   if (!value->GetAsBoolean(&boolean_value))
    213     return;
    214   base::HistogramBase* histogram = base::BooleanHistogram::FactoryGet(
    215       histogram_name, base::HistogramBase::kUmaTargetedHistogramFlag);
    216   histogram->Add(boolean_value);
    217 }
    218 
    219 void PrefMetricsService::LogIntegerPrefChange(int boundary_value,
    220                                               const std::string& histogram_name,
    221                                               const Value* value) {
    222   int integer_value = 0;
    223   if (!value->GetAsInteger(&integer_value))
    224     return;
    225   base::HistogramBase* histogram = base::LinearHistogram::FactoryGet(
    226       histogram_name,
    227       1,
    228       boundary_value,
    229       boundary_value + 1,
    230       base::HistogramBase::kUmaTargetedHistogramFlag);
    231   histogram->Add(integer_value);
    232 }
    233 
    234 void PrefMetricsService::LogListPrefChange(
    235     const LogHistogramValueCallback& item_callback,
    236     const std::string& histogram_name,
    237     const Value* value) {
    238   const ListValue* items = NULL;
    239   if (!value->GetAsList(&items))
    240     return;
    241   for (size_t i = 0; i < items->GetSize(); ++i) {
    242     const Value *item_value = NULL;
    243     if (items->Get(i, &item_value))
    244       item_callback.Run(histogram_name, item_value);
    245   }
    246 }
    247 
    248 void PrefMetricsService::GetDeviceIdCallback(const std::string& device_id) {
    249   device_id_ = device_id;
    250   // On Aura, this seems to be called twice.
    251   if (!checked_tracked_prefs_)
    252     CheckTrackedPreferences();
    253 }
    254 
    255 // To detect changes to Preferences that happen outside of Chrome, we hash
    256 // selected pref values and save them in local state. CheckTrackedPreferences
    257 // compares the saved values to the values observed in the profile's prefs. A
    258 // dictionary of dictionaries in local state holds the hashed values, grouped by
    259 // profile. To make the system more resistant to spoofing, pref values are
    260 // hashed with the pref path and the device id.
    261 void PrefMetricsService::CheckTrackedPreferences() {
    262   DCHECK(!checked_tracked_prefs_);
    263 
    264   const base::DictionaryValue* pref_hash_dicts =
    265       local_state_->GetDictionary(prefs::kProfilePreferenceHashes);
    266   // Get the hashed prefs dictionary if it exists. If it doesn't, it will be
    267   // created if we set preference values below.
    268   const base::DictionaryValue* hashed_prefs = NULL;
    269   pref_hash_dicts->GetDictionary(profile_name_, &hashed_prefs);
    270   for (int i = 0; i < tracked_pref_path_count_; ++i) {
    271     // Skip prefs that haven't been registered.
    272     if (!prefs_->FindPreference(tracked_pref_paths_[i]))
    273       continue;
    274 
    275     bool changed = false;
    276     const base::Value* value = prefs_->GetUserPrefValue(tracked_pref_paths_[i]);
    277     if (value) {
    278       std::string value_hash =
    279           GetHashedPrefValue(tracked_pref_paths_[i], value);
    280       std::string last_hash;
    281       if (hashed_prefs &&
    282           hashed_prefs->GetString(tracked_pref_paths_[i], &last_hash)) {
    283         if (value_hash != last_hash) {
    284           changed = true;
    285           // Record that the preference changed from its last value.
    286           UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceChanged",
    287               i, tracked_pref_path_count_);
    288           UpdateTrackedPreference(tracked_pref_paths_[i]);
    289         }
    290       } else {
    291         changed = true;
    292         // Record that we haven't tracked this preference yet, or the hash in
    293         // local state was removed.
    294         UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceInitialized",
    295             i, tracked_pref_path_count_);
    296         UpdateTrackedPreference(tracked_pref_paths_[i]);
    297       }
    298     } else {
    299       // There is no preference set. Remove any hashed value from local state
    300       // and if one was present, record that a pref was cleared.
    301       if (RemoveTrackedPreference(tracked_pref_paths_[i])) {
    302         changed = true;
    303         UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceCleared",
    304             i, tracked_pref_path_count_);
    305       }
    306     }
    307     if (!changed) {
    308       UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceUnchanged",
    309           i, tracked_pref_path_count_);
    310     }
    311   }
    312 
    313   checked_tracked_prefs_ = true;
    314 
    315   // Now that we've checked the incoming preferences, register for change
    316   // notifications, unless this is test code.
    317   // TODO(bbudge) Fix failing browser_tests and so we can remove this test.
    318   // Several tests fail when they shutdown before they can write local state.
    319   if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType) &&
    320       !CommandLine::ForCurrentProcess()->HasSwitch(switches::kChromeFrame)) {
    321     InitializePrefObservers();
    322   }
    323 }
    324 
    325 void PrefMetricsService::UpdateTrackedPreference(const char* path) {
    326   const base::Value* value = prefs_->GetUserPrefValue(path);
    327   // If the pref value is now the default, remove the hash.
    328   const ListValue* list_value;
    329   const DictionaryValue* dict_value;
    330   if (!value ||
    331       (value->GetAsList(&list_value) && list_value->GetSize() == 0) ||
    332       (value->GetAsDictionary(&dict_value) && dict_value->size() == 0)) {
    333     RemoveTrackedPreference(path);
    334   } else {
    335     DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes);
    336     update->SetString(GetHashedPrefPath(path),
    337                       GetHashedPrefValue(path, value));
    338   }
    339 }
    340 
    341 bool PrefMetricsService::RemoveTrackedPreference(const char* path) {
    342   DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes);
    343   return update->Remove(GetHashedPrefPath(path), NULL);
    344 }
    345 
    346 std::string PrefMetricsService::GetHashedPrefPath(const char* path) {
    347   std::string hash_pref_path(profile_name_);
    348   hash_pref_path.append(".");
    349   hash_pref_path.append(path);
    350   return hash_pref_path;
    351 }
    352 
    353 std::string PrefMetricsService::GetHashedPrefValue(
    354     const char* path,
    355     const base::Value* value) {
    356   DCHECK(value);
    357 
    358   // Dictionary values may contain empty lists and sub-dictionaries. Make a
    359   // deep copy with those removed to make the hash more stable.
    360   const DictionaryValue* dict_value;
    361   scoped_ptr<DictionaryValue> canonical_dict_value;
    362   if (value->GetAsDictionary(&dict_value)) {
    363     canonical_dict_value.reset(dict_value->DeepCopyWithoutEmptyChildren());
    364     value = canonical_dict_value.get();
    365   }
    366 
    367   std::string string_to_hash(device_id_);
    368   string_to_hash.append(path);
    369   JSONStringValueSerializer serializer(&string_to_hash);
    370   serializer.Serialize(*value);
    371 
    372   crypto::HMAC hmac(crypto::HMAC::SHA256);
    373   unsigned char digest[kSHA256DigestSize];
    374   if (!hmac.Init(pref_hash_seed_) ||
    375       !hmac.Sign(string_to_hash, digest, kSHA256DigestSize)) {
    376     NOTREACHED();
    377     return std::string();
    378   }
    379 
    380   return base::HexEncode(digest, kSHA256DigestSize);
    381 }
    382 
    383 void PrefMetricsService::InitializePrefObservers() {
    384   pref_registrar_.Init(prefs_);
    385   for (int i = 0; i < tracked_pref_path_count_; ++i) {
    386     // Skip prefs that haven't been registered.
    387     if (!prefs_->FindPreference(tracked_pref_paths_[i]))
    388       continue;
    389 
    390     pref_registrar_.Add(
    391         tracked_pref_paths_[i],
    392         base::Bind(&PrefMetricsService::UpdateTrackedPreference,
    393                    weak_factory_.GetWeakPtr(),
    394                    tracked_pref_paths_[i]));
    395   }
    396 }
    397 
    398 // static
    399 PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() {
    400   return Singleton<PrefMetricsService::Factory>::get();
    401 }
    402 
    403 // static
    404 PrefMetricsService* PrefMetricsService::Factory::GetForProfile(
    405     Profile* profile) {
    406   return static_cast<PrefMetricsService*>(
    407       GetInstance()->GetServiceForBrowserContext(profile, true));
    408 }
    409 
    410 PrefMetricsService::Factory::Factory()
    411     : BrowserContextKeyedServiceFactory(
    412         "PrefMetricsService",
    413         BrowserContextDependencyManager::GetInstance()) {
    414 }
    415 
    416 PrefMetricsService::Factory::~Factory() {
    417 }
    418 
    419 BrowserContextKeyedService*
    420 PrefMetricsService::Factory::BuildServiceInstanceFor(
    421     content::BrowserContext* profile) const {
    422   return new PrefMetricsService(static_cast<Profile*>(profile));
    423 }
    424 
    425 bool PrefMetricsService::Factory::ServiceIsCreatedWithBrowserContext() const {
    426   return true;
    427 }
    428 
    429 bool PrefMetricsService::Factory::ServiceIsNULLWhileTesting() const {
    430   return false;
    431 }
    432 
    433 content::BrowserContext* PrefMetricsService::Factory::GetBrowserContextToUse(
    434     content::BrowserContext* context) const {
    435   return chrome::GetBrowserContextRedirectedInIncognito(context);
    436 }
    437