| Index: chrome/browser/profile_resetter/automatic_profile_resetter.cc | 
| diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter.cc b/chrome/browser/profile_resetter/automatic_profile_resetter.cc | 
| new file mode 100644 | 
| index 0000000000000000000000000000000000000000..51ea44a6a4ddccedab0bdc58f4467cd8f341d1a7 | 
| --- /dev/null | 
| +++ b/chrome/browser/profile_resetter/automatic_profile_resetter.cc | 
| @@ -0,0 +1,763 @@ | 
| +// 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/metrics/sparse_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_factory.h" | 
| +#include "components/search_engines/template_url_service.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; | 
| +  variations::GetVariationParams(kAutomaticProfileResetStudyName, ¶ms); | 
| +  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<base::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), | 
| +      should_show_reset_banner_(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; | 
| +  should_show_reset_banner_ = false; | 
| + | 
| +  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); | 
| + | 
| +  should_show_reset_banner_ = false; | 
| + | 
| +  ReportPromptResult(PROMPT_ACTION_NO_RESET); | 
| +  delegate_->DismissPrompt(); | 
| +  FinishResetPromptFlow(); | 
| +} | 
| + | 
| +bool AutomaticProfileResetter::IsResetPromptFlowActive() const { | 
| +  return state_ == STATE_HAS_TRIGGERED_PROMPT || | 
| +      state_ == STATE_HAS_SHOWN_BUBBLE; | 
| +} | 
| + | 
| +bool AutomaticProfileResetter::ShouldShowResetBanner() const { | 
| +  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 
| +  return should_show_reset_banner_ && ShouldPerformLiveRun(); | 
| +} | 
| + | 
| +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::NotifyDidCloseWebUIResetBanner() { | 
| +  DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); | 
| +  should_show_reset_banner_ = false; | 
| +} | 
| + | 
| +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); | 
| +  UMA_HISTOGRAM_SPARSE_SLOWLY("AutomaticProfileReset.ProgramChecksum", | 
| +                              interpreter.CalculateProgramChecksum()); | 
| + | 
| +  // 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) | 
| +    should_show_reset_banner_ = true; | 
| + | 
| +  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; | 
| +} | 
|  |