OLD | NEW |
1 // Copyright 2013 The Chromium Authors. All rights reserved. | 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 | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 #include "chrome/browser/prefs/pref_metrics_service.h" | 5 #include "chrome/browser/prefs/pref_metrics_service.h" |
6 | 6 |
7 #include "base/bind.h" | 7 #include "base/bind.h" |
| 8 #include "base/command_line.h" |
| 9 #include "base/json/json_string_value_serializer.h" |
8 #include "base/metrics/histogram.h" | 10 #include "base/metrics/histogram.h" |
| 11 #include "base/prefs/pref_registry_simple.h" |
9 #include "base/prefs/pref_service.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" |
10 #include "chrome/browser/prefs/pref_service_syncable.h" | 21 #include "chrome/browser/prefs/pref_service_syncable.h" |
| 22 #include "chrome/browser/prefs/scoped_user_pref_update.h" |
11 #include "chrome/browser/prefs/session_startup_pref.h" | 23 #include "chrome/browser/prefs/session_startup_pref.h" |
12 #include "chrome/browser/prefs/synced_pref_change_registrar.h" | 24 #include "chrome/browser/prefs/synced_pref_change_registrar.h" |
13 #include "chrome/browser/profiles/incognito_helpers.h" | 25 #include "chrome/browser/profiles/incognito_helpers.h" |
14 #include "chrome/browser/profiles/profile.h" | 26 #include "chrome/browser/profiles/profile.h" |
15 #include "chrome/browser/search_engines/template_url_prepopulate_data.h" | 27 #include "chrome/browser/search_engines/template_url_prepopulate_data.h" |
| 28 #include "chrome/common/chrome_switches.h" |
16 #include "chrome/common/pref_names.h" | 29 #include "chrome/common/pref_names.h" |
17 #include "components/browser_context_keyed_service/browser_context_dependency_ma
nager.h" | 30 #include "components/browser_context_keyed_service/browser_context_dependency_ma
nager.h" |
| 31 #include "crypto/hmac.h" |
| 32 #include "grit/browser_resources.h" |
18 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" | 33 #include "net/base/registry_controlled_domains/registry_controlled_domain.h" |
| 34 #include "ui/base/resource/resource_bundle.h" |
19 | 35 |
20 namespace { | 36 namespace { |
21 | 37 |
22 // Converts a host name into a domain name for easier matching. | 38 const int kSessionStartupPrefValueMax = SessionStartupPref::kPrefValueMax; |
23 std::string GetDomainFromHost(const std::string& host) { | |
24 return net::registry_controlled_domains::GetDomainAndRegistry( | |
25 host, | |
26 net::registry_controlled_domains::EXCLUDE_PRIVATE_REGISTRIES); | |
27 } | |
28 | 39 |
29 const int kSessionStartupPrefValueMax = SessionStartupPref::kPrefValueMax; | 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 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; |
30 | 61 |
31 } // namespace | 62 } // namespace |
32 | 63 |
33 PrefMetricsService::PrefMetricsService(Profile* profile) | 64 PrefMetricsService::PrefMetricsService(Profile* profile) |
34 : 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 |
35 RecordLaunchPrefs(); | 76 RecordLaunchPrefs(); |
36 | 77 |
37 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); | 78 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); |
38 synced_pref_change_registrar_.reset(new SyncedPrefChangeRegistrar(prefs)); | 79 synced_pref_change_registrar_.reset(new SyncedPrefChangeRegistrar(prefs)); |
39 | 80 |
40 RegisterSyncedPrefObservers(); | 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 |
| 86 // Android has no GetDeviceId. |
| 87 #if !defined(OS_ANDROID) |
| 88 // We need the machine id to compute pref value hashes. Fetch that, and then |
| 89 // call CheckTrackedPreferences in the callback. |
| 90 extensions::api::DeviceId::GetDeviceId( |
| 91 "PrefMetricsService", // non-empty string to obfuscate the device id. |
| 92 Bind(&PrefMetricsService::GetDeviceIdCallback, |
| 93 weak_factory_.GetWeakPtr())); |
| 94 #endif // !defined(OS_ANDROID) |
| 95 } |
| 96 |
| 97 // For unit testing only. |
| 98 PrefMetricsService::PrefMetricsService(Profile* profile, |
| 99 PrefService* local_state, |
| 100 const std::string& device_id, |
| 101 const char** tracked_pref_paths, |
| 102 int tracked_pref_path_count) |
| 103 : profile_(profile), |
| 104 prefs_(profile->GetPrefs()), |
| 105 local_state_(local_state), |
| 106 pref_hash_seed_(kSHA256DigestSize, 0), |
| 107 device_id_(device_id), |
| 108 tracked_pref_paths_(tracked_pref_paths), |
| 109 tracked_pref_path_count_(tracked_pref_path_count), |
| 110 checked_tracked_prefs_(false), |
| 111 weak_factory_(this) { |
| 112 CheckTrackedPreferences(); |
41 } | 113 } |
42 | 114 |
43 PrefMetricsService::~PrefMetricsService() { | 115 PrefMetricsService::~PrefMetricsService() { |
44 } | 116 } |
45 | 117 |
46 void PrefMetricsService::RecordLaunchPrefs() { | 118 void PrefMetricsService::RecordLaunchPrefs() { |
47 PrefService* prefs = profile_->GetPrefs(); | 119 bool show_home_button = prefs_->GetBoolean(prefs::kShowHomeButton); |
48 bool show_home_button = prefs->GetBoolean(prefs::kShowHomeButton); | 120 bool home_page_is_ntp = prefs_->GetBoolean(prefs::kHomePageIsNewTabPage); |
49 bool home_page_is_ntp = prefs->GetBoolean(prefs::kHomePageIsNewTabPage); | |
50 UMA_HISTOGRAM_BOOLEAN("Settings.ShowHomeButton", show_home_button); | 121 UMA_HISTOGRAM_BOOLEAN("Settings.ShowHomeButton", show_home_button); |
51 if (show_home_button) { | 122 if (show_home_button) { |
52 UMA_HISTOGRAM_BOOLEAN("Settings.GivenShowHomeButton_HomePageIsNewTabPage", | 123 UMA_HISTOGRAM_BOOLEAN("Settings.GivenShowHomeButton_HomePageIsNewTabPage", |
53 home_page_is_ntp); | 124 home_page_is_ntp); |
54 } | 125 } |
55 | 126 |
56 // For non-NTP homepages, see if the URL comes from the same TLD+1 as a known | 127 // For non-NTP homepages, see if the URL comes from the same TLD+1 as a known |
57 // search engine. Note that this is only an approximation of search engine | 128 // search engine. Note that this is only an approximation of search engine |
58 // use, due to both false negatives (pages that come from unknown TLD+1 X but | 129 // use, due to both false negatives (pages that come from unknown TLD+1 X but |
59 // consist of a search box that sends to known TLD+1 Y) and false positives | 130 // consist of a search box that sends to known TLD+1 Y) and false positives |
60 // (pages that share a TLD+1 with a known engine but aren't actually search | 131 // (pages that share a TLD+1 with a known engine but aren't actually search |
61 // pages, e.g. plus.google.com). | 132 // pages, e.g. plus.google.com). |
62 if (!home_page_is_ntp) { | 133 if (!home_page_is_ntp) { |
63 GURL homepage_url(prefs->GetString(prefs::kHomePage)); | 134 GURL homepage_url(prefs_->GetString(prefs::kHomePage)); |
64 if (homepage_url.is_valid()) { | 135 if (homepage_url.is_valid()) { |
65 UMA_HISTOGRAM_ENUMERATION( | 136 UMA_HISTOGRAM_ENUMERATION( |
66 "Settings.HomePageEngineType", | 137 "Settings.HomePageEngineType", |
67 TemplateURLPrepopulateData::GetEngineType(homepage_url), | 138 TemplateURLPrepopulateData::GetEngineType(homepage_url), |
68 SEARCH_ENGINE_MAX); | 139 SEARCH_ENGINE_MAX); |
69 } | 140 } |
70 } | 141 } |
71 | 142 |
72 int restore_on_startup = prefs->GetInteger(prefs::kRestoreOnStartup); | 143 int restore_on_startup = prefs_->GetInteger(prefs::kRestoreOnStartup); |
73 UMA_HISTOGRAM_ENUMERATION("Settings.StartupPageLoadSettings", | 144 UMA_HISTOGRAM_ENUMERATION("Settings.StartupPageLoadSettings", |
74 restore_on_startup, kSessionStartupPrefValueMax); | 145 restore_on_startup, kSessionStartupPrefValueMax); |
75 if (restore_on_startup == SessionStartupPref::kPrefValueURLs) { | 146 if (restore_on_startup == SessionStartupPref::kPrefValueURLs) { |
76 const ListValue* url_list = prefs->GetList(prefs::kURLsToRestoreOnStartup); | 147 const ListValue* url_list = prefs_->GetList(prefs::kURLsToRestoreOnStartup); |
77 UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.StartupPageLoadURLs", | 148 UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.StartupPageLoadURLs", |
78 url_list->GetSize(), 1, 50, 20); | 149 url_list->GetSize(), 1, 50, 20); |
79 // Similarly, check startup pages for known search engine TLD+1s. | 150 // Similarly, check startup pages for known search engine TLD+1s. |
80 std::string url_text; | 151 std::string url_text; |
81 for (size_t i = 0; i < url_list->GetSize(); i++) { | 152 for (size_t i = 0; i < url_list->GetSize(); i++) { |
82 if (url_list->GetString(i, &url_text)) { | 153 if (url_list->GetString(i, &url_text)) { |
83 GURL start_url(url_text); | 154 GURL start_url(url_text); |
84 if (start_url.is_valid()) { | 155 if (start_url.is_valid()) { |
85 UMA_HISTOGRAM_ENUMERATION( | 156 UMA_HISTOGRAM_ENUMERATION( |
86 "Settings.StartupPageEngineTypes", | 157 "Settings.StartupPageEngineTypes", |
87 TemplateURLPrepopulateData::GetEngineType(start_url), | 158 TemplateURLPrepopulateData::GetEngineType(start_url), |
88 SEARCH_ENGINE_MAX); | 159 SEARCH_ENGINE_MAX); |
89 } | 160 } |
90 } | 161 } |
91 } | 162 } |
92 } | 163 } |
93 } | 164 } |
94 | 165 |
| 166 // static |
| 167 void PrefMetricsService::RegisterPrefs(PrefRegistrySimple* registry) { |
| 168 // Register the top level dictionary to map profile names to dictionaries of |
| 169 // tracked preferences. |
| 170 registry->RegisterDictionaryPref(prefs::kProfilePreferenceHashes); |
| 171 } |
| 172 |
95 void PrefMetricsService::RegisterSyncedPrefObservers() { | 173 void PrefMetricsService::RegisterSyncedPrefObservers() { |
96 LogHistogramValueCallback booleanHandler = base::Bind( | 174 LogHistogramValueCallback booleanHandler = base::Bind( |
97 &PrefMetricsService::LogBooleanPrefChange, base::Unretained(this)); | 175 &PrefMetricsService::LogBooleanPrefChange, base::Unretained(this)); |
98 | 176 |
99 AddPrefObserver(prefs::kShowHomeButton, "ShowHomeButton", booleanHandler); | 177 AddPrefObserver(prefs::kShowHomeButton, "ShowHomeButton", booleanHandler); |
100 AddPrefObserver(prefs::kHomePageIsNewTabPage, "HomePageIsNewTabPage", | 178 AddPrefObserver(prefs::kHomePageIsNewTabPage, "HomePageIsNewTabPage", |
101 booleanHandler); | 179 booleanHandler); |
102 | 180 |
103 AddPrefObserver(prefs::kRestoreOnStartup, "StartupPageLoadSettings", | 181 AddPrefObserver(prefs::kRestoreOnStartup, "StartupPageLoadSettings", |
104 base::Bind(&PrefMetricsService::LogIntegerPrefChange, | 182 base::Bind(&PrefMetricsService::LogIntegerPrefChange, |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
162 const ListValue* items = NULL; | 240 const ListValue* items = NULL; |
163 if (!value->GetAsList(&items)) | 241 if (!value->GetAsList(&items)) |
164 return; | 242 return; |
165 for (size_t i = 0; i < items->GetSize(); ++i) { | 243 for (size_t i = 0; i < items->GetSize(); ++i) { |
166 const Value *item_value = NULL; | 244 const Value *item_value = NULL; |
167 if (items->Get(i, &item_value)) | 245 if (items->Get(i, &item_value)) |
168 item_callback.Run(histogram_name, item_value); | 246 item_callback.Run(histogram_name, item_value); |
169 } | 247 } |
170 } | 248 } |
171 | 249 |
| 250 void PrefMetricsService::GetDeviceIdCallback(const std::string& device_id) { |
| 251 device_id_ = device_id; |
| 252 // On Aura, this seems to be called twice. |
| 253 if (!checked_tracked_prefs_) |
| 254 CheckTrackedPreferences(); |
| 255 } |
| 256 |
| 257 // To detect changes to Preferences that happen outside of Chrome, we hash |
| 258 // selected pref values and save them in local state. CheckTrackedPreferences |
| 259 // compares the saved values to the values observed in the profile's prefs. A |
| 260 // dictionary of dictionaries in local state holds the hashed values, grouped by |
| 261 // profile. To make the system more resistant to spoofing, pref values are |
| 262 // hashed with the pref path and the device id. |
| 263 void PrefMetricsService::CheckTrackedPreferences() { |
| 264 DCHECK(!checked_tracked_prefs_); |
| 265 |
| 266 const base::DictionaryValue* pref_hash_dicts = |
| 267 local_state_->GetDictionary(prefs::kProfilePreferenceHashes); |
| 268 // Get the hashed prefs dictionary if it exists. If it doesn't, it will be |
| 269 // created if we set preference values below. |
| 270 const base::DictionaryValue* hashed_prefs = NULL; |
| 271 pref_hash_dicts->GetDictionary(profile_name_, &hashed_prefs); |
| 272 for (int i = 0; i < tracked_pref_path_count_; ++i) { |
| 273 // Skip prefs that haven't been registered. |
| 274 if (!prefs_->FindPreference(tracked_pref_paths_[i])) |
| 275 continue; |
| 276 |
| 277 bool changed = false; |
| 278 const base::Value* value = prefs_->GetUserPrefValue(tracked_pref_paths_[i]); |
| 279 if (value) { |
| 280 std::string value_hash = |
| 281 GetHashedPrefValue(tracked_pref_paths_[i], value); |
| 282 std::string last_hash; |
| 283 if (hashed_prefs && |
| 284 hashed_prefs->GetString(tracked_pref_paths_[i], &last_hash)) { |
| 285 if (value_hash != last_hash) { |
| 286 changed = true; |
| 287 // Record that the preference changed from its last value. |
| 288 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceChanged", |
| 289 i, tracked_pref_path_count_); |
| 290 UpdateTrackedPreference(tracked_pref_paths_[i]); |
| 291 } |
| 292 } else { |
| 293 changed = true; |
| 294 // Record that we haven't tracked this preference yet, or the hash in |
| 295 // local state was removed. |
| 296 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceInitialized", |
| 297 i, tracked_pref_path_count_); |
| 298 UpdateTrackedPreference(tracked_pref_paths_[i]); |
| 299 } |
| 300 } else { |
| 301 // There is no preference set. Remove any hashed value from local state |
| 302 // and if one was present, record that a pref was cleared. |
| 303 if (RemoveTrackedPreference(tracked_pref_paths_[i])) { |
| 304 changed = true; |
| 305 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceCleared", |
| 306 i, tracked_pref_path_count_); |
| 307 } |
| 308 } |
| 309 if (!changed) { |
| 310 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceUnchanged", |
| 311 i, tracked_pref_path_count_); |
| 312 } |
| 313 } |
| 314 |
| 315 checked_tracked_prefs_ = true; |
| 316 |
| 317 // Now that we've checked the incoming preferences, register for change |
| 318 // notifications, unless this is test code. |
| 319 // TODO(bbudge) Fix failing browser_tests so we can remove this test. Several |
| 320 // tests fail when they shutdown before they can write local state. |
| 321 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) |
| 322 InitializePrefObservers(); |
| 323 } |
| 324 |
| 325 void PrefMetricsService::UpdateTrackedPreference(const char* path) { |
| 326 const base::Value* value = prefs_->GetUserPrefValue(path); |
| 327 if (value) { |
| 328 DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes); |
| 329 update->SetString(GetHashedPrefPath(path), |
| 330 GetHashedPrefValue(path, value)); |
| 331 } else { |
| 332 RemoveTrackedPreference(path); |
| 333 } |
| 334 } |
| 335 |
| 336 bool PrefMetricsService::RemoveTrackedPreference(const char* path) { |
| 337 DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes); |
| 338 return update->Remove(GetHashedPrefPath(path), NULL); |
| 339 } |
| 340 |
| 341 std::string PrefMetricsService::GetHashedPrefPath(const char* path) { |
| 342 std::string hash_pref_path(profile_name_); |
| 343 hash_pref_path.append("."); |
| 344 hash_pref_path.append(path); |
| 345 return hash_pref_path; |
| 346 } |
| 347 |
| 348 std::string PrefMetricsService::GetHashedPrefValue( |
| 349 const char* path, |
| 350 const base::Value* value) { |
| 351 DCHECK(value); |
| 352 |
| 353 std::string string_to_hash(device_id_); |
| 354 string_to_hash.append(path); |
| 355 JSONStringValueSerializer serializer(&string_to_hash); |
| 356 serializer.Serialize(*value); |
| 357 |
| 358 crypto::HMAC hmac(crypto::HMAC::SHA256); |
| 359 unsigned char digest[kSHA256DigestSize]; |
| 360 if (!hmac.Init(pref_hash_seed_) || |
| 361 !hmac.Sign(string_to_hash, digest, kSHA256DigestSize)) { |
| 362 NOTREACHED(); |
| 363 return std::string(); |
| 364 } |
| 365 |
| 366 return base::HexEncode(digest, kSHA256DigestSize); |
| 367 } |
| 368 |
| 369 void PrefMetricsService::InitializePrefObservers() { |
| 370 pref_registrar_.Init(prefs_); |
| 371 for (int i = 0; i < tracked_pref_path_count_; ++i) { |
| 372 // Skip prefs that haven't been registered. |
| 373 if (!prefs_->FindPreference(tracked_pref_paths_[i])) |
| 374 continue; |
| 375 |
| 376 pref_registrar_.Add( |
| 377 tracked_pref_paths_[i], |
| 378 base::Bind(&PrefMetricsService::UpdateTrackedPreference, |
| 379 weak_factory_.GetWeakPtr(), |
| 380 tracked_pref_paths_[i])); |
| 381 } |
| 382 } |
| 383 |
172 // static | 384 // static |
173 PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() { | 385 PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() { |
174 return Singleton<PrefMetricsService::Factory>::get(); | 386 return Singleton<PrefMetricsService::Factory>::get(); |
175 } | 387 } |
176 | 388 |
177 // static | 389 // static |
178 PrefMetricsService* PrefMetricsService::Factory::GetForProfile( | 390 PrefMetricsService* PrefMetricsService::Factory::GetForProfile( |
179 Profile* profile) { | 391 Profile* profile) { |
180 return static_cast<PrefMetricsService*>( | 392 return static_cast<PrefMetricsService*>( |
181 GetInstance()->GetServiceForBrowserContext(profile, true)); | 393 GetInstance()->GetServiceForBrowserContext(profile, true)); |
(...skipping 19 matching lines...) Expand all Loading... |
201 } | 413 } |
202 | 414 |
203 bool PrefMetricsService::Factory::ServiceIsNULLWhileTesting() const { | 415 bool PrefMetricsService::Factory::ServiceIsNULLWhileTesting() const { |
204 return false; | 416 return false; |
205 } | 417 } |
206 | 418 |
207 content::BrowserContext* PrefMetricsService::Factory::GetBrowserContextToUse( | 419 content::BrowserContext* PrefMetricsService::Factory::GetBrowserContextToUse( |
208 content::BrowserContext* context) const { | 420 content::BrowserContext* context) const { |
209 return chrome::GetBrowserContextRedirectedInIncognito(context); | 421 return chrome::GetBrowserContextRedirectedInIncognito(context); |
210 } | 422 } |
OLD | NEW |