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 prefs::kPinnedTabs, | |
56 }; | |
57 | |
58 static const size_t kSHA256DigestSize = 32; | |
30 | 59 |
31 } // namespace | 60 } // namespace |
32 | 61 |
33 PrefMetricsService::PrefMetricsService(Profile* profile) | 62 PrefMetricsService::PrefMetricsService(Profile* profile) |
34 : profile_(profile) { | 63 : weak_factory_(this), |
64 profile_(profile), | |
65 prefs_(profile_->GetPrefs()), | |
66 local_state_(g_browser_process->local_state()), | |
67 tracked_pref_paths_(kTrackedPrefs), | |
68 tracked_pref_path_count_(arraysize(kTrackedPrefs)) { | |
69 // Check that the pref is registered; otherwise some browser_tests crash. | |
70 if (prefs_->FindPreference(prefs::kGoogleServicesUsername)) | |
71 profile_name_ = profile_->GetProfileName(); | |
battre
2013/09/06 11:35:33
This should probably use profile_->GetPath() becau
bbudge
2013/09/06 13:31:34
Done.
| |
72 | |
73 pref_hash_seed_ = ResourceBundle::GetSharedInstance().GetRawDataResource( | |
74 IDR_PREF_HASH_SEED_BIN).as_string(); | |
75 | |
35 RecordLaunchPrefs(); | 76 RecordLaunchPrefs(); |
36 | 77 |
78 // Don't track preferences in browser tests. Otherwise, tests that change | |
79 // prefs after shutdown begins will write to local state, which will DCHECK | |
80 // when trying to write. | |
81 if (!CommandLine::ForCurrentProcess()->HasSwitch(switches::kTestType)) | |
82 InitializePrefObservers(); | |
83 | |
84 #if !defined(OS_ANDROID) | |
85 // We need the machine id to compute pref value hashes. Fetch that, and then | |
86 // call CheckTrackedPreferences in the callback. | |
87 extensions::api::DeviceId::GetDeviceId( | |
battre
2013/09/06 11:35:33
This can deliver a synchronous callback at a time
bbudge
2013/09/06 13:31:34
I moved this to the end of the ctor, with a commen
| |
88 "PrefMetricsService", // non-empty string to obfuscate the device id. | |
89 Bind(&PrefMetricsService::GetDeviceIdCallback, | |
90 weak_factory_.GetWeakPtr())); | |
91 #else | |
92 // Android has no GetDeviceId. | |
93 CheckTrackedPreferences(); | |
94 #endif // !defined(OS_ANDROID) | |
95 | |
37 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); | 96 PrefServiceSyncable* prefs = PrefServiceSyncable::FromProfile(profile_); |
38 synced_pref_change_registrar_.reset(new SyncedPrefChangeRegistrar(prefs)); | 97 synced_pref_change_registrar_.reset(new SyncedPrefChangeRegistrar(prefs)); |
39 | 98 |
40 RegisterSyncedPrefObservers(); | 99 RegisterSyncedPrefObservers(); |
41 } | 100 } |
42 | 101 |
102 // For unit testing only. | |
103 PrefMetricsService::PrefMetricsService(Profile* profile, | |
104 PrefService* local_state, | |
105 const std::string& device_id, | |
106 const char** tracked_pref_paths, | |
107 int tracked_pref_path_count) | |
108 : weak_factory_(this), | |
109 profile_(profile), | |
110 prefs_(profile->GetPrefs()), | |
111 local_state_(local_state), | |
112 pref_hash_seed_(kSHA256DigestSize, 0), | |
113 device_id_(device_id), | |
114 tracked_pref_paths_(tracked_pref_paths), | |
115 tracked_pref_path_count_(tracked_pref_path_count) { | |
116 CheckTrackedPreferences(); | |
117 InitializePrefObservers(); | |
118 } | |
119 | |
43 PrefMetricsService::~PrefMetricsService() { | 120 PrefMetricsService::~PrefMetricsService() { |
44 } | 121 } |
45 | 122 |
46 void PrefMetricsService::RecordLaunchPrefs() { | 123 void PrefMetricsService::RecordLaunchPrefs() { |
47 PrefService* prefs = profile_->GetPrefs(); | 124 bool show_home_button = prefs_->GetBoolean(prefs::kShowHomeButton); |
48 bool show_home_button = prefs->GetBoolean(prefs::kShowHomeButton); | 125 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); | 126 UMA_HISTOGRAM_BOOLEAN("Settings.ShowHomeButton", show_home_button); |
51 if (show_home_button) { | 127 if (show_home_button) { |
52 UMA_HISTOGRAM_BOOLEAN("Settings.GivenShowHomeButton_HomePageIsNewTabPage", | 128 UMA_HISTOGRAM_BOOLEAN("Settings.GivenShowHomeButton_HomePageIsNewTabPage", |
53 home_page_is_ntp); | 129 home_page_is_ntp); |
54 } | 130 } |
55 | 131 |
56 // For non-NTP homepages, see if the URL comes from the same TLD+1 as a known | 132 // 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 | 133 // 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 | 134 // 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 | 135 // 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 | 136 // (pages that share a TLD+1 with a known engine but aren't actually search |
61 // pages, e.g. plus.google.com). | 137 // pages, e.g. plus.google.com). |
62 if (!home_page_is_ntp) { | 138 if (!home_page_is_ntp) { |
63 GURL homepage_url(prefs->GetString(prefs::kHomePage)); | 139 GURL homepage_url(prefs_->GetString(prefs::kHomePage)); |
64 if (homepage_url.is_valid()) { | 140 if (homepage_url.is_valid()) { |
65 UMA_HISTOGRAM_ENUMERATION( | 141 UMA_HISTOGRAM_ENUMERATION( |
66 "Settings.HomePageEngineType", | 142 "Settings.HomePageEngineType", |
67 TemplateURLPrepopulateData::GetEngineType(homepage_url), | 143 TemplateURLPrepopulateData::GetEngineType(homepage_url), |
68 SEARCH_ENGINE_MAX); | 144 SEARCH_ENGINE_MAX); |
69 } | 145 } |
70 } | 146 } |
71 | 147 |
72 int restore_on_startup = prefs->GetInteger(prefs::kRestoreOnStartup); | 148 int restore_on_startup = prefs_->GetInteger(prefs::kRestoreOnStartup); |
73 UMA_HISTOGRAM_ENUMERATION("Settings.StartupPageLoadSettings", | 149 UMA_HISTOGRAM_ENUMERATION("Settings.StartupPageLoadSettings", |
74 restore_on_startup, kSessionStartupPrefValueMax); | 150 restore_on_startup, kSessionStartupPrefValueMax); |
75 if (restore_on_startup == SessionStartupPref::kPrefValueURLs) { | 151 if (restore_on_startup == SessionStartupPref::kPrefValueURLs) { |
76 const ListValue* url_list = prefs->GetList(prefs::kURLsToRestoreOnStartup); | 152 const ListValue* url_list = prefs_->GetList(prefs::kURLsToRestoreOnStartup); |
77 UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.StartupPageLoadURLs", | 153 UMA_HISTOGRAM_CUSTOM_COUNTS("Settings.StartupPageLoadURLs", |
78 url_list->GetSize(), 1, 50, 20); | 154 url_list->GetSize(), 1, 50, 20); |
79 // Similarly, check startup pages for known search engine TLD+1s. | 155 // Similarly, check startup pages for known search engine TLD+1s. |
80 std::string url_text; | 156 std::string url_text; |
81 for (size_t i = 0; i < url_list->GetSize(); i++) { | 157 for (size_t i = 0; i < url_list->GetSize(); i++) { |
82 if (url_list->GetString(i, &url_text)) { | 158 if (url_list->GetString(i, &url_text)) { |
83 GURL start_url(url_text); | 159 GURL start_url(url_text); |
84 if (start_url.is_valid()) { | 160 if (start_url.is_valid()) { |
85 UMA_HISTOGRAM_ENUMERATION( | 161 UMA_HISTOGRAM_ENUMERATION( |
86 "Settings.StartupPageEngineTypes", | 162 "Settings.StartupPageEngineTypes", |
87 TemplateURLPrepopulateData::GetEngineType(start_url), | 163 TemplateURLPrepopulateData::GetEngineType(start_url), |
88 SEARCH_ENGINE_MAX); | 164 SEARCH_ENGINE_MAX); |
89 } | 165 } |
90 } | 166 } |
91 } | 167 } |
92 } | 168 } |
93 } | 169 } |
94 | 170 |
171 // static | |
172 void PrefMetricsService::RegisterPrefs(PrefRegistrySimple* registry) { | |
173 // Register the top level dictionary to map profile names to dictionaries of | |
174 // tracked preferences. | |
175 registry->RegisterDictionaryPref(prefs::kProfilePreferenceHashes); | |
176 } | |
177 | |
95 void PrefMetricsService::RegisterSyncedPrefObservers() { | 178 void PrefMetricsService::RegisterSyncedPrefObservers() { |
96 LogHistogramValueCallback booleanHandler = base::Bind( | 179 LogHistogramValueCallback booleanHandler = base::Bind( |
97 &PrefMetricsService::LogBooleanPrefChange, base::Unretained(this)); | 180 &PrefMetricsService::LogBooleanPrefChange, base::Unretained(this)); |
98 | 181 |
99 AddPrefObserver(prefs::kShowHomeButton, "ShowHomeButton", booleanHandler); | 182 AddPrefObserver(prefs::kShowHomeButton, "ShowHomeButton", booleanHandler); |
100 AddPrefObserver(prefs::kHomePageIsNewTabPage, "HomePageIsNewTabPage", | 183 AddPrefObserver(prefs::kHomePageIsNewTabPage, "HomePageIsNewTabPage", |
101 booleanHandler); | 184 booleanHandler); |
102 | 185 |
103 AddPrefObserver(prefs::kRestoreOnStartup, "StartupPageLoadSettings", | 186 AddPrefObserver(prefs::kRestoreOnStartup, "StartupPageLoadSettings", |
104 base::Bind(&PrefMetricsService::LogIntegerPrefChange, | 187 base::Bind(&PrefMetricsService::LogIntegerPrefChange, |
(...skipping 57 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
162 const ListValue* items = NULL; | 245 const ListValue* items = NULL; |
163 if (!value->GetAsList(&items)) | 246 if (!value->GetAsList(&items)) |
164 return; | 247 return; |
165 for (size_t i = 0; i < items->GetSize(); ++i) { | 248 for (size_t i = 0; i < items->GetSize(); ++i) { |
166 const Value *item_value = NULL; | 249 const Value *item_value = NULL; |
167 if (items->Get(i, &item_value)) | 250 if (items->Get(i, &item_value)) |
168 item_callback.Run(histogram_name, item_value); | 251 item_callback.Run(histogram_name, item_value); |
169 } | 252 } |
170 } | 253 } |
171 | 254 |
255 void PrefMetricsService::GetDeviceIdCallback(const std::string& device_id) { | |
256 device_id_ = device_id; | |
257 CheckTrackedPreferences(); | |
258 } | |
259 | |
260 // To detect changes to Preferences that happen outside of Chrome, we hash | |
261 // selected pref values and save them in local state. CheckTrackedPreferences | |
262 // compares the saved values to the values observed in the profile's prefs. A | |
263 // dictionary of dictionaries in local state holds the hashed values, grouped by | |
264 // profile. To make the system more resistant to spoofing, pref values are | |
265 // hashed with the pref path and the device id. | |
266 void PrefMetricsService::CheckTrackedPreferences() { | |
267 const base::DictionaryValue* pref_hash_dicts = | |
268 local_state_->GetDictionary(prefs::kProfilePreferenceHashes); | |
269 // Get the hashed prefs dictionary if it exists. If it doesn't, it will be | |
270 // created if we set preference values below. | |
271 const base::DictionaryValue* hashed_prefs = NULL; | |
272 pref_hash_dicts->GetDictionary(profile_name_, &hashed_prefs); | |
273 for (int i = 0; i < tracked_pref_path_count_; ++i) { | |
274 // Skip prefs that haven't been registered (e.g. in browser_tests). | |
275 if (!prefs_->FindPreference(tracked_pref_paths_[i])) | |
276 continue; | |
277 | |
278 bool changed = false; | |
279 const base::Value* value = prefs_->GetUserPrefValue(tracked_pref_paths_[i]); | |
280 if (value) { | |
281 std::string value_hash = | |
282 GetHashedPrefValue(tracked_pref_paths_[i], value); | |
283 std::string last_hash; | |
284 if (hashed_prefs && | |
285 hashed_prefs->GetString(tracked_pref_paths_[i], &last_hash)) { | |
286 if (value_hash != last_hash) { | |
287 changed = true; | |
288 // Record that the preference changed from its last value. | |
289 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceChanged", | |
290 i, tracked_pref_path_count_); | |
291 UpdateTrackedPreference(tracked_pref_paths_[i]); | |
292 } | |
293 } else { | |
294 changed = true; | |
295 // Record that we haven't tracked this preference yet, or the hash in | |
296 // local state was removed. | |
297 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceInitialized", | |
298 i, tracked_pref_path_count_); | |
299 UpdateTrackedPreference(tracked_pref_paths_[i]); | |
300 } | |
301 } else { | |
302 // There is no preference set. Remove any hashed value from local state | |
303 // and if one was present, record that a pref was cleared. | |
304 if (RemoveTrackedPreference(tracked_pref_paths_[i])) { | |
305 changed = true; | |
306 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceCleared", | |
307 i, tracked_pref_path_count_); | |
308 } | |
309 } | |
310 if (!changed) { | |
311 UMA_HISTOGRAM_ENUMERATION("Settings.TrackedPreferenceUnchanged", | |
312 i, tracked_pref_path_count_); | |
313 } | |
314 } | |
315 } | |
316 | |
317 void PrefMetricsService::UpdateTrackedPreference(const char* path) { | |
318 const base::Value* value = prefs_->GetUserPrefValue(path); | |
319 if (value) { | |
320 DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes); | |
321 base::DictionaryValue* dict = update.Get(); | |
322 if (dict) { | |
323 dict->SetString(GetHashedPrefPath(path), | |
324 GetHashedPrefValue(path, value)); | |
325 } | |
battre
2013/09/06 11:35:33
A value is created on demand by DictionaryPrefUpda
bbudge
2013/09/06 13:31:34
Nice. Done.
| |
326 } else { | |
327 RemoveTrackedPreference(path); | |
328 } | |
329 } | |
330 | |
331 bool PrefMetricsService::RemoveTrackedPreference(const char* path) { | |
332 DictionaryPrefUpdate update(local_state_, prefs::kProfilePreferenceHashes); | |
333 base::DictionaryValue* dict = update.Get(); | |
334 if (dict) | |
335 return dict->Remove(GetHashedPrefPath(path), NULL); | |
336 | |
337 return false; | |
338 } | |
339 | |
340 std::string PrefMetricsService::GetHashedPrefPath(const char* path) { | |
341 std::string hash_pref_path(profile_name_); | |
342 hash_pref_path.append("."); | |
343 hash_pref_path.append(path); | |
344 return hash_pref_path; | |
345 } | |
346 | |
347 std::string PrefMetricsService::GetHashedPrefValue( | |
348 const char* path, | |
349 const base::Value* value) { | |
350 DCHECK(value); | |
351 | |
352 std::string string_to_hash(device_id_); | |
353 string_to_hash.append(path); | |
354 JSONStringValueSerializer serializer(&string_to_hash); | |
355 serializer.Serialize(*value); | |
356 | |
357 crypto::HMAC hmac(crypto::HMAC::SHA256); | |
358 unsigned char digest[kSHA256DigestSize]; | |
359 if (!hmac.Init(pref_hash_seed_) || | |
360 !hmac.Sign(string_to_hash, digest, kSHA256DigestSize)) { | |
361 NOTREACHED(); | |
362 return std::string(); | |
363 } | |
364 | |
365 return base::HexEncode(digest, kSHA256DigestSize); | |
366 } | |
367 | |
368 void PrefMetricsService::InitializePrefObservers() { | |
369 pref_registrar_.Init(prefs_); | |
370 for (int i = 0; i < tracked_pref_path_count_; ++i) { | |
371 pref_registrar_.Add( | |
372 tracked_pref_paths_[i], | |
373 base::Bind(&PrefMetricsService::UpdateTrackedPreference, | |
374 weak_factory_.GetWeakPtr(), | |
375 tracked_pref_paths_[i])); | |
376 } | |
377 } | |
378 | |
172 // static | 379 // static |
173 PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() { | 380 PrefMetricsService::Factory* PrefMetricsService::Factory::GetInstance() { |
174 return Singleton<PrefMetricsService::Factory>::get(); | 381 return Singleton<PrefMetricsService::Factory>::get(); |
175 } | 382 } |
176 | 383 |
177 // static | 384 // static |
178 PrefMetricsService* PrefMetricsService::Factory::GetForProfile( | 385 PrefMetricsService* PrefMetricsService::Factory::GetForProfile( |
179 Profile* profile) { | 386 Profile* profile) { |
180 return static_cast<PrefMetricsService*>( | 387 return static_cast<PrefMetricsService*>( |
181 GetInstance()->GetServiceForBrowserContext(profile, true)); | 388 GetInstance()->GetServiceForBrowserContext(profile, true)); |
(...skipping 19 matching lines...) Expand all Loading... | |
201 } | 408 } |
202 | 409 |
203 bool PrefMetricsService::Factory::ServiceIsNULLWhileTesting() const { | 410 bool PrefMetricsService::Factory::ServiceIsNULLWhileTesting() const { |
204 return false; | 411 return false; |
205 } | 412 } |
206 | 413 |
207 content::BrowserContext* PrefMetricsService::Factory::GetBrowserContextToUse( | 414 content::BrowserContext* PrefMetricsService::Factory::GetBrowserContextToUse( |
208 content::BrowserContext* context) const { | 415 content::BrowserContext* context) const { |
209 return chrome::GetBrowserContextRedirectedInIncognito(context); | 416 return chrome::GetBrowserContextRedirectedInIncognito(context); |
210 } | 417 } |
OLD | NEW |