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.
5 #include "chrome/browser/profile_resetter/automatic_profile_resetter.h"
8 #include "base/bind_helpers.h"
9 #include "base/logging.h"
10 #include "base/memory/ref_counted.h"
11 #include "base/metrics/field_trial.h"
12 #include "base/metrics/histogram.h"
13 #include "base/prefs/pref_service.h"
14 #include "base/strings/string_number_conversions.h"
15 #include "base/task_runner.h"
16 #include "base/task_runner_util.h"
17 #include "base/threading/sequenced_worker_pool.h"
18 #include "base/time/time.h"
19 #include "base/values.h"
20 #include "chrome/browser/browser_process.h"
21 #include "chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h"
22 #include "chrome/browser/profile_resetter/jtl_interpreter.h"
23 #include "chrome/browser/profiles/profile.h"
24 #include "chrome/browser/search_engines/template_url_service.h"
25 #include "chrome/browser/search_engines/template_url_service_factory.h"
26 #include "content/public/browser/browser_thread.h"
27 #include "grit/browser_resources.h"
28 #include "ui/base/resource/resource_bundle.h"
31 // AutomaticProfileResetter::EvaluationResults -------------------------------
33 // Encapsulates the output values extracted from the evaluator program.
34 struct AutomaticProfileResetter::EvaluationResults {
36 : had_prompted_already(false),
37 satisfied_criteria_mask(0),
38 combined_status_mask(0) {}
40 std::string memento_value_in_prefs;
41 std::string memento_value_in_local_state;
42 std::string memento_value_in_file;
44 bool had_prompted_already;
45 uint32 satisfied_criteria_mask;
46 uint32 combined_status_mask;
50 // Helpers -------------------------------------------------------------------
54 // Name constants for the field trial behind which we enable this feature.
55 const char kAutomaticProfileResetStudyName[] = "AutomaticProfileReset";
56 const char kAutomaticProfileResetStudyDryRunGroupName[] = "DryRun";
57 const char kAutomaticProfileResetStudyEnabledGroupName[] = "Enabled";
59 // How long to wait after start-up before unleashing the evaluation flow.
60 const int64 kEvaluationFlowDelayInSeconds = 55;
62 // Keys used in the input dictionary of the program.
63 const char kDefaultSearchProviderKey[] = "default_search_provider";
64 const char kDefaultSearchProviderIsUserControlledKey[] =
65 "default_search_provider_iuc";
66 const char kLoadedModuleDigestsKey[] = "loaded_modules";
67 const char kLocalStateKey[] = "local_state";
68 const char kLocalStateIsUserControlledKey[] = "local_state_iuc";
69 const char kSearchProvidersKey[] = "search_providers";
70 const char kUserPreferencesKey[] = "preferences";
71 const char kUserPreferencesIsUserControlledKey[] = "preferences_iuc";
73 // Keys used in the output dictionary of the program.
74 const char kCombinedStatusMaskKeys[][26] = {
75 "combined_status_mask_bit1", "combined_status_mask_bit2",
76 "combined_status_mask_bit3", "combined_status_mask_bit4"};
77 const char kHadPromptedAlreadyKey[] = "had_prompted_already";
78 const char kSatisfiedCriteriaMaskKeys[][29] = {"satisfied_criteria_mask_bit1",
79 "satisfied_criteria_mask_bit2"};
81 // Keys used in both the input and output dictionary of the program.
82 const char kMementoValueInFileKey[] = "memento_value_in_file";
83 const char kMementoValueInLocalStateKey[] = "memento_value_in_local_state";
84 const char kMementoValueInPrefsKey[] = "memento_value_in_prefs";
86 // Number of bits, and maximum value (exclusive) for the mask whose bits
87 // indicate which of reset criteria were satisfied.
88 const size_t kSatisfiedCriteriaMaskNumberOfBits = 2u;
89 const uint32 kSatisfiedCriteriaMaskMaximumValue =
90 (1u << kSatisfiedCriteriaMaskNumberOfBits);
92 // Number of bits, and maximum value (exclusive) for the mask whose bits
93 // indicate if any of reset criteria were satisfied, and which of the mementos
94 // were already present.
95 const size_t kCombinedStatusMaskNumberOfBits = 4u;
96 const uint32 kCombinedStatusMaskMaximumValue =
97 (1u << kCombinedStatusMaskNumberOfBits);
100 arraysize(kSatisfiedCriteriaMaskKeys) == kSatisfiedCriteriaMaskNumberOfBits,
101 satisfied_criteria_mask_bits_mismatch);
103 arraysize(kCombinedStatusMaskKeys) == kCombinedStatusMaskNumberOfBits,
104 combined_status_mask_bits_mismatch);
106 // Enumeration of the possible outcomes of showing the profile reset prompt.
108 // Prompt was not shown because only a dry-run was performed.
111 PROMPT_ACTION_NO_RESET,
113 // Prompt was still shown (not dismissed by the user) when Chrome was closed.
118 // Returns whether or not a dry-run shall be performed.
119 bool ShouldPerformDryRun() {
120 return base::FieldTrialList::FindFullName(kAutomaticProfileResetStudyName) ==
121 kAutomaticProfileResetStudyDryRunGroupName;
124 // Returns whether or not a live-run shall be performed.
125 bool ShouldPerformLiveRun() {
126 return base::FieldTrialList::FindFullName(kAutomaticProfileResetStudyName) ==
127 kAutomaticProfileResetStudyEnabledGroupName;
130 // Deep-copies all preferences in |source| to a sub-tree named |value_tree_key|
131 // in |target_dictionary|, with path expansion, and also creates an isomorphic
132 // sub-tree under the key |is_user_controlled_tree_key| that contains only
133 // Boolean values, indicating whether or not the corresponding preferences are
134 // coming from the 'user' PrefStore.
135 void BuildSubTreesFromPreferences(const PrefService* source,
136 const char* value_tree_key,
137 const char* is_user_controlled_tree_key,
138 base::DictionaryValue* target_dictionary) {
139 scoped_ptr<base::DictionaryValue> pref_name_to_value_map(
140 source->GetPreferenceValuesWithoutPathExpansion());
141 std::vector<std::string> pref_names;
142 pref_names.reserve(pref_name_to_value_map->size());
143 for (base::DictionaryValue::Iterator it(*pref_name_to_value_map);
144 !it.IsAtEnd(); it.Advance())
145 pref_names.push_back(it.key());
147 base::DictionaryValue* value_tree = new base::DictionaryValue;
148 base::DictionaryValue* is_user_controlled_tree = new base::DictionaryValue;
149 for (std::vector<std::string>::const_iterator it = pref_names.begin();
150 it != pref_names.end(); ++it) {
151 scoped_ptr<Value> pref_value_owned;
152 if (pref_name_to_value_map->RemoveWithoutPathExpansion(*it,
153 &pref_value_owned)) {
154 value_tree->Set(*it, pref_value_owned.release());
155 const PrefService::Preference* pref = source->FindPreference(it->c_str());
156 is_user_controlled_tree->Set(
157 *it, new base::FundamentalValue(pref->IsUserControlled()));
160 target_dictionary->Set(value_tree_key, value_tree);
161 target_dictionary->Set(is_user_controlled_tree_key, is_user_controlled_tree);
167 // AutomaticProfileResetter --------------------------------------------------
169 AutomaticProfileResetter::AutomaticProfileResetter(Profile* profile)
171 state_(STATE_UNINITIALIZED),
172 enumeration_of_loaded_modules_ready_(false),
173 template_url_service_ready_(false),
174 memento_in_prefs_(profile_),
175 memento_in_local_state_(profile_),
176 memento_in_file_(profile_),
177 weak_ptr_factory_(this) {
182 AutomaticProfileResetter::~AutomaticProfileResetter() {}
184 void AutomaticProfileResetter::Activate() {
185 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
186 DCHECK(state_ == STATE_INITIALIZED || state_ == STATE_DISABLED);
188 if (state_ == STATE_INITIALIZED) {
189 if (!program_.empty()) {
190 // Some steps in the flow (e.g. loaded modules, file-based memento) are
191 // IO-intensive, so defer execution until some time later.
192 task_runner_for_waiting_->PostDelayedTask(
194 base::Bind(&AutomaticProfileResetter::PrepareEvaluationFlow,
195 weak_ptr_factory_.GetWeakPtr()),
196 base::TimeDelta::FromSeconds(kEvaluationFlowDelayInSeconds));
198 // Terminate early if there is no program included (nor set by tests).
199 state_ = STATE_DISABLED;
204 void AutomaticProfileResetter::SetHashSeedForTesting(
205 const base::StringPiece& hash_key) {
206 hash_seed_ = hash_key;
209 void AutomaticProfileResetter::SetProgramForTesting(
210 const base::StringPiece& program) {
214 void AutomaticProfileResetter::SetDelegateForTesting(
215 scoped_ptr<AutomaticProfileResetterDelegate> delegate) {
216 delegate_ = delegate.Pass();
219 void AutomaticProfileResetter::SetTaskRunnerForWaitingForTesting(
220 const scoped_refptr<base::TaskRunner>& task_runner) {
221 task_runner_for_waiting_ = task_runner;
224 void AutomaticProfileResetter::Initialize() {
225 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
226 DCHECK_EQ(state_, STATE_UNINITIALIZED);
228 if (ShouldPerformDryRun() || ShouldPerformLiveRun()) {
229 ui::ResourceBundle& resources(ui::ResourceBundle::GetSharedInstance());
230 if (ShouldPerformLiveRun()) {
232 resources.GetRawDataResource(IDR_AUTOMATIC_PROFILE_RESET_RULES);
234 resources.GetRawDataResource(IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED);
235 } else { // ShouldPerformDryRun()
237 resources.GetRawDataResource(IDR_AUTOMATIC_PROFILE_RESET_RULES_DRY);
238 hash_seed_ = resources.GetRawDataResource(
239 IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED_DRY);
241 delegate_.reset(new AutomaticProfileResetterDelegateImpl(
242 TemplateURLServiceFactory::GetForProfile(profile_)));
243 task_runner_for_waiting_ =
244 content::BrowserThread::GetMessageLoopProxyForThread(
245 content::BrowserThread::UI);
246 state_ = STATE_INITIALIZED;
248 state_ = STATE_DISABLED;
252 void AutomaticProfileResetter::PrepareEvaluationFlow() {
253 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
254 DCHECK_EQ(state_, STATE_INITIALIZED);
256 state_ = STATE_WAITING_ON_DEPENDENCIES;
258 delegate_->RequestCallbackWhenTemplateURLServiceIsLoaded(
259 base::Bind(&AutomaticProfileResetter::OnTemplateURLServiceIsLoaded,
260 weak_ptr_factory_.GetWeakPtr()));
261 delegate_->RequestCallbackWhenLoadedModulesAreEnumerated(
262 base::Bind(&AutomaticProfileResetter::OnLoadedModulesAreEnumerated,
263 weak_ptr_factory_.GetWeakPtr()));
264 delegate_->LoadTemplateURLServiceIfNeeded();
265 delegate_->EnumerateLoadedModulesIfNeeded();
268 void AutomaticProfileResetter::OnTemplateURLServiceIsLoaded() {
269 template_url_service_ready_ = true;
270 OnDependencyIsReady();
273 void AutomaticProfileResetter::OnLoadedModulesAreEnumerated() {
274 enumeration_of_loaded_modules_ready_ = true;
275 OnDependencyIsReady();
278 void AutomaticProfileResetter::OnDependencyIsReady() {
279 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
280 DCHECK_EQ(state_, STATE_WAITING_ON_DEPENDENCIES);
282 if (template_url_service_ready_ && enumeration_of_loaded_modules_ready_) {
283 state_ = STATE_READY;
284 content::BrowserThread::PostTask(
285 content::BrowserThread::UI,
287 base::Bind(&AutomaticProfileResetter::BeginEvaluationFlow,
288 weak_ptr_factory_.GetWeakPtr()));
292 void AutomaticProfileResetter::BeginEvaluationFlow() {
293 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
294 DCHECK_EQ(state_, STATE_READY);
295 DCHECK(!program_.empty());
297 state_ = STATE_WORKING;
298 memento_in_file_.ReadValue(
299 base::Bind(&AutomaticProfileResetter::ContinueWithEvaluationFlow,
300 weak_ptr_factory_.GetWeakPtr()));
303 scoped_ptr<base::DictionaryValue>
304 AutomaticProfileResetter::BuildEvaluatorProgramInput(
305 const std::string& memento_value_in_file) {
306 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
308 scoped_ptr<base::DictionaryValue> input(new base::DictionaryValue);
310 // Include memento values (or empty strings in case mementos are not there).
311 input->SetString(kMementoValueInPrefsKey, memento_in_prefs_.ReadValue());
312 input->SetString(kMementoValueInLocalStateKey,
313 memento_in_local_state_.ReadValue());
314 input->SetString(kMementoValueInFileKey, memento_value_in_file);
316 // Include all user (i.e. profile-specific) preferences, along with
317 // information about whether the value is coming from the 'user' PrefStore.
318 PrefService* prefs = profile_->GetPrefs();
320 BuildSubTreesFromPreferences(prefs,
322 kUserPreferencesIsUserControlledKey,
325 // Include all local state (i.e. shared) preferences, along with information
326 // about whether the value is coming from the 'user' PrefStore.
327 PrefService* local_state = g_browser_process->local_state();
329 BuildSubTreesFromPreferences(
330 local_state, kLocalStateKey, kLocalStateIsUserControlledKey, input.get());
332 // Include all information related to search engines.
333 scoped_ptr<base::DictionaryValue> default_search_provider_details(
334 delegate_->GetDefaultSearchProviderDetails());
335 input->Set(kDefaultSearchProviderKey,
336 default_search_provider_details.release());
338 scoped_ptr<base::ListValue> search_providers_details(
339 delegate_->GetPrepopulatedSearchProvidersDetails());
340 input->Set(kSearchProvidersKey, search_providers_details.release());
342 input->SetBoolean(kDefaultSearchProviderIsUserControlledKey,
343 !delegate_->IsDefaultSearchProviderManaged());
345 // Include information about loaded modules.
346 scoped_ptr<base::ListValue> loaded_module_digests(
347 delegate_->GetLoadedModuleNameDigests());
348 input->Set(kLoadedModuleDigestsKey, loaded_module_digests.release());
353 void AutomaticProfileResetter::ContinueWithEvaluationFlow(
354 const std::string& memento_value_in_file) {
355 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
356 DCHECK_EQ(state_, STATE_WORKING);
357 PrefService* prefs = profile_->GetPrefs();
360 scoped_ptr<base::DictionaryValue> input(
361 BuildEvaluatorProgramInput(memento_value_in_file));
363 base::SequencedWorkerPool* blocking_pool =
364 content::BrowserThread::GetBlockingPool();
365 scoped_refptr<base::TaskRunner> task_runner =
366 blocking_pool->GetTaskRunnerWithShutdownBehavior(
367 base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
369 base::PostTaskAndReplyWithResult(
372 base::Bind(&EvaluateConditionsOnWorkerPoolThread,
375 base::Passed(&input)),
376 base::Bind(&AutomaticProfileResetter::FinishEvaluationFlow,
377 weak_ptr_factory_.GetWeakPtr()));
381 scoped_ptr<AutomaticProfileResetter::EvaluationResults>
382 AutomaticProfileResetter::EvaluateConditionsOnWorkerPoolThread(
383 const base::StringPiece& hash_seed,
384 const base::StringPiece& program,
385 scoped_ptr<base::DictionaryValue> program_input) {
386 JtlInterpreter interpreter(
387 hash_seed.as_string(), program.as_string(), program_input.get());
388 interpreter.Execute();
389 UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.InterpreterResult",
390 interpreter.result(),
391 JtlInterpreter::RESULT_MAX);
393 // In each case below, the respective field in result originally contains the
394 // default, so if the getter fails, we still have the correct value there.
395 scoped_ptr<EvaluationResults> results(new EvaluationResults);
396 interpreter.GetOutputBoolean(kHadPromptedAlreadyKey,
397 &results->had_prompted_already);
398 interpreter.GetOutputString(kMementoValueInPrefsKey,
399 &results->memento_value_in_prefs);
400 interpreter.GetOutputString(kMementoValueInLocalStateKey,
401 &results->memento_value_in_local_state);
402 interpreter.GetOutputString(kMementoValueInFileKey,
403 &results->memento_value_in_file);
404 for (size_t i = 0; i < arraysize(kCombinedStatusMaskKeys); ++i) {
406 if (interpreter.GetOutputBoolean(kCombinedStatusMaskKeys[i], &flag) && flag)
407 results->combined_status_mask |= (1 << i);
409 for (size_t i = 0; i < arraysize(kSatisfiedCriteriaMaskKeys); ++i) {
411 if (interpreter.GetOutputBoolean(kSatisfiedCriteriaMaskKeys[i], &flag) &&
413 results->satisfied_criteria_mask |= (1 << i);
415 return results.Pass();
418 void AutomaticProfileResetter::FinishEvaluationFlow(
419 scoped_ptr<EvaluationResults> results) {
420 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
421 DCHECK_EQ(state_, STATE_WORKING);
423 ReportStatistics(results->satisfied_criteria_mask,
424 results->combined_status_mask);
426 if (results->satisfied_criteria_mask != 0 && !results->had_prompted_already) {
427 memento_in_prefs_.StoreValue(results->memento_value_in_prefs);
428 memento_in_local_state_.StoreValue(results->memento_value_in_local_state);
429 memento_in_file_.StoreValue(results->memento_value_in_file);
431 if (ShouldPerformLiveRun()) {
432 delegate_->ShowPrompt();
434 UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.PromptResult",
443 void AutomaticProfileResetter::ReportStatistics(uint32 satisfied_criteria_mask,
444 uint32 combined_status_mask) {
445 UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.SatisfiedCriteriaMask",
446 satisfied_criteria_mask,
447 kSatisfiedCriteriaMaskMaximumValue);
448 UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.CombinedStatusMask",
449 combined_status_mask,
450 kCombinedStatusMaskMaximumValue);
453 void AutomaticProfileResetter::Shutdown() {
454 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
456 state_ = STATE_DISABLED;
458 weak_ptr_factory_.InvalidateWeakPtrs();