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