blob: 648538687c6f5e045ff071737e4eb7abb5b987b5 [file] [log] [blame]
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "chrome/browser/profile_resetter/automatic_profile_resetter.h"
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
#include "base/metrics/field_trial.h"
#include "base/metrics/histogram.h"
#include "base/prefs/pref_service.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "base/time/time.h"
#include "base/timer/elapsed_timer.h"
#include "base/values.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h"
#include "chrome/browser/profile_resetter/jtl_interpreter.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/search_engines/template_url_service.h"
#include "chrome/browser/search_engines/template_url_service_factory.h"
#include "components/variations/variations_associated_data.h"
#include "content/public/browser/browser_thread.h"
#include "grit/browser_resources.h"
#include "ui/base/resource/resource_bundle.h"
// Helpers -------------------------------------------------------------------
namespace {
// Name constants for the field trial behind which we enable this feature.
const char kAutomaticProfileResetStudyName[] = "AutomaticProfileReset";
const char kAutomaticProfileResetStudyDryRunGroupName[] = "DryRun";
const char kAutomaticProfileResetStudyEnabledGroupName[] = "Enabled";
#if defined(GOOGLE_CHROME_BUILD)
const char kAutomaticProfileResetStudyProgramParameterName[] = "program";
const char kAutomaticProfileResetStudyHashSeedParameterName[] = "hash_seed";
#endif
// How long to wait after start-up before unleashing the evaluation flow.
const int64 kEvaluationFlowDelayInSeconds = 55;
// Keys used in the input dictionary of the program.
const char kDefaultSearchProviderKey[] = "default_search_provider";
const char kDefaultSearchProviderIsUserControlledKey[] =
"default_search_provider_iuc";
const char kLoadedModuleDigestsKey[] = "loaded_modules";
const char kLocalStateKey[] = "local_state";
const char kLocalStateIsUserControlledKey[] = "local_state_iuc";
const char kSearchProvidersKey[] = "search_providers";
const char kUserPreferencesKey[] = "preferences";
const char kUserPreferencesIsUserControlledKey[] = "preferences_iuc";
// Keys used in the output dictionary of the program.
const char kCombinedStatusMaskKeyPrefix[] = "combined_status_mask_bit";
const char kHadPromptedAlreadyKey[] = "had_prompted_already";
const char kShouldPromptKey[] = "should_prompt";
const char kSatisfiedCriteriaMaskKeyPrefix[] = "satisfied_criteria_mask_bit";
// Keys used in both the input and output dictionary of the program.
const char kMementoValueInFileKey[] = "memento_value_in_file";
const char kMementoValueInLocalStateKey[] = "memento_value_in_local_state";
const char kMementoValueInPrefsKey[] = "memento_value_in_prefs";
// Number of bits, and maximum value (exclusive) for the mask whose bits
// indicate which of reset criteria were satisfied.
const size_t kSatisfiedCriteriaMaskNumberOfBits = 5u;
const uint32 kSatisfiedCriteriaMaskMaximumValue =
(1u << kSatisfiedCriteriaMaskNumberOfBits);
// Number of bits, and maximum value (exclusive) for the mask whose bits
// indicate if any of reset criteria were satisfied, and which of the mementos
// were already present.
const size_t kCombinedStatusMaskNumberOfBits = 4u;
const uint32 kCombinedStatusMaskMaximumValue =
(1u << kCombinedStatusMaskNumberOfBits);
// Returns whether or not a dry-run shall be performed.
bool ShouldPerformDryRun() {
return StartsWithASCII(
base::FieldTrialList::FindFullName(kAutomaticProfileResetStudyName),
kAutomaticProfileResetStudyDryRunGroupName, true);
}
// Returns whether or not a live-run shall be performed.
bool ShouldPerformLiveRun() {
return StartsWithASCII(
base::FieldTrialList::FindFullName(kAutomaticProfileResetStudyName),
kAutomaticProfileResetStudyEnabledGroupName, true);
}
// If the currently active experiment group prescribes a |program| and
// |hash_seed| to use instead of the baked-in ones, retrieves those and returns
// true. Otherwise, returns false.
bool GetProgramAndHashSeedOverridesFromExperiment(std::string* program,
std::string* hash_seed) {
DCHECK(program);
DCHECK(hash_seed);
#if defined(GOOGLE_CHROME_BUILD)
std::map<std::string, std::string> params;
chrome_variations::GetVariationParams(kAutomaticProfileResetStudyName,
&params);
if (params.count(kAutomaticProfileResetStudyProgramParameterName) &&
params.count(kAutomaticProfileResetStudyHashSeedParameterName)) {
program->swap(params[kAutomaticProfileResetStudyProgramParameterName]);
hash_seed->swap(params[kAutomaticProfileResetStudyHashSeedParameterName]);
return true;
}
#endif
return false;
}
// Takes |pref_name_to_value_map|, which shall be a deep-copy of all preferences
// in |source| without path expansion; and (1.) creates a sub-tree from it named
// |value_tree_key| in |target_dictionary| with path expansion, and (2.) also
// creates an isomorphic sub-tree under the key |is_user_controlled_tree_key|
// that contains only Boolean values indicating whether or not the corresponding
// preference is coming from the 'user' PrefStore.
void BuildSubTreesFromPreferences(
scoped_ptr<base::DictionaryValue> pref_name_to_value_map,
const PrefService* source,
const char* value_tree_key,
const char* is_user_controlled_tree_key,
base::DictionaryValue* target_dictionary) {
std::vector<std::string> pref_names;
pref_names.reserve(pref_name_to_value_map->size());
for (base::DictionaryValue::Iterator it(*pref_name_to_value_map);
!it.IsAtEnd(); it.Advance())
pref_names.push_back(it.key());
base::DictionaryValue* value_tree = new base::DictionaryValue;
base::DictionaryValue* is_user_controlled_tree = new base::DictionaryValue;
for (std::vector<std::string>::const_iterator it = pref_names.begin();
it != pref_names.end(); ++it) {
scoped_ptr<Value> pref_value_owned;
if (pref_name_to_value_map->RemoveWithoutPathExpansion(*it,
&pref_value_owned)) {
value_tree->Set(*it, pref_value_owned.release());
const PrefService::Preference* pref = source->FindPreference(it->c_str());
is_user_controlled_tree->Set(
*it, new base::FundamentalValue(pref->IsUserControlled()));
}
}
target_dictionary->Set(value_tree_key, value_tree);
target_dictionary->Set(is_user_controlled_tree_key, is_user_controlled_tree);
}
} // namespace
// AutomaticProfileResetter::InputBuilder ------------------------------------
// Collects all the information that is required by the evaluator program to
// assess whether or not the conditions for showing the reset prompt are met.
//
// This necessitates a lot of work that has to be performed on the UI thread,
// such as: accessing the Preferences, Local State, and TemplateURLService.
// In order to keep the browser responsive, the UI thread shall not be blocked
// for long consecutive periods of time. Unfortunately, we cannot reduce the
// total amount of work. Instead, what this class does is to split the work into
// shorter tasks that are posted one-at-a-time to the UI thread in a serial
// fashion, so as to give a chance to run other tasks that have accumulated in
// the meantime.
class AutomaticProfileResetter::InputBuilder
: public base::SupportsWeakPtr<InputBuilder> {
public:
typedef base::Callback<void(scoped_ptr<base::DictionaryValue>)>
ProgramInputCallback;
// The dependencies must have been initialized through |delegate|, i.e. the
// RequestCallback[...] methods must have already fired before calling this.
InputBuilder(Profile* profile, AutomaticProfileResetterDelegate* delegate)
: profile_(profile),
delegate_(delegate),
memento_in_prefs_(profile_),
memento_in_local_state_(profile_),
memento_in_file_(profile_) {}
~InputBuilder() {}
// Assembles the data required by the evaluator program into a dictionary
// format, and posts it back to the UI thread with |callback| once ready. In
// order not to block the UI thread for long consecutive periods of time, the
// work is divided into smaller tasks, see class comment above for details.
// It is safe to destroy |this| immediately from within the |callback|.
void BuildEvaluatorProgramInput(const ProgramInputCallback& callback) {
DCHECK(!data_);
DCHECK(!callback.is_null());
data_.reset(new base::DictionaryValue);
callback_ = callback;
AddAsyncTask(base::Bind(&InputBuilder::IncludeMementoValues, AsWeakPtr()));
AddTask(base::Bind(&InputBuilder::IncludeUserPreferences, AsWeakPtr()));
AddTask(base::Bind(&InputBuilder::IncludeLocalState, AsWeakPtr()));
AddTask(base::Bind(&InputBuilder::IncludeSearchEngines, AsWeakPtr()));
AddTask(base::Bind(&InputBuilder::IncludeLoadedModules, AsWeakPtr()));
// Each task will post the next one. Just trigger the chain reaction.
PostNextTask();
}
private:
// Asynchronous task that includes memento values (or empty strings in case
// mementos are not there).
void IncludeMementoValues() {
data_->SetString(kMementoValueInPrefsKey, memento_in_prefs_.ReadValue());
data_->SetString(kMementoValueInLocalStateKey,
memento_in_local_state_.ReadValue());
memento_in_file_.ReadValue(base::Bind(
&InputBuilder::IncludeFileBasedMementoCallback, AsWeakPtr()));
}
// Called back by |memento_in_file_| once the |memento_value| has been read.
void IncludeFileBasedMementoCallback(const std::string& memento_value) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
data_->SetString(kMementoValueInFileKey, memento_value);
// As an asynchronous task, we need to take care of posting the next task.
PostNextTask();
}
// Task that includes all user (i.e. profile-specific) preferences, along with
// information about whether the value is coming from the 'user' PrefStore.
// This is the most expensive operation, so it is itself split into two parts.
void IncludeUserPreferences() {
PrefService* prefs = profile_->GetPrefs();
DCHECK(prefs);
scoped_ptr<base::DictionaryValue> pref_name_to_value_map(
prefs->GetPreferenceValuesWithoutPathExpansion());
AddTask(base::Bind(&InputBuilder::IncludeUserPreferencesPartTwo,
AsWeakPtr(),
base::Passed(&pref_name_to_value_map)));
}
// Second part to above.
void IncludeUserPreferencesPartTwo(
scoped_ptr<base::DictionaryValue> pref_name_to_value_map) {
PrefService* prefs = profile_->GetPrefs();
DCHECK(prefs);
BuildSubTreesFromPreferences(
pref_name_to_value_map.Pass(),
prefs,
kUserPreferencesKey,
kUserPreferencesIsUserControlledKey,
data_.get());
}
// Task that includes all local state (i.e. shared) preferences, along with
// information about whether the value is coming from the 'user' PrefStore.
void IncludeLocalState() {
PrefService* local_state = g_browser_process->local_state();
DCHECK(local_state);
scoped_ptr<base::DictionaryValue> pref_name_to_value_map(
local_state->GetPreferenceValuesWithoutPathExpansion());
BuildSubTreesFromPreferences(
pref_name_to_value_map.Pass(),
local_state,
kLocalStateKey,
kLocalStateIsUserControlledKey,
data_.get());
}
// Task that includes all information related to search engines.
void IncludeSearchEngines() {
scoped_ptr<base::DictionaryValue> default_search_provider_details(
delegate_->GetDefaultSearchProviderDetails());
data_->Set(kDefaultSearchProviderKey,
default_search_provider_details.release());
scoped_ptr<base::ListValue> search_providers_details(
delegate_->GetPrepopulatedSearchProvidersDetails());
data_->Set(kSearchProvidersKey, search_providers_details.release());
data_->SetBoolean(kDefaultSearchProviderIsUserControlledKey,
!delegate_->IsDefaultSearchProviderManaged());
}
// Task that includes information about loaded modules.
void IncludeLoadedModules() {
scoped_ptr<base::ListValue> loaded_module_digests(
delegate_->GetLoadedModuleNameDigests());
data_->Set(kLoadedModuleDigestsKey, loaded_module_digests.release());
}
// -------------------------------------------------------------------------
// Adds a |task| that can do as much asynchronous processing as it wants, but
// will need to finally call PostNextTask() on the UI thread when done.
void AddAsyncTask(const base::Closure& task) {
task_queue_.push(task);
}
// Convenience wrapper for synchronous tasks.
void SynchronousTaskWrapper(const base::Closure& task) {
base::ElapsedTimer timer;
task.Run();
UMA_HISTOGRAM_CUSTOM_TIMES(
"AutomaticProfileReset.InputBuilder.TaskDuration",
timer.Elapsed(),
base::TimeDelta::FromMilliseconds(1),
base::TimeDelta::FromSeconds(2),
50);
PostNextTask();
}
// Adds a task that needs to finish synchronously. In exchange, PostNextTask()
// is called automatically when the |task| returns, and execution time is
// measured.
void AddTask(const base::Closure& task) {
task_queue_.push(
base::Bind(&InputBuilder::SynchronousTaskWrapper, AsWeakPtr(), task));
}
// Posts the next task from the |task_queue_|, unless it is exhausted, in
// which case it posts |callback_| to return with the results.
void PostNextTask() {
base::Closure next_task;
if (task_queue_.empty()) {
next_task = base::Bind(callback_, base::Passed(&data_));
} else {
next_task = task_queue_.front();
task_queue_.pop();
}
content::BrowserThread::PostTask(
content::BrowserThread::UI, FROM_HERE, next_task);
}
Profile* profile_;
AutomaticProfileResetterDelegate* delegate_;
ProgramInputCallback callback_;
PreferenceHostedPromptMemento memento_in_prefs_;
LocalStateHostedPromptMemento memento_in_local_state_;
FileHostedPromptMemento memento_in_file_;
scoped_ptr<base::DictionaryValue> data_;
std::queue<base::Closure> task_queue_;
DISALLOW_COPY_AND_ASSIGN(InputBuilder);
};
// AutomaticProfileResetter::EvaluationResults -------------------------------
// Encapsulates the output values extracted from the evaluator program.
struct AutomaticProfileResetter::EvaluationResults {
EvaluationResults()
: should_prompt(false),
had_prompted_already(false),
satisfied_criteria_mask(0),
combined_status_mask(0) {}
std::string memento_value_in_prefs;
std::string memento_value_in_local_state;
std::string memento_value_in_file;
bool should_prompt;
bool had_prompted_already;
uint32 satisfied_criteria_mask;
uint32 combined_status_mask;
};
// AutomaticProfileResetter --------------------------------------------------
AutomaticProfileResetter::AutomaticProfileResetter(Profile* profile)
: profile_(profile),
state_(STATE_UNINITIALIZED),
enumeration_of_loaded_modules_ready_(false),
template_url_service_ready_(false),
has_already_dismissed_prompt_(false),
weak_ptr_factory_(this) {
DCHECK(profile_);
}
AutomaticProfileResetter::~AutomaticProfileResetter() {}
void AutomaticProfileResetter::Initialize() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_UNINITIALIZED);
if (!ShouldPerformDryRun() && !ShouldPerformLiveRun()) {
state_ = STATE_DISABLED;
return;
}
if (!GetProgramAndHashSeedOverridesFromExperiment(&program_, &hash_seed_)) {
ui::ResourceBundle& resources(ui::ResourceBundle::GetSharedInstance());
if (ShouldPerformLiveRun()) {
program_ = resources.GetRawDataResource(
IDR_AUTOMATIC_PROFILE_RESET_RULES).as_string();
hash_seed_ = resources.GetRawDataResource(
IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED).as_string();
} else { // ShouldPerformDryRun()
program_ = resources.GetRawDataResource(
IDR_AUTOMATIC_PROFILE_RESET_RULES_DRY).as_string();
hash_seed_ = resources.GetRawDataResource(
IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED_DRY).as_string();
}
}
delegate_.reset(new AutomaticProfileResetterDelegateImpl(
profile_, ProfileResetter::ALL));
task_runner_for_waiting_ =
content::BrowserThread::GetMessageLoopProxyForThread(
content::BrowserThread::UI);
state_ = STATE_INITIALIZED;
}
void AutomaticProfileResetter::Activate() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK(state_ == STATE_INITIALIZED || state_ == STATE_DISABLED);
if (state_ == STATE_INITIALIZED) {
if (!program_.empty()) {
// Some steps in the flow (e.g. loaded modules, file-based memento) are
// IO-intensive, so defer execution until some time later.
task_runner_for_waiting_->PostDelayedTask(
FROM_HERE,
base::Bind(&AutomaticProfileResetter::PrepareEvaluationFlow,
weak_ptr_factory_.GetWeakPtr()),
base::TimeDelta::FromSeconds(kEvaluationFlowDelayInSeconds));
} else {
// Terminate early if there is no program included (nor set by tests).
state_ = STATE_DISABLED;
}
}
}
void AutomaticProfileResetter::TriggerProfileReset(bool send_feedback) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_HAS_SHOWN_BUBBLE);
state_ = STATE_PERFORMING_RESET;
ReportPromptResult(PROMPT_ACTION_RESET);
delegate_->TriggerProfileSettingsReset(
send_feedback,
base::Bind(&AutomaticProfileResetter::OnProfileSettingsResetCompleted,
weak_ptr_factory_.GetWeakPtr()));
}
void AutomaticProfileResetter::SkipProfileReset() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_HAS_SHOWN_BUBBLE);
ReportPromptResult(PROMPT_ACTION_NO_RESET);
delegate_->DismissPrompt();
FinishResetPromptFlow();
}
bool AutomaticProfileResetter::IsResetPromptFlowActive() const {
return state_ == STATE_HAS_TRIGGERED_PROMPT ||
state_ == STATE_HAS_SHOWN_BUBBLE;
}
void AutomaticProfileResetter::NotifyDidShowResetBubble() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_HAS_TRIGGERED_PROMPT);
state_ = STATE_HAS_SHOWN_BUBBLE;
PersistMementos();
ReportPromptResult(PROMPT_SHOWN_BUBBLE);
}
void AutomaticProfileResetter::NotifyDidOpenWebUIResetDialog() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// This notification is invoked unconditionally by the WebUI, only care about
// it when the prompt flow is currently active (and not yet resetting).
if (state_ == STATE_HAS_TRIGGERED_PROMPT ||
state_ == STATE_HAS_SHOWN_BUBBLE) {
has_already_dismissed_prompt_ = true;
delegate_->DismissPrompt();
}
}
void AutomaticProfileResetter::NotifyDidCloseWebUIResetDialog(
bool performed_reset) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// This notification is invoked unconditionally by the WebUI, only care about
// it when the prompt flow is currently active (and not yet resetting).
if (state_ == STATE_HAS_TRIGGERED_PROMPT ||
state_ == STATE_HAS_SHOWN_BUBBLE) {
if (!has_already_dismissed_prompt_)
delegate_->DismissPrompt();
if (state_ == STATE_HAS_TRIGGERED_PROMPT) {
PersistMementos();
ReportPromptResult(performed_reset ?
PROMPT_NOT_SHOWN_BUBBLE_BUT_HAD_WEBUI_RESET :
PROMPT_NOT_SHOWN_BUBBLE_BUT_HAD_WEBUI_NO_RESET);
} else { // if (state_ == STATE_HAS_SHOWN_PROMPT)
ReportPromptResult(performed_reset ?
PROMPT_FOLLOWED_BY_WEBUI_RESET :
PROMPT_FOLLOWED_BY_WEBUI_NO_RESET);
}
FinishResetPromptFlow();
}
}
void AutomaticProfileResetter::SetProgramForTesting(
const std::string& program) {
program_ = program;
}
void AutomaticProfileResetter::SetHashSeedForTesting(
const std::string& hash_key) {
hash_seed_ = hash_key;
}
void AutomaticProfileResetter::SetDelegateForTesting(
scoped_ptr<AutomaticProfileResetterDelegate> delegate) {
delegate_ = delegate.Pass();
}
void AutomaticProfileResetter::SetTaskRunnerForWaitingForTesting(
const scoped_refptr<base::TaskRunner>& task_runner) {
task_runner_for_waiting_ = task_runner;
}
void AutomaticProfileResetter::Shutdown() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// We better not do anything substantial at this point. The metrics service
// has already been shut down; and local state has already been commited to
// file (in the regular fashion) for the last time.
state_ = STATE_DISABLED;
weak_ptr_factory_.InvalidateWeakPtrs();
delegate_.reset();
}
void AutomaticProfileResetter::PrepareEvaluationFlow() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_INITIALIZED);
state_ = STATE_WAITING_ON_DEPENDENCIES;
delegate_->RequestCallbackWhenTemplateURLServiceIsLoaded(
base::Bind(&AutomaticProfileResetter::OnTemplateURLServiceIsLoaded,
weak_ptr_factory_.GetWeakPtr()));
delegate_->RequestCallbackWhenLoadedModulesAreEnumerated(
base::Bind(&AutomaticProfileResetter::OnLoadedModulesAreEnumerated,
weak_ptr_factory_.GetWeakPtr()));
delegate_->LoadTemplateURLServiceIfNeeded();
delegate_->EnumerateLoadedModulesIfNeeded();
}
void AutomaticProfileResetter::OnTemplateURLServiceIsLoaded() {
template_url_service_ready_ = true;
OnDependencyIsReady();
}
void AutomaticProfileResetter::OnLoadedModulesAreEnumerated() {
enumeration_of_loaded_modules_ready_ = true;
OnDependencyIsReady();
}
void AutomaticProfileResetter::OnDependencyIsReady() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_WAITING_ON_DEPENDENCIES);
if (template_url_service_ready_ && enumeration_of_loaded_modules_ready_) {
state_ = STATE_READY;
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&AutomaticProfileResetter::BeginEvaluationFlow,
weak_ptr_factory_.GetWeakPtr()));
}
}
void AutomaticProfileResetter::BeginEvaluationFlow() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_READY);
DCHECK(!program_.empty());
DCHECK(!input_builder_);
state_ = STATE_EVALUATING_CONDITIONS;
input_builder_.reset(new InputBuilder(profile_, delegate_.get()));
input_builder_->BuildEvaluatorProgramInput(
base::Bind(&AutomaticProfileResetter::ContinueWithEvaluationFlow,
weak_ptr_factory_.GetWeakPtr()));
}
void AutomaticProfileResetter::ContinueWithEvaluationFlow(
scoped_ptr<base::DictionaryValue> program_input) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_EVALUATING_CONDITIONS);
input_builder_.reset();
base::SequencedWorkerPool* blocking_pool =
content::BrowserThread::GetBlockingPool();
scoped_refptr<base::TaskRunner> task_runner =
blocking_pool->GetTaskRunnerWithShutdownBehavior(
base::SequencedWorkerPool::SKIP_ON_SHUTDOWN);
base::PostTaskAndReplyWithResult(
task_runner.get(),
FROM_HERE,
base::Bind(&EvaluateConditionsOnWorkerPoolThread,
hash_seed_,
program_,
base::Passed(&program_input)),
base::Bind(&AutomaticProfileResetter::FinishEvaluationFlow,
weak_ptr_factory_.GetWeakPtr()));
}
// static
scoped_ptr<AutomaticProfileResetter::EvaluationResults>
AutomaticProfileResetter::EvaluateConditionsOnWorkerPoolThread(
const std::string& hash_seed,
const std::string& program,
scoped_ptr<base::DictionaryValue> program_input) {
JtlInterpreter interpreter(hash_seed, program, program_input.get());
interpreter.Execute();
UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.InterpreterResult",
interpreter.result(),
JtlInterpreter::RESULT_MAX);
// In each case below, the respective field in result originally contains the
// default, so if the getter fails, we still have the correct value there.
scoped_ptr<EvaluationResults> results(new EvaluationResults);
interpreter.GetOutputBoolean(kShouldPromptKey, &results->should_prompt);
interpreter.GetOutputBoolean(kHadPromptedAlreadyKey,
&results->had_prompted_already);
interpreter.GetOutputString(kMementoValueInPrefsKey,
&results->memento_value_in_prefs);
interpreter.GetOutputString(kMementoValueInLocalStateKey,
&results->memento_value_in_local_state);
interpreter.GetOutputString(kMementoValueInFileKey,
&results->memento_value_in_file);
for (size_t i = 0; i < kCombinedStatusMaskNumberOfBits; ++i) {
bool flag = false;
std::string mask_i_th_bit_key =
kCombinedStatusMaskKeyPrefix + base::IntToString(i + 1);
if (interpreter.GetOutputBoolean(mask_i_th_bit_key, &flag) && flag)
results->combined_status_mask |= (1 << i);
}
for (size_t i = 0; i < kSatisfiedCriteriaMaskNumberOfBits; ++i) {
bool flag = false;
std::string mask_i_th_bit_key =
kSatisfiedCriteriaMaskKeyPrefix + base::IntToString(i + 1);
if (interpreter.GetOutputBoolean(mask_i_th_bit_key, &flag) && flag)
results->satisfied_criteria_mask |= (1 << i);
}
return results.Pass();
}
void AutomaticProfileResetter::ReportStatistics(uint32 satisfied_criteria_mask,
uint32 combined_status_mask) {
UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.SatisfiedCriteriaMask",
satisfied_criteria_mask,
kSatisfiedCriteriaMaskMaximumValue);
UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.CombinedStatusMask",
combined_status_mask,
kCombinedStatusMaskMaximumValue);
}
void AutomaticProfileResetter::FinishEvaluationFlow(
scoped_ptr<EvaluationResults> results) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_EVALUATING_CONDITIONS);
ReportStatistics(results->satisfied_criteria_mask,
results->combined_status_mask);
if (results->should_prompt && !results->had_prompted_already) {
evaluation_results_ = results.Pass();
BeginResetPromptFlow();
} else {
state_ = STATE_DONE;
}
}
void AutomaticProfileResetter::BeginResetPromptFlow() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_EVALUATING_CONDITIONS);
state_ = STATE_HAS_TRIGGERED_PROMPT;
if (ShouldPerformLiveRun() && delegate_->TriggerPrompt()) {
// Start fetching the brandcoded default settings speculatively in the
// background, so as to reduce waiting time if the user chooses to go
// through with the reset.
delegate_->FetchBrandcodedDefaultSettingsIfNeeded();
} else {
PersistMementos();
ReportPromptResult(PROMPT_NOT_TRIGGERED);
FinishResetPromptFlow();
}
}
void AutomaticProfileResetter::OnProfileSettingsResetCompleted() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_PERFORMING_RESET);
delegate_->DismissPrompt();
FinishResetPromptFlow();
}
void AutomaticProfileResetter::ReportPromptResult(PromptResult result) {
UMA_HISTOGRAM_ENUMERATION(
"AutomaticProfileReset.PromptResult", result, PROMPT_RESULT_MAX);
}
void AutomaticProfileResetter::PersistMementos() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK(state_ == STATE_HAS_TRIGGERED_PROMPT ||
state_ == STATE_HAS_SHOWN_BUBBLE);
DCHECK(evaluation_results_);
PreferenceHostedPromptMemento memento_in_prefs(profile_);
LocalStateHostedPromptMemento memento_in_local_state(profile_);
FileHostedPromptMemento memento_in_file(profile_);
memento_in_prefs.StoreValue(evaluation_results_->memento_value_in_prefs);
memento_in_local_state.StoreValue(
evaluation_results_->memento_value_in_local_state);
memento_in_file.StoreValue(evaluation_results_->memento_value_in_file);
evaluation_results_.reset();
}
void AutomaticProfileResetter::FinishResetPromptFlow() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK(state_ == STATE_HAS_TRIGGERED_PROMPT ||
state_ == STATE_HAS_SHOWN_BUBBLE ||
state_ == STATE_PERFORMING_RESET);
DCHECK(!evaluation_results_);
state_ = STATE_DONE;
}