summaryrefslogtreecommitdiffstats
path: root/chrome/browser/profile_resetter
diff options
context:
space:
mode:
Diffstat (limited to 'chrome/browser/profile_resetter')
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter.cc763
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter.h268
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_delegate.cc397
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h239
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_delegate_unittest.cc608
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_factory.cc72
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_factory.h55
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_mementos.cc129
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_mementos.h101
-rw-r--r--chrome/browser/profile_resetter/automatic_profile_resetter_unittest.cc1380
-rw-r--r--chrome/browser/profile_resetter/jtl_foundation.cc48
-rw-r--r--chrome/browser/profile_resetter/jtl_foundation.h193
-rw-r--r--chrome/browser/profile_resetter/jtl_instructions.h38
-rw-r--r--chrome/browser/profile_resetter/jtl_interpreter.cc748
-rw-r--r--chrome/browser/profile_resetter/jtl_interpreter.h61
-rw-r--r--chrome/browser/profile_resetter/jtl_interpreter_unittest.cc689
-rw-r--r--chrome/browser/profile_resetter/profile_reset_global_error.cc110
-rw-r--r--chrome/browser/profile_resetter/profile_reset_global_error.h72
-rw-r--r--chrome/browser/profile_resetter/resettable_settings_snapshot.cc17
-rw-r--r--chrome/browser/profile_resetter/resettable_settings_snapshot.h9
20 files changed, 5993 insertions, 4 deletions
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 0000000..51ea44a
--- /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, &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<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;
+}
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter.h b/chrome/browser/profile_resetter/automatic_profile_resetter.h
new file mode 100644
index 0000000..9db561e
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter.h
@@ -0,0 +1,268 @@
+// 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.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/strings/string_piece.h"
+#include "base/task_runner.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_mementos.h"
+#include "components/keyed_service/core/keyed_service.h"
+
+class AutomaticProfileResetterDelegate;
+class Profile;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+// This service is responsible for evaluating whether the criteria for showing
+// the one-time profile reset prompt are satisfied, and for potentially
+// triggering the prompt. To ensure that the prompt only appears at most once
+// for any given profile, a "memento" that the prompt has appeared is written to
+// the profile on disk; see automatic_profile_resetter_mementos.h for details.
+// The service is created automatically with the Profile and is activated right
+// away by its factory. To avoid delaying start-up, however, it will only start
+// working after a short delay.
+// All methods in this class shall be called on the UI thread, except when noted
+// otherwise.
+class AutomaticProfileResetter : public KeyedService {
+ public:
+ // Enumeration listing the possible outcomes of triggering the profile reset
+ // prompt.
+ enum PromptResult {
+ // The reset prompt was not triggered because only a dry-run was performed,
+ // or because it was not supported on the current platform.
+ PROMPT_NOT_TRIGGERED,
+ // The reset bubble actually got shown. In contrast to the wrench menu item
+ // that can always be shown, the bubble might be delayed or might never be
+ // shown if another bubble was shown at the time of triggering the prompt.
+ // This enumeration value is usually recorded in conjunction with another
+ // PromptResult, the absence of which indicates that the prompt was ignored.
+ PROMPT_SHOWN_BUBBLE,
+ // The user selected "Reset" or "No, thanks" (respectively) directly from
+ // within the bubble.
+ PROMPT_ACTION_RESET,
+ PROMPT_ACTION_NO_RESET,
+ // The reset bubble was shown, then dismissed without taking definitive
+ // action. Then, however, the user initiated or refrained from doing a reset
+ // (respectively) from the conventional, WebUI-based reset dialog.
+ PROMPT_FOLLOWED_BY_WEBUI_RESET,
+ PROMPT_FOLLOWED_BY_WEBUI_NO_RESET,
+ // The reset bubble was suppressed (not shown) because another bubble was
+ // already being shown at the time. Regardless, however, the user initiated
+ // or refrained from doing a reset (respectively) from the conventional,
+ // WebUI-based reset dialog.
+ PROMPT_NOT_SHOWN_BUBBLE_BUT_HAD_WEBUI_RESET,
+ PROMPT_NOT_SHOWN_BUBBLE_BUT_HAD_WEBUI_NO_RESET,
+ PROMPT_RESULT_MAX
+ };
+
+ explicit AutomaticProfileResetter(Profile* profile);
+ virtual ~AutomaticProfileResetter();
+
+ // Initializes the service if it is enabled in the field trial. Otherwise,
+ // skips the initialization steps, and also permanently disables the service.
+ // Called by AutomaticProfileResetterFactory.
+ void Initialize();
+
+ // Fires up the service by unleashing the asynchronous evaluation flow, unless
+ // the service has been already disabled in Initialize() or there is no
+ // |program_| to run (in which case the service also gets disabled).
+ // Called by the AutomaticProfileResetterFactory.
+ void Activate();
+
+ // Called in case the user chooses to reset their profile settings from inside
+ // the reset bubble. Will trigger the reset, optionally |send_feedback|, and
+ // conclude the reset prompt flow.
+ void TriggerProfileReset(bool send_feedback);
+
+ // Called in case the user chooses from inside the reset bubble that they do
+ // not want to reset their profile settings. Will conclude the reset prompt
+ // flow without setting off a reset.
+ void SkipProfileReset();
+
+ // Returns whether or not the profile reset prompt flow is currently active,
+ // that is, we have triggered the prompt and are waiting for the user to take
+ // definitive action (and we are not yet performing a reset).
+ bool IsResetPromptFlowActive() const;
+
+ // Returns whether or not the profile reset banner should be shown on the
+ // WebUI-based settings page.
+ bool ShouldShowResetBanner() const;
+
+ // Called to give notice that the reset bubble has actually been shown.
+ void NotifyDidShowResetBubble();
+
+ // Called to give notice that the conventional, WebUI-based settings reset
+ // dialog has been opened. This will dismiss the menu item in the wrench menu.
+ // This should always be followed by a corresponding call to
+ // NotifyDidCloseWebUIResetDialog().
+ void NotifyDidOpenWebUIResetDialog();
+
+ // Called to give notice that the conventional, WebUI-based settings reset
+ // dialog has been closed, with |performed_reset| indicating whether or not a
+ // reset was requested. This is required so that we can record the appropriate
+ // PromptResult, dismiss the prompt, and conclude the reset prompt flow early
+ // without setting off any resets in the future.
+ void NotifyDidCloseWebUIResetDialog(bool performed_reset);
+
+ // Called to give notice that reset banner has been dismissed as a result of
+ // user action on the WebUI-based settings page itself.
+ void NotifyDidCloseWebUIResetBanner();
+
+ base::WeakPtr<AutomaticProfileResetter> AsWeakPtr() {
+ return weak_ptr_factory_.GetWeakPtr();
+ }
+
+ // Should be called before Activate().
+ void SetProgramForTesting(const std::string& program);
+
+ // Should be called before Activate().
+ void SetHashSeedForTesting(const std::string& hash_seed);
+
+ // Should be called before Activate().
+ void SetDelegateForTesting(
+ scoped_ptr<AutomaticProfileResetterDelegate> delegate);
+
+ // Should be called before Activate(). Sets the task runner to be used to post
+ // task |PrepareEvaluationFlow| in a delayed manner.
+ void SetTaskRunnerForWaitingForTesting(
+ const scoped_refptr<base::TaskRunner>& task_runner);
+
+ // KeyedService:
+ virtual void Shutdown() OVERRIDE;
+
+ private:
+ class InputBuilder;
+ struct EvaluationResults;
+
+ enum State {
+ STATE_UNINITIALIZED,
+ STATE_INITIALIZED,
+ STATE_DISABLED,
+ STATE_WAITING_ON_DEPENDENCIES,
+ STATE_READY,
+ STATE_EVALUATING_CONDITIONS,
+ // The reset prompt has been triggered; but the reset bubble has not yet
+ // been shown.
+ STATE_HAS_TRIGGERED_PROMPT,
+ // The reset prompt has been triggered; the reset bubble has been shown, and
+ // potentially already dismissed by the user.
+ STATE_HAS_SHOWN_BUBBLE,
+ STATE_PERFORMING_RESET,
+ STATE_DONE
+ };
+
+ // Prepares the asynchronous evaluation flow by requesting services that it
+ // depends on to make themselves ready.
+ void PrepareEvaluationFlow();
+
+ // Called back by |resetter_delegate_| when the template URL service is ready.
+ void OnTemplateURLServiceIsLoaded();
+
+ // Called back by |resetter_delegate_| when the loaded modules have been
+ // enumerated.
+ void OnLoadedModulesAreEnumerated();
+
+ // Invoked by the above two methods. Kicks off the actual evaluation flow.
+ void OnDependencyIsReady();
+
+ // Begins the asynchronous evaluation flow, which will assess whether the
+ // criteria for showing the reset prompt are met, whether we have already
+ // shown the prompt; and, in the end, will potentially trigger the prompt.
+ void BeginEvaluationFlow();
+
+ // Called by InputBuilder once it has finished assembling the |program_input|,
+ // and will continue with the evaluation flow by triggering the evaluator
+ // program on the worker thread.
+ void ContinueWithEvaluationFlow(
+ scoped_ptr<base::DictionaryValue> program_input);
+
+ // Performs the bulk of the work. Invokes the JTL interpreter to run the
+ // |program| that will evaluate whether the conditions are met for showing the
+ // reset prompt. The program will make this decision based on the state
+ // information contained in |input| in the form of key-value pairs. The
+ // program will only see hashed keys and values that are produced using
+ // |hash_seed| as a key.
+ static scoped_ptr<EvaluationResults> EvaluateConditionsOnWorkerPoolThread(
+ const std::string& hash_seed,
+ const std::string& program,
+ scoped_ptr<base::DictionaryValue> program_input);
+
+ // Reports the given metrics through UMA. Virtual, so it can be mocked out in
+ // tests to verify that the correct value are being reported.
+ virtual void ReportStatistics(uint32 satisfied_criteria_mask,
+ uint32 combined_status_mask);
+
+ // Called back when EvaluateConditionsOnWorkerPoolThread completes executing
+ // the program with |results|. Finishes the evaluation flow, and, based on the
+ // result, potentially initiates the reset prompt flow.
+ void FinishEvaluationFlow(scoped_ptr<EvaluationResults> results);
+
+ // Begins the reset prompt flow by triggering the reset prompt, which consists
+ // of two parts: (1.) the profile reset (pop-up) bubble, and (2.) a menu item
+ // in the wrench menu (provided by a GlobalError).
+ // The flow lasts until we receive a clear indication from the user about
+ // whether or not they wish to reset their settings. This indication can come
+ // in a variety of flavors:
+ // * taking definitive action (i.e. selecting either "Reset" or "No, thanks")
+ // in the pop-up reset bubble itself,
+ // * dismissing the bubble, but then selecting the wrench menu item, which
+ // takes them to the WebUI reset dialog in chrome://settings, and then the
+ // user can make their choice there,
+ // * the user going to the WebUI reset dialog by themself.
+ // For the most part, the conclusion of the reset flow coincides with when the
+ // reset prompt is dismissed, with the one exception being that the prompt is
+ // closed as soon as the WebUI reset dialog is opened, we do not wait until
+ // the user actually makes a choice in that dialog.
+ void BeginResetPromptFlow();
+
+ // Called back by the ProfileResetter once resetting the profile settings has
+ // been completed, when requested by the user from inside the reset bubble.
+ // Will dismiss the prompt and conclude the reset prompt flow.
+ void OnProfileSettingsResetCompleted();
+
+ // Reports the result of triggering the prompt through UMA. Virtual, so it can
+ // be mocked out in tests to verify that the correct value is being reported.
+ virtual void ReportPromptResult(PromptResult result);
+
+ // Writes the memento values returned by the evaluation program to disk, and
+ // then destroys |evaluation_results_|.
+ void PersistMementos();
+
+ // Concludes the reset prompt flow.
+ void FinishResetPromptFlow();
+
+ Profile* profile_;
+
+ State state_;
+ bool enumeration_of_loaded_modules_ready_;
+ bool template_url_service_ready_;
+ bool has_already_dismissed_prompt_;
+
+ scoped_ptr<InputBuilder> input_builder_;
+ std::string hash_seed_;
+ std::string program_;
+
+ scoped_ptr<EvaluationResults> evaluation_results_;
+
+ bool should_show_reset_banner_;
+
+ scoped_ptr<AutomaticProfileResetterDelegate> delegate_;
+ scoped_refptr<base::TaskRunner> task_runner_for_waiting_;
+
+ base::WeakPtrFactory<AutomaticProfileResetter> weak_ptr_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetter);
+};
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_H_
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_delegate.cc b/chrome/browser/profile_resetter/automatic_profile_resetter_delegate.cc
new file mode 100644
index 0000000..3f1c3c7
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_delegate.cc
@@ -0,0 +1,397 @@
+// 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_delegate.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
+#include "base/logging.h"
+#include "base/md5.h"
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "base/values.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/google/google_brand.h"
+#include "chrome/browser/profile_resetter/brandcode_config_fetcher.h"
+#include "chrome/browser/profile_resetter/profile_reset_global_error.h"
+#include "chrome/browser/profile_resetter/profile_resetter.h"
+#include "chrome/browser/profile_resetter/resettable_settings_snapshot.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/browser_finder.h"
+#include "chrome/browser/ui/global_error/global_error_service.h"
+#include "chrome/browser/ui/global_error/global_error_service_factory.h"
+#include "components/search_engines/template_url_prepopulate_data.h"
+#include "components/search_engines/template_url_service.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/browser/notification_service.h"
+
+#if defined(OS_WIN)
+#include "chrome/browser/enumerate_modules_model_win.h"
+#endif
+
+namespace {
+
+scoped_ptr<base::DictionaryValue> BuildSubTreeFromTemplateURL(
+ const TemplateURL* template_url) {
+ // If this value contains a placeholder in the pre-populated data, it will
+ // have been replaced as it was loaded into a TemplateURL.
+ // BuildSubTreeFromTemplateURL works with TemplateURL (not TemplateURLData)
+ // in order to maintain this behaviour.
+ // TODO(engedy): Confirm the expected behaviour and convert to use
+ // TemplateURLData if possible."
+ scoped_ptr<base::DictionaryValue> tree(new base::DictionaryValue);
+ tree->SetString("name", template_url->short_name());
+ tree->SetString("short_name", template_url->short_name());
+ tree->SetString("keyword", template_url->keyword());
+ tree->SetString("search_url", template_url->url());
+ tree->SetString("url", template_url->url());
+ tree->SetString("suggestions_url", template_url->suggestions_url());
+ tree->SetString("instant_url", template_url->instant_url());
+ tree->SetString("image_url", template_url->image_url());
+ tree->SetString("new_tab_url", template_url->new_tab_url());
+ tree->SetString("search_url_post_params",
+ template_url->search_url_post_params());
+ tree->SetString("suggestions_url_post_params",
+ template_url->suggestions_url_post_params());
+ tree->SetString("instant_url_post_params",
+ template_url->instant_url_post_params());
+ tree->SetString("image_url_post_params",
+ template_url->image_url_post_params());
+ base::ListValue* alternate_urls = new base::ListValue;
+ alternate_urls->AppendStrings(template_url->alternate_urls());
+ tree->Set("alternate_urls", alternate_urls);
+ tree->SetString("favicon_url", template_url->favicon_url().spec());
+ tree->SetString("originating_url", template_url->originating_url().spec());
+ tree->SetBoolean("safe_for_autoreplace",
+ template_url->safe_for_autoreplace());
+ base::ListValue* input_encodings = new base::ListValue;
+ input_encodings->AppendStrings(template_url->input_encodings());
+ tree->Set("input_encodings", input_encodings);
+ tree->SetString("id", base::Int64ToString(template_url->id()));
+ tree->SetString("date_created",
+ base::Int64ToString(
+ template_url->date_created().ToInternalValue()));
+ tree->SetString("last_modified",
+ base::Int64ToString(
+ template_url->last_modified().ToInternalValue()));
+ tree->SetBoolean("created_by_policy", template_url->created_by_policy());
+ tree->SetInteger("usage_count", template_url->usage_count());
+ tree->SetInteger("prepopulate_id", template_url->prepopulate_id());
+ tree->SetString("search_terms_replacement_key",
+ template_url->search_terms_replacement_key());
+ return tree.Pass();
+}
+
+#if defined(OS_WIN)
+void ExtractLoadedModuleNameDigests(
+ const base::ListValue& module_list,
+ base::ListValue* module_name_digests) {
+ DCHECK(module_name_digests);
+
+ // EnumerateModulesModel produces a list of dictionaries.
+ // Each dictionary corresponds to a module and exposes a number of properties.
+ // We care only about 'type' and 'name'.
+ for (size_t i = 0; i < module_list.GetSize(); ++i) {
+ const base::DictionaryValue* module_dictionary = NULL;
+ if (!module_list.GetDictionary(i, &module_dictionary))
+ continue;
+ ModuleEnumerator::ModuleType module_type =
+ ModuleEnumerator::LOADED_MODULE;
+ if (!module_dictionary->GetInteger(
+ "type", reinterpret_cast<int*>(&module_type)) ||
+ module_type != ModuleEnumerator::LOADED_MODULE) {
+ continue;
+ }
+ std::string module_name;
+ if (!module_dictionary->GetString("name", &module_name))
+ continue;
+ base::StringToLowerASCII(&module_name);
+ module_name_digests->AppendString(base::MD5String(module_name));
+ }
+}
+#endif
+
+} // namespace
+
+
+// AutomaticProfileResetterDelegateImpl --------------------------------------
+
+AutomaticProfileResetterDelegateImpl::AutomaticProfileResetterDelegateImpl(
+ Profile* profile,
+ ProfileResetter::ResettableFlags resettable_aspects)
+ : profile_(profile),
+ global_error_service_(GlobalErrorServiceFactory::GetForProfile(profile_)),
+ template_url_service_(TemplateURLServiceFactory::GetForProfile(profile_)),
+ resettable_aspects_(resettable_aspects) {
+ DCHECK(profile_);
+ if (template_url_service_) {
+ template_url_service_->AddObserver(this);
+ // Needed so that |template_url_service_ready_event_| will be signaled even
+ // when TemplateURLService had been already initialized before this point.
+ OnTemplateURLServiceChanged();
+ }
+
+#if defined(OS_WIN)
+ module_list_.reset(EnumerateModulesModel::GetInstance()->GetModuleList());
+#endif
+ if (module_list_) {
+ // Having a non-empty module list proves that enumeration had been already
+ // performed before this point.
+ modules_have_been_enumerated_event_.Signal();
+ }
+ registrar_.Add(this,
+ chrome::NOTIFICATION_MODULE_LIST_ENUMERATED,
+ content::NotificationService::AllSources());
+}
+
+AutomaticProfileResetterDelegateImpl::~AutomaticProfileResetterDelegateImpl() {
+ if (template_url_service_)
+ template_url_service_->RemoveObserver(this);
+}
+
+void AutomaticProfileResetterDelegateImpl::EnumerateLoadedModulesIfNeeded() {
+ if (!modules_have_been_enumerated_event_.is_signaled()) {
+#if defined(OS_WIN)
+ EnumerateModulesModel::GetInstance()->ScanNow();
+#else
+ modules_have_been_enumerated_event_.Signal();
+#endif
+ }
+}
+
+void AutomaticProfileResetterDelegateImpl::
+ RequestCallbackWhenLoadedModulesAreEnumerated(
+ const base::Closure& ready_callback) const {
+ DCHECK(!ready_callback.is_null());
+ modules_have_been_enumerated_event_.Post(FROM_HERE, ready_callback);
+}
+
+void AutomaticProfileResetterDelegateImpl::LoadTemplateURLServiceIfNeeded() {
+ DCHECK(template_url_service_);
+ template_url_service_->Load(); // Safe to call even if it has loaded already.
+}
+
+void AutomaticProfileResetterDelegateImpl::
+ RequestCallbackWhenTemplateURLServiceIsLoaded(
+ const base::Closure& ready_callback) const {
+ DCHECK(!ready_callback.is_null());
+ template_url_service_ready_event_.Post(FROM_HERE, ready_callback);
+}
+
+void AutomaticProfileResetterDelegateImpl::
+ FetchBrandcodedDefaultSettingsIfNeeded() {
+ if (brandcoded_config_fetcher_ ||
+ brandcoded_defaults_fetched_event_.is_signaled())
+ return;
+
+ std::string brandcode;
+ google_brand::GetBrand(&brandcode);
+ if (brandcode.empty()) {
+ brandcoded_defaults_.reset(new BrandcodedDefaultSettings);
+ brandcoded_defaults_fetched_event_.Signal();
+ } else {
+ brandcoded_config_fetcher_.reset(new BrandcodeConfigFetcher(
+ base::Bind(
+ &AutomaticProfileResetterDelegateImpl::OnBrandcodedDefaultsFetched,
+ base::Unretained(this)),
+ GURL("https://tools.google.com/service/update2"),
+ brandcode));
+ }
+}
+
+void AutomaticProfileResetterDelegateImpl::
+ RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ const base::Closure& ready_callback) const {
+ DCHECK(!ready_callback.is_null());
+ brandcoded_defaults_fetched_event_.Post(FROM_HERE, ready_callback);
+}
+
+scoped_ptr<base::ListValue> AutomaticProfileResetterDelegateImpl::
+ GetLoadedModuleNameDigests() const {
+ DCHECK(modules_have_been_enumerated_event_.is_signaled());
+ scoped_ptr<base::ListValue> result(new base::ListValue);
+#if defined(OS_WIN)
+ if (module_list_)
+ ExtractLoadedModuleNameDigests(*module_list_, result.get());
+#endif
+ return result.Pass();
+}
+
+scoped_ptr<base::DictionaryValue> AutomaticProfileResetterDelegateImpl::
+ GetDefaultSearchProviderDetails() const {
+ DCHECK(template_url_service_);
+ DCHECK(template_url_service_->loaded());
+
+ const TemplateURL* default_search_provider =
+ template_url_service_->GetDefaultSearchProvider();
+
+ // Having a NULL default search provider is due to either:
+ // 1.) default search providers being disabled by policy,
+ // 2.) directly tampering with the Preferences and/or the SQLite DBs.
+ // In this state, Omnibox non-keyword search functionality is disabled.
+ return default_search_provider ?
+ BuildSubTreeFromTemplateURL(default_search_provider) :
+ scoped_ptr<base::DictionaryValue>(new base::DictionaryValue);
+}
+
+bool AutomaticProfileResetterDelegateImpl::
+ IsDefaultSearchProviderManaged() const {
+ DCHECK(template_url_service_);
+ DCHECK(template_url_service_->loaded());
+ return template_url_service_->is_default_search_managed();
+}
+
+scoped_ptr<base::ListValue> AutomaticProfileResetterDelegateImpl::
+ GetPrepopulatedSearchProvidersDetails() const {
+ size_t default_search_index = 0;
+ ScopedVector<TemplateURLData> engines(
+ TemplateURLPrepopulateData::GetPrepopulatedEngines(
+ profile_->GetPrefs(), &default_search_index));
+ scoped_ptr<base::ListValue> engines_details_list(new base::ListValue);
+ for (ScopedVector<TemplateURLData>::const_iterator it = engines.begin();
+ it != engines.end(); ++it) {
+ TemplateURL template_url(**it);
+ engines_details_list->Append(
+ BuildSubTreeFromTemplateURL(&template_url).release());
+ }
+ return engines_details_list.Pass();
+}
+
+bool AutomaticProfileResetterDelegateImpl::TriggerPrompt() {
+ DCHECK(global_error_service_);
+
+ if (!ProfileResetGlobalError::IsSupportedOnPlatform())
+ return false;
+
+ ProfileResetGlobalError* global_error = new ProfileResetGlobalError(profile_);
+ global_error_service_->AddGlobalError(global_error);
+
+ // Do not try to show bubble if another GlobalError is already showing one.
+ const GlobalErrorService::GlobalErrorList& global_errors(
+ global_error_service_->errors());
+ GlobalErrorService::GlobalErrorList::const_iterator it;
+ for (it = global_errors.begin(); it != global_errors.end(); ++it) {
+ if ((*it)->GetBubbleView())
+ break;
+ }
+ if (it == global_errors.end()) {
+ Browser* browser = chrome::FindTabbedBrowser(
+ profile_,
+ false /*match_original_profiles*/,
+ chrome::GetActiveDesktop());
+ if (browser)
+ global_error->ShowBubbleView(browser);
+ }
+ return true;
+}
+
+void AutomaticProfileResetterDelegateImpl::TriggerProfileSettingsReset(
+ bool send_feedback,
+ const base::Closure& completion) {
+ DCHECK(!profile_resetter_);
+ DCHECK(!completion.is_null());
+
+ profile_resetter_.reset(new ProfileResetter(profile_));
+ FetchBrandcodedDefaultSettingsIfNeeded();
+ RequestCallbackWhenBrandcodedDefaultsAreFetched(base::Bind(
+ &AutomaticProfileResetterDelegateImpl::RunProfileSettingsReset,
+ AsWeakPtr(),
+ send_feedback,
+ completion));
+}
+
+void AutomaticProfileResetterDelegateImpl::OnTemplateURLServiceChanged() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ DCHECK(template_url_service_);
+ if (template_url_service_->loaded() &&
+ !template_url_service_ready_event_.is_signaled())
+ template_url_service_ready_event_.Signal();
+}
+
+void AutomaticProfileResetterDelegateImpl::DismissPrompt() {
+ DCHECK(global_error_service_);
+ GlobalError* global_error =
+ global_error_service_->GetGlobalErrorByMenuItemCommandID(
+ IDC_SHOW_SETTINGS_RESET_BUBBLE);
+ if (global_error) {
+ // This will also close/destroy the Bubble UI if it is currently shown.
+ global_error_service_->RemoveGlobalError(global_error);
+ delete global_error;
+ }
+}
+
+void AutomaticProfileResetterDelegateImpl::Observe(
+ int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (type == chrome::NOTIFICATION_MODULE_LIST_ENUMERATED &&
+ !modules_have_been_enumerated_event_.is_signaled()) {
+#if defined(OS_WIN)
+ module_list_.reset(EnumerateModulesModel::GetInstance()->GetModuleList());
+#endif
+ modules_have_been_enumerated_event_.Signal();
+ }
+}
+
+void AutomaticProfileResetterDelegateImpl::SendFeedback(
+ const std::string& report) const {
+ SendSettingsFeedback(report, profile_, PROFILE_RESET_PROMPT);
+}
+
+void AutomaticProfileResetterDelegateImpl::RunProfileSettingsReset(
+ bool send_feedback,
+ const base::Closure& completion) {
+ DCHECK(brandcoded_defaults_);
+ scoped_ptr<ResettableSettingsSnapshot> old_settings_snapshot;
+ if (send_feedback) {
+ old_settings_snapshot.reset(new ResettableSettingsSnapshot(profile_));
+ old_settings_snapshot->RequestShortcuts(base::Closure());
+ }
+ profile_resetter_->Reset(resettable_aspects_,
+ brandcoded_defaults_.Pass(),
+ send_feedback,
+ base::Bind(&AutomaticProfileResetterDelegateImpl::
+ OnProfileSettingsResetCompleted,
+ AsWeakPtr(),
+ completion,
+ base::Passed(&old_settings_snapshot)));
+}
+
+void AutomaticProfileResetterDelegateImpl::
+ OnBrandcodedDefaultsFetched() {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ DCHECK(brandcoded_config_fetcher_);
+ DCHECK(!brandcoded_config_fetcher_->IsActive());
+ brandcoded_defaults_ = brandcoded_config_fetcher_->GetSettings();
+ if (!brandcoded_defaults_)
+ brandcoded_defaults_.reset(new BrandcodedDefaultSettings);
+ brandcoded_defaults_fetched_event_.Signal();
+}
+
+void AutomaticProfileResetterDelegateImpl::OnProfileSettingsResetCompleted(
+ const base::Closure& user_callback,
+ scoped_ptr<ResettableSettingsSnapshot> old_settings_snapshot) {
+ DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
+ if (old_settings_snapshot) {
+ ResettableSettingsSnapshot new_settings_snapshot(profile_);
+ int difference =
+ old_settings_snapshot->FindDifferentFields(new_settings_snapshot);
+ if (difference) {
+ old_settings_snapshot->Subtract(new_settings_snapshot);
+ std::string report =
+ SerializeSettingsReport(*old_settings_snapshot, difference);
+ SendFeedback(report);
+ }
+ }
+ content::BrowserThread::PostTask(
+ content::BrowserThread::UI, FROM_HERE, user_callback);
+}
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h b/chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h
new file mode 100644
index 0000000..015c466
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h
@@ -0,0 +1,239 @@
+// 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.
+
+// Declares a delegate that interacts with the rest of the browser on behalf of
+// the AutomaticProfileResetter.
+//
+// The reason for this separation is to facilitate unit testing. Factoring out
+// the implementation for each interaction step (encapsulated by one method of
+// the delegate) allows it to be tested independently in itself. It also becomes
+// easier to verify that the state machine inside AutomaticProfileResetter works
+// correctly: by mocking out the interaction methods in the delegate, we can, in
+// effect, mock out the entire rest of the browser, allowing us to easily
+// simulate scenarios that are interesting for testing the state machine.
+//
+// The delegate is normally instantiated by AutomaticProfileResetter internally,
+// while a mock implementation can be injected during unit tests.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_DELEGATE_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_DELEGATE_H_
+
+#include "base/basictypes.h"
+#include "base/callback_forward.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "chrome/browser/profile_resetter/profile_resetter.h"
+#include "components/search_engines/template_url_service_observer.h"
+#include "content/public/browser/notification_observer.h"
+#include "content/public/browser/notification_registrar.h"
+#include "extensions/common/one_shot_event.h"
+
+class BrandcodeConfigFetcher;
+class GlobalErrorService;
+class Profile;
+class ResettableSettingsSnapshot;
+class TemplateURLService;
+
+namespace base {
+class DictionaryValue;
+class ListValue;
+}
+
+// Defines the interface for the delegate that will interact with the rest of
+// the browser on behalf of the AutomaticProfileResetter.
+class AutomaticProfileResetterDelegate {
+ public:
+ virtual ~AutomaticProfileResetterDelegate() {}
+
+ // Requests the module enumerator to start scanning for loaded modules now, if
+ // it has not done so already.
+ virtual void EnumerateLoadedModulesIfNeeded() = 0;
+
+ // Requests |ready_callback| to be posted on the UI thread once the module
+ // enumerator has finished scanning for loaded modules.
+ virtual void RequestCallbackWhenLoadedModulesAreEnumerated(
+ const base::Closure& ready_callback) const = 0;
+
+ // Requests the template URL service to load its database (asynchronously).
+ virtual void LoadTemplateURLServiceIfNeeded() = 0;
+
+ // Requests |ready_callback| to be posted on the UI thread once the template
+ // URL service has finished loading its database.
+ virtual void RequestCallbackWhenTemplateURLServiceIsLoaded(
+ const base::Closure& ready_callback) const = 0;
+
+ // Starts downloading the configuration file that specifies the default
+ // settings for the current brandcode; or establishes that we are running a
+ // vanilla (non-branded) build.
+ virtual void FetchBrandcodedDefaultSettingsIfNeeded() = 0;
+
+ // Requests |ready_callback| to be posted on the UI thread once the brandcoded
+ // default settings have been downloaded, or once it has been established that
+ // we are running a vanilla (non-branded) build.
+ virtual void RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ const base::Closure& ready_callback) const = 0;
+
+ // Returns a list of loaded module name digests.
+ virtual scoped_ptr<base::ListValue> GetLoadedModuleNameDigests() const = 0;
+
+ // Returns attributes of the search engine currently set as the default (or
+ // an empty dictionary if there is none).
+ // The dictionary is in the same format as persisted to preferences by
+ // DefaultSearchManager::SetUserSelectedDefaultSearchEngine.
+ virtual scoped_ptr<base::DictionaryValue>
+ GetDefaultSearchProviderDetails() const = 0;
+
+ // Returns whether or not the default search provider is set by policy.
+ virtual bool IsDefaultSearchProviderManaged() const = 0;
+
+ // Returns a list of dictionaries, each containing attributes for each of the
+ // pre-populated search engines, in the format described above.
+ virtual scoped_ptr<base::ListValue>
+ GetPrepopulatedSearchProvidersDetails() const = 0;
+
+ // Triggers showing the one-time profile settings reset prompt, which consists
+ // of two parts: (1.) the profile reset (pop-up) bubble, and (2.) a menu item
+ // in the wrench menu. Showing the bubble might be delayed if there is another
+ // bubble currently shown, in which case the bubble will be shown the first
+ // time a new browser window is opened.
+ // Returns true unless the reset prompt is not supported on the current
+ // platform, in which case it returns false and does nothing.
+ virtual bool TriggerPrompt() = 0;
+
+ // Triggers the ProfileResetter to reset all supported settings and optionally
+ // |send_feedback|. Will post |completion| on the UI thread once completed.
+ // Brandcoded default settings will be fetched if they are not yet available,
+ // the reset will be performed once the download is complete.
+ // NOTE: Should only be called at most once during the lifetime of the object.
+ virtual void TriggerProfileSettingsReset(bool send_feedback,
+ const base::Closure& completion) = 0;
+
+ // Dismisses the one-time profile settings reset prompt, if it is currently
+ // triggered. Will synchronously destroy the corresponding GlobalError.
+ virtual void DismissPrompt() = 0;
+};
+
+// Implementation for AutomaticProfileResetterDelegate.
+class AutomaticProfileResetterDelegateImpl
+ : public AutomaticProfileResetterDelegate,
+ public base::SupportsWeakPtr<AutomaticProfileResetterDelegateImpl>,
+ public TemplateURLServiceObserver,
+ public content::NotificationObserver {
+ public:
+ explicit AutomaticProfileResetterDelegateImpl(
+ Profile* profile,
+ ProfileResetter::ResettableFlags resettable_aspects);
+ virtual ~AutomaticProfileResetterDelegateImpl();
+
+ // Returns the brandcoded default settings; empty defaults if this is not a
+ // branded build; or NULL if FetchBrandcodedDefaultSettingsIfNeeded() has not
+ // yet been called or not yet finished. Exposed only for unit tests.
+ const BrandcodedDefaultSettings* brandcoded_defaults() const {
+ return brandcoded_defaults_.get();
+ }
+
+ // AutomaticProfileResetterDelegate:
+ virtual void EnumerateLoadedModulesIfNeeded() OVERRIDE;
+ virtual void RequestCallbackWhenLoadedModulesAreEnumerated(
+ const base::Closure& ready_callback) const OVERRIDE;
+ virtual void LoadTemplateURLServiceIfNeeded() OVERRIDE;
+ virtual void RequestCallbackWhenTemplateURLServiceIsLoaded(
+ const base::Closure& ready_callback) const OVERRIDE;
+ virtual void FetchBrandcodedDefaultSettingsIfNeeded() OVERRIDE;
+ virtual void RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ const base::Closure& ready_callback) const OVERRIDE;
+ virtual scoped_ptr<base::ListValue>
+ GetLoadedModuleNameDigests() const OVERRIDE;
+ virtual scoped_ptr<base::DictionaryValue>
+ GetDefaultSearchProviderDetails() const OVERRIDE;
+ virtual bool IsDefaultSearchProviderManaged() const OVERRIDE;
+ virtual scoped_ptr<base::ListValue>
+ GetPrepopulatedSearchProvidersDetails() const OVERRIDE;
+ virtual bool TriggerPrompt() OVERRIDE;
+ virtual void TriggerProfileSettingsReset(
+ bool send_feedback,
+ const base::Closure& completion) OVERRIDE;
+ virtual void DismissPrompt() OVERRIDE;
+
+ // TemplateURLServiceObserver:
+ virtual void OnTemplateURLServiceChanged() OVERRIDE;
+
+ // content::NotificationObserver:
+ virtual void Observe(int type,
+ const content::NotificationSource& source,
+ const content::NotificationDetails& details) OVERRIDE;
+
+ private:
+ // Sends a feedback |report|, where |report| is supposed to be result of
+ // ::SerializeSettingsReport(). Virtual, so it can be mocked out for tests.
+ virtual void SendFeedback(const std::string& report) const;
+
+ // Triggers the ProfileResetter to reset |resettable_aspects_| and optionally
+ // |send_feedback|. Will invoke |completion| on the UI thread once completed
+ // The |brandcoded_defaults_| must already be initialized when this is called.
+ void RunProfileSettingsReset(bool send_feedback,
+ const base::Closure& completion);
+
+ // Called by |brandcoded_config_fetcher_| when it finishes downloading the
+ // brandcoded default settings (or the download times out).
+ void OnBrandcodedDefaultsFetched();
+
+ // Called back by the ProfileResetter once resetting the profile settings has
+ // been completed. If |old_settings_snapshot| is non-NULL, will compare it to
+ // the new settings and send the differences to Google for analysis. Finally,
+ // will post |user_callback|.
+ void OnProfileSettingsResetCompleted(
+ const base::Closure& user_callback,
+ scoped_ptr<ResettableSettingsSnapshot> old_settings_snapshot);
+
+ // The profile that this delegate operates on.
+ Profile* profile_;
+
+ // Shortcuts to |profile_| keyed services, to reduce boilerplate. These may be
+ // NULL in unit tests that do not initialize the respective service(s).
+ GlobalErrorService* global_error_service_;
+ TemplateURLService* template_url_service_;
+
+ // Helper to asynchronously download the default settings for the current
+ // distribution channel (identified by brand code). Instantiated on-demand.
+ scoped_ptr<BrandcodeConfigFetcher> brandcoded_config_fetcher_;
+
+ // Once |brandcoded_defaults_fetched_event_| has fired, this will contain the
+ // brandcoded default settings, or empty settings for a non-branded build.
+ scoped_ptr<BrandcodedDefaultSettings> brandcoded_defaults_;
+
+ // Overwritten to avoid resetting aspects that will not work in unit tests.
+ const ProfileResetter::ResettableFlags resettable_aspects_;
+
+ // The profile resetter to perform the actual reset. Instantiated on-demand.
+ scoped_ptr<ProfileResetter> profile_resetter_;
+
+ // Manages registrations/unregistrations for notifications.
+ content::NotificationRegistrar registrar_;
+
+ // The list of modules found. Even when |modules_have_been_enumerated_event_|
+ // is signaled, this may still be NULL.
+ scoped_ptr<base::ListValue> module_list_;
+
+ // This event is signaled once module enumeration has been attempted. If
+ // during construction, EnumerateModulesModel can supply a non-empty list of
+ // modules, module enumeration has clearly already happened, so the event will
+ // be signaled immediately; otherwise, when EnumerateLoadedModulesIfNeeded()
+ // is called, it will ask the model to scan the modules, and then signal the
+ // event once this process is completed.
+ extensions::OneShotEvent modules_have_been_enumerated_event_;
+
+ // This event is signaled once the TemplateURLService has loaded. If the
+ // TemplateURLService was already loaded prior to the creation of this class,
+ // the event will be signaled during construction.
+ extensions::OneShotEvent template_url_service_ready_event_;
+
+ // This event is signaled once brandcoded default settings have been fetched,
+ // or once it has been established that this is not a branded build.
+ extensions::OneShotEvent brandcoded_defaults_fetched_event_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetterDelegateImpl);
+};
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_DELEGATE_H_
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_delegate_unittest.cc b/chrome/browser/profile_resetter/automatic_profile_resetter_delegate_unittest.cc
new file mode 100644
index 0000000..560b277
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_delegate_unittest.cc
@@ -0,0 +1,608 @@
+// 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_delegate.h"
+
+#include <algorithm>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/memory/ref_counted.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/prefs/pref_service.h"
+#include "base/run_loop.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "base/strings/string_util.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/test/values_test_util.h"
+#include "base/values.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/chrome_notification_types.h"
+#include "chrome/browser/extensions/extension_service.h"
+#include "chrome/browser/extensions/extension_service_test_base.h"
+#include "chrome/browser/google/google_brand.h"
+#include "chrome/browser/profile_resetter/brandcoded_default_settings.h"
+#include "chrome/browser/profile_resetter/profile_reset_global_error.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/search_engines/template_url_service_factory_test_util.h"
+#include "chrome/browser/ui/global_error/global_error.h"
+#include "chrome/browser/ui/global_error/global_error_service.h"
+#include "chrome/browser/ui/global_error/global_error_service_factory.h"
+#include "chrome/common/pref_names.h"
+#include "chrome/test/base/testing_pref_service_syncable.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/search_engines/default_search_manager.h"
+#include "components/search_engines/template_url_prepopulate_data.h"
+#include "components/search_engines/template_url_service.h"
+#include "content/public/browser/notification_service.h"
+#include "net/http/http_response_headers.h"
+#include "net/url_request/test_url_fetcher_factory.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+#if defined(OS_WIN)
+#include "chrome/browser/enumerate_modules_model_win.h"
+#endif
+
+namespace {
+
+const char kTestBrandcode[] = "FOOBAR";
+
+const char kTestHomepage[] = "http://google.com";
+const char kTestBrandedHomepage[] = "http://example.com";
+
+const ProfileResetter::ResettableFlags kResettableAspectsForTest =
+ ProfileResetter::ALL & ~ProfileResetter::COOKIES_AND_SITE_DATA;
+
+// Helpers -------------------------------------------------------------------
+
+// A testing version of the AutomaticProfileResetterDelegate that differs from
+// the real one only in that it has its feedback reporting mocked out, and it
+// will not reset COOKIES_AND_SITE_DATA, due to difficulties to set up some
+// required URLRequestContexts in unit tests.
+class AutomaticProfileResetterDelegateUnderTest
+ : public AutomaticProfileResetterDelegateImpl {
+ public:
+ explicit AutomaticProfileResetterDelegateUnderTest(Profile* profile)
+ : AutomaticProfileResetterDelegateImpl(
+ profile, kResettableAspectsForTest) {}
+ virtual ~AutomaticProfileResetterDelegateUnderTest() {}
+
+ MOCK_CONST_METHOD1(SendFeedback, void(const std::string&));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetterDelegateUnderTest);
+};
+
+class MockCallbackTarget {
+ public:
+ MockCallbackTarget() {}
+ ~MockCallbackTarget() {}
+
+ MOCK_CONST_METHOD0(Run, void(void));
+
+ base::Closure CreateClosure() {
+ return base::Bind(&MockCallbackTarget::Run, base::Unretained(this));
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(MockCallbackTarget);
+};
+
+// Returns the details of the default search provider from |prefs| in a format
+// suitable for usage as |expected_details| in ExpectDetailsMatch().
+const base::DictionaryValue* GetDefaultSearchProviderDetailsFromPrefs(
+ const PrefService* prefs) {
+ return prefs->GetDictionary(
+ DefaultSearchManager::kDefaultSearchProviderDataPrefName);
+}
+
+// Verifies that the |details| of a search engine as provided by the delegate
+// are correct in comparison to the |expected_details| coming from the Prefs.
+void ExpectDetailsMatch(const base::DictionaryValue& expected_details,
+ const base::DictionaryValue& details) {
+ for (base::DictionaryValue::Iterator it(expected_details); !it.IsAtEnd();
+ it.Advance()) {
+ SCOPED_TRACE(testing::Message("Key: ") << it.key());
+ if (it.key() == "enabled" || it.key() == "synced_guid") {
+ // These attributes should not be present.
+ EXPECT_FALSE(details.HasKey(it.key()));
+ continue;
+ }
+ const base::Value* expected_value = &it.value();
+ const base::Value* actual_value = NULL;
+ ASSERT_TRUE(details.Get(it.key(), &actual_value));
+
+ // Ignore ID as it is dynamically assigned by the TemplateURLService.
+ // last_modified may get updated during a run, so ignore value differences.
+ if (it.key() != "id" && it.key() != "last_modified") {
+ // Everything else is the same format.
+ EXPECT_TRUE(actual_value->Equals(expected_value))
+ << "Expected: " << *expected_value << ". Actual: " << *actual_value;
+ }
+ }
+}
+
+// If |simulate_failure| is false, then replies to the pending request on
+// |fetcher| with a brandcoded config that only specifies a home page URL.
+// If |simulate_failure| is true, replies with 404.
+void ServicePendingBrancodedConfigFetch(net::TestURLFetcher* fetcher,
+ bool simulate_failure) {
+ const char kBrandcodedXmlSettings[] =
+ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+ "<response protocol=\"3.0\" server=\"prod\">"
+ "<app appid=\"{8A69D345-D564-463C-AFF1-A69D9E530F96}\" status=\"ok\">"
+ "<data index=\"skipfirstrunui-importsearch-defaultbrowser\" "
+ "name=\"install\" status=\"ok\">"
+ "{\"homepage\" : \"$1\"}"
+ "</data>"
+ "</app>"
+ "</response>";
+
+ fetcher->set_response_code(simulate_failure ? 404 : 200);
+ scoped_refptr<net::HttpResponseHeaders> response_headers(
+ new net::HttpResponseHeaders(""));
+ response_headers->AddHeader("Content-Type: text/xml");
+ fetcher->set_response_headers(response_headers);
+ if (!simulate_failure) {
+ std::string response(kBrandcodedXmlSettings);
+ size_t placeholder_index = response.find("$1");
+ ASSERT_NE(std::string::npos, placeholder_index);
+ response.replace(placeholder_index, 2, kTestBrandedHomepage);
+ fetcher->SetResponseString(response);
+ }
+ fetcher->delegate()->OnURLFetchComplete(fetcher);
+}
+
+
+// Test fixture --------------------------------------------------------------
+
+// ExtensionServiceTestBase sets up a TestingProfile with the ExtensionService,
+// we then add the TemplateURLService, so the ProfileResetter can be exercised.
+class AutomaticProfileResetterDelegateTest
+ : public extensions::ExtensionServiceTestBase {
+ protected:
+ AutomaticProfileResetterDelegateTest() {}
+ virtual ~AutomaticProfileResetterDelegateTest() {}
+
+ virtual void SetUp() OVERRIDE {
+ extensions::ExtensionServiceTestBase::SetUp();
+ ExtensionServiceInitParams params = CreateDefaultInitParams();
+ params.pref_file.clear(); // Prescribes a TestingPrefService to be created.
+ InitializeExtensionService(params);
+ template_url_service_test_util_.reset(
+ new TemplateURLServiceFactoryTestUtil(profile_.get()));
+ resetter_delegate_.reset(
+ new AutomaticProfileResetterDelegateUnderTest(profile()));
+ }
+
+ virtual void TearDown() OVERRIDE {
+ resetter_delegate_.reset();
+ template_url_service_test_util_.reset();
+ extensions::ExtensionServiceTestBase::TearDown();
+ }
+
+ scoped_ptr<TemplateURL> CreateTestTemplateURL() {
+ TemplateURLData data;
+
+ data.SetURL("http://example.com/search?q={searchTerms}");
+ data.suggestions_url = "http://example.com/suggest?q={searchTerms}";
+ data.instant_url = "http://example.com/instant?q={searchTerms}";
+ data.image_url = "http://example.com/image?q={searchTerms}";
+ data.search_url_post_params = "search-post-params";
+ data.suggestions_url_post_params = "suggest-post-params";
+ data.instant_url_post_params = "instant-post-params";
+ data.image_url_post_params = "image-post-params";
+
+ data.favicon_url = GURL("http://example.com/favicon.ico");
+ data.new_tab_url = "http://example.com/newtab.html";
+ data.alternate_urls.push_back("http://example.com/s?q={searchTerms}");
+
+ data.short_name = base::ASCIIToUTF16("name");
+ data.SetKeyword(base::ASCIIToUTF16("keyword"));
+ data.search_terms_replacement_key = "search-terms-replacment-key";
+ data.prepopulate_id = 42;
+ data.input_encodings.push_back("UTF-8");
+ data.safe_for_autoreplace = true;
+
+ return scoped_ptr<TemplateURL>(new TemplateURL(data));
+ }
+
+ void ExpectNoPendingBrandcodedConfigFetch() {
+ EXPECT_FALSE(test_url_fetcher_factory_.GetFetcherByID(0));
+ }
+
+ void ExpectAndServicePendingBrandcodedConfigFetch(bool simulate_failure) {
+ net::TestURLFetcher* fetcher = test_url_fetcher_factory_.GetFetcherByID(0);
+ ASSERT_TRUE(fetcher);
+ EXPECT_THAT(fetcher->upload_data(),
+ testing::HasSubstr(kTestBrandcode));
+ ServicePendingBrancodedConfigFetch(fetcher, simulate_failure);
+ }
+
+ void ExpectResetPromptState(bool active) {
+ GlobalErrorService* global_error_service =
+ GlobalErrorServiceFactory::GetForProfile(profile());
+ GlobalError* global_error = global_error_service->
+ GetGlobalErrorByMenuItemCommandID(IDC_SHOW_SETTINGS_RESET_BUBBLE);
+ EXPECT_EQ(active, !!global_error);
+ }
+
+ AutomaticProfileResetterDelegateUnderTest* resetter_delegate() {
+ return resetter_delegate_.get();
+ }
+
+ TemplateURLServiceFactoryTestUtil* template_url_service_test_util() {
+ return template_url_service_test_util_.get();
+ }
+
+ private:
+ scoped_ptr<TemplateURLServiceFactoryTestUtil> template_url_service_test_util_;
+ net::TestURLFetcherFactory test_url_fetcher_factory_;
+ scoped_ptr<AutomaticProfileResetterDelegateUnderTest> resetter_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetterDelegateTest);
+};
+
+
+// Tests ---------------------------------------------------------------------
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ TriggerAndWaitOnModuleEnumeration) {
+ // Expect ready_callback to be called just after the modules have been
+ // enumerated. Fail if it is not called. Note: as the EnumerateModulesModel is
+ // a global singleton, the callback might be invoked immediately if another
+ // test-case (e.g. the one below) has already performed module enumeration.
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->RequestCallbackWhenLoadedModulesAreEnumerated(
+ mock_target.CreateClosure());
+ resetter_delegate()->EnumerateLoadedModulesIfNeeded();
+ base::RunLoop().RunUntilIdle();
+
+ testing::Mock::VerifyAndClearExpectations(&mock_target);
+
+ // Expect ready_callback to be posted immediately when the modules have
+ // already been enumerated.
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->RequestCallbackWhenLoadedModulesAreEnumerated(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+
+#if defined(OS_WIN)
+ testing::Mock::VerifyAndClearExpectations(&mock_target);
+
+ // Expect ready_callback to be posted immediately even when the modules had
+ // already been enumerated when the delegate was constructed.
+ scoped_ptr<AutomaticProfileResetterDelegate> late_resetter_delegate(
+ new AutomaticProfileResetterDelegateImpl(profile(),
+ ProfileResetter::ALL));
+
+ EXPECT_CALL(mock_target, Run());
+ late_resetter_delegate->RequestCallbackWhenLoadedModulesAreEnumerated(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+#endif
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest, GetLoadedModuleNameDigests) {
+ resetter_delegate()->EnumerateLoadedModulesIfNeeded();
+ base::RunLoop().RunUntilIdle();
+ scoped_ptr<base::ListValue> module_name_digests(
+ resetter_delegate()->GetLoadedModuleNameDigests());
+
+ // Just verify that each element looks like an MD5 hash in hexadecimal, and
+ // also that we have at least one element on Win.
+ ASSERT_TRUE(module_name_digests);
+ for (base::ListValue::const_iterator it = module_name_digests->begin();
+ it != module_name_digests->end(); ++it) {
+ std::string digest_hex;
+ std::vector<uint8> digest_raw;
+
+ ASSERT_TRUE((*it)->GetAsString(&digest_hex));
+ ASSERT_TRUE(base::HexStringToBytes(digest_hex, &digest_raw));
+ EXPECT_EQ(16u, digest_raw.size());
+ }
+#if defined(OS_WIN)
+ EXPECT_LE(1u, module_name_digests->GetSize());
+#endif
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest, LoadAndWaitOnTemplateURLService) {
+ // Expect ready_callback to be called just after the template URL service gets
+ // initialized. Fail if it is not called, or called too early.
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ resetter_delegate()->RequestCallbackWhenTemplateURLServiceIsLoaded(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->LoadTemplateURLServiceIfNeeded();
+ base::RunLoop().RunUntilIdle();
+
+ testing::Mock::VerifyAndClearExpectations(&mock_target);
+
+ // Expect ready_callback to be posted immediately when the template URL
+ // service is already initialized.
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->RequestCallbackWhenTemplateURLServiceIsLoaded(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+
+ testing::Mock::VerifyAndClearExpectations(&mock_target);
+
+ // Expect ready_callback to be posted immediately even when the template URL
+ // service had already been initialized when the delegate was constructed.
+ scoped_ptr<AutomaticProfileResetterDelegate> late_resetter_delegate(
+ new AutomaticProfileResetterDelegateImpl(profile(),
+ ProfileResetter::ALL));
+
+ EXPECT_CALL(mock_target, Run());
+ late_resetter_delegate->RequestCallbackWhenTemplateURLServiceIsLoaded(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ DefaultSearchProviderDataWhenNotManaged) {
+ TemplateURLService* template_url_service =
+ TemplateURLServiceFactory::GetForProfile(profile());
+ template_url_service_test_util()->VerifyLoad();
+
+ // Check that the "managed state" and the details returned by the delegate are
+ // correct. We verify the details against the data stored by
+ // TemplateURLService into Prefs.
+ scoped_ptr<TemplateURL> owned_custom_dsp(CreateTestTemplateURL());
+ TemplateURL* custom_dsp = owned_custom_dsp.get();
+ template_url_service->Add(owned_custom_dsp.release());
+ template_url_service->SetUserSelectedDefaultSearchProvider(custom_dsp);
+
+ PrefService* prefs = profile()->GetPrefs();
+ ASSERT_TRUE(prefs);
+ scoped_ptr<base::DictionaryValue> dsp_details(
+ resetter_delegate()->GetDefaultSearchProviderDetails());
+ const base::DictionaryValue* expected_dsp_details =
+ GetDefaultSearchProviderDetailsFromPrefs(prefs);
+
+ ExpectDetailsMatch(*expected_dsp_details, *dsp_details);
+ EXPECT_FALSE(resetter_delegate()->IsDefaultSearchProviderManaged());
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ DefaultSearchProviderDataWhenManaged) {
+ const char kTestSearchURL[] = "http://example.com/search?q={searchTerms}";
+ const char kTestName[] = "name";
+ const char kTestKeyword[] = "keyword";
+
+ template_url_service_test_util()->VerifyLoad();
+
+ EXPECT_FALSE(resetter_delegate()->IsDefaultSearchProviderManaged());
+
+ // Set managed preferences to emulate a default search provider set by policy.
+ template_url_service_test_util()->SetManagedDefaultSearchPreferences(
+ true, kTestName, kTestKeyword, kTestSearchURL, std::string(),
+ std::string(), std::string(), std::string(), std::string());
+
+ EXPECT_TRUE(resetter_delegate()->IsDefaultSearchProviderManaged());
+ scoped_ptr<base::DictionaryValue> dsp_details(
+ resetter_delegate()->GetDefaultSearchProviderDetails());
+ // Checking that all details are correct is already done by the above test.
+ // Just make sure details are reported about the correct engine.
+ base::ExpectDictStringValue(kTestSearchURL, *dsp_details, "search_url");
+
+ // Set managed preferences to emulate that having a default search provider is
+ // disabled by policy.
+ template_url_service_test_util()->RemoveManagedDefaultSearchPreferences();
+ template_url_service_test_util()->SetManagedDefaultSearchPreferences(
+ false, std::string(), std::string(), std::string(), std::string(),
+ std::string(), std::string(), std::string(), std::string());
+
+ dsp_details = resetter_delegate()->GetDefaultSearchProviderDetails();
+ EXPECT_TRUE(resetter_delegate()->IsDefaultSearchProviderManaged());
+ EXPECT_TRUE(dsp_details->empty());
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ GetPrepopulatedSearchProvidersDetails) {
+ TemplateURLService* template_url_service =
+ TemplateURLServiceFactory::GetForProfile(profile());
+ template_url_service_test_util()->VerifyLoad();
+
+ scoped_ptr<base::ListValue> search_engines_details(
+ resetter_delegate()->GetPrepopulatedSearchProvidersDetails());
+
+ // Do the same kind of verification as for GetDefaultSearchEngineDetails:
+ // subsequently set each pre-populated engine as the default, so we can verify
+ // that the details returned by the delegate about one particular engine are
+ // correct in comparison to what has been stored to the Prefs.
+ std::vector<TemplateURL*> prepopulated_engines =
+ template_url_service->GetTemplateURLs();
+
+ ASSERT_EQ(prepopulated_engines.size(), search_engines_details->GetSize());
+
+ for (size_t i = 0; i < search_engines_details->GetSize(); ++i) {
+ const base::DictionaryValue* details = NULL;
+ ASSERT_TRUE(search_engines_details->GetDictionary(i, &details));
+
+ std::string keyword;
+ ASSERT_TRUE(details->GetString("keyword", &keyword));
+ TemplateURL* search_engine =
+ template_url_service->GetTemplateURLForKeyword(
+ base::ASCIIToUTF16(keyword));
+ ASSERT_TRUE(search_engine);
+ template_url_service->SetUserSelectedDefaultSearchProvider(
+ prepopulated_engines[i]);
+
+ PrefService* prefs = profile()->GetPrefs();
+ ASSERT_TRUE(prefs);
+ const base::DictionaryValue* expected_dsp_details =
+ GetDefaultSearchProviderDetailsFromPrefs(prefs);
+ ExpectDetailsMatch(*expected_dsp_details, *details);
+ }
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ FetchAndWaitOnDefaultSettingsVanilla) {
+ google_brand::BrandForTesting scoped_brand_for_testing((std::string()));
+
+ // Expect ready_callback to be called just after empty brandcoded settings
+ // are loaded, given this is a vanilla build. Fail if it is not called, or
+ // called too early.
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ resetter_delegate()->RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(resetter_delegate()->brandcoded_defaults());
+
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->FetchBrandcodedDefaultSettingsIfNeeded();
+ base::RunLoop().RunUntilIdle();
+ ExpectNoPendingBrandcodedConfigFetch();
+
+ testing::Mock::VerifyAndClearExpectations(&mock_target);
+ EXPECT_TRUE(resetter_delegate()->brandcoded_defaults());
+
+ // Expect ready_callback to be posted immediately when the brandcoded settings
+ // have already been loaded.
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+
+ // No test for a new instance of AutomaticProfileResetterDelegate. That will
+ // need to fetch the brandcoded settings again.
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ FetchAndWaitOnDefaultSettingsBranded) {
+ google_brand::BrandForTesting scoped_brand_for_testing(kTestBrandcode);
+
+ // Expect ready_callback to be called just after the brandcoded settings are
+ // downloaded. Fail if it is not called, or called too early.
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ resetter_delegate()->RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+ EXPECT_FALSE(resetter_delegate()->brandcoded_defaults());
+
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->FetchBrandcodedDefaultSettingsIfNeeded();
+ ExpectAndServicePendingBrandcodedConfigFetch(false /*simulate_failure*/);
+ base::RunLoop().RunUntilIdle();
+
+ testing::Mock::VerifyAndClearExpectations(&mock_target);
+ const BrandcodedDefaultSettings* brandcoded_defaults =
+ resetter_delegate()->brandcoded_defaults();
+ ASSERT_TRUE(brandcoded_defaults);
+ std::string homepage_url;
+ EXPECT_TRUE(brandcoded_defaults->GetHomepage(&homepage_url));
+ EXPECT_EQ(kTestBrandedHomepage, homepage_url);
+
+ // Expect ready_callback to be posted immediately when the brandcoded settings
+ // have already been downloaded.
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ FetchAndWaitOnDefaultSettingsBrandedFailure) {
+ google_brand::BrandForTesting scoped_brand_for_testing(kTestBrandcode);
+
+ // Expect ready_callback to be called just after the brandcoded settings have
+ // failed to download. Fail if it is not called, or called too early.
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ resetter_delegate()->RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->FetchBrandcodedDefaultSettingsIfNeeded();
+ ExpectAndServicePendingBrandcodedConfigFetch(true /*simulate_failure*/);
+ base::RunLoop().RunUntilIdle();
+
+ testing::Mock::VerifyAndClearExpectations(&mock_target);
+ EXPECT_TRUE(resetter_delegate()->brandcoded_defaults());
+
+ // Expect ready_callback to be posted immediately when the brandcoded settings
+ // have already been attempted to be downloaded, but failed.
+ EXPECT_CALL(mock_target, Run());
+ resetter_delegate()->RequestCallbackWhenBrandcodedDefaultsAreFetched(
+ mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest, TriggerReset) {
+ google_brand::BrandForTesting scoped_brand_for_testing(kTestBrandcode);
+
+ PrefService* prefs = profile()->GetPrefs();
+ DCHECK(prefs);
+ prefs->SetString(prefs::kHomePage, kTestHomepage);
+
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ EXPECT_CALL(mock_target, Run());
+ EXPECT_CALL(*resetter_delegate(), SendFeedback(testing::_)).Times(0);
+ resetter_delegate()->TriggerProfileSettingsReset(
+ false /*send_feedback*/, mock_target.CreateClosure());
+ ExpectAndServicePendingBrandcodedConfigFetch(false /*simulate_failure*/);
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(kTestBrandedHomepage, prefs->GetString(prefs::kHomePage));
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ TriggerResetWithDefaultSettingsAlreadyLoaded) {
+ google_brand::BrandForTesting scoped_brand_for_testing(kTestBrandcode);
+
+ PrefService* prefs = profile()->GetPrefs();
+ DCHECK(prefs);
+ prefs->SetString(prefs::kHomePage, kTestHomepage);
+
+ resetter_delegate()->FetchBrandcodedDefaultSettingsIfNeeded();
+ ExpectAndServicePendingBrandcodedConfigFetch(false /*simulate_failure*/);
+ base::RunLoop().RunUntilIdle();
+
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ EXPECT_CALL(mock_target, Run());
+ EXPECT_CALL(*resetter_delegate(), SendFeedback(testing::_)).Times(0);
+ resetter_delegate()->TriggerProfileSettingsReset(
+ false /*send_feedback*/, mock_target.CreateClosure());
+ base::RunLoop().RunUntilIdle();
+
+ EXPECT_EQ(kTestBrandedHomepage, prefs->GetString(prefs::kHomePage));
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest,
+ TriggerResetAndSendFeedback) {
+ google_brand::BrandForTesting scoped_brand_for_testing(kTestBrandcode);
+
+ PrefService* prefs = profile()->GetPrefs();
+ DCHECK(prefs);
+ prefs->SetString(prefs::kHomePage, kTestHomepage);
+
+ testing::StrictMock<MockCallbackTarget> mock_target;
+ EXPECT_CALL(mock_target, Run());
+ EXPECT_CALL(*resetter_delegate(),
+ SendFeedback(testing::HasSubstr(kTestHomepage)));
+
+ resetter_delegate()->TriggerProfileSettingsReset(
+ true /*send_feedback*/, mock_target.CreateClosure());
+ ExpectAndServicePendingBrandcodedConfigFetch(false /*simulate_failure*/);
+ base::RunLoop().RunUntilIdle();
+}
+
+TEST_F(AutomaticProfileResetterDelegateTest, ShowAndDismissPrompt) {
+ resetter_delegate()->TriggerPrompt();
+ if (ProfileResetGlobalError::IsSupportedOnPlatform())
+ ExpectResetPromptState(true /*active*/);
+ else
+ ExpectResetPromptState(false /*active*/);
+ resetter_delegate()->DismissPrompt();
+ ExpectResetPromptState(false /*active*/);
+ resetter_delegate()->DismissPrompt();
+}
+
+} // namespace
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_factory.cc b/chrome/browser/profile_resetter/automatic_profile_resetter_factory.cc
new file mode 100644
index 0000000..7acdff7
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_factory.cc
@@ -0,0 +1,72 @@
+// 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_factory.h"
+
+#include "base/memory/singleton.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/search_engines/template_url_service_factory.h"
+#include "chrome/browser/ui/global_error/global_error_service_factory.h"
+#include "chrome/common/pref_names.h"
+#include "components/keyed_service/content/browser_context_dependency_manager.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "content/public/browser/browser_context.h"
+
+// static
+AutomaticProfileResetter* AutomaticProfileResetterFactory::GetForBrowserContext(
+ content::BrowserContext* context) {
+ return static_cast<AutomaticProfileResetter*>(
+ GetInstance()->GetServiceForBrowserContext(context, true));
+}
+
+// static
+AutomaticProfileResetterFactory*
+AutomaticProfileResetterFactory::GetInstance() {
+ return Singleton<AutomaticProfileResetterFactory>::get();
+}
+
+// static
+void AutomaticProfileResetterFactory::RegisterPrefs(
+ PrefRegistrySimple* registry) {
+ registry->RegisterDictionaryPref(
+ prefs::kProfileResetPromptMementosInLocalState);
+}
+
+AutomaticProfileResetterFactory::AutomaticProfileResetterFactory()
+ : BrowserContextKeyedServiceFactory(
+ "AutomaticProfileResetter",
+ BrowserContextDependencyManager::GetInstance()) {
+ DependsOn(TemplateURLServiceFactory::GetInstance());
+ DependsOn(GlobalErrorServiceFactory::GetInstance());
+}
+
+AutomaticProfileResetterFactory::~AutomaticProfileResetterFactory() {}
+
+KeyedService* AutomaticProfileResetterFactory::BuildServiceInstanceFor(
+ content::BrowserContext* context) const {
+ Profile* profile = Profile::FromBrowserContext(context);
+ AutomaticProfileResetter* service = new AutomaticProfileResetter(profile);
+ service->Initialize();
+ service->Activate();
+ return service;
+}
+
+void AutomaticProfileResetterFactory::RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) {
+ registry->RegisterStringPref(
+ prefs::kProfileResetPromptMementoInProfilePrefs,
+ "",
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
+}
+
+bool AutomaticProfileResetterFactory::
+ ServiceIsCreatedWithBrowserContext() const {
+ return true;
+}
+
+bool AutomaticProfileResetterFactory::ServiceIsNULLWhileTesting() const {
+ return true;
+}
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_factory.h b/chrome/browser/profile_resetter/automatic_profile_resetter_factory.h
new file mode 100644
index 0000000..ddd3777
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_factory.h
@@ -0,0 +1,55 @@
+// 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.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_FACTORY_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_FACTORY_H_
+
+#include "base/basictypes.h"
+#include "components/keyed_service/content/browser_context_keyed_service_factory.h"
+
+template <typename T>
+struct DefaultSingletonTraits;
+
+class PrefRegistrySimple;
+
+namespace content {
+class BrowserContext;
+}
+
+namespace user_prefs {
+class PrefRegistrySyncable;
+}
+
+class AutomaticProfileResetter;
+
+class AutomaticProfileResetterFactory
+ : public BrowserContextKeyedServiceFactory {
+ public:
+ static AutomaticProfileResetter* GetForBrowserContext(
+ content::BrowserContext* context);
+ static AutomaticProfileResetterFactory* GetInstance();
+
+ // Registers Local State preferences.
+ static void RegisterPrefs(PrefRegistrySimple* registry);
+
+ private:
+ friend struct DefaultSingletonTraits<AutomaticProfileResetterFactory>;
+
+ AutomaticProfileResetterFactory();
+ virtual ~AutomaticProfileResetterFactory();
+
+ // BrowserContextKeyedServiceFactory:
+ virtual KeyedService* BuildServiceInstanceFor(
+ content::BrowserContext* context) const OVERRIDE;
+
+ // BrowserContextKeyedBaseFactory:
+ virtual void RegisterProfilePrefs(
+ user_prefs::PrefRegistrySyncable* registry) OVERRIDE;
+ virtual bool ServiceIsCreatedWithBrowserContext() const OVERRIDE;
+ virtual bool ServiceIsNULLWhileTesting() const OVERRIDE;
+
+ DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetterFactory);
+};
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_FACTORY_H_
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_mementos.cc b/chrome/browser/profile_resetter/automatic_profile_resetter_mementos.cc
new file mode 100644
index 0000000..a76b98b
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_mementos.cc
@@ -0,0 +1,129 @@
+// 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_mementos.h"
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "base/logging.h"
+#include "base/prefs/pref_service.h"
+#include "base/prefs/scoped_user_pref_update.h"
+#include "base/values.h"
+#include "chrome/browser/browser_process.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/common/chrome_constants.h"
+#include "chrome/common/pref_names.h"
+#include "content/public/browser/browser_thread.h"
+
+using base::DictionaryValue;
+
+
+// AutomaticProfileResetter::PreferenceHostedPromptMemento -------------------
+
+PreferenceHostedPromptMemento::PreferenceHostedPromptMemento(Profile* profile)
+ : profile_(profile) {}
+
+PreferenceHostedPromptMemento::~PreferenceHostedPromptMemento() {}
+
+std::string PreferenceHostedPromptMemento::ReadValue() const {
+ PrefService* prefs = profile_->GetPrefs();
+ DCHECK(prefs);
+ return prefs->GetString(prefs::kProfileResetPromptMementoInProfilePrefs);
+}
+
+void PreferenceHostedPromptMemento::StoreValue(const std::string& value) {
+ PrefService* prefs = profile_->GetPrefs();
+ DCHECK(prefs);
+ prefs->SetString(prefs::kProfileResetPromptMementoInProfilePrefs, value);
+}
+
+
+// AutomaticProfileResetter::LocalStateHostedPromptMemento -------------------
+
+LocalStateHostedPromptMemento::LocalStateHostedPromptMemento(Profile* profile)
+ : profile_(profile) {}
+
+LocalStateHostedPromptMemento::~LocalStateHostedPromptMemento() {}
+
+std::string LocalStateHostedPromptMemento::ReadValue() const {
+ PrefService* local_state = g_browser_process->local_state();
+ DCHECK(local_state);
+
+ const base::DictionaryValue* prompt_shown_dict = local_state->GetDictionary(
+ prefs::kProfileResetPromptMementosInLocalState);
+ std::string profile_key = GetProfileKey();
+ if (!prompt_shown_dict || profile_key.empty()) {
+ NOTREACHED();
+ return std::string();
+ }
+ std::string value;
+ return prompt_shown_dict->GetStringWithoutPathExpansion(profile_key, &value) ?
+ value : std::string();
+}
+
+void LocalStateHostedPromptMemento::StoreValue(const std::string& value) {
+ PrefService* local_state = g_browser_process->local_state();
+ DCHECK(local_state);
+
+ DictionaryPrefUpdate prompt_shown_dict_update(
+ local_state, prefs::kProfileResetPromptMementosInLocalState);
+ std::string profile_key = GetProfileKey();
+ if (profile_key.empty()) {
+ NOTREACHED();
+ return;
+ }
+ prompt_shown_dict_update.Get()->SetStringWithoutPathExpansion(profile_key,
+ value);
+}
+
+std::string LocalStateHostedPromptMemento::GetProfileKey() const {
+ return profile_->GetPath().BaseName().MaybeAsASCII();
+}
+
+
+// AutomaticProfileResetter::FileHostedPromptMemento -------------------------
+
+FileHostedPromptMemento::FileHostedPromptMemento(Profile* profile)
+ : profile_(profile) {}
+
+FileHostedPromptMemento::~FileHostedPromptMemento() {}
+
+void FileHostedPromptMemento::ReadValue(
+ const ReadValueCallback& callback) const {
+ base::FilePath path = GetMementoFilePath();
+ content::BrowserThread::PostTaskAndReplyWithResult(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&ReadValueOnFileThread, path),
+ callback);
+}
+
+void FileHostedPromptMemento::StoreValue(const std::string& value) {
+ base::FilePath path = GetMementoFilePath();
+ content::BrowserThread::PostTask(
+ content::BrowserThread::FILE,
+ FROM_HERE,
+ base::Bind(&StoreValueOnFileThread, path, value));
+}
+
+std::string FileHostedPromptMemento::ReadValueOnFileThread(
+ const base::FilePath& memento_file_path) {
+ std::string value;
+ base::ReadFileToString(memento_file_path, &value);
+ return value;
+}
+
+void FileHostedPromptMemento::StoreValueOnFileThread(
+ const base::FilePath& memento_file_path,
+ const std::string& value) {
+ int retval =
+ base::WriteFile(memento_file_path, value.c_str(), value.size());
+ DCHECK_EQ(retval, (int)value.size());
+}
+
+base::FilePath FileHostedPromptMemento::GetMementoFilePath() const {
+ return profile_->GetPath().Append(chrome::kResetPromptMementoFilename);
+}
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_mementos.h b/chrome/browser/profile_resetter/automatic_profile_resetter_mementos.h
new file mode 100644
index 0000000..025ffb2
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_mementos.h
@@ -0,0 +1,101 @@
+// 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.
+
+// The classes in this file are alternative implementations of the concept of a
+// "prompt memento", a token of some kind that gets stored when we show the
+// one-time profile reset prompt, and which then serves as a reminder that we
+// should not show the prompt again.
+//
+// In an ideal world, a single implementation would suffice, however, we expect
+// that third party software might accidentally interfere with some of these
+// methods. We need this redundancy because we want to make absolutely sure that
+// we do not annoy the user with the prompt multiple times.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_MEMENTOS_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_MEMENTOS_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/callback.h"
+
+namespace base {
+class FilePath;
+}
+
+class Profile;
+
+// This class is a wrapper around the user preference that gets stored when we
+// show the one-time profile reset prompt, and which is kept as a reminder that
+// we should not show the prompt again.
+class PreferenceHostedPromptMemento {
+ public:
+ explicit PreferenceHostedPromptMemento(Profile* profile);
+ ~PreferenceHostedPromptMemento();
+
+ std::string ReadValue() const;
+ void StoreValue(const std::string& value);
+
+ private:
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(PreferenceHostedPromptMemento);
+};
+
+// This class is a wrapper around the Local State preference that gets stored
+// when we show the one-time profile reset prompt, and which is kept as a
+// reminder that we should not show the prompt again.
+class LocalStateHostedPromptMemento {
+ public:
+ explicit LocalStateHostedPromptMemento(Profile* profile);
+ ~LocalStateHostedPromptMemento();
+
+ std::string ReadValue() const;
+ void StoreValue(const std::string& value);
+
+ private:
+ // Returns the key that shall be used in the dictionary preference in Local
+ // State to uniquely identify this profile.
+ std::string GetProfileKey() const;
+
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(LocalStateHostedPromptMemento);
+};
+
+// This class manages a marker file that gets stored when we show the one-time
+// profile reset prompt, and which is kept as a reminder that we should not show
+// the prompt again.
+class FileHostedPromptMemento {
+ public:
+ typedef base::Callback<void(const std::string&)> ReadValueCallback;
+
+ explicit FileHostedPromptMemento(Profile* profile);
+ ~FileHostedPromptMemento();
+
+ // Posts to the FILE thread to read the value, then returns the value to the
+ // calling thread. It is safe to destroy this object as soon as this method
+ // (synchronously) returns.
+ void ReadValue(const ReadValueCallback& callback) const;
+
+ // Asynchronously stores the value on the FILE thread. However, it is safe to
+ // destroy this object as soon as this method (synchronously) returns.
+ void StoreValue(const std::string& value);
+
+ private:
+ static std::string ReadValueOnFileThread(
+ const base::FilePath& memento_file_path);
+ static void StoreValueOnFileThread(const base::FilePath& memento_file_path,
+ const std::string& value);
+
+ // Returns the path to the file that shall be used to store this kind of
+ // memento for this profile.
+ base::FilePath GetMementoFilePath() const;
+
+ Profile* profile_;
+
+ DISALLOW_COPY_AND_ASSIGN(FileHostedPromptMemento);
+};
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_AUTOMATIC_PROFILE_RESETTER_MEMENTOS_H_
diff --git a/chrome/browser/profile_resetter/automatic_profile_resetter_unittest.cc b/chrome/browser/profile_resetter/automatic_profile_resetter_unittest.cc
new file mode 100644
index 0000000..ad6ed03
--- /dev/null
+++ b/chrome/browser/profile_resetter/automatic_profile_resetter_unittest.cc
@@ -0,0 +1,1380 @@
+// 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 <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/metrics/field_trial.h"
+#include "base/prefs/pref_registry_simple.h"
+#include "base/prefs/testing_pref_service.h"
+#include "base/run_loop.h"
+#include "base/test/test_simple_task_runner.h"
+#include "base/threading/sequenced_worker_pool.h"
+#include "base/values.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_delegate.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_factory.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_mementos.h"
+#include "chrome/browser/profile_resetter/jtl_foundation.h"
+#include "chrome/browser/profile_resetter/jtl_instructions.h"
+#include "chrome/test/base/scoped_testing_local_state.h"
+#include "chrome/test/base/testing_browser_process.h"
+#include "chrome/test/base/testing_pref_service_syncable.h"
+#include "chrome/test/base/testing_profile.h"
+#include "components/pref_registry/pref_registry_syncable.h"
+#include "components/variations/variations_associated_data.h"
+#include "content/public/browser/browser_thread.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+using testing::_;
+
+namespace {
+
+const char kAutomaticProfileResetStudyName[] = "AutomaticProfileReset";
+const char kStudyDisabledGroupName[] = "Disabled";
+const char kStudyDryRunGroupName[] = "DryRun";
+const char kStudyEnabledGroupName[] = "Enabled";
+
+const char kTestHashSeed[] = "testing-hash-seed";
+const char kTestMementoValue[] = "01234567890123456789012345678901";
+const char kTestInvalidMementoValue[] = "12345678901234567890123456789012";
+
+const char kTestPreferencePath[] = "testing.preference";
+const char kTestPreferenceValue[] = "testing-preference-value";
+
+const char kSearchURLAttributeKey[] = "search_url";
+const char kTestSearchURL[] = "http://example.com/search?q={searchTerms}";
+const char kTestSearchURL2[] = "http://google.com/?q={searchTerms}";
+
+const char kTestModuleDigest[] = "01234567890123456789012345678901";
+const char kTestModuleDigest2[] = "12345678901234567890123456789012";
+
+// Helpers ------------------------------------------------------------------
+
+// A testing version of the AutomaticProfileResetter that differs from the real
+// one only in that it has its statistics reporting mocked out for verification.
+class AutomaticProfileResetterUnderTest : public AutomaticProfileResetter {
+ public:
+ explicit AutomaticProfileResetterUnderTest(Profile* profile)
+ : AutomaticProfileResetter(profile) {}
+ virtual ~AutomaticProfileResetterUnderTest() {}
+
+ MOCK_METHOD2(ReportStatistics, void(uint32, uint32));
+ MOCK_METHOD1(ReportPromptResult,
+ void(AutomaticProfileResetter::PromptResult));
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetterUnderTest);
+};
+
+class MockProfileResetterDelegate : public AutomaticProfileResetterDelegate {
+ public:
+ MockProfileResetterDelegate()
+ : emulated_is_default_search_provider_managed_(false) {}
+ virtual ~MockProfileResetterDelegate() {}
+
+ MOCK_METHOD0(EnumerateLoadedModulesIfNeeded, void());
+ MOCK_CONST_METHOD1(RequestCallbackWhenLoadedModulesAreEnumerated,
+ void(const base::Closure&));
+
+ MOCK_METHOD0(LoadTemplateURLServiceIfNeeded, void());
+ MOCK_CONST_METHOD1(RequestCallbackWhenTemplateURLServiceIsLoaded,
+ void(const base::Closure&));
+
+ MOCK_METHOD0(FetchBrandcodedDefaultSettingsIfNeeded, void());
+ MOCK_CONST_METHOD1(RequestCallbackWhenBrandcodedDefaultsAreFetched,
+ void(const base::Closure&));
+
+ MOCK_CONST_METHOD0(OnGetLoadedModuleNameDigestsCalled, void());
+ MOCK_CONST_METHOD0(OnGetDefaultSearchProviderDetailsCalled, void());
+ MOCK_CONST_METHOD0(OnIsDefaultSearchProviderManagedCalled, void());
+ MOCK_CONST_METHOD0(OnGetPrepopulatedSearchProvidersDetailsCalled, void());
+
+ MOCK_METHOD0(TriggerPrompt, bool());
+ MOCK_METHOD2(TriggerProfileSettingsReset, void(bool, const base::Closure&));
+ MOCK_METHOD0(DismissPrompt, void());
+
+ virtual scoped_ptr<base::ListValue>
+ GetLoadedModuleNameDigests() const OVERRIDE {
+ OnGetLoadedModuleNameDigestsCalled();
+ return scoped_ptr<base::ListValue>(
+ emulated_loaded_module_digests_.DeepCopy());
+ }
+
+ virtual scoped_ptr<base::DictionaryValue>
+ GetDefaultSearchProviderDetails() const OVERRIDE {
+ OnGetDefaultSearchProviderDetailsCalled();
+ return scoped_ptr<base::DictionaryValue>(
+ emulated_default_search_provider_details_.DeepCopy());
+ }
+
+ virtual bool IsDefaultSearchProviderManaged() const OVERRIDE {
+ OnIsDefaultSearchProviderManagedCalled();
+ return emulated_is_default_search_provider_managed_;
+ }
+
+ virtual scoped_ptr<base::ListValue>
+ GetPrepopulatedSearchProvidersDetails() const OVERRIDE {
+ OnGetPrepopulatedSearchProvidersDetailsCalled();
+ return scoped_ptr<base::ListValue>(
+ emulated_search_providers_details_.DeepCopy());
+ }
+
+ static void ClosureInvoker(const base::Closure& closure) { closure.Run(); }
+
+ void ExpectCallsToDependenciesSetUpMethods() {
+ EXPECT_CALL(*this, EnumerateLoadedModulesIfNeeded());
+ EXPECT_CALL(*this, LoadTemplateURLServiceIfNeeded());
+ EXPECT_CALL(*this, RequestCallbackWhenLoadedModulesAreEnumerated(_))
+ .WillOnce(testing::Invoke(ClosureInvoker));
+ EXPECT_CALL(*this, RequestCallbackWhenTemplateURLServiceIsLoaded(_))
+ .WillOnce(testing::Invoke(ClosureInvoker));
+ }
+
+ void ExpectCallsToGetterMethods() {
+ EXPECT_CALL(*this, OnGetLoadedModuleNameDigestsCalled());
+ EXPECT_CALL(*this, OnGetDefaultSearchProviderDetailsCalled());
+ EXPECT_CALL(*this, OnIsDefaultSearchProviderManagedCalled());
+ EXPECT_CALL(*this, OnGetPrepopulatedSearchProvidersDetailsCalled());
+ }
+
+ void ExpectCallToShowPrompt() {
+ EXPECT_CALL(*this, TriggerPrompt()).WillOnce(testing::Return(true));
+ EXPECT_CALL(*this, FetchBrandcodedDefaultSettingsIfNeeded());
+ }
+
+ void ExpectCallToTriggerReset(bool send_feedback) {
+ EXPECT_CALL(*this, TriggerProfileSettingsReset(send_feedback, _))
+ .WillOnce(testing::SaveArg<1>(&reset_completion_));
+ }
+
+ base::DictionaryValue& emulated_default_search_provider_details() {
+ return emulated_default_search_provider_details_;
+ }
+
+ base::ListValue& emulated_search_providers_details() {
+ return emulated_search_providers_details_;
+ }
+
+ base::ListValue& emulated_loaded_module_digests() {
+ return emulated_loaded_module_digests_;
+ }
+
+ void set_emulated_is_default_search_provider_managed(bool value) {
+ emulated_is_default_search_provider_managed_ = value;
+ }
+
+ void EmulateProfileResetCompleted() {
+ reset_completion_.Run();
+ }
+
+ private:
+ base::DictionaryValue emulated_default_search_provider_details_;
+ base::ListValue emulated_search_providers_details_;
+ base::ListValue emulated_loaded_module_digests_;
+ bool emulated_is_default_search_provider_managed_;
+ base::Closure reset_completion_;
+
+ DISALLOW_COPY_AND_ASSIGN(MockProfileResetterDelegate);
+};
+
+class FileHostedPromptMementoSynchronous : protected FileHostedPromptMemento {
+ public:
+ explicit FileHostedPromptMementoSynchronous(Profile* profile)
+ : FileHostedPromptMemento(profile) {}
+
+ std::string ReadValue() const {
+ std::string result;
+ FileHostedPromptMemento::ReadValue(base::Bind(&AssignArgumentTo, &result));
+ base::RunLoop().RunUntilIdle();
+ return result;
+ }
+
+ void StoreValue(const std::string& value) {
+ FileHostedPromptMemento::StoreValue(value);
+ base::RunLoop().RunUntilIdle();
+ }
+
+ private:
+ static void AssignArgumentTo(std::string* target, const std::string& value) {
+ *target = value;
+ }
+
+ DISALLOW_COPY_AND_ASSIGN(FileHostedPromptMementoSynchronous);
+};
+
+std::string GetHash(const std::string& input) {
+ return jtl_foundation::Hasher(kTestHashSeed).GetHash(input);
+}
+
+// Encodes a Boolean argument value into JTL bytecode.
+std::string EncodeBool(bool value) { return value ? VALUE_TRUE : VALUE_FALSE; }
+
+// Constructs a simple evaluation program to test that basic input/output works
+// well. It will emulate a scenario in which the reset criteria are satisfied as
+// prescribed by |emulate_satisfied_criterion_{1|2}|, and the reset is triggered
+// when either of them is true. The bits in the combined status mask will be set
+// according to whether or not the memento values received in the input were as
+// expected.
+//
+// More specifically, the output of the program will be as follows:
+// {
+// "satisfied_criteria_mask_bit1": emulate_satisfied_criterion_1,
+// "satisfied_criteria_mask_bit2": emulate_satisfied_criterion_2,
+// "combined_status_mask_bit1":
+// (emulate_satisfied_criterion_1 || emulate_satisfied_criterion_2),
+// "combined_status_mask_bit2":
+// (input["memento_value_in_prefs"] == kTestMementoValue),
+// "combined_status_mask_bit3":
+// (input["memento_value_in_local_state"] == kTestMementoValue),
+// "combined_status_mask_bit4":
+// (input["memento_value_in_file"] == kTestMementoValue),
+// "should_prompt":
+// (emulate_satisfied_criterion_1 || emulate_satisfied_criterion_2),
+// "had_prompted_already": <OR-combination of above three>,
+// "memento_value_in_prefs": kTestMementoValue,
+// "memento_value_in_local_state": kTestMementoValue,
+// "memento_value_in_file": kTestMementoValue
+// }
+std::string ConstructProgram(bool emulate_satisfied_criterion_1,
+ bool emulate_satisfied_criterion_2) {
+ std::string bytecode;
+ bytecode += OP_STORE_BOOL(GetHash("satisfied_criteria_mask_bit1"),
+ EncodeBool(emulate_satisfied_criterion_1));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("satisfied_criteria_mask_bit2"),
+ EncodeBool(emulate_satisfied_criterion_2));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("should_prompt"),
+ EncodeBool(emulate_satisfied_criterion_1 ||
+ emulate_satisfied_criterion_2));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit1"),
+ EncodeBool(emulate_satisfied_criterion_1 ||
+ emulate_satisfied_criterion_2));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("memento_value_in_prefs"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestMementoValue));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit2"), VALUE_TRUE);
+ bytecode += OP_STORE_BOOL(GetHash("had_prompted_already"), VALUE_TRUE);
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("memento_value_in_local_state"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestMementoValue));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit3"), VALUE_TRUE);
+ bytecode += OP_STORE_BOOL(GetHash("had_prompted_already"), VALUE_TRUE);
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("memento_value_in_file"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestMementoValue));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit4"), VALUE_TRUE);
+ bytecode += OP_STORE_BOOL(GetHash("had_prompted_already"), VALUE_TRUE);
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_HASH(GetHash("memento_value_in_prefs"),
+ kTestMementoValue);
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_HASH(GetHash("memento_value_in_local_state"),
+ kTestMementoValue);
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_HASH(GetHash("memento_value_in_file"),
+ kTestMementoValue);
+ bytecode += OP_END_OF_SENTENCE;
+ return bytecode;
+}
+
+// Constructs another evaluation program to specifically test that bits of the
+// "satisfied_criteria_mask" are correctly assigned, and so is "should_prompt";
+// and that reset is triggered iff the latter is true, regardless of the bits
+// in the mask (so as to allow for a non-disjunctive compound criterion).
+//
+// More specifically, the output of the program will be as follows:
+// {
+// "satisfied_criteria_mask_bitN": emulate_satisfied_odd_criteria,
+// "satisfied_criteria_mask_bitM": emulate_satisfied_even_criteria,
+// "combined_status_mask_bit1": emulate_should_prompt,
+// "should_prompt": emulate_should_prompt,
+// "memento_value_in_prefs": kTestMementoValue,
+// "memento_value_in_local_state": kTestMementoValue,
+// "memento_value_in_file": kTestMementoValue
+// }
+// ... such that N is {1,3,5} and M is {2,4}.
+std::string ConstructProgramToExerciseCriteria(
+ bool emulate_should_prompt,
+ bool emulate_satisfied_odd_criteria,
+ bool emulate_satisfied_even_criteria) {
+ std::string bytecode;
+ bytecode += OP_STORE_BOOL(GetHash("satisfied_criteria_mask_bit1"),
+ EncodeBool(emulate_satisfied_odd_criteria));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("satisfied_criteria_mask_bit3"),
+ EncodeBool(emulate_satisfied_odd_criteria));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("satisfied_criteria_mask_bit5"),
+ EncodeBool(emulate_satisfied_odd_criteria));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("satisfied_criteria_mask_bit2"),
+ EncodeBool(emulate_satisfied_even_criteria));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("satisfied_criteria_mask_bit4"),
+ EncodeBool(emulate_satisfied_even_criteria));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("should_prompt"),
+ EncodeBool(emulate_should_prompt));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit1"),
+ EncodeBool(emulate_should_prompt));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_HASH(GetHash("memento_value_in_prefs"),
+ kTestMementoValue);
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_HASH(GetHash("memento_value_in_local_state"),
+ kTestMementoValue);
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_STORE_HASH(GetHash("memento_value_in_file"),
+ kTestMementoValue);
+ bytecode += OP_END_OF_SENTENCE;
+ return bytecode;
+}
+
+// Constructs another evaluation program to specifically test that local state
+// and user preference values are included in the input as expected. We will
+// re-purpose the output bitmasks to channel out information about the outcome
+// of the checks.
+//
+// More specifically, the output of the program will be as follows:
+// {
+// "combined_status_mask_bit1":
+// (input["preferences.testing.preference"] == kTestPreferenceValue)
+// "combined_status_mask_bit2":
+// (input["local_state.testing.preference"] == kTestPreferenceValue)
+// "combined_status_mask_bit3": input["preferences_iuc.testing.preference"]
+// "combined_status_mask_bit4": input["local_state_iuc.testing.preference"]
+// }
+std::string ConstructProgramToCheckPreferences() {
+ std::string bytecode;
+ bytecode += OP_NAVIGATE(GetHash("preferences"));
+ bytecode += OP_NAVIGATE(GetHash("testing"));
+ bytecode += OP_NAVIGATE(GetHash("preference"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestPreferenceValue));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit1"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("local_state"));
+ bytecode += OP_NAVIGATE(GetHash("testing"));
+ bytecode += OP_NAVIGATE(GetHash("preference"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestPreferenceValue));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit2"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("preferences_iuc"));
+ bytecode += OP_NAVIGATE(GetHash("testing"));
+ bytecode += OP_NAVIGATE(GetHash("preference"));
+ bytecode += OP_COMPARE_NODE_BOOL(EncodeBool(true));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit3"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("local_state_iuc"));
+ bytecode += OP_NAVIGATE(GetHash("testing"));
+ bytecode += OP_NAVIGATE(GetHash("preference"));
+ bytecode += OP_COMPARE_NODE_BOOL(EncodeBool(true));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit4"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ return bytecode;
+}
+
+// Legend for the bitmask returned by the above program.
+enum CombinedStatusMaskLegendForCheckingPreferences {
+ HAS_EXPECTED_USER_PREFERENCE = 1 << 0,
+ HAS_EXPECTED_LOCAL_STATE_PREFERENCE = 1 << 1,
+ USER_PREFERENCE_IS_USER_CONTROLLED = 1 << 2,
+ LOCAL_STATE_IS_USER_CONTROLLED = 1 << 3,
+};
+
+// Constructs yet another evaluation program to specifically test that default
+// and pre-populated search engines are included in the input as expected. We
+// will re-purpose the output bitmasks to channel out information about the
+// outcome of the checks.
+//
+// More specifically, the output of the program will be as follows:
+// {
+// "combined_status_mask_bit1":
+// (input["default_search_provider.search_url"] == kTestSearchURL)
+// "combined_status_mask_bit2": input["default_search_provider_iuc"]
+// "combined_status_mask_bit3":
+// (input["search_providers.*.search_url"] == kTestSearchURL)
+// "combined_status_mask_bit4":
+// (input["search_providers.*.search_url"] == kTestSearchURL2)
+// }
+std::string ConstructProgramToCheckSearchEngines() {
+ std::string bytecode;
+ bytecode += OP_NAVIGATE(GetHash("default_search_provider"));
+ bytecode += OP_NAVIGATE(GetHash("search_url"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestSearchURL));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit1"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("default_search_provider_iuc"));
+ bytecode += OP_COMPARE_NODE_BOOL(EncodeBool(true));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit2"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("search_providers"));
+ bytecode += OP_NAVIGATE_ANY;
+ bytecode += OP_NAVIGATE(GetHash("search_url"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestSearchURL));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit3"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("search_providers"));
+ bytecode += OP_NAVIGATE_ANY;
+ bytecode += OP_NAVIGATE(GetHash("search_url"));
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestSearchURL2));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit4"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ return bytecode;
+}
+
+// Legend for the bitmask returned by the above program.
+enum CombinedStatusMaskLegendForCheckingSearchEngines {
+ HAS_EXPECTED_DEFAULT_SEARCH_PROVIDER = 1 << 0,
+ DEFAULT_SEARCH_PROVIDER_IS_USER_CONTROLLED = 1 << 1,
+ HAS_EXPECTED_PREPOPULATED_SEARCH_PROVIDER_1 = 1 << 2,
+ HAS_EXPECTED_PREPOPULATED_SEARCH_PROVIDER_2 = 1 << 3,
+};
+
+// Constructs yet another evaluation program to specifically test that loaded
+// module digests are included in the input as expected. We will re-purpose the
+// output bitmasks to channel out information about the outcome of the checks.
+//
+// More specifically, the output of the program will be as follows:
+// {
+// "combined_status_mask_bit1":
+// (input["loaded_modules.*"] == kTestModuleDigest)
+// "combined_status_mask_bit2":
+// (input["loaded_modules.*"] == kTestModuleDigest2)
+// }
+std::string ConstructProgramToCheckLoadedModuleDigests() {
+ std::string bytecode;
+ bytecode += OP_NAVIGATE(GetHash("loaded_modules"));
+ bytecode += OP_NAVIGATE_ANY;
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestModuleDigest));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit1"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ bytecode += OP_NAVIGATE(GetHash("loaded_modules"));
+ bytecode += OP_NAVIGATE_ANY;
+ bytecode += OP_COMPARE_NODE_HASH(GetHash(kTestModuleDigest2));
+ bytecode += OP_STORE_BOOL(GetHash("combined_status_mask_bit2"),
+ EncodeBool(true));
+ bytecode += OP_END_OF_SENTENCE;
+ return bytecode;
+}
+
+// Legend for the bitmask returned by the above program.
+enum CombinedStatusMaskLegendForCheckingLoadedModules {
+ HAS_EXPECTED_MODULE_DIGEST_1 = 1 << 0,
+ HAS_EXPECTED_MODULE_DIGEST_2 = 1 << 1,
+};
+
+// Test fixtures -------------------------------------------------------------
+
+class AutomaticProfileResetterTestBase : public testing::Test {
+ protected:
+ explicit AutomaticProfileResetterTestBase(
+ const std::string& experiment_group_name)
+ : waiting_task_runner_(new base::TestSimpleTaskRunner),
+ local_state_(TestingBrowserProcess::GetGlobal()),
+ profile_(new TestingProfile()),
+ field_trials_(new base::FieldTrialList(NULL)),
+ memento_in_prefs_(new PreferenceHostedPromptMemento(profile())),
+ memento_in_local_state_(new LocalStateHostedPromptMemento(profile())),
+ memento_in_file_(new FileHostedPromptMementoSynchronous(profile())),
+ experiment_group_name_(experiment_group_name),
+ inject_data_through_variation_params_(false),
+ mock_delegate_(NULL) {
+ // Make sure the factory is not optimized away, so whatever preferences it
+ // wants to register will actually get registered.
+ AutomaticProfileResetterFactory::GetInstance();
+
+ // Register some additional local state preferences for testing purposes.
+ PrefRegistrySimple* local_state_registry = local_state_.Get()->registry();
+ DCHECK(local_state_registry);
+ local_state_registry->RegisterStringPref(kTestPreferencePath, "");
+
+ // Register some additional user preferences for testing purposes.
+ user_prefs::PrefRegistrySyncable* user_prefs_registry =
+ profile_->GetTestingPrefService()->registry();
+ DCHECK(user_prefs_registry);
+ user_prefs_registry->RegisterStringPref(
+ kTestPreferencePath, std::string(),
+ user_prefs::PrefRegistrySyncable::UNSYNCABLE_PREF);
+ }
+
+ virtual void SetUp() OVERRIDE {
+ variations::testing::ClearAllVariationParams();
+ base::FieldTrialList::CreateFieldTrial(kAutomaticProfileResetStudyName,
+ experiment_group_name_);
+ resetter_.reset(
+ new testing::StrictMock<AutomaticProfileResetterUnderTest>(profile()));
+ mock_delegate_owned_.reset(
+ new testing::StrictMock<MockProfileResetterDelegate>());
+ mock_delegate_ = mock_delegate_owned_.get();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+ }
+
+ void SetTestingHashSeed(const std::string& hash_seed) {
+ testing_hash_seed_ = hash_seed;
+ }
+
+ void SetTestingProgram(const std::string& source_code) {
+ testing_program_ = source_code;
+ }
+
+ void AllowInjectingTestDataThroughVariationParams(bool value) {
+ inject_data_through_variation_params_ = value;
+ }
+
+ void ExpectAllMementoValuesEqualTo(const std::string& value) {
+ EXPECT_EQ(value, memento_in_prefs_->ReadValue());
+ EXPECT_EQ(value, memento_in_local_state_->ReadValue());
+ EXPECT_EQ(value, memento_in_file_->ReadValue());
+ }
+
+ void UnleashResetterAndWait() {
+ if (inject_data_through_variation_params_) {
+ std::map<std::string, std::string> variation_params;
+ variation_params["program"] = testing_program_;
+ variation_params["hash_seed"] = testing_hash_seed_;
+ ASSERT_TRUE(variations::AssociateVariationParams(
+ kAutomaticProfileResetStudyName,
+ experiment_group_name_,
+ variation_params));
+ }
+ resetter_->Initialize();
+ resetter_->SetDelegateForTesting(
+ mock_delegate_owned_.PassAs<AutomaticProfileResetterDelegate>());
+ resetter_->SetTaskRunnerForWaitingForTesting(waiting_task_runner_);
+ if (!inject_data_through_variation_params_) {
+ resetter_->SetProgramForTesting(testing_program_);
+ resetter_->SetHashSeedForTesting(testing_hash_seed_);
+ }
+ resetter_->Activate();
+
+ if (waiting_task_runner_->HasPendingTask()) {
+ ASSERT_EQ(base::TimeDelta::FromSeconds(55),
+ waiting_task_runner_->NextPendingTaskDelay());
+ waiting_task_runner_->RunPendingTasks();
+ }
+ base::RunLoop().RunUntilIdle();
+ content::BrowserThread::GetBlockingPool()->FlushForTesting();
+ base::RunLoop().RunUntilIdle();
+ }
+
+ // Goes through an evaluation flow such that the reset criteria are satisfied.
+ // Used to reduce boilerplate for tests that need to verify behavior during
+ // the reset prompt flow.
+ void OrchestrateThroughEvaluationFlow() {
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ mock_delegate().ExpectCallToShowPrompt();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x01u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+ }
+
+ // Explicitly shut down the service to double-check that nothing explodes, but
+ // first, verify expectations to make sure the service makes no more calls to
+ // any mocked functions during or after shutdown.
+ void VerifyExpectationsThenShutdownResetter() {
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+
+ resetter_->Shutdown();
+ resetter_.reset();
+ }
+
+ TestingProfile* profile() { return profile_.get(); }
+ TestingPrefServiceSimple* local_state() { return local_state_.Get(); }
+
+ PreferenceHostedPromptMemento& memento_in_prefs() {
+ return *memento_in_prefs_;
+ }
+
+ LocalStateHostedPromptMemento& memento_in_local_state() {
+ return *memento_in_local_state_;
+ }
+
+ FileHostedPromptMementoSynchronous& memento_in_file() {
+ return *memento_in_file_;
+ }
+
+ MockProfileResetterDelegate& mock_delegate() { return *mock_delegate_; }
+ AutomaticProfileResetterUnderTest& resetter() { return *resetter_; }
+
+ private:
+ content::TestBrowserThreadBundle thread_bundle_;
+ scoped_refptr<base::TestSimpleTaskRunner> waiting_task_runner_;
+ ScopedTestingLocalState local_state_;
+ scoped_ptr<TestingProfile> profile_;
+ scoped_ptr<base::FieldTrialList> field_trials_;
+ scoped_ptr<PreferenceHostedPromptMemento> memento_in_prefs_;
+ scoped_ptr<LocalStateHostedPromptMemento> memento_in_local_state_;
+ scoped_ptr<FileHostedPromptMementoSynchronous> memento_in_file_;
+
+ std::string experiment_group_name_;
+ std::string testing_program_;
+ std::string testing_hash_seed_;
+ bool inject_data_through_variation_params_;
+
+ scoped_ptr<AutomaticProfileResetterUnderTest> resetter_;
+ scoped_ptr<MockProfileResetterDelegate> mock_delegate_owned_;
+ MockProfileResetterDelegate* mock_delegate_;
+
+ DISALLOW_COPY_AND_ASSIGN(AutomaticProfileResetterTestBase);
+};
+
+class AutomaticProfileResetterTest : public AutomaticProfileResetterTestBase {
+ protected:
+ AutomaticProfileResetterTest()
+ : AutomaticProfileResetterTestBase(kStudyEnabledGroupName) {}
+};
+
+class AutomaticProfileResetterTestDryRun
+ : public AutomaticProfileResetterTestBase {
+ protected:
+ AutomaticProfileResetterTestDryRun()
+ : AutomaticProfileResetterTestBase(kStudyDryRunGroupName) {}
+};
+
+class AutomaticProfileResetterTestDisabled
+ : public AutomaticProfileResetterTestBase {
+ protected:
+ AutomaticProfileResetterTestDisabled()
+ : AutomaticProfileResetterTestBase(kStudyDisabledGroupName) {}
+};
+
+// Tests ---------------------------------------------------------------------
+
+TEST_F(AutomaticProfileResetterTestDisabled, NothingIsDoneWhenDisabled) {
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ // No calls are expected to the delegate.
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+}
+
+TEST_F(AutomaticProfileResetterTestDryRun, CriteriaNotSatisfied) {
+ SetTestingProgram(ConstructProgramToExerciseCriteria(false, true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x1fu, 0x00u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+}
+
+TEST_F(AutomaticProfileResetterTestDryRun, OddCriteriaSatisfied) {
+ SetTestingProgram(ConstructProgramToExerciseCriteria(true, true, false));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x15u, 0x01u));
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_NOT_TRIGGERED));
+
+ UnleashResetterAndWait();
+
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTestDryRun, EvenCriteriaSatisfied) {
+ SetTestingProgram(ConstructProgramToExerciseCriteria(true, false, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x0au, 0x01u));
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_NOT_TRIGGERED));
+
+ UnleashResetterAndWait();
+
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+#if defined(GOOGLE_CHROME_BUILD)
+TEST_F(AutomaticProfileResetterTestDryRun, ProgramSetThroughVariationParams) {
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+ AllowInjectingTestDataThroughVariationParams(true);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x01u));
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_NOT_TRIGGERED));
+
+ UnleashResetterAndWait();
+
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+#endif
+
+TEST_F(AutomaticProfileResetterTestDryRun,
+ ConditionsSatisfiedAndInvalidMementos) {
+ memento_in_prefs().StoreValue(kTestInvalidMementoValue);
+ memento_in_local_state().StoreValue(kTestInvalidMementoValue);
+ memento_in_file().StoreValue(kTestInvalidMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x01u));
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_NOT_TRIGGERED));
+
+ UnleashResetterAndWait();
+
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTestDryRun, AlreadyHadPrefHostedMemento) {
+ memento_in_prefs().StoreValue(kTestMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x03u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ EXPECT_EQ(kTestMementoValue, memento_in_prefs().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_local_state().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_file().ReadValue());
+}
+
+TEST_F(AutomaticProfileResetterTestDryRun, AlreadyHadLocalStateHostedMemento) {
+ memento_in_local_state().StoreValue(kTestMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x05u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ EXPECT_EQ(std::string(), memento_in_prefs().ReadValue());
+ EXPECT_EQ(kTestMementoValue, memento_in_local_state().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_file().ReadValue());
+}
+
+TEST_F(AutomaticProfileResetterTestDryRun, AlreadyHadFileHostedMemento) {
+ memento_in_file().StoreValue(kTestMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x09u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ EXPECT_EQ(std::string(), memento_in_prefs().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_local_state().ReadValue());
+ EXPECT_EQ(kTestMementoValue, memento_in_file().ReadValue());
+}
+
+TEST_F(AutomaticProfileResetterTestDryRun, DoNothingWhenResourcesAreMissing) {
+ SetTestingProgram(std::string());
+ SetTestingHashSeed(std::string());
+
+ // No calls are expected to the delegate.
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+}
+
+TEST_F(AutomaticProfileResetterTest, CriteriaNotSatisfied) {
+ SetTestingProgram(ConstructProgramToExerciseCriteria(false, true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x1fu, 0x00u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+}
+
+TEST_F(AutomaticProfileResetterTest, OddCriteriaSatisfied) {
+ SetTestingProgram(ConstructProgramToExerciseCriteria(true, true, false));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ mock_delegate().ExpectCallToShowPrompt();
+ EXPECT_CALL(resetter(), ReportStatistics(0x15u, 0x01u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, EvenCriteriaSatisfied) {
+ SetTestingProgram(ConstructProgramToExerciseCriteria(true, false, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ mock_delegate().ExpectCallToShowPrompt();
+ EXPECT_CALL(resetter(), ReportStatistics(0x0au, 0x01u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+#if defined(GOOGLE_CHROME_BUILD)
+TEST_F(AutomaticProfileResetterTest, ProgramSetThroughVariationParams) {
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+ AllowInjectingTestDataThroughVariationParams(true);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ mock_delegate().ExpectCallToShowPrompt();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x01u));
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+
+ UnleashResetterAndWait();
+ resetter().NotifyDidShowResetBubble();
+
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+#endif
+
+TEST_F(AutomaticProfileResetterTest, ConditionsSatisfiedAndInvalidMementos) {
+ memento_in_prefs().StoreValue(kTestInvalidMementoValue);
+ memento_in_local_state().StoreValue(kTestInvalidMementoValue);
+ memento_in_file().StoreValue(kTestInvalidMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ mock_delegate().ExpectCallToShowPrompt();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x01u));
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+
+ UnleashResetterAndWait();
+ resetter().NotifyDidShowResetBubble();
+
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PrefHostedMementoPreventsPrompt) {
+ memento_in_prefs().StoreValue(kTestMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x03u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ EXPECT_EQ(kTestMementoValue, memento_in_prefs().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_local_state().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_file().ReadValue());
+}
+
+TEST_F(AutomaticProfileResetterTest, LocalStateHostedMementoPreventsPrompt) {
+ memento_in_local_state().StoreValue(kTestMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x05u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ EXPECT_EQ(std::string(), memento_in_prefs().ReadValue());
+ EXPECT_EQ(kTestMementoValue, memento_in_local_state().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_file().ReadValue());
+}
+
+TEST_F(AutomaticProfileResetterTest, FileHostedMementoPreventsPrompt) {
+ memento_in_file().StoreValue(kTestMementoValue);
+
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x09u));
+
+ UnleashResetterAndWait();
+
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ EXPECT_EQ(std::string(), memento_in_prefs().ReadValue());
+ EXPECT_EQ(std::string(), memento_in_local_state().ReadValue());
+ EXPECT_EQ(kTestMementoValue, memento_in_file().ReadValue());
+}
+
+TEST_F(AutomaticProfileResetterTest, DoNothingWhenResourcesAreMissing) {
+ SetTestingProgram(std::string());
+ SetTestingHashSeed(std::string());
+
+ // No calls are expected to the delegate.
+
+ UnleashResetterAndWait();
+
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptSuppressed) {
+ OrchestrateThroughEvaluationFlow();
+
+ VerifyExpectationsThenShutdownResetter();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptNotSupported) {
+ SetTestingProgram(ConstructProgram(true, true));
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ EXPECT_CALL(mock_delegate(), TriggerPrompt())
+ .WillOnce(testing::Return(false));
+ EXPECT_CALL(resetter(), ReportStatistics(0x03u, 0x01u));
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_NOT_TRIGGERED));
+
+ UnleashResetterAndWait();
+
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptIgnored) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptActionReset) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+
+ mock_delegate().ExpectCallToTriggerReset(false);
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_ACTION_RESET));
+ resetter().TriggerProfileReset(false /*send_feedback*/);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ mock_delegate().EmulateProfileResetCompleted();
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptActionResetWithFeedback) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+
+ mock_delegate().ExpectCallToTriggerReset(true);
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_ACTION_RESET));
+ resetter().TriggerProfileReset(true /*send_feedback*/);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ mock_delegate().EmulateProfileResetCompleted();
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptActionNoReset) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_ACTION_NO_RESET));
+ resetter().SkipProfileReset();
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptFollowedByWebUIReset) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ resetter().NotifyDidOpenWebUIResetDialog();
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_FOLLOWED_BY_WEBUI_RESET));
+ resetter().NotifyDidCloseWebUIResetDialog(true);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptFollowedByWebUINoReset) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ resetter().NotifyDidOpenWebUIResetDialog();
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_FOLLOWED_BY_WEBUI_NO_RESET));
+ resetter().NotifyDidCloseWebUIResetDialog(false);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptFollowedByIncidentalWebUIReset) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+
+ // Missing NotifyDidOpenWebUIResetDialog().
+ // This can arise if a settings page was already opened at the time the prompt
+ // was triggered, and this already opened dialog was used to initiate a reset
+ // after having dismissed the prompt.
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_FOLLOWED_BY_WEBUI_RESET));
+ resetter().NotifyDidCloseWebUIResetDialog(true);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptSuppressedButHadWebUIReset) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ resetter().NotifyDidOpenWebUIResetDialog();
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_NOT_SHOWN_BUBBLE_BUT_HAD_WEBUI_RESET));
+ resetter().NotifyDidCloseWebUIResetDialog(true);
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, PromptSuppressedButHadWebUINoReset) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(mock_delegate(), DismissPrompt());
+ resetter().NotifyDidOpenWebUIResetDialog();
+ testing::Mock::VerifyAndClearExpectations(&mock_delegate());
+
+ EXPECT_CALL(resetter(), ReportPromptResult(AutomaticProfileResetter::
+ PROMPT_NOT_SHOWN_BUBBLE_BUT_HAD_WEBUI_NO_RESET));
+ resetter().NotifyDidCloseWebUIResetDialog(false);
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ EXPECT_TRUE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, BannerDismissed) {
+ OrchestrateThroughEvaluationFlow();
+
+ EXPECT_CALL(resetter(), ReportPromptResult(
+ AutomaticProfileResetter::PROMPT_SHOWN_BUBBLE));
+ resetter().NotifyDidShowResetBubble();
+ ExpectAllMementoValuesEqualTo(kTestMementoValue);
+ testing::Mock::VerifyAndClearExpectations(&resetter());
+
+ resetter().NotifyDidCloseWebUIResetBanner();
+
+ EXPECT_TRUE(resetter().IsResetPromptFlowActive());
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+
+ // Note: we use strict mocks, so this also checks the bubble is not closed.
+ VerifyExpectationsThenShutdownResetter();
+}
+
+TEST_F(AutomaticProfileResetterTest, BannerDismissedWhilePromptSuppressed) {
+ OrchestrateThroughEvaluationFlow();
+
+ resetter().NotifyDidCloseWebUIResetBanner();
+
+ EXPECT_TRUE(resetter().IsResetPromptFlowActive());
+ EXPECT_FALSE(resetter().ShouldShowResetBanner());
+ VerifyExpectationsThenShutdownResetter();
+
+ ExpectAllMementoValuesEqualTo(std::string());
+}
+
+// Please see comments above ConstructProgramToCheckPreferences() to understand
+// how the following tests work.
+
+TEST_F(AutomaticProfileResetterTest, InputUserPreferencesCorrect) {
+ SetTestingProgram(ConstructProgramToCheckPreferences());
+ SetTestingHashSeed(kTestHashSeed);
+
+ PrefService* prefs = profile()->GetPrefs();
+ prefs->SetString(kTestPreferencePath, kTestPreferenceValue);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask = HAS_EXPECTED_USER_PREFERENCE |
+ USER_PREFERENCE_IS_USER_CONTROLLED;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+TEST_F(AutomaticProfileResetterTest, InputLocalStateCorrect) {
+ SetTestingProgram(ConstructProgramToCheckPreferences());
+ SetTestingHashSeed(kTestHashSeed);
+
+ PrefService* prefs = local_state();
+ prefs->SetString(kTestPreferencePath, kTestPreferenceValue);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask = HAS_EXPECTED_LOCAL_STATE_PREFERENCE |
+ LOCAL_STATE_IS_USER_CONTROLLED;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+TEST_F(AutomaticProfileResetterTest, InputManagedUserPreferencesCorrect) {
+ SetTestingProgram(ConstructProgramToCheckPreferences());
+ SetTestingHashSeed(kTestHashSeed);
+
+ TestingPrefServiceSyncable* prefs = profile()->GetTestingPrefService();
+ prefs->SetManagedPref(kTestPreferencePath,
+ new base::StringValue(kTestPreferenceValue));
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask = HAS_EXPECTED_USER_PREFERENCE;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+TEST_F(AutomaticProfileResetterTest, InputManagedLocalStateCorrect) {
+ SetTestingProgram(ConstructProgramToCheckPreferences());
+ SetTestingHashSeed(kTestHashSeed);
+
+ TestingPrefServiceSimple* prefs = local_state();
+ prefs->SetManagedPref(kTestPreferencePath,
+ new base::StringValue(kTestPreferenceValue));
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask = HAS_EXPECTED_LOCAL_STATE_PREFERENCE;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+// Please see comments above ConstructProgramToCheckSearchEngines() to
+// understand how the following tests work.
+
+TEST_F(AutomaticProfileResetterTest, InputDefaultSearchProviderCorrect) {
+ SetTestingProgram(ConstructProgramToCheckSearchEngines());
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().emulated_default_search_provider_details().SetString(
+ kSearchURLAttributeKey, kTestSearchURL);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask = HAS_EXPECTED_DEFAULT_SEARCH_PROVIDER |
+ DEFAULT_SEARCH_PROVIDER_IS_USER_CONTROLLED;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+TEST_F(AutomaticProfileResetterTest, InputSearchProviderManagedCorrect) {
+ SetTestingProgram(ConstructProgramToCheckSearchEngines());
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().emulated_default_search_provider_details().SetString(
+ kSearchURLAttributeKey, kTestSearchURL);
+ mock_delegate().set_emulated_is_default_search_provider_managed(true);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask = HAS_EXPECTED_DEFAULT_SEARCH_PROVIDER;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+TEST_F(AutomaticProfileResetterTest, InputSearchProvidersCorrect) {
+ SetTestingProgram(ConstructProgramToCheckSearchEngines());
+ SetTestingHashSeed(kTestHashSeed);
+
+ base::DictionaryValue* search_provider_1 = new base::DictionaryValue;
+ base::DictionaryValue* search_provider_2 = new base::DictionaryValue;
+ search_provider_1->SetString(kSearchURLAttributeKey, kTestSearchURL);
+ search_provider_2->SetString(kSearchURLAttributeKey, kTestSearchURL2);
+ mock_delegate().emulated_search_providers_details().Append(search_provider_1);
+ mock_delegate().emulated_search_providers_details().Append(search_provider_2);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask = DEFAULT_SEARCH_PROVIDER_IS_USER_CONTROLLED |
+ HAS_EXPECTED_PREPOPULATED_SEARCH_PROVIDER_1 |
+ HAS_EXPECTED_PREPOPULATED_SEARCH_PROVIDER_2;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+// Please see comments above ConstructProgramToCheckLoadedModuleDigests() to
+// understand how the following tests work.
+
+TEST_F(AutomaticProfileResetterTest, InputModuleDigestsCorrect) {
+ SetTestingProgram(ConstructProgramToCheckLoadedModuleDigests());
+ SetTestingHashSeed(kTestHashSeed);
+
+ mock_delegate().emulated_loaded_module_digests().AppendString(
+ kTestModuleDigest);
+ mock_delegate().emulated_loaded_module_digests().AppendString(
+ kTestModuleDigest2);
+
+ mock_delegate().ExpectCallsToDependenciesSetUpMethods();
+ mock_delegate().ExpectCallsToGetterMethods();
+ uint32 expected_mask =
+ HAS_EXPECTED_MODULE_DIGEST_1 | HAS_EXPECTED_MODULE_DIGEST_2;
+ EXPECT_CALL(resetter(), ReportStatistics(0x00u, expected_mask));
+
+ UnleashResetterAndWait();
+}
+
+} // namespace
diff --git a/chrome/browser/profile_resetter/jtl_foundation.cc b/chrome/browser/profile_resetter/jtl_foundation.cc
new file mode 100644
index 0000000..0739040
--- /dev/null
+++ b/chrome/browser/profile_resetter/jtl_foundation.cc
@@ -0,0 +1,48 @@
+// 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/jtl_foundation.h"
+
+#include "base/logging.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+
+namespace jtl_foundation {
+
+Hasher::Hasher(const std::string& seed) : hmac_(crypto::HMAC::SHA256) {
+ if (!hmac_.Init(seed))
+ NOTREACHED();
+}
+
+Hasher::~Hasher() {}
+
+std::string Hasher::GetHash(const std::string& input) const {
+ if (cached_hashes_.find(input) == cached_hashes_.end()) {
+ // Calculate value.
+ unsigned char digest[kHashSizeInBytes];
+ if (!hmac_.Sign(input, digest, arraysize(digest))) {
+ NOTREACHED();
+ return std::string();
+ }
+ // Instead of using the full SHA256, we only use the hex encoding of the
+ // first 16 bytes.
+ cached_hashes_[input] = base::HexEncode(digest, kHashSizeInBytes / 2);
+ DCHECK_EQ(kHashSizeInBytes, cached_hashes_[input].size());
+ }
+ return cached_hashes_[input];
+}
+
+// static
+bool Hasher::IsHash(const std::string& maybe_hash) {
+ if (maybe_hash.size() != kHashSizeInBytes)
+ return false;
+ for (std::string::const_iterator it = maybe_hash.begin();
+ it != maybe_hash.end(); ++it) {
+ if (!IsHexDigit(*it))
+ return false;
+ }
+ return true;
+}
+
+} // namespace jtl_foundation
diff --git a/chrome/browser/profile_resetter/jtl_foundation.h b/chrome/browser/profile_resetter/jtl_foundation.h
new file mode 100644
index 0000000..cc4a5f4
--- /dev/null
+++ b/chrome/browser/profile_resetter/jtl_foundation.h
@@ -0,0 +1,193 @@
+// 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.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_JTL_FOUNDATION_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_JTL_FOUNDATION_H_
+
+#include <map>
+#include <string>
+
+#include "base/basictypes.h"
+#include "crypto/hmac.h"
+
+namespace jtl_foundation {
+
+// A JTL (JSON Traversal Language) program is composed of one or more
+// sentences. Each sentence consists of a sequence of operations. The input of
+// the program is a hierarchical JSON data structure.
+//
+// The execution of each sentence starts at the root of an input dictionary. The
+// operations include navigation in the JSON data structure, as well as
+// comparing the current (leaf) node to fixed values. The program also has a
+// separate dictionary as working memory, into which it can memorize data, then
+// later recall it for comparisons.
+//
+// Example program:
+// NAVIGATE_ANY
+// NAVIGATE(hash("bar"))
+// COMPARE_NODE_BOOL(1)
+// STORE_BOOL(hash("found_foo"), 1)
+// STOP_EXECUTING_SENTENCE
+//
+// Example input:
+// {
+// 'key1': 1,
+// 'key2': {'foo': 0, 'bar': false, 'baz': 2}
+// 'key3': {'foo': 0, 'bar': true, 'baz': 2}
+// 'key4': {'foo': 0, 'bar': true, 'baz': 2}
+// }
+//
+// This program navigates from the root of the dictionary to all children
+// ("key1", "key2", "key3", "key4") and executes the remaining program on each
+// of the children. The navigation happens in depth-first pre-order. On each of
+// the children it tries to navigate into the child "bar", which fails for
+// "key1", so execution stops for this sub-branch. On key2 the program navigates
+// to "bar" and moves the execution context to this node which has the value
+// "false". Therefore, the following COMPARE_NODE_BOOL is not fulfilled and the
+// execution does not continue on this branch, so we back track and proceed with
+// "key3" and its "bar" child. For this node, COMPARE_NODE_BOOL is fulfilled and
+// the execution continues to store "found_foo = true" into the working memory
+// of the interpreter. Next the interpreter executes STOP_EXECUTING_SENTENCE
+// which prevents the traversal from descending into the "key4" branch from the
+// NAVIGATE_ANY operation and can therefore speedup the processing.
+//
+// All node names, and node values of type string, integer and double are hashed
+// before being compared to hash values listed in |program|.
+
+// JTL byte code consists of uint8 opcodes followed by parameters. Parameters
+// are either boolean (uint8 with value \x00 or \x01), uint32 (in little-endian
+// notation), or hash string of 32 bytes.
+// The following opcodes are defined:
+enum OpCodes {
+ // Continues execution with the next operation on the element of a
+ // dictionary that matches the passed key parameter. If no such element
+ // exists, the command execution returns from the current instruction.
+ // Parameters:
+ // - a 32 byte hash of the target dictionary key.
+ NAVIGATE = 0x00,
+ // Continues execution with the next operation on each element of a
+ // dictionary or list. If no such element exists or the current element is
+ // neither a dictionary or list, the command execution returns from the
+ // current instruction.
+ NAVIGATE_ANY = 0x01,
+ // Continues execution with the next operation on the parent node of the
+ // current node. If the current node is the root of the input dictionary, the
+ // program execution fails with a runtime error.
+ NAVIGATE_BACK = 0x02,
+ // Stores a boolean value in the working memory.
+ // Parameters:
+ // - a 32 byte hash of the parameter name,
+ // - the value to store (\x00 or \x01)
+ STORE_BOOL = 0x10,
+ // Checks whether a boolean stored in working memory matches the expected
+ // value and continues execution with the next operation in case of a match.
+ // Parameters:
+ // - a 32 byte hash of the parameter name,
+ // - the expected value (\x00 or \x01),
+ // - the default value in case the working memory contains no corresponding
+ // entry (\x00 or\x01).
+ COMPARE_STORED_BOOL = 0x11,
+ // Same as STORE_BOOL but takes a hash instead of a boolean value as
+ // parameter.
+ STORE_HASH = 0x12,
+ // Same as COMPARE_STORED_BOOL but takes a hash instead of two boolean values
+ // as parameters.
+ COMPARE_STORED_HASH = 0x13,
+ // Stores the current node into the working memory. If the current node is not
+ // a boolean value, the program execution returns from the current
+ // instruction.
+ // Parameters:
+ // - a 32 byte hash of the parameter name.
+ STORE_NODE_BOOL = 0x14,
+ // Stores the hashed value of the current node into the working memory. If
+ // the current node is not a string, integer or double, the program execution
+ // returns from the current instruction.
+ // Parameters:
+ // - a 32 byte hash of the parameter name.
+ STORE_NODE_HASH = 0x15,
+ // Parses the value of the current node as a URL, extracts the subcomponent of
+ // the domain name that is immediately below the registrar-controlled portion,
+ // and stores the hash of this subcomponent into working memory. In case the
+ // domain name ends in a suffix not on the Public Suffix List (i.e. is neither
+ // an ICANN, nor a private domain suffix), the last subcomponent is assumed to
+ // be the registry, and the second-to-last subcomponent is extracted.
+ // If the current node is not a string that represents a URL, or if this URL
+ // does not have a domain component as described above, the program execution
+ // returns from the current instruction.
+ // For example, "foo.com", "www.foo.co.uk", "foo.appspot.com", and "foo.bar"
+ // will all yield (the hash of) "foo" as a result.
+ // See the unit test for more details.
+ // Parameters:
+ // - a 32 byte hash of the parameter name.
+ STORE_NODE_REGISTERABLE_DOMAIN_HASH = 0x16,
+ // Compares the current node against a boolean value and continues execution
+ // with the next operation in case of a match. If the current node does not
+ // match or is not a boolean value, the program execution returns from the
+ // current instruction.
+ // Parameters:
+ // - a boolean value (\x00 or \x01).
+ COMPARE_NODE_BOOL = 0x20,
+ // Compares the hashed value of the current node against the given hash, and
+ // continues execution with the next operation in case of a match. If the
+ // current node is not a string, integer or double, or if it is either, but
+ // its hash does not match, the program execution returns from the current
+ // instruction.
+ // Parameters:
+ // - a 32 byte hash of the expected value.
+ COMPARE_NODE_HASH = 0x21,
+ // The negation of the above.
+ COMPARE_NODE_HASH_NOT = 0x22,
+ // Compares the current node against a boolean value stored in working memory,
+ // and continues with the next operation in case of a match. If the current
+ // node is not a boolean value, the working memory contains no corresponding
+ // boolean value, or if they do not match, the program execution returns from
+ // the current instruction.
+ // Parameters:
+ // - a 32 byte hash of the parameter name.
+ COMPARE_NODE_TO_STORED_BOOL = 0x23,
+ // Compares the hashed value of the current node against a hash value stored
+ // in working memory, and continues with the next operation in case of a
+ // match. If the current node is not a string, integer or double, or if the
+ // working memory contains no corresponding hash string, or if the hashes do
+ // not match, the program execution returns from the current instruction.
+ // Parameters:
+ // - a 32 byte hash of the parameter name.
+ COMPARE_NODE_TO_STORED_HASH = 0x24,
+ // Checks whether the current node is a string value that contains the given
+ // pattern as a substring, and continues execution with the next operation in
+ // case it does. If the current node is not a string, or if does not match the
+ // pattern, the program execution returns from the current instruction.
+ // Parameters:
+ // - a 32 byte hash of the pattern,
+ // - a 4 byte unsigned integer: the length (>0) of the pattern in bytes,
+ // - a 4 byte unsigned integer: the sum of all bytes in the pattern, serving
+ // as a heuristic to reduce the number of actual SHA-256 calculations.
+ COMPARE_NODE_SUBSTRING = 0x25,
+ // Stop execution in this specific sentence.
+ STOP_EXECUTING_SENTENCE = 0x30,
+ // Separator between sentences, starts a new sentence.
+ END_OF_SENTENCE = 0x31
+};
+
+const size_t kHashSizeInBytes = 32u;
+
+// A class that provides SHA256 hash values for strings using a fixed hash seed.
+class Hasher {
+ public:
+ explicit Hasher(const std::string& seed);
+ ~Hasher();
+
+ std::string GetHash(const std::string& input) const;
+
+ static bool IsHash(const std::string& maybe_hash);
+
+ private:
+ crypto::HMAC hmac_;
+ mutable std::map<std::string, std::string> cached_hashes_;
+ DISALLOW_COPY_AND_ASSIGN(Hasher);
+};
+
+} // namespace jtl_foundation
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_JTL_FOUNDATION_H_
diff --git a/chrome/browser/profile_resetter/jtl_instructions.h b/chrome/browser/profile_resetter/jtl_instructions.h
new file mode 100644
index 0000000..76b57a6
--- /dev/null
+++ b/chrome/browser/profile_resetter/jtl_instructions.h
@@ -0,0 +1,38 @@
+// 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.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_JTL_INSTRUCTIONS_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_JTL_INSTRUCTIONS_H_
+
+#include <string>
+
+// These are instructions to generate binary code for JTL programs.
+
+#define VALUE_FALSE std::string(1, '\x00')
+#define VALUE_TRUE std::string(1, '\x01')
+
+#define OP_NAVIGATE(key) std::string(1, '\x00') + key
+#define OP_NAVIGATE_ANY std::string(1, '\x01')
+#define OP_NAVIGATE_BACK std::string(1, '\x02')
+#define OP_STORE_BOOL(name, value) std::string(1, '\x10') + name + value
+#define OP_COMPARE_STORED_BOOL(name, value, default_value) \
+ std::string(1, '\x11') + name + value + default_value
+#define OP_STORE_HASH(name, value) std::string(1, '\x12') + name + value
+#define OP_COMPARE_STORED_HASH(name, value, default_value) \
+ std::string(1, '\x13') + name + value + default_value
+#define OP_STORE_NODE_BOOL(name) std::string(1, '\x14') + name
+#define OP_STORE_NODE_HASH(name) std::string(1, '\x15') + name
+#define OP_STORE_NODE_REGISTERABLE_DOMAIN_HASH(name) \
+ std::string(1, '\x16') + name
+#define OP_COMPARE_NODE_BOOL(value) std::string(1, '\x20') + value
+#define OP_COMPARE_NODE_HASH(value) std::string(1, '\x21') + value
+#define OP_COMPARE_NODE_HASH_NOT(value) std::string(1, '\x22') + value
+#define OP_COMPARE_NODE_TO_STORED_BOOL(name) std::string(1, '\x23') + name
+#define OP_COMPARE_NODE_TO_STORED_HASH(name) std::string(1, '\x24') + name
+#define OP_COMPARE_NODE_SUBSTRING(pattern, pattern_length, pattern_sum) \
+ std::string(1, '\x25') + pattern + pattern_length + pattern_sum
+#define OP_STOP_EXECUTING_SENTENCE std::string(1, '\x30')
+#define OP_END_OF_SENTENCE std::string(1, '\x31')
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_JTL_INSTRUCTIONS_H_
diff --git a/chrome/browser/profile_resetter/jtl_interpreter.cc b/chrome/browser/profile_resetter/jtl_interpreter.cc
new file mode 100644
index 0000000..e258078
--- /dev/null
+++ b/chrome/browser/profile_resetter/jtl_interpreter.cc
@@ -0,0 +1,748 @@
+// 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/jtl_interpreter.h"
+
+#include <numeric>
+
+#include "base/memory/scoped_vector.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_util.h"
+#include "chrome/browser/profile_resetter/jtl_foundation.h"
+#include "crypto/hmac.h"
+#include "crypto/sha2.h"
+#include "net/base/registry_controlled_domains/registry_controlled_domain.h"
+#include "url/gurl.h"
+
+namespace {
+
+class ExecutionContext;
+
+// An operation in an interpreted program.
+class Operation {
+ public:
+ virtual ~Operation() {}
+ // Executes the operation on the specified context and instructs the context
+ // to continue execution with the next instruction if appropriate.
+ // Returns true if we should continue with any potential backtracking that
+ // needs to be done.
+ virtual bool Execute(ExecutionContext* context) = 0;
+};
+
+// An execution context of operations.
+class ExecutionContext {
+ public:
+ // |input| is the root of a dictionary that stores the information the
+ // sentence is evaluated on.
+ ExecutionContext(const jtl_foundation::Hasher* hasher,
+ const std::vector<Operation*>& sentence,
+ const base::DictionaryValue* input,
+ base::DictionaryValue* working_memory)
+ : hasher_(hasher),
+ sentence_(sentence),
+ next_instruction_index_(0u),
+ working_memory_(working_memory),
+ error_(false) {
+ stack_.push_back(input);
+ }
+ ~ExecutionContext() {}
+
+ // Returns true in case of success.
+ bool ContinueExecution() {
+ if (error_ || stack_.empty()) {
+ error_ = true;
+ return false;
+ }
+ if (next_instruction_index_ >= sentence_.size())
+ return true;
+
+ Operation* op = sentence_[next_instruction_index_];
+ next_instruction_index_++;
+ bool continue_traversal = op->Execute(this);
+ next_instruction_index_--;
+ return continue_traversal;
+ }
+
+ std::string GetHash(const std::string& input) {
+ return hasher_->GetHash(input);
+ }
+
+ // Calculates the |hash| of a string, integer or double |value|, and returns
+ // true. Returns false otherwise.
+ bool GetValueHash(const base::Value& value, std::string* hash) {
+ DCHECK(hash);
+ std::string value_as_string;
+ int tmp_int = 0;
+ double tmp_double = 0.0;
+ if (value.GetAsInteger(&tmp_int))
+ value_as_string = base::IntToString(tmp_int);
+ else if (value.GetAsDouble(&tmp_double))
+ value_as_string = base::DoubleToString(tmp_double);
+ else if (!value.GetAsString(&value_as_string))
+ return false;
+ *hash = GetHash(value_as_string);
+ return true;
+ }
+
+ const base::Value* current_node() const { return stack_.back(); }
+ std::vector<const base::Value*>* stack() { return &stack_; }
+ base::DictionaryValue* working_memory() { return working_memory_; }
+ bool error() const { return error_; }
+
+ private:
+ // A hasher used to hash node names in a dictionary.
+ const jtl_foundation::Hasher* hasher_;
+ // The sentence to be executed.
+ const std::vector<Operation*> sentence_;
+ // Position in |sentence_|.
+ size_t next_instruction_index_;
+ // A stack of Values, indicating a navigation path from the root node of
+ // |input| (see constructor) to the current node on which the
+ // sentence_[next_instruction_index_] is evaluated.
+ std::vector<const base::Value*> stack_;
+ // Memory into which values can be stored by the program.
+ base::DictionaryValue* working_memory_;
+ // Whether a runtime error occurred.
+ bool error_;
+ DISALLOW_COPY_AND_ASSIGN(ExecutionContext);
+};
+
+class NavigateOperation : public Operation {
+ public:
+ explicit NavigateOperation(const std::string& hashed_key)
+ : hashed_key_(hashed_key) {}
+ virtual ~NavigateOperation() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ const base::DictionaryValue* dict = NULL;
+ if (!context->current_node()->GetAsDictionary(&dict)) {
+ // Just ignore this node gracefully as this navigation is a dead end.
+ // If this NavigateOperation occurred after a NavigateAny operation, those
+ // may still be fulfillable, so we allow continuing the execution of the
+ // sentence on other nodes.
+ return true;
+ }
+ for (base::DictionaryValue::Iterator i(*dict); !i.IsAtEnd(); i.Advance()) {
+ if (context->GetHash(i.key()) != hashed_key_)
+ continue;
+ context->stack()->push_back(&i.value());
+ bool continue_traversal = context->ContinueExecution();
+ context->stack()->pop_back();
+ if (!continue_traversal)
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ std::string hashed_key_;
+ DISALLOW_COPY_AND_ASSIGN(NavigateOperation);
+};
+
+class NavigateAnyOperation : public Operation {
+ public:
+ NavigateAnyOperation() {}
+ virtual ~NavigateAnyOperation() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ const base::DictionaryValue* dict = NULL;
+ const base::ListValue* list = NULL;
+ if (context->current_node()->GetAsDictionary(&dict)) {
+ for (base::DictionaryValue::Iterator i(*dict);
+ !i.IsAtEnd(); i.Advance()) {
+ context->stack()->push_back(&i.value());
+ bool continue_traversal = context->ContinueExecution();
+ context->stack()->pop_back();
+ if (!continue_traversal)
+ return false;
+ }
+ } else if (context->current_node()->GetAsList(&list)) {
+ for (base::ListValue::const_iterator i = list->begin();
+ i != list->end(); ++i) {
+ context->stack()->push_back(*i);
+ bool continue_traversal = context->ContinueExecution();
+ context->stack()->pop_back();
+ if (!continue_traversal)
+ return false;
+ }
+ } else {
+ // Do nothing, just ignore this node.
+ }
+ return true;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NavigateAnyOperation);
+};
+
+class NavigateBackOperation : public Operation {
+ public:
+ NavigateBackOperation() {}
+ virtual ~NavigateBackOperation() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ const base::Value* current_node = context->current_node();
+ context->stack()->pop_back();
+ bool continue_traversal = context->ContinueExecution();
+ context->stack()->push_back(current_node);
+ return continue_traversal;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(NavigateBackOperation);
+};
+
+class StoreValue : public Operation {
+ public:
+ StoreValue(const std::string& hashed_name, scoped_ptr<base::Value> value)
+ : hashed_name_(hashed_name),
+ value_(value.Pass()) {
+ DCHECK(base::IsStringUTF8(hashed_name));
+ DCHECK(value_);
+ }
+ virtual ~StoreValue() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ context->working_memory()->Set(hashed_name_, value_->DeepCopy());
+ return context->ContinueExecution();
+ }
+
+ private:
+ std::string hashed_name_;
+ scoped_ptr<base::Value> value_;
+ DISALLOW_COPY_AND_ASSIGN(StoreValue);
+};
+
+class CompareStoredValue : public Operation {
+ public:
+ CompareStoredValue(const std::string& hashed_name,
+ scoped_ptr<base::Value> value,
+ scoped_ptr<base::Value> default_value)
+ : hashed_name_(hashed_name),
+ value_(value.Pass()),
+ default_value_(default_value.Pass()) {
+ DCHECK(base::IsStringUTF8(hashed_name));
+ DCHECK(value_);
+ DCHECK(default_value_);
+ }
+ virtual ~CompareStoredValue() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ const base::Value* actual_value = NULL;
+ if (!context->working_memory()->Get(hashed_name_, &actual_value))
+ actual_value = default_value_.get();
+ if (!value_->Equals(actual_value))
+ return true;
+ return context->ContinueExecution();
+ }
+
+ private:
+ std::string hashed_name_;
+ scoped_ptr<base::Value> value_;
+ scoped_ptr<base::Value> default_value_;
+ DISALLOW_COPY_AND_ASSIGN(CompareStoredValue);
+};
+
+template<bool ExpectedTypeIsBooleanNotHashable>
+class StoreNodeValue : public Operation {
+ public:
+ explicit StoreNodeValue(const std::string& hashed_name)
+ : hashed_name_(hashed_name) {
+ DCHECK(base::IsStringUTF8(hashed_name));
+ }
+ virtual ~StoreNodeValue() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ scoped_ptr<base::Value> value;
+ if (ExpectedTypeIsBooleanNotHashable) {
+ if (!context->current_node()->IsType(base::Value::TYPE_BOOLEAN))
+ return true;
+ value.reset(context->current_node()->DeepCopy());
+ } else {
+ std::string hash;
+ if (!context->GetValueHash(*context->current_node(), &hash))
+ return true;
+ value.reset(new base::StringValue(hash));
+ }
+ context->working_memory()->Set(hashed_name_, value.release());
+ return context->ContinueExecution();
+ }
+
+ private:
+ std::string hashed_name_;
+ DISALLOW_COPY_AND_ASSIGN(StoreNodeValue);
+};
+
+// Stores the hash of the registerable domain name -- as in, the portion of the
+// domain that is registerable, as opposed to controlled by a registrar; without
+// subdomains -- of the URL represented by the current node into working memory.
+class StoreNodeRegisterableDomain : public Operation {
+ public:
+ explicit StoreNodeRegisterableDomain(const std::string& hashed_name)
+ : hashed_name_(hashed_name) {
+ DCHECK(base::IsStringUTF8(hashed_name));
+ }
+ virtual ~StoreNodeRegisterableDomain() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ std::string possibly_invalid_url;
+ std::string domain;
+ if (!context->current_node()->GetAsString(&possibly_invalid_url) ||
+ !GetRegisterableDomain(possibly_invalid_url, &domain))
+ return true;
+ context->working_memory()->Set(
+ hashed_name_, new base::StringValue(context->GetHash(domain)));
+ return context->ContinueExecution();
+ }
+
+ private:
+ // If |possibly_invalid_url| is a valid URL having a registerable domain name
+ // part, outputs that in |registerable_domain| and returns true. Otherwise,
+ // returns false.
+ static bool GetRegisterableDomain(const std::string& possibly_invalid_url,
+ std::string* registerable_domain) {
+ namespace domains = net::registry_controlled_domains;
+ DCHECK(registerable_domain);
+ GURL url(possibly_invalid_url);
+ if (!url.is_valid())
+ return false;
+ std::string registry_plus_one = domains::GetDomainAndRegistry(
+ url.host(), domains::INCLUDE_PRIVATE_REGISTRIES);
+ size_t registry_length = domains::GetRegistryLength(
+ url.host(),
+ domains::INCLUDE_UNKNOWN_REGISTRIES,
+ domains::INCLUDE_PRIVATE_REGISTRIES);
+ // Fail unless (1.) the URL has a host part; and (2.) that host part is a
+ // well-formed domain name consisting of at least one subcomponent; followed
+ // by either a recognized registry identifier, or exactly one subcomponent,
+ // which is then assumed to be the unknown registry identifier.
+ if (registry_length == std::string::npos || registry_length == 0)
+ return false;
+ DCHECK_LT(registry_length, registry_plus_one.size());
+ // Subtract one to cut off the dot separating the SLD and the registry.
+ registerable_domain->assign(
+ registry_plus_one, 0, registry_plus_one.size() - registry_length - 1);
+ return true;
+ }
+
+ std::string hashed_name_;
+ DISALLOW_COPY_AND_ASSIGN(StoreNodeRegisterableDomain);
+};
+
+class CompareNodeBool : public Operation {
+ public:
+ explicit CompareNodeBool(bool value) : value_(value) {}
+ virtual ~CompareNodeBool() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ bool actual_value = false;
+ if (!context->current_node()->GetAsBoolean(&actual_value))
+ return true;
+ if (actual_value != value_)
+ return true;
+ return context->ContinueExecution();
+ }
+
+ private:
+ bool value_;
+ DISALLOW_COPY_AND_ASSIGN(CompareNodeBool);
+};
+
+class CompareNodeHash : public Operation {
+ public:
+ explicit CompareNodeHash(const std::string& hashed_value)
+ : hashed_value_(hashed_value) {}
+ virtual ~CompareNodeHash() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ std::string actual_hash;
+ if (!context->GetValueHash(*context->current_node(), &actual_hash) ||
+ actual_hash != hashed_value_)
+ return true;
+ return context->ContinueExecution();
+ }
+
+ private:
+ std::string hashed_value_;
+ DISALLOW_COPY_AND_ASSIGN(CompareNodeHash);
+};
+
+class CompareNodeHashNot : public Operation {
+ public:
+ explicit CompareNodeHashNot(const std::string& hashed_value)
+ : hashed_value_(hashed_value) {}
+ virtual ~CompareNodeHashNot() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ std::string actual_hash;
+ if (context->GetValueHash(*context->current_node(), &actual_hash) &&
+ actual_hash == hashed_value_)
+ return true;
+ return context->ContinueExecution();
+ }
+
+ private:
+ std::string hashed_value_;
+ DISALLOW_COPY_AND_ASSIGN(CompareNodeHashNot);
+};
+
+template<bool ExpectedTypeIsBooleanNotHashable>
+class CompareNodeToStored : public Operation {
+ public:
+ explicit CompareNodeToStored(const std::string& hashed_name)
+ : hashed_name_(hashed_name) {}
+ virtual ~CompareNodeToStored() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ const base::Value* stored_value = NULL;
+ if (!context->working_memory()->Get(hashed_name_, &stored_value))
+ return true;
+ if (ExpectedTypeIsBooleanNotHashable) {
+ if (!context->current_node()->IsType(base::Value::TYPE_BOOLEAN) ||
+ !context->current_node()->Equals(stored_value))
+ return true;
+ } else {
+ std::string actual_hash;
+ std::string stored_hash;
+ if (!context->GetValueHash(*context->current_node(), &actual_hash) ||
+ !stored_value->GetAsString(&stored_hash) ||
+ actual_hash != stored_hash)
+ return true;
+ }
+ return context->ContinueExecution();
+ }
+
+ private:
+ std::string hashed_name_;
+ DISALLOW_COPY_AND_ASSIGN(CompareNodeToStored);
+};
+
+class CompareNodeSubstring : public Operation {
+ public:
+ explicit CompareNodeSubstring(const std::string& hashed_pattern,
+ size_t pattern_length,
+ uint32 pattern_sum)
+ : hashed_pattern_(hashed_pattern),
+ pattern_length_(pattern_length),
+ pattern_sum_(pattern_sum) {
+ DCHECK(pattern_length_);
+ }
+ virtual ~CompareNodeSubstring() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ std::string value_as_string;
+ if (!context->current_node()->GetAsString(&value_as_string) ||
+ !pattern_length_ || value_as_string.size() < pattern_length_)
+ return true;
+ // Go over the string with a sliding window. Meanwhile, maintain the sum in
+ // an incremental fashion, and only calculate the SHA-256 hash when the sum
+ // checks out so as to improve performance.
+ std::string::const_iterator window_begin = value_as_string.begin();
+ std::string::const_iterator window_end = window_begin + pattern_length_ - 1;
+ uint32 window_sum =
+ std::accumulate(window_begin, window_end, static_cast<uint32>(0u));
+ while (window_end != value_as_string.end()) {
+ window_sum += *window_end++;
+ if (window_sum == pattern_sum_ && context->GetHash(std::string(
+ window_begin, window_end)) == hashed_pattern_)
+ return context->ContinueExecution();
+ window_sum -= *window_begin++;
+ }
+ return true;
+ }
+
+ private:
+ std::string hashed_pattern_;
+ size_t pattern_length_;
+ uint32 pattern_sum_;
+ DISALLOW_COPY_AND_ASSIGN(CompareNodeSubstring);
+};
+
+class StopExecutingSentenceOperation : public Operation {
+ public:
+ StopExecutingSentenceOperation() {}
+ virtual ~StopExecutingSentenceOperation() {}
+ virtual bool Execute(ExecutionContext* context) OVERRIDE {
+ return false;
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(StopExecutingSentenceOperation);
+};
+
+class Parser {
+ public:
+ explicit Parser(const std::string& program)
+ : program_(program),
+ next_instruction_index_(0u) {}
+ ~Parser() {}
+ bool ParseNextSentence(ScopedVector<Operation>* output) {
+ ScopedVector<Operation> operators;
+ bool sentence_ended = false;
+ while (next_instruction_index_ < program_.size() && !sentence_ended) {
+ uint8 op_code = 0;
+ if (!ReadOpCode(&op_code))
+ return false;
+ switch (static_cast<jtl_foundation::OpCodes>(op_code)) {
+ case jtl_foundation::NAVIGATE: {
+ std::string hashed_key;
+ if (!ReadHash(&hashed_key))
+ return false;
+ operators.push_back(new NavigateOperation(hashed_key));
+ break;
+ }
+ case jtl_foundation::NAVIGATE_ANY:
+ operators.push_back(new NavigateAnyOperation);
+ break;
+ case jtl_foundation::NAVIGATE_BACK:
+ operators.push_back(new NavigateBackOperation);
+ break;
+ case jtl_foundation::STORE_BOOL: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ bool value = false;
+ if (!ReadBool(&value))
+ return false;
+ operators.push_back(new StoreValue(
+ hashed_name,
+ scoped_ptr<base::Value>(new base::FundamentalValue(value))));
+ break;
+ }
+ case jtl_foundation::COMPARE_STORED_BOOL: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ bool value = false;
+ if (!ReadBool(&value))
+ return false;
+ bool default_value = false;
+ if (!ReadBool(&default_value))
+ return false;
+ operators.push_back(new CompareStoredValue(
+ hashed_name,
+ scoped_ptr<base::Value>(new base::FundamentalValue(value)),
+ scoped_ptr<base::Value>(
+ new base::FundamentalValue(default_value))));
+ break;
+ }
+ case jtl_foundation::STORE_HASH: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ std::string hashed_value;
+ if (!ReadHash(&hashed_value))
+ return false;
+ operators.push_back(new StoreValue(
+ hashed_name,
+ scoped_ptr<base::Value>(new base::StringValue(hashed_value))));
+ break;
+ }
+ case jtl_foundation::COMPARE_STORED_HASH: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ std::string hashed_value;
+ if (!ReadHash(&hashed_value))
+ return false;
+ std::string hashed_default_value;
+ if (!ReadHash(&hashed_default_value))
+ return false;
+ operators.push_back(new CompareStoredValue(
+ hashed_name,
+ scoped_ptr<base::Value>(new base::StringValue(hashed_value)),
+ scoped_ptr<base::Value>(
+ new base::StringValue(hashed_default_value))));
+ break;
+ }
+ case jtl_foundation::STORE_NODE_BOOL: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ operators.push_back(new StoreNodeValue<true>(hashed_name));
+ break;
+ }
+ case jtl_foundation::STORE_NODE_HASH: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ operators.push_back(new StoreNodeValue<false>(hashed_name));
+ break;
+ }
+ case jtl_foundation::STORE_NODE_REGISTERABLE_DOMAIN_HASH: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ operators.push_back(new StoreNodeRegisterableDomain(hashed_name));
+ break;
+ }
+ case jtl_foundation::COMPARE_NODE_BOOL: {
+ bool value = false;
+ if (!ReadBool(&value))
+ return false;
+ operators.push_back(new CompareNodeBool(value));
+ break;
+ }
+ case jtl_foundation::COMPARE_NODE_HASH: {
+ std::string hashed_value;
+ if (!ReadHash(&hashed_value))
+ return false;
+ operators.push_back(new CompareNodeHash(hashed_value));
+ break;
+ }
+ case jtl_foundation::COMPARE_NODE_HASH_NOT: {
+ std::string hashed_value;
+ if (!ReadHash(&hashed_value))
+ return false;
+ operators.push_back(new CompareNodeHashNot(hashed_value));
+ break;
+ }
+ case jtl_foundation::COMPARE_NODE_TO_STORED_BOOL: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ operators.push_back(new CompareNodeToStored<true>(hashed_name));
+ break;
+ }
+ case jtl_foundation::COMPARE_NODE_TO_STORED_HASH: {
+ std::string hashed_name;
+ if (!ReadHash(&hashed_name) || !base::IsStringUTF8(hashed_name))
+ return false;
+ operators.push_back(new CompareNodeToStored<false>(hashed_name));
+ break;
+ }
+ case jtl_foundation::COMPARE_NODE_SUBSTRING: {
+ std::string hashed_pattern;
+ uint32 pattern_length = 0, pattern_sum = 0;
+ if (!ReadHash(&hashed_pattern))
+ return false;
+ if (!ReadUint32(&pattern_length) || pattern_length == 0)
+ return false;
+ if (!ReadUint32(&pattern_sum))
+ return false;
+ operators.push_back(new CompareNodeSubstring(
+ hashed_pattern, pattern_length, pattern_sum));
+ break;
+ }
+ case jtl_foundation::STOP_EXECUTING_SENTENCE:
+ operators.push_back(new StopExecutingSentenceOperation);
+ break;
+ case jtl_foundation::END_OF_SENTENCE:
+ sentence_ended = true;
+ break;
+ default:
+ return false;
+ }
+ }
+ output->swap(operators);
+ return true;
+ }
+
+ bool HasNextSentence() const {
+ return next_instruction_index_ < program_.size();
+ }
+
+ private:
+ // Reads an uint8 and returns whether this operation was successful.
+ bool ReadUint8(uint8* out) {
+ DCHECK(out);
+ if (next_instruction_index_ + 1u > program_.size())
+ return false;
+ *out = static_cast<uint8>(program_[next_instruction_index_]);
+ ++next_instruction_index_;
+ return true;
+ }
+
+ // Reads an uint32 and returns whether this operation was successful.
+ bool ReadUint32(uint32* out) {
+ DCHECK(out);
+ if (next_instruction_index_ + 4u > program_.size())
+ return false;
+ *out = 0u;
+ for (int i = 0; i < 4; ++i) {
+ *out >>= 8;
+ *out |= static_cast<uint8>(program_[next_instruction_index_]) << 24;
+ ++next_instruction_index_;
+ }
+ return true;
+ }
+
+ // Reads an operator code and returns whether this operation was successful.
+ bool ReadOpCode(uint8* out) { return ReadUint8(out); }
+
+ bool ReadHash(std::string* out) {
+ DCHECK(out);
+ if (next_instruction_index_ + jtl_foundation::kHashSizeInBytes >
+ program_.size())
+ return false;
+ *out = program_.substr(next_instruction_index_,
+ jtl_foundation::kHashSizeInBytes);
+ next_instruction_index_ += jtl_foundation::kHashSizeInBytes;
+ DCHECK(jtl_foundation::Hasher::IsHash(*out));
+ return true;
+ }
+
+ bool ReadBool(bool* out) {
+ DCHECK(out);
+ uint8 value = 0;
+ if (!ReadUint8(&value))
+ return false;
+ if (value == 0)
+ *out = false;
+ else if (value == 1)
+ *out = true;
+ else
+ return false;
+ return true;
+ }
+
+ std::string program_;
+ size_t next_instruction_index_;
+ DISALLOW_COPY_AND_ASSIGN(Parser);
+};
+
+} // namespace
+
+JtlInterpreter::JtlInterpreter(
+ const std::string& hasher_seed,
+ const std::string& program,
+ const base::DictionaryValue* input)
+ : hasher_seed_(hasher_seed),
+ program_(program),
+ input_(input),
+ working_memory_(new base::DictionaryValue),
+ result_(OK) {
+ DCHECK(input->IsType(base::Value::TYPE_DICTIONARY));
+}
+
+JtlInterpreter::~JtlInterpreter() {}
+
+void JtlInterpreter::Execute() {
+ jtl_foundation::Hasher hasher(hasher_seed_);
+ Parser parser(program_);
+ while (parser.HasNextSentence()) {
+ ScopedVector<Operation> sentence;
+ if (!parser.ParseNextSentence(&sentence)) {
+ result_ = PARSE_ERROR;
+ return;
+ }
+ ExecutionContext context(
+ &hasher, sentence.get(), input_, working_memory_.get());
+ context.ContinueExecution();
+ if (context.error()) {
+ result_ = RUNTIME_ERROR;
+ return;
+ }
+ }
+}
+
+bool JtlInterpreter::GetOutputBoolean(const std::string& unhashed_key,
+ bool* output) const {
+ std::string hashed_key =
+ jtl_foundation::Hasher(hasher_seed_).GetHash(unhashed_key);
+ return working_memory_->GetBoolean(hashed_key, output);
+}
+
+bool JtlInterpreter::GetOutputString(const std::string& unhashed_key,
+ std::string* output) const {
+ std::string hashed_key =
+ jtl_foundation::Hasher(hasher_seed_).GetHash(unhashed_key);
+ return working_memory_->GetString(hashed_key, output);
+}
+
+int JtlInterpreter::CalculateProgramChecksum() const {
+ uint8 digest[3] = {};
+ crypto::SHA256HashString(program_, digest, arraysize(digest));
+ return static_cast<uint32>(digest[0]) << 16 |
+ static_cast<uint32>(digest[1]) << 8 |
+ static_cast<uint32>(digest[2]);
+}
diff --git a/chrome/browser/profile_resetter/jtl_interpreter.h b/chrome/browser/profile_resetter/jtl_interpreter.h
new file mode 100644
index 0000000..cafb7fd
--- /dev/null
+++ b/chrome/browser/profile_resetter/jtl_interpreter.h
@@ -0,0 +1,61 @@
+// 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.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_JTL_INTERPRETER_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_JTL_INTERPRETER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+#include "base/values.h"
+
+// Executes a JTL program on a given dictionary.
+//
+// JTL (Json Traversal Language) programs are defined in jtl_foundation.h
+class JtlInterpreter {
+ public:
+ enum Result {
+ OK,
+ PARSE_ERROR,
+ RUNTIME_ERROR,
+ RESULT_MAX,
+ };
+
+ // |hasher_seed| is a value used in jtl_foundation::Hasher. All node names,
+ // strings, integers and doubles are hashed before being compared to hash
+ // values listed in |program|.
+ // |program| is a byte array containing a JTL program.
+ // |input| is a dictionary on which the program is evaluated.
+ JtlInterpreter(const std::string& hasher_seed,
+ const std::string& program,
+ const base::DictionaryValue* input);
+ ~JtlInterpreter();
+
+ void Execute();
+
+ Result result() const { return result_; }
+ const base::DictionaryValue* working_memory() const {
+ return working_memory_.get();
+ }
+ bool GetOutputBoolean(const std::string& unhashed_key, bool* output) const;
+ bool GetOutputString(const std::string& unhashed_key,
+ std::string* output) const;
+
+ // Generates a checksum of the loaded program, defined as the first 3 bytes of
+ // the program's SHA-256 hash interpreted as a big-endian integer.
+ int CalculateProgramChecksum() const;
+
+ private:
+ // Input.
+ std::string hasher_seed_;
+ std::string program_;
+ const base::DictionaryValue* input_;
+ // Output.
+ scoped_ptr<base::DictionaryValue> working_memory_;
+ Result result_;
+
+ DISALLOW_COPY_AND_ASSIGN(JtlInterpreter);
+};
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_JTL_INTERPRETER_H_
diff --git a/chrome/browser/profile_resetter/jtl_interpreter_unittest.cc b/chrome/browser/profile_resetter/jtl_interpreter_unittest.cc
new file mode 100644
index 0000000..67d89df
--- /dev/null
+++ b/chrome/browser/profile_resetter/jtl_interpreter_unittest.cc
@@ -0,0 +1,689 @@
+// Copyright 2011 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/jtl_interpreter.h"
+
+#include <numeric>
+
+#include "base/strings/string_util.h"
+#include "base/test/values_test_util.h"
+#include "chrome/browser/profile_resetter/jtl_foundation.h"
+#include "chrome/browser/profile_resetter/jtl_instructions.h"
+#include "crypto/hmac.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char seed[] = "foobar";
+
+#define KEY_HASH_1 GetHash("KEY_HASH_1")
+#define KEY_HASH_2 GetHash("KEY_HASH_2")
+#define KEY_HASH_3 GetHash("KEY_HASH_3")
+#define KEY_HASH_4 GetHash("KEY_HASH_4")
+
+#define VALUE_HASH_1 GetHash("VALUE_HASH_1")
+#define VALUE_HASH_2 GetHash("VALUE_HASH_2")
+
+#define VAR_HASH_1 "01234567890123456789012345678901"
+#define VAR_HASH_2 "12345678901234567890123456789012"
+
+std::string GetHash(const std::string& input) {
+ return jtl_foundation::Hasher(seed).GetHash(input);
+}
+
+std::string EncodeUint32(uint32 value) {
+ std::string bytecode;
+ for (int i = 0; i < 4; ++i) {
+ bytecode.push_back(static_cast<char>(value & 0xFFu));
+ value >>= 8;
+ }
+ return bytecode;
+}
+
+// escaped_json_param may contain ' characters that are replaced with ". This
+// makes the code more readable because we need less escaping.
+#define INIT_INTERPRETER(program_param, escaped_json_param) \
+ const char* escaped_json = escaped_json_param; \
+ std::string json; \
+ base::ReplaceChars(escaped_json, "'", "\"", &json); \
+ scoped_ptr<base::Value> json_value(ParseJson(json)); \
+ JtlInterpreter interpreter( \
+ seed, \
+ program_param, \
+ static_cast<const base::DictionaryValue*>(json_value.get())); \
+ interpreter.Execute()
+
+using base::test::ParseJson;
+
+TEST(JtlInterpreter, Store) {
+ INIT_INTERPRETER(
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1': 'VALUE_HASH_1' }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+}
+
+TEST(JtlInterpreter, NavigateAndStore) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1': 'VALUE_HASH_1' }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+}
+
+TEST(JtlInterpreter, FailNavigate) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1': 'VALUE_HASH_1' }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+}
+
+TEST(JtlInterpreter, ConsecutiveNavigate) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+}
+
+TEST(JtlInterpreter, FailConsecutiveNavigate) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1': 'foo' }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+}
+
+TEST(JtlInterpreter, NavigateAnyInDictionary) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE_ANY +
+ OP_NAVIGATE(KEY_HASH_4) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1':"
+ " { 'KEY_HASH_2': {'KEY_HASH_3': 'VALUE_HASH_1' },"
+ " 'KEY_HASH_3': {'KEY_HASH_4': 'VALUE_HASH_1' }"
+ " } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+}
+
+TEST(JtlInterpreter, NavigateAnyInList) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE_ANY +
+ OP_NAVIGATE(KEY_HASH_4) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1':"
+ " [ {'KEY_HASH_3': 'VALUE_HASH_1' },"
+ " {'KEY_HASH_4': 'VALUE_HASH_1' }"
+ " ] }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+}
+
+TEST(JtlInterpreter, NavigateBack) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_NAVIGATE_BACK +
+ OP_NAVIGATE(KEY_HASH_3) +
+ OP_NAVIGATE(KEY_HASH_4) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ "{ 'KEY_HASH_1':"
+ " { 'KEY_HASH_2': {'KEY_HASH_3': 'VALUE_HASH_1' },"
+ " 'KEY_HASH_3': {'KEY_HASH_4': 'VALUE_HASH_1' }"
+ " } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+}
+
+TEST(JtlInterpreter, StoreTwoValues) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE) +
+ OP_STORE_HASH(VAR_HASH_2, VALUE_HASH_1),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+ base::ExpectDictStringValue(VALUE_HASH_1, *interpreter.working_memory(),
+ VAR_HASH_2);
+}
+
+TEST(JtlInterpreter, CompareStoredMatch) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_COMPARE_STORED_BOOL(VAR_HASH_1, VALUE_TRUE, VALUE_FALSE) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_2);
+}
+
+TEST(JtlInterpreter, CompareStoredMismatch) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_COMPARE_STORED_BOOL(VAR_HASH_1, VALUE_FALSE, VALUE_TRUE) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+}
+
+TEST(JtlInterpreter, CompareStoredNoValueMatchingDefault) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_COMPARE_STORED_BOOL(VAR_HASH_1, VALUE_TRUE, VALUE_TRUE) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_2);
+}
+
+TEST(JtlInterpreter, CompareStoredNoValueMismatchingDefault) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_COMPARE_STORED_BOOL(VAR_HASH_1, VALUE_TRUE, VALUE_FALSE) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+}
+
+TEST(JtlInterpreter, CompareBool) {
+ struct TestCase {
+ std::string expected_value;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { VALUE_TRUE, "{ 'KEY_HASH_1': true }", true },
+ { VALUE_FALSE, "{ 'KEY_HASH_1': false }", true },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': false }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 'abc' }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 1 }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 1.2 }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': [1] }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_COMPARE_NODE_BOOL(cases[i].expected_value) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_1);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+ }
+ }
+}
+
+TEST(JtlInterpreter, CompareHashString) {
+ struct TestCase {
+ std::string expected_value;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", true },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 'VALUE_HASH_2' }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': true }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 1 }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 1.1 }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': [1] }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1.2 }", true },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': true }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1 }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1.3 }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': [1] }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+
+ { GetHash("1"), "{ 'KEY_HASH_1': 1 }", true },
+ { GetHash("1"), "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': true }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': 2 }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': 1.1 }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': [1] }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_COMPARE_NODE_HASH(cases[i].expected_value) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_1);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+ }
+ }
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Negated, Iteration " << i);
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_COMPARE_NODE_HASH_NOT(cases[i].expected_value) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (!cases[i].expected_success) {
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_1);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+ }
+ }
+}
+
+TEST(JtlInterpreter, StoreNodeBool) {
+ struct TestCase {
+ bool expected_value;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { true, "{ 'KEY_HASH_1': true }", true },
+ { false, "{ 'KEY_HASH_1': false }", true },
+ { false, "{ 'KEY_HASH_1': 'abc' }", false },
+ { false, "{ 'KEY_HASH_1': 1 }", false },
+ { false, "{ 'KEY_HASH_1': 1.2 }", false },
+ { false, "{ 'KEY_HASH_1': [1] }", false },
+ { false, "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_STORE_NODE_BOOL(VAR_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictBooleanValue(
+ cases[i].expected_value, *interpreter.working_memory(), VAR_HASH_1);
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_2);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+ }
+ }
+}
+
+TEST(JtlInterpreter, CompareNodeToStoredBool) {
+ struct TestCase {
+ std::string stored_value;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { VALUE_TRUE, "{ 'KEY_HASH_1': true }", true },
+ { VALUE_FALSE, "{ 'KEY_HASH_1': false }", true },
+ { VALUE_FALSE, "{ 'KEY_HASH_1': true }", false },
+ { std::string(), "{ 'KEY_HASH_1': true }", false },
+
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': 1 }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1.2 }", false },
+
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': true }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': true }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': true }", false },
+
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 1 }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 1.2 }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': [1] }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ std::string store_op;
+ if (cases[i].stored_value == VALUE_TRUE ||
+ cases[i].stored_value == VALUE_FALSE)
+ store_op = OP_STORE_BOOL(VAR_HASH_1, cases[i].stored_value);
+ else if (!cases[i].stored_value.empty())
+ store_op = OP_STORE_HASH(VAR_HASH_1, cases[i].stored_value);
+ INIT_INTERPRETER(
+ store_op +
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_COMPARE_NODE_TO_STORED_BOOL(VAR_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_2);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+ }
+ }
+}
+
+TEST(JtlInterpreter, StoreNodeHash) {
+ struct TestCase {
+ std::string expected_value;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", true },
+ { VALUE_HASH_2, "{ 'KEY_HASH_1': 'VALUE_HASH_2' }", true },
+ { GetHash("1"), "{ 'KEY_HASH_1': 1 }", true },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1.2 }", true },
+ { std::string(), "{ 'KEY_HASH_1': true }", false },
+ { std::string(), "{ 'KEY_HASH_1': [1] }", false },
+ { std::string(), "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_STORE_NODE_HASH(VAR_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictStringValue(
+ cases[i].expected_value, *interpreter.working_memory(), VAR_HASH_1);
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_2);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+ }
+ }
+}
+
+TEST(JtlInterpreter, CompareNodeToStoredHash) {
+ struct TestCase {
+ std::string stored_value;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", true },
+ { VALUE_HASH_2, "{ 'KEY_HASH_1': 'VALUE_HASH_2' }", true },
+ { std::string(), "{ 'KEY_HASH_1': 'VALUE_HASH_2' }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 'VALUE_HASH_2' }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': true }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 1 }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 1.1 }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': [1] }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1.2 }", true },
+ { GetHash("1.3"), "{ 'KEY_HASH_1': 1.3 }", true },
+ { std::string(), "{ 'KEY_HASH_1': 1.2 }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': true }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1 }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': 1.3 }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': [1] }", false },
+ { GetHash("1.2"), "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+
+ { GetHash("1"), "{ 'KEY_HASH_1': 1 }", true },
+ { GetHash("2"), "{ 'KEY_HASH_1': 2 }", true },
+ { std::string(), "{ 'KEY_HASH_1': 2 }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': true }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': 2 }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': 1.1 }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': [1] }", false },
+ { GetHash("1"), "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 'VALUE_HASH_1' }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 1 }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': 1.3 }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': [1] }", false },
+ { VALUE_TRUE, "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+
+ { VALUE_TRUE, "{ 'KEY_HASH_1': true }", false },
+ { VALUE_FALSE, "{ 'KEY_HASH_1': false }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ std::string store_op;
+ if (cases[i].stored_value == VALUE_TRUE ||
+ cases[i].stored_value == VALUE_FALSE)
+ store_op = OP_STORE_BOOL(VAR_HASH_1, cases[i].stored_value);
+ else if (!cases[i].stored_value.empty())
+ store_op = OP_STORE_HASH(VAR_HASH_1, cases[i].stored_value);
+ INIT_INTERPRETER(
+ store_op +
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_COMPARE_NODE_TO_STORED_HASH(VAR_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_2);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+ }
+ }
+}
+
+TEST(JtlInterpreter, CompareSubstring) {
+ struct TestCase {
+ std::string pattern;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { "abc", "{ 'KEY_HASH_1': 'abcdefghijklmnopqrstuvwxyz' }", true },
+ { "xyz", "{ 'KEY_HASH_1': 'abcdefghijklmnopqrstuvwxyz' }", true },
+ { "m", "{ 'KEY_HASH_1': 'abcdefghijklmnopqrstuvwxyz' }", true },
+ { "abc", "{ 'KEY_HASH_1': 'abc' }", true },
+ { "cba", "{ 'KEY_HASH_1': 'abcdefghijklmnopqrstuvwxyz' }", false },
+ { "acd", "{ 'KEY_HASH_1': 'abcdefghijklmnopqrstuvwxyz' }", false },
+ { "waaaaaaay_too_long", "{ 'KEY_HASH_1': 'abc' }", false },
+
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': true }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 1 }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': 1.1 }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': [1] }", false },
+ { VALUE_HASH_1, "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ std::string pattern = cases[i].pattern;
+ uint32 pattern_sum = std::accumulate(
+ pattern.begin(), pattern.end(), static_cast<uint32>(0u));
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_COMPARE_NODE_SUBSTRING(GetHash(pattern),
+ EncodeUint32(pattern.size()),
+ EncodeUint32(pattern_sum)) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_1);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+ }
+ }
+}
+
+TEST(JtlInterpreter, StoreNodeRegisterableDomainHash) {
+ struct TestCase {
+ std::string expected_value;
+ const char* json;
+ bool expected_success;
+ } cases[] = {
+ { GetHash("google"), "{ 'KEY_HASH_1': 'http://google.com/path' }", true },
+ { GetHash("google"), "{ 'KEY_HASH_1': 'http://mail.google.com/' }", true },
+ { GetHash("google"), "{ 'KEY_HASH_1': 'http://google.co.uk/' }", true },
+ { GetHash("google"), "{ 'KEY_HASH_1': 'http://google.com./' }", true },
+ { GetHash("google"), "{ 'KEY_HASH_1': 'http://..google.com/' }", true },
+
+ { GetHash("foo"), "{ 'KEY_HASH_1': 'http://foo.bar/path' }", true },
+ { GetHash("foo"), "{ 'KEY_HASH_1': 'http://sub.foo.bar' }", true },
+ { GetHash("foo"), "{ 'KEY_HASH_1': 'http://foo.appspot.com/' }", true },
+ { GetHash("foo"), "{ 'KEY_HASH_1': 'http://sub.foo.appspot.com' }", true },
+
+ { std::string(), "{ 'KEY_HASH_1': 'http://google.com../' }", false },
+
+ { std::string(), "{ 'KEY_HASH_1': 'http://bar/path' }", false },
+ { std::string(), "{ 'KEY_HASH_1': 'http://co.uk/path' }", false },
+ { std::string(), "{ 'KEY_HASH_1': 'http://appspot.com/path' }", false },
+ { std::string(), "{ 'KEY_HASH_1': 'http://127.0.0.1/path' }", false },
+ { std::string(), "{ 'KEY_HASH_1': 'file:///C:/bar.html' }", false },
+
+ { std::string(), "{ 'KEY_HASH_1': 1 }", false },
+ { std::string(), "{ 'KEY_HASH_1': 1.2 }", false },
+ { std::string(), "{ 'KEY_HASH_1': true }", false },
+ { std::string(), "{ 'KEY_HASH_1': [1] }", false },
+ { std::string(), "{ 'KEY_HASH_1': {'a': 'b'} }", false },
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_STORE_NODE_REGISTERABLE_DOMAIN_HASH(VAR_HASH_1) +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ cases[i].json);
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ if (cases[i].expected_success) {
+ base::ExpectDictStringValue(
+ cases[i].expected_value, *interpreter.working_memory(), VAR_HASH_1);
+ base::ExpectDictBooleanValue(
+ true, *interpreter.working_memory(), VAR_HASH_2);
+ } else {
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_1));
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+ }
+ }
+}
+
+TEST(JtlInterpreter, Stop) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE) +
+ OP_STOP_EXECUTING_SENTENCE +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+ EXPECT_FALSE(interpreter.working_memory()->HasKey(VAR_HASH_2));
+}
+
+TEST(JtlInterpreter, EndOfSentence) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE(KEY_HASH_2) +
+ OP_STORE_BOOL(VAR_HASH_1, VALUE_TRUE) +
+ OP_END_OF_SENTENCE +
+ OP_STORE_BOOL(VAR_HASH_2, VALUE_TRUE),
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::OK, interpreter.result());
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_1);
+ base::ExpectDictBooleanValue(true, *interpreter.working_memory(), VAR_HASH_2);
+}
+
+TEST(JtlInterpreter, InvalidBack) {
+ INIT_INTERPRETER(
+ OP_NAVIGATE(KEY_HASH_1) +
+ OP_NAVIGATE_BACK +
+ OP_NAVIGATE_BACK,
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::RUNTIME_ERROR, interpreter.result());
+}
+
+TEST(JtlInterpreter, IncorrectPrograms) {
+ std::string missing_hash;
+ std::string missing_bool;
+ std::string invalid_hash("123");
+ std::string invalid_bool("\x02", 1);
+ std::string invalid_operation("\x99", 1);
+ std::string programs[] = {
+ OP_NAVIGATE(missing_hash),
+ OP_NAVIGATE(invalid_hash),
+ OP_STORE_BOOL(VAR_HASH_1, invalid_bool),
+ OP_STORE_BOOL(missing_hash, VALUE_TRUE),
+ OP_STORE_BOOL(invalid_hash, VALUE_TRUE),
+ OP_COMPARE_STORED_BOOL(invalid_hash, VALUE_TRUE, VALUE_TRUE),
+ OP_COMPARE_STORED_BOOL(VAR_HASH_1, invalid_bool, VALUE_TRUE),
+ OP_COMPARE_STORED_BOOL(VAR_HASH_1, VALUE_TRUE, invalid_bool),
+ OP_COMPARE_STORED_BOOL(VAR_HASH_1, VALUE_TRUE, missing_bool),
+ OP_STORE_NODE_BOOL(missing_hash),
+ OP_STORE_NODE_BOOL(invalid_hash),
+ OP_STORE_NODE_HASH(missing_hash),
+ OP_STORE_NODE_HASH(invalid_hash),
+ OP_COMPARE_NODE_BOOL(missing_bool),
+ OP_COMPARE_NODE_BOOL(invalid_bool),
+ OP_COMPARE_NODE_HASH(missing_hash),
+ OP_COMPARE_NODE_HASH(invalid_hash),
+ OP_COMPARE_NODE_TO_STORED_BOOL(missing_hash),
+ OP_COMPARE_NODE_TO_STORED_BOOL(invalid_hash),
+ OP_COMPARE_NODE_TO_STORED_HASH(missing_hash),
+ OP_COMPARE_NODE_TO_STORED_HASH(invalid_hash),
+ invalid_operation,
+ };
+ for (size_t i = 0; i < arraysize(programs); ++i) {
+ SCOPED_TRACE(testing::Message() << "Iteration " << i);
+ INIT_INTERPRETER(programs[i],
+ "{ 'KEY_HASH_1': { 'KEY_HASH_2': 'VALUE_HASH_1' } }");
+ EXPECT_EQ(JtlInterpreter::PARSE_ERROR, interpreter.result());
+ }
+}
+
+TEST(JtlInterpreter, GetOutput) {
+ INIT_INTERPRETER(
+ OP_STORE_BOOL(GetHash("output1"), VALUE_TRUE) +
+ OP_STORE_HASH(GetHash("output2"), VALUE_HASH_1),
+ "{}");
+ bool output1 = false;
+ std::string output2;
+ EXPECT_TRUE(interpreter.GetOutputBoolean("output1", &output1));
+ EXPECT_EQ(true, output1);
+ EXPECT_TRUE(interpreter.GetOutputString("output2", &output2));
+ EXPECT_EQ(VALUE_HASH_1, output2);
+ EXPECT_FALSE(interpreter.GetOutputBoolean("outputxx", &output1));
+ EXPECT_FALSE(interpreter.GetOutputString("outputxx", &output2));
+}
+
+TEST(JtlInterpreter, CalculateProgramChecksum) {
+ const char kTestSeed[] = "Irrelevant seed value.";
+ const char kTestProgram[] = "The quick brown fox jumps over the lazy dog.";
+ // This program is invalid, but we are not actually executing it.
+ base::DictionaryValue input;
+ JtlInterpreter interpreter(kTestSeed, kTestProgram, &input);
+ EXPECT_EQ(0xef537f, interpreter.CalculateProgramChecksum());
+}
+
+} // namespace
diff --git a/chrome/browser/profile_resetter/profile_reset_global_error.cc b/chrome/browser/profile_resetter/profile_reset_global_error.cc
new file mode 100644
index 0000000..40228007
--- /dev/null
+++ b/chrome/browser/profile_resetter/profile_reset_global_error.cc
@@ -0,0 +1,110 @@
+// 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/profile_reset_global_error.h"
+
+#include "base/metrics/histogram.h"
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_factory.h"
+#include "chrome/browser/profiles/profile.h"
+#include "chrome/browser/ui/browser.h"
+#include "chrome/browser/ui/chrome_pages.h"
+#include "chrome/browser/ui/global_error/global_error_service.h"
+#include "chrome/browser/ui/global_error/global_error_service_factory.h"
+#include "chrome/browser/ui/profile_reset_bubble.h"
+#include "chrome/common/url_constants.h"
+#include "grit/chromium_strings.h"
+#include "grit/generated_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+
+namespace {
+
+base::TimeDelta GetPromptDelayHistogramMaximum() {
+ return base::TimeDelta::FromDays(7);
+}
+
+// Records the delay between when the reset prompt is triggered and when the
+// bubble can actually be shown.
+void RecordPromptDelay(const base::TimeDelta& delay) {
+ UMA_HISTOGRAM_CUSTOM_TIMES(
+ "AutomaticProfileReset.PromptDelay", delay,
+ base::TimeDelta::FromSeconds(1), GetPromptDelayHistogramMaximum(), 50);
+}
+
+} // namespace
+
+
+// ProfileResetGlobalError ---------------------------------------------------
+
+ProfileResetGlobalError::ProfileResetGlobalError(Profile* profile)
+ : profile_(profile), has_shown_bubble_view_(false), bubble_view_(NULL) {
+ AutomaticProfileResetter* automatic_profile_resetter =
+ AutomaticProfileResetterFactory::GetForBrowserContext(profile_);
+ if (automatic_profile_resetter)
+ automatic_profile_resetter_ = automatic_profile_resetter->AsWeakPtr();
+}
+
+ProfileResetGlobalError::~ProfileResetGlobalError() {
+ if (!has_shown_bubble_view_)
+ RecordPromptDelay(GetPromptDelayHistogramMaximum());
+}
+
+// static
+bool ProfileResetGlobalError::IsSupportedOnPlatform() {
+ return IsProfileResetBubbleSupported();
+}
+
+bool ProfileResetGlobalError::HasMenuItem() { return true; }
+
+int ProfileResetGlobalError::MenuItemCommandID() {
+ return IDC_SHOW_SETTINGS_RESET_BUBBLE;
+}
+
+base::string16 ProfileResetGlobalError::MenuItemLabel() {
+ return l10n_util::GetStringFUTF16(
+ IDS_RESET_SETTINGS_MENU_ITEM,
+ l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
+}
+
+void ProfileResetGlobalError::ExecuteMenuItem(Browser* browser) {
+ chrome::ShowSettingsSubPage(browser, chrome::kResetProfileSettingsSubPage);
+}
+
+bool ProfileResetGlobalError::HasBubbleView() { return true; }
+
+bool ProfileResetGlobalError::HasShownBubbleView() {
+ return has_shown_bubble_view_;
+}
+
+void ProfileResetGlobalError::ShowBubbleView(Browser* browser) {
+ if (has_shown_bubble_view_)
+ return;
+
+ has_shown_bubble_view_ = true;
+ bubble_view_ = ShowProfileResetBubble(AsWeakPtr(), browser);
+
+ if (automatic_profile_resetter_)
+ automatic_profile_resetter_->NotifyDidShowResetBubble();
+ RecordPromptDelay(timer_.Elapsed());
+}
+
+void ProfileResetGlobalError::OnBubbleViewDidClose() {
+ bubble_view_ = NULL;
+}
+
+void ProfileResetGlobalError::OnBubbleViewResetButtonPressed(
+ bool send_feedback) {
+ if (automatic_profile_resetter_)
+ automatic_profile_resetter_->TriggerProfileReset(send_feedback);
+}
+
+void ProfileResetGlobalError::OnBubbleViewNoThanksButtonPressed() {
+ if (automatic_profile_resetter_)
+ automatic_profile_resetter_->SkipProfileReset();
+}
+
+GlobalErrorBubbleViewBase* ProfileResetGlobalError::GetBubbleView() {
+ return bubble_view_;
+}
diff --git a/chrome/browser/profile_resetter/profile_reset_global_error.h b/chrome/browser/profile_resetter/profile_reset_global_error.h
new file mode 100644
index 0000000..6529df49
--- /dev/null
+++ b/chrome/browser/profile_resetter/profile_reset_global_error.h
@@ -0,0 +1,72 @@
+// 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.
+
+#ifndef CHROME_BROWSER_PROFILE_RESETTER_PROFILE_RESET_GLOBAL_ERROR_H_
+#define CHROME_BROWSER_PROFILE_RESETTER_PROFILE_RESET_GLOBAL_ERROR_H_
+
+#include "base/basictypes.h"
+#include "base/memory/weak_ptr.h"
+#include "base/timer/elapsed_timer.h"
+#include "chrome/browser/ui/global_error/global_error.h"
+
+class AutomaticProfileResetter;
+class GlobalErrorBubbleViewBase;
+class Profile;
+
+// Encapsulates UI-related functionality for the one-time profile settings reset
+// prompt. The UI consists of two parts: (1.) the profile reset (pop-up) bubble,
+// and (2.) a menu item in the wrench menu (provided by us being a GlobalError).
+class ProfileResetGlobalError
+ : public GlobalError,
+ public base::SupportsWeakPtr<ProfileResetGlobalError> {
+ public:
+ explicit ProfileResetGlobalError(Profile* profile);
+ virtual ~ProfileResetGlobalError();
+
+ // Returns whether or not the reset prompt is supported on this platform.
+ static bool IsSupportedOnPlatform();
+
+ // Called by the bubble view when it is closed.
+ void OnBubbleViewDidClose();
+
+ // Called when the user clicks on the 'Reset' button. The user can choose to
+ // send feedback containing the old settings that are now being reset, this is
+ // indicated by |send_feedback|.
+ void OnBubbleViewResetButtonPressed(bool send_feedback);
+
+ // Called when the user clicks the 'No, thanks' button.
+ void OnBubbleViewNoThanksButtonPressed();
+
+ // GlobalError:
+ virtual bool HasMenuItem() OVERRIDE;
+ virtual int MenuItemCommandID() OVERRIDE;
+ virtual base::string16 MenuItemLabel() OVERRIDE;
+ virtual void ExecuteMenuItem(Browser* browser) OVERRIDE;
+ virtual bool HasBubbleView() OVERRIDE;
+ virtual bool HasShownBubbleView() OVERRIDE;
+ virtual void ShowBubbleView(Browser* browser) OVERRIDE;
+ virtual GlobalErrorBubbleViewBase* GetBubbleView() OVERRIDE;
+
+ private:
+ Profile* profile_;
+
+ // GlobalErrorService owns us, on which AutomaticProfileResetter depends, so
+ // during shutdown, it may get destroyed before we are.
+ // Note: the AutomaticProfileResetter expects call-backs from us to always be
+ // synchronous, so that there will be no call-backs once we are destroyed.
+ base::WeakPtr<AutomaticProfileResetter> automatic_profile_resetter_;
+
+ // Used to measure the delay before the bubble actually gets shown.
+ base::ElapsedTimer timer_;
+
+ // Whether or not we have already shown the bubble.
+ bool has_shown_bubble_view_;
+
+ // The reset bubble, if we're currently showing one.
+ GlobalErrorBubbleViewBase* bubble_view_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfileResetGlobalError);
+};
+
+#endif // CHROME_BROWSER_PROFILE_RESETTER_PROFILE_RESET_GLOBAL_ERROR_H_
diff --git a/chrome/browser/profile_resetter/resettable_settings_snapshot.cc b/chrome/browser/profile_resetter/resettable_settings_snapshot.cc
index 439153c..fb58ee3 100644
--- a/chrome/browser/profile_resetter/resettable_settings_snapshot.cc
+++ b/chrome/browser/profile_resetter/resettable_settings_snapshot.cc
@@ -29,7 +29,8 @@ using feedback::FeedbackData;
namespace {
-// Feedback bucket label.
+// Feedback bucket labels.
+const char kProfileResetPromptBucket[] = "SamplingOfSettingsResetPrompt";
const char kProfileResetWebUIBucket[] = "ProfileResetReport";
// Dictionary keys for feedback report.
@@ -219,9 +220,19 @@ std::string SerializeSettingsReport(const ResettableSettingsSnapshot& snapshot,
}
void SendSettingsFeedback(const std::string& report,
- Profile* profile) {
+ Profile* profile,
+ SnapshotCaller caller) {
scoped_refptr<FeedbackData> feedback_data = new FeedbackData();
- feedback_data->set_category_tag(kProfileResetWebUIBucket);
+ std::string bucket;
+ switch (caller) {
+ case PROFILE_RESET_WEBUI:
+ bucket = kProfileResetWebUIBucket;
+ break;
+ case PROFILE_RESET_PROMPT:
+ bucket = kProfileResetPromptBucket;
+ break;
+ }
+ feedback_data->set_category_tag(bucket);
feedback_data->set_description(report);
feedback_data->set_image(make_scoped_ptr(new std::string));
diff --git a/chrome/browser/profile_resetter/resettable_settings_snapshot.h b/chrome/browser/profile_resetter/resettable_settings_snapshot.h
index 84aa6e2..04b4541 100644
--- a/chrome/browser/profile_resetter/resettable_settings_snapshot.h
+++ b/chrome/browser/profile_resetter/resettable_settings_snapshot.h
@@ -114,6 +114,12 @@ class ResettableSettingsSnapshot {
DISALLOW_COPY_AND_ASSIGN(ResettableSettingsSnapshot);
};
+// The caller of ResettableSettingsSnapshot.
+enum SnapshotCaller {
+ PROFILE_RESET_WEBUI = 0,
+ PROFILE_RESET_PROMPT,
+};
+
// Serializes specified |snapshot| members to JSON format. |field_mask| is a bit
// mask of ResettableSettingsSnapshot::Field values.
std::string SerializeSettingsReport(const ResettableSettingsSnapshot& snapshot,
@@ -122,7 +128,8 @@ std::string SerializeSettingsReport(const ResettableSettingsSnapshot& snapshot,
// Sends |report| as a feedback. |report| is supposed to be result of
// SerializeSettingsReport().
void SendSettingsFeedback(const std::string& report,
- Profile* profile);
+ Profile* profile,
+ SnapshotCaller caller);
// Returns list of key/value pairs for all available reported information
// from the |profile| and some additional fields.