blob: c0f7d252608a04107ed2dd37500e80b534f7384b [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_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/task_runner.h"
#include "base/task_runner_util.h"
#include "base/threading/sequenced_worker_pool.h"
#include "chrome/browser/profile_resetter/jtl_interpreter.h"
#include "chrome/browser/profiles/profile.h"
#include "content/public/browser/browser_thread.h"
#include "grit/browser_resources.h"
#include "ui/base/resource/resource_bundle.h"
namespace {
// Number of bits, and maximum value (exclusive) for the mask whose bits
// indicate which of reset criteria were satisfied.
const size_t kSatisfiedCriteriaMaskBits = 2;
const uint32 kSatisfiedCriteriaMaskMaximumValue =
(1 << kSatisfiedCriteriaMaskBits);
// 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 kCombinedStatusMaskBits = 4;
const uint32 kCombinedStatusMaskMaximumValue = (1 << kCombinedStatusMaskBits);
// Name constants for the field trial behind which we enable this feature.
const char kAutomaticProfileResetStudyName[] = "AutomaticProfileReset";
const char kAutomaticProfileResetStudyDryRunGroupName[] = "DryRun";
const char kAutomaticProfileResetStudyEnabledGroupName[] = "Enabled";
// Keys used in the input dictionary of the program.
// TODO(engedy): Add these here on an as-needed basis.
// Keys used in the output dictionary of the program.
const char kHadPromptedAlreadyKey[] = "had_prompted_already";
const char kSatisfiedCriteriaMaskKeys[][29] = {"satisfied_criteria_mask_bit1",
"satisfied_criteria_mask_bit2"};
const char kCombinedStatusMaskKeys[][26] = {
"combined_status_mask_bit1", "combined_status_mask_bit2",
"combined_status_mask_bit3", "combined_status_mask_bit4"};
// Keys used in both the input and output dictionary of the program.
const char kMementoValueInPrefsKey[] = "memento_value_in_prefs";
const char kMementoValueInLocalStateKey[] = "memento_value_in_local_state";
const char kMementoValueInFileKey[] = "memento_value_in_file";
COMPILE_ASSERT(
arraysize(kSatisfiedCriteriaMaskKeys) == kSatisfiedCriteriaMaskBits,
satisfied_criteria_mask_bits_mismatch);
COMPILE_ASSERT(arraysize(kCombinedStatusMaskKeys) == kCombinedStatusMaskBits,
combined_status_mask_bits_mismatch);
// Implementation detail classes ---------------------------------------------
class AutomaticProfileResetterDelegateImpl
: public AutomaticProfileResetterDelegate {
public:
AutomaticProfileResetterDelegateImpl() {}
virtual ~AutomaticProfileResetterDelegateImpl() {}
// AutomaticProfileResetterDelegate overrides:
virtual void ShowPrompt() OVERRIDE {
// TODO(engedy): Call the UI from here once we have it.
}
virtual void ReportStatistics(uint32 satisfied_criteria_mask,
uint32 combined_status_mask) OVERRIDE {
UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.SatisfiedCriteriaMask",
satisfied_criteria_mask,
kSatisfiedCriteriaMaskMaximumValue);
UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.CombinedStatusMask",
combined_status_mask,
kCombinedStatusMaskMaximumValue);
}
private:
DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetterDelegateImpl);
};
// Enumeration of the possible outcomes of showing the profile reset prompt.
enum PromptResult {
// Prompt was not shown because only a dry-run was performed.
PROMPT_NOT_SHOWN,
PROMPT_ACTION_RESET,
PROMPT_ACTION_NO_RESET,
PROMPT_DISMISSED,
// Prompt was still shown (not dismissed by the user) when Chrome was closed.
PROMPT_IGNORED,
PROMPT_RESULT_MAX
};
} // namespace
// AutomaticProfileResetter::EvaluationResults -------------------------------
// Encapsulates the output values extracted from the evaluator program.
struct AutomaticProfileResetter::EvaluationResults {
EvaluationResults()
: 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 had_prompted_already;
uint32 satisfied_criteria_mask;
uint32 combined_status_mask;
};
// AutomaticProfileResetter --------------------------------------------------
AutomaticProfileResetter::AutomaticProfileResetter(Profile* profile)
: profile_(profile),
state_(STATE_UNINITIALIZED),
memento_in_prefs_(profile_),
memento_in_local_state_(profile_),
memento_in_file_(profile_),
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()) {
ui::ResourceBundle& resources(ui::ResourceBundle::GetSharedInstance());
if (ShouldPerformLiveRun()) {
program_ =
resources.GetRawDataResource(IDR_AUTOMATIC_PROFILE_RESET_RULES);
hash_seed_ =
resources.GetRawDataResource(IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED);
} else { // ShouldPerformDryRun()
program_ =
resources.GetRawDataResource(IDR_AUTOMATIC_PROFILE_RESET_RULES_DRY);
hash_seed_ = resources.GetRawDataResource(
IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED_DRY);
}
delegate_.reset(new AutomaticProfileResetterDelegateImpl());
state_ = STATE_READY;
content::BrowserThread::PostTask(
content::BrowserThread::UI,
FROM_HERE,
base::Bind(&AutomaticProfileResetter::BeginEvaluationFlow,
weak_ptr_factory_.GetWeakPtr()));
} else {
state_ = STATE_DISABLED;
}
}
bool AutomaticProfileResetter::ShouldPerformDryRun() const {
return base::FieldTrialList::FindFullName(kAutomaticProfileResetStudyName) ==
kAutomaticProfileResetStudyDryRunGroupName;
}
bool AutomaticProfileResetter::ShouldPerformLiveRun() const {
return base::FieldTrialList::FindFullName(kAutomaticProfileResetStudyName) ==
kAutomaticProfileResetStudyEnabledGroupName;
}
void AutomaticProfileResetter::BeginEvaluationFlow() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_READY);
if (!program_.empty()) {
state_ = STATE_WORKING;
memento_in_file_.ReadValue(
base::Bind(&AutomaticProfileResetter::ContinueWithEvaluationFlow,
weak_ptr_factory_.GetWeakPtr()));
} else {
// Terminate early if there is no program included (nor set by tests).
state_ = STATE_DISABLED;
}
}
scoped_ptr<base::DictionaryValue>
AutomaticProfileResetter::BuildEvaluatorProgramInput(
const std::string& memento_value_in_file) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
// TODO(engedy): Add any additional state here that is needed by the program.
scoped_ptr<base::DictionaryValue> input(new base::DictionaryValue);
input->SetString(kMementoValueInPrefsKey, memento_in_prefs_.ReadValue());
input->SetString(kMementoValueInLocalStateKey,
memento_in_local_state_.ReadValue());
input->SetString(kMementoValueInFileKey, memento_value_in_file);
return input.Pass();
}
void AutomaticProfileResetter::ContinueWithEvaluationFlow(
const std::string& memento_value_in_file) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_WORKING);
PrefService* prefs = profile_->GetPrefs();
DCHECK(prefs);
scoped_ptr<base::DictionaryValue> input(
BuildEvaluatorProgramInput(memento_value_in_file));
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(&input)),
base::Bind(&AutomaticProfileResetter::FinishEvaluationFlow,
weak_ptr_factory_.GetWeakPtr()));
}
// static
scoped_ptr<AutomaticProfileResetter::EvaluationResults>
AutomaticProfileResetter::EvaluateConditionsOnWorkerPoolThread(
const base::StringPiece& hash_seed,
const base::StringPiece& program,
scoped_ptr<base::DictionaryValue> program_input) {
std::string hash_seed_str(hash_seed.as_string());
std::string program_str(program.as_string());
JtlInterpreter interpreter(hash_seed_str, program_str, 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(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 < arraysize(kCombinedStatusMaskKeys); ++i) {
bool flag = false;
if (interpreter.GetOutputBoolean(kCombinedStatusMaskKeys[i], &flag) && flag)
results->combined_status_mask |= (1 << i);
}
for (size_t i = 0; i < arraysize(kSatisfiedCriteriaMaskKeys); ++i) {
bool flag = false;
if (interpreter.GetOutputBoolean(kSatisfiedCriteriaMaskKeys[i], &flag) &&
flag)
results->satisfied_criteria_mask |= (1 << i);
}
return results.Pass();
}
void AutomaticProfileResetter::FinishEvaluationFlow(
scoped_ptr<EvaluationResults> results) {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
DCHECK_EQ(state_, STATE_WORKING);
delegate_->ReportStatistics(results->satisfied_criteria_mask,
results->combined_status_mask);
if (results->satisfied_criteria_mask != 0 && !results->had_prompted_already) {
memento_in_prefs_.StoreValue(results->memento_value_in_prefs);
memento_in_local_state_.StoreValue(results->memento_value_in_local_state);
memento_in_file_.StoreValue(results->memento_value_in_file);
if (ShouldPerformLiveRun()) {
delegate_->ShowPrompt();
} else {
UMA_HISTOGRAM_ENUMERATION("AutomaticProfileReset.PromptResult",
PROMPT_NOT_SHOWN,
PROMPT_RESULT_MAX);
}
}
state_ = STATE_DONE;
}
void AutomaticProfileResetter::SetHashSeedForTesting(
const base::StringPiece& hash_key) {
hash_seed_ = hash_key;
}
void AutomaticProfileResetter::SetProgramForTesting(
const base::StringPiece& program) {
program_ = program;
}
void AutomaticProfileResetter::SetDelegateForTesting(
AutomaticProfileResetterDelegate* delegate) {
delegate_.reset(delegate);
}
void AutomaticProfileResetter::Shutdown() {
DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
state_ = STATE_DISABLED;
delegate_.reset();
weak_ptr_factory_.InvalidateWeakPtrs();
}