summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--build/all.gyp1
-rw-r--r--chrome/app/generated_resources.grd30
-rw-r--r--chrome/browser/browser_resources.grd4
-rw-r--r--chrome/browser/prefs/browser_prefs.cc15
-rw-r--r--chrome/browser/prefs/chrome_pref_service_factory.cc5
-rw-r--r--chrome/browser/prefs/pref_hash_filter.cc4
-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
-rw-r--r--chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc4
-rw-r--r--chrome/browser/profiles/profile_impl.cc18
-rw-r--r--chrome/browser/resources/options/automatic_settings_reset_banner.js40
-rw-r--r--chrome/browser/resources/options/browser_options.html1
-rw-r--r--chrome/browser/resources/options/options.js2
-rw-r--r--chrome/browser/resources/options/options_bundle.js1
-rw-r--r--chrome/browser/resources/options/reset_profile_settings_banner.html18
-rw-r--r--chrome/browser/resources/options/reset_profile_settings_banner.js62
-rw-r--r--chrome/browser/resources/options/reset_profile_settings_overlay.js2
-rw-r--r--chrome/browser/resources/options/settings_banner.css4
-rw-r--r--chrome/browser/resources/options/settings_banner.js41
-rw-r--r--chrome/browser/ui/profile_reset_bubble.h23
-rw-r--r--chrome/browser/ui/profile_reset_bubble_stub.cc21
-rw-r--r--chrome/browser/ui/views/profiles/profile_reset_bubble_view.cc447
-rw-r--r--chrome/browser/ui/views/profiles/profile_reset_bubble_view.h138
-rw-r--r--chrome/browser/ui/webui/options/reset_profile_settings_handler.cc44
-rw-r--r--chrome/browser/ui/webui/options/reset_profile_settings_handler.h13
-rw-r--r--chrome/chrome_browser.gypi15
-rw-r--r--chrome/chrome_browser_ui.gypi8
-rw-r--r--chrome/chrome_tests_unit.gypi3
-rw-r--r--chrome/common/chrome_constants.cc2
-rw-r--r--chrome/common/chrome_constants.h1
-rw-r--r--chrome/common/pref_names.cc13
-rw-r--r--chrome/common/pref_names.h3
-rw-r--r--chrome/tools/profile_reset/OWNERS3
-rw-r--r--chrome/tools/profile_reset/jtl_compiler.cc252
-rw-r--r--chrome/tools/profile_reset/jtl_compiler.gyp38
-rw-r--r--chrome/tools/profile_reset/jtl_compiler.h74
-rw-r--r--chrome/tools/profile_reset/jtl_compiler_frontend.cc112
-rw-r--r--chrome/tools/profile_reset/jtl_compiler_unittest.cc217
-rw-r--r--chrome/tools/profile_reset/jtl_parser.cc167
-rw-r--r--chrome/tools/profile_reset/jtl_parser.h83
-rw-r--r--chrome/tools/profile_reset/jtl_parser_unittest.cc346
-rw-r--r--tools/gritsettings/resource_ids2
-rw-r--r--tools/metrics/histograms/histograms.xml2
61 files changed, 8200 insertions, 76 deletions
diff --git a/build/all.gyp b/build/all.gyp
index 4e71a39..eff9af4 100644
--- a/build/all.gyp
+++ b/build/all.gyp
@@ -98,6 +98,7 @@
'dependencies': [
'../third_party/re2/re2.gyp:re2',
'../chrome/chrome.gyp:*',
+ '../chrome/tools/profile_reset/jtl_compiler.gyp:*',
'../cc/blink/cc_blink_tests.gyp:*',
'../cc/cc_tests.gyp:*',
'../device/bluetooth/bluetooth.gyp:*',
diff --git a/chrome/app/generated_resources.grd b/chrome/app/generated_resources.grd
index 8556823..2e97de6 100644
--- a/chrome/app/generated_resources.grd
+++ b/chrome/app/generated_resources.grd
@@ -8032,6 +8032,33 @@ Keep your key file in a safe place. You will need it to create new versions of y
Report an issue
</message>
+ <!-- settings reset bubble messages -->
+ <message name="IDS_RESETTING" desc="Text for the button, once the user clicked to reset settings.">
+ Resetting...
+ </message>
+ <message name="IDS_NO_THANKS" desc="Text for the button the user clicks to refuse settings reset.">
+ No thanks
+ </message>
+ <if expr="use_titlecase">
+ <message name="IDS_RESET_SETTINGS_MENU_ITEM" desc="In Title Case: Text for the Chrome menu option replacing Update required.">
+ Reset Altered <ph name="IDS_SHORT_PRODUCT_NAME">$1<ex>Chrome</ex></ph> Settings
+ </message>
+ </if>
+ <if expr="not use_titlecase">
+ <message name="IDS_RESET_SETTINGS_MENU_ITEM" desc="Text for the Chrome menu option replacing Update required.">
+ Reset altered <ph name="IDS_SHORT_PRODUCT_NAME">$1<ex>Chrome</ex></ph> settings
+ </message>
+ </if>
+ <message name="IDS_RESET_BUBBLE_TITLE" desc="Text for the title of the settings reset bubble view.">
+ Reset altered <ph name="IDS_SHORT_PRODUCT_NAME">$1<ex>Chrome</ex></ph> settings?
+ </message>
+ <message name="IDS_RESET_BUBBLE_TEXT" desc="Text for the settings reset bubble view full description.">
+ <ph name="IDS_SHORT_PRODUCT_NAME">$1<ex>Chrome</ex></ph> detected that your browser settings may have been changed without your knowledge. Would you like to reset them to their original defaults?
+ </message>
+ <message name="IDS_REPORT_BUBBLE_TEXT" desc="Text for the settings reset bubble reporting checkbox.">
+ Help make Google Chrome better by reporting the current settings
+ </message>
+
<!-- Upgrade bubble messages -->
<message name="IDS_REENABLE_UPDATES" desc="Text for the button the user clicks to re-enable automatic updates.">
Enable autoupdate
@@ -10309,6 +10336,9 @@ Chrome ran out of memory.
</message>
<!-- Reset Profile Settings strings -->
+ <message name="IDS_RESET_PROFILE_SETTINGS_BANNER_TEXT" desc="The text to show in a banner at the top of the chrome://settings page. The banner is displayed when Chrome detects that the settings might have been changed without the user's knowledge.">
+ Some of your settings may have been changed without your knowledge.
+ </message>
<message name="IDS_RESET_PROFILE_SETTINGS_SECTION_TITLE" desc="The title of the section in chrome://settings that allows resetting some settings in a profile">
Reset settings
</message>
diff --git a/chrome/browser/browser_resources.grd b/chrome/browser/browser_resources.grd
index 7c244f0..22e40fe 100644
--- a/chrome/browser/browser_resources.grd
+++ b/chrome/browser/browser_resources.grd
@@ -390,6 +390,10 @@
</if>
<if expr="_google_chrome">
<include name="IDR_PREF_HASH_SEED_BIN" file="resources\settings_internal\pref_hash_seed.bin" type="BINDATA" />
+ <include name="IDR_AUTOMATIC_PROFILE_RESET_RULES" file="internal\resources\profile_reset\automatic_profile_reset_rules.dat" type="BINDATA" />
+ <include name="IDR_AUTOMATIC_PROFILE_RESET_RULES_DRY" file="internal\resources\profile_reset\automatic_profile_reset_rules_dry.dat" type="BINDATA" />
+ <include name="IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED" file="internal\resources\profile_reset\automatic_profile_reset_rules.seed" type="BINDATA" />
+ <include name="IDR_AUTOMATIC_PROFILE_RESET_HASH_SEED_DRY" file="internal\resources\profile_reset\automatic_profile_reset_rules_dry.seed" type="BINDATA" />
<include name="IDR_ADDITIONAL_MODULE_IDS" file="${additional_modules_list_file}" use_base_dir="false" type="BINDATA" />
</if>
<if expr="chromeos">
diff --git a/chrome/browser/prefs/browser_prefs.cc b/chrome/browser/prefs/browser_prefs.cc
index 2dc4c325e..3d27e95 100644
--- a/chrome/browser/prefs/browser_prefs.cc
+++ b/chrome/browser/prefs/browser_prefs.cc
@@ -134,6 +134,7 @@
#include "chrome/browser/android/new_tab_page_prefs.h"
#else
#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_factory.h"
#include "chrome/browser/ui/autofill/generated_credit_card_bubble_controller.h"
#endif
@@ -220,11 +221,12 @@ const char kBackupPref[] = "backup";
// be cleared from user data.
const char kSyncPromoErrorMessage[] = "sync_promo.error_message";
-// The AutomaticProfileResetter service, which has since been unimplemented,
-// used this preference to save that the profile reset prompt had already been
-// shown. We keep the name here for now so that we can clear out legacy values.
+// The AutomaticProfileResetter service used this preference to save that the
+// profile reset prompt had already been shown, however, the preference has been
+// renamed in Local State. We keep the name here for now so that we can clear
+// out legacy values.
// TODO(engedy): Remove this and usages in M42 or later. See crbug.com/398813.
-const char kProfileResetPromptMemento[] = "profile.reset_prompt_memento";
+const char kLegacyProfileResetPromptMemento[] = "profile.reset_prompt_memento";
#endif
} // namespace
@@ -288,6 +290,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) {
#endif // defined(ENABLE_TASK_MANAGER)
#if !defined(OS_ANDROID)
+ AutomaticProfileResetterFactory::RegisterPrefs(registry);
BackgroundModeManager::RegisterPrefs(registry);
RegisterBrowserPrefs(registry);
#if !defined(OS_CHROMEOS)
@@ -349,7 +352,7 @@ void RegisterLocalState(PrefRegistrySimple* registry) {
// Preferences registered only for migration (clearing or moving to a new key)
// go here.
#if !defined(OS_ANDROID)
- registry->RegisterDictionaryPref(kProfileResetPromptMemento);
+ registry->RegisterDictionaryPref(kLegacyProfileResetPromptMemento);
#endif // !defined(OS_ANDROID)
}
@@ -601,7 +604,7 @@ void MigrateBrowserPrefs(Profile* profile, PrefService* local_state) {
}
#if !defined(OS_ANDROID)
- local_state->ClearPref(kProfileResetPromptMemento);
+ local_state->ClearPref(kLegacyProfileResetPromptMemento);
#endif
#if defined(OS_CHROMEOS)
diff --git a/chrome/browser/prefs/chrome_pref_service_factory.cc b/chrome/browser/prefs/chrome_pref_service_factory.cc
index 57a184c..733491a 100644
--- a/chrome/browser/prefs/chrome_pref_service_factory.cc
+++ b/chrome/browser/prefs/chrome_pref_service_factory.cc
@@ -153,6 +153,11 @@ const PrefHashFilter::TrackedPreferenceMetadata kTrackedPrefs[] = {
PrefHashFilter::ENFORCE_ON_LOAD,
PrefHashFilter::TRACKING_STRATEGY_ATOMIC
},
+ {
+ 13, prefs::kProfileResetPromptMementoInProfilePrefs,
+ PrefHashFilter::ENFORCE_ON_LOAD,
+ PrefHashFilter::TRACKING_STRATEGY_ATOMIC
+ },
#endif
{
14, DefaultSearchManager::kDefaultSearchProviderDataPrefName,
diff --git a/chrome/browser/prefs/pref_hash_filter.cc b/chrome/browser/prefs/pref_hash_filter.cc
index 3aceb62..fe216e8 100644
--- a/chrome/browser/prefs/pref_hash_filter.cc
+++ b/chrome/browser/prefs/pref_hash_filter.cc
@@ -31,10 +31,6 @@ void CleanupDeprecatedTrackedPreferences(
static const char* kDeprecatedTrackedPreferences[] = {
// TODO(gab): Remove in M41+.
"extensions.known_disabled",
-#if !defined(OS_ANDROID)
- // TODO(engedy): Remove this in M42 or later. See crbug.com/398813.
- "profile.reset_prompt_memento",
-#endif
};
for (size_t i = 0; i < arraysize(kDeprecatedTrackedPreferences); ++i) {
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.
diff --git a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
index a31257b..d7c72b0 100644
--- a/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
+++ b/chrome/browser/profiles/chrome_browser_main_extra_parts_profiles.cc
@@ -101,6 +101,7 @@
#include "chrome/browser/media/protected_media_identifier_permission_context_factory.h"
#else
#include "chrome/browser/notifications/sync_notifier/chrome_notifier_service_factory.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_factory.h"
#endif
#if defined(ENABLE_SPELLCHECK)
@@ -154,6 +155,9 @@ EnsureBrowserContextKeyedServiceFactoriesBuilt() {
AboutSigninInternalsFactory::GetInstance();
AccountTrackerServiceFactory::GetInstance();
autofill::PersonalDataManagerFactory::GetInstance();
+#if !defined(OS_ANDROID)
+ AutomaticProfileResetterFactory::GetInstance();
+#endif
#if defined(ENABLE_BACKGROUND)
BackgroundContentsServiceFactory::GetInstance();
#endif
diff --git a/chrome/browser/profiles/profile_impl.cc b/chrome/browser/profiles/profile_impl.cc
index 9cfe535..ce44ca2 100644
--- a/chrome/browser/profiles/profile_impl.cc
+++ b/chrome/browser/profiles/profile_impl.cc
@@ -282,17 +282,6 @@ PrefStore* CreateExtensionPrefStore(Profile* profile,
#endif
}
-#if !defined(OS_ANDROID)
-// Deletes the file that was used by the AutomaticProfileResetter service, which
-// has since been removed, to store that the prompt had already been shown.
-// TODO(engedy): Remove this and caller in M42 or later. See crbug.com/398813.
-void DeleteResetPromptMementoFile(const base::FilePath& profile_dir) {
- base::FilePath memento_path =
- profile_dir.Append(FILE_PATH_LITERAL("Reset Prompt Memento"));
- base::DeleteFile(memento_path, false);
-}
-#endif
-
} // namespace
// static
@@ -724,13 +713,6 @@ void ProfileImpl::DoFinalInit() {
// as a URLDataSource early.
RegisterDomDistillerViewerSource(this);
-#if !defined(OS_ANDROID)
- BrowserThread::GetBlockingPool()->PostDelayedWorkerTask(
- FROM_HERE,
- base::Bind(&DeleteResetPromptMementoFile, GetPath()),
- base::TimeDelta::FromMilliseconds(2 * create_readme_delay_ms));
-#endif
-
// Creation has been finished.
TRACE_EVENT_END1("browser",
"Profile::CreateProfile",
diff --git a/chrome/browser/resources/options/automatic_settings_reset_banner.js b/chrome/browser/resources/options/automatic_settings_reset_banner.js
index 3da6f5d..b16f6cb 100644
--- a/chrome/browser/resources/options/automatic_settings_reset_banner.js
+++ b/chrome/browser/resources/options/automatic_settings_reset_banner.js
@@ -25,12 +25,12 @@ cr.define('options', function() {
* Initializes the banner's event handlers.
*/
initialize: function() {
- this.showMetricName_ = 'AutomaticSettingsReset_WebUIBanner_BannerShown';
+ this.showMetricName = 'AutomaticSettingsReset_WebUIBanner_BannerShown';
- this.dismissNativeCallbackName_ =
+ this.dismissNativeCallbackName =
'onDismissedAutomaticSettingsResetBanner';
- this.setVisibilibyDomElement_ = $('automatic-settings-reset-banner');
+ this.visibilityDomElement = $('automatic-settings-reset-banner');
$('automatic-settings-reset-banner-close').onclick = function(event) {
chrome.send('metricsHandler:recordAction',
@@ -48,36 +48,18 @@ cr.define('options', function() {
PageManager.showPageByName('resetProfileSettings');
};
},
-
- /**
- * Called by the native code to show the banner if needed.
- * @private
- */
- show_: function() {
- if (!this.hadBeenDismissed_) {
- chrome.send('metricsHandler:recordAction', [this.showMetricName_]);
- this.setVisibility(true);
- }
- },
-
- /**
- * Called when the banner should be closed as a result of something taking
- * place on the WebUI page, i.e. when its close button is pressed, or when
- * the confirmation dialog for the profile settings reset feature is opened.
- * @private
- */
- dismiss_: function() {
- chrome.send(this.dismissNativeCallbackName_);
- this.hadBeenDismissed_ = true;
- this.setVisibility(false);
- },
};
- // Forward public APIs to private implementations.
- cr.makePublic(AutomaticSettingsResetBanner, [
+ // Forward public APIs to protected implementations.
+ [
'show',
'dismiss',
- ]);
+ ].forEach(function(name) {
+ AutomaticSettingsResetBanner[name] = function() {
+ var instance = AutomaticSettingsResetBanner.getInstance();
+ return instance[name].apply(instance, arguments);
+ };
+ });
// Export
return {
diff --git a/chrome/browser/resources/options/browser_options.html b/chrome/browser/resources/options/browser_options.html
index 452ddf8..da3641b 100644
--- a/chrome/browser/resources/options/browser_options.html
+++ b/chrome/browser/resources/options/browser_options.html
@@ -2,6 +2,7 @@
<header>
<h1 i18n-content="settingsTitle"></h1>
</header>
+ <include src="reset_profile_settings_banner.html">
<include src="automatic_settings_reset_banner.html">
<if expr="chromeos">
<include src="secondary_user_banner.html">
diff --git a/chrome/browser/resources/options/options.js b/chrome/browser/resources/options/options.js
index c90dba53..9d62c10 100644
--- a/chrome/browser/resources/options/options.js
+++ b/chrome/browser/resources/options/options.js
@@ -35,6 +35,7 @@ var PageManager = cr.ui.pageManager.PageManager;
var PasswordManager = options.PasswordManager;
var Preferences = options.Preferences;
var PreferredNetworks = options.PreferredNetworks;
+var ResetProfileSettingsBanner = options.ResetProfileSettingsBanner;
var ResetProfileSettingsOverlay = options.ResetProfileSettingsOverlay;
var SearchEngineManager = options.SearchEngineManager;
var SearchPage = options.SearchPage;
@@ -238,6 +239,7 @@ function load() {
cr.ui.FocusManager.disableMouseFocusOnButtons();
OptionsFocusManager.getInstance().initialize();
Preferences.getInstance().initialize();
+ ResetProfileSettingsBanner.getInstance().initialize();
AutomaticSettingsResetBanner.getInstance().initialize();
OptionsPage.initialize();
PageManager.initialize(BrowserOptions.getInstance());
diff --git a/chrome/browser/resources/options/options_bundle.js b/chrome/browser/resources/options/options_bundle.js
index 34ede49..bfb23ed 100644
--- a/chrome/browser/resources/options/options_bundle.js
+++ b/chrome/browser/resources/options/options_bundle.js
@@ -103,6 +103,7 @@ var CertificateImportErrorOverlay = options.CertificateImportErrorOverlay;
<include src="password_manager.js">
<include src="password_manager_list.js">
<include src="profiles_icon_grid.js">
+<include src="reset_profile_settings_banner.js">
<include src="reset_profile_settings_overlay.js">
<include src="search_engine_manager.js">
<include src="search_engine_manager_engine_list.js">
diff --git a/chrome/browser/resources/options/reset_profile_settings_banner.html b/chrome/browser/resources/options/reset_profile_settings_banner.html
new file mode 100644
index 0000000..fd95c3c
--- /dev/null
+++ b/chrome/browser/resources/options/reset_profile_settings_banner.html
@@ -0,0 +1,18 @@
+<div id="reset-profile-settings-banner" class="settings-banner" hidden>
+ <div id="reset-profile-settings-banner-close" class="close-button"></div>
+ <div class="content-area">
+ <div class="badge"></div>
+ <div class="text">
+ <p>
+ <span i18n-values=".innerHTML:resetProfileSettingsBannerText">
+ </span>
+ <a class="nowrap" i18n-values="href:resetProfileSettingsLearnMoreUrl"
+ i18n-content="learnMore" target="_blank"></a>
+ </p>
+ </div>
+ <div class="button-area">
+ <button id="reset-profile-settings-banner-activate"
+ i18n-content="resetProfileSettings"></button>
+ </div>
+ </div>
+</div>
diff --git a/chrome/browser/resources/options/reset_profile_settings_banner.js b/chrome/browser/resources/options/reset_profile_settings_banner.js
new file mode 100644
index 0000000..e54be25
--- /dev/null
+++ b/chrome/browser/resources/options/reset_profile_settings_banner.js
@@ -0,0 +1,62 @@
+// 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.
+
+// Note: the native-side handler for this is ResetProfileSettingsHandler.
+
+cr.define('options', function() {
+ /** @const */ var PageManager = cr.ui.pageManager.PageManager;
+ /** @const */ var SettingsBannerBase = options.SettingsBannerBase;
+
+ /**
+ * ResetProfileSettingsBanner class
+ * Provides encapsulated handling of the Reset Profile Settings banner.
+ * @constructor
+ */
+ function ResetProfileSettingsBanner() {}
+
+ cr.addSingletonGetter(ResetProfileSettingsBanner);
+
+ ResetProfileSettingsBanner.prototype = {
+ __proto__: SettingsBannerBase.prototype,
+
+ /**
+ * Initializes the banner's event handlers.
+ */
+ initialize: function() {
+ this.showMetricName = 'AutomaticReset_WebUIBanner_BannerShown';
+
+ this.dismissNativeCallbackName =
+ 'onDismissedResetProfileSettingsBanner';
+
+ this.visibilityDomElement = $('reset-profile-settings-banner');
+
+ $('reset-profile-settings-banner-close').onclick = function(event) {
+ chrome.send('metricsHandler:recordAction',
+ ['AutomaticReset_WebUIBanner_ManuallyClosed']);
+ ResetProfileSettingsBanner.dismiss();
+ };
+ $('reset-profile-settings-banner-activate').onclick = function(event) {
+ chrome.send('metricsHandler:recordAction',
+ ['AutomaticReset_WebUIBanner_ResetClicked']);
+ PageManager.showPageByName('resetProfileSettings');
+ };
+ },
+ };
+
+ // Forward public APIs to protected implementations.
+ [
+ 'show',
+ 'dismiss',
+ ].forEach(function(name) {
+ ResetProfileSettingsBanner[name] = function() {
+ var instance = ResetProfileSettingsBanner.getInstance();
+ return instance[name].apply(instance, arguments);
+ };
+ });
+
+ // Export
+ return {
+ ResetProfileSettingsBanner: ResetProfileSettingsBanner
+ };
+});
diff --git a/chrome/browser/resources/options/reset_profile_settings_overlay.js b/chrome/browser/resources/options/reset_profile_settings_overlay.js
index 3dd1b79..60cf57d 100644
--- a/chrome/browser/resources/options/reset_profile_settings_overlay.js
+++ b/chrome/browser/resources/options/reset_profile_settings_overlay.js
@@ -6,6 +6,7 @@ cr.define('options', function() {
var Page = cr.ui.pageManager.Page;
var AutomaticSettingsResetBanner = options.AutomaticSettingsResetBanner;
+ var ResetProfileSettingsBanner = options.ResetProfileSettingsBanner;
/**
* ResetProfileSettingsOverlay class
@@ -45,6 +46,7 @@ cr.define('options', function() {
/** @override */
didShowPage: function() {
+ ResetProfileSettingsBanner.dismiss();
chrome.send('onShowResetProfileDialog');
},
diff --git a/chrome/browser/resources/options/settings_banner.css b/chrome/browser/resources/options/settings_banner.css
index 82a84d7..33ac8c5 100644
--- a/chrome/browser/resources/options/settings_banner.css
+++ b/chrome/browser/resources/options/settings_banner.css
@@ -2,8 +2,8 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file. */
-/* TODO(engedy): These styles are used by automatic_settings_reset_banner.html
- * only. Rename/refactor this into automatic_settings_reset_banner.css. */
+/* These styles are used by both reset_profile_settings_banner.html and
+ * automatic_settings_reset_banner.html. */
.settings-banner {
background-color: #f5f5f5;
diff --git a/chrome/browser/resources/options/settings_banner.js b/chrome/browser/resources/options/settings_banner.js
index 5d030a4..666aaf2 100644
--- a/chrome/browser/resources/options/settings_banner.js
+++ b/chrome/browser/resources/options/settings_banner.js
@@ -2,9 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
-// TODO(engedy): AutomaticSettingsResetBanner is the sole class to derive from
-// SettingsBannerBase. Refactor this into automatic_settings_reset_banner.js.
-
cr.define('options', function() {
/**
@@ -36,26 +33,52 @@ cr.define('options', function() {
/**
* Metric name to send when a show event occurs.
+ * @protected
*/
- showMetricName_: '',
+ showMetricName: '',
/**
* Name of the native callback invoked when the banner is dismised.
+ * @protected
*/
- dismissNativeCallbackName_: '',
+ dismissNativeCallbackName: '',
/**
* DOM element whose visibility is set when setVisibility_ is called.
+ * @protected
+ */
+ visibilityDomElement: null,
+
+ /**
+ * Called by the native code to show the banner if needed.
+ * @protected
*/
- setVisibilibyDomElement_: null,
+ show: function() {
+ if (!this.hadBeenDismissed_) {
+ chrome.send('metricsHandler:recordAction', [this.showMetricName]);
+ this.setVisibility_(true);
+ }
+ },
+
+ /**
+ * Called when the banner should be closed as a result of something taking
+ * place on the WebUI page, i.e. when its close button is pressed, or when
+ * the confirmation dialog for the profile settings reset feature is opened.
+ * @protected
+ */
+ dismiss: function() {
+ chrome.send(this.dismissNativeCallbackName);
+ this.hadBeenDismissed_ = true;
+ this.setVisibility_(false);
+ },
/**
* Sets whether or not the reset profile settings banner shall be visible.
* @param {boolean} show Whether or not to show the banner.
- * @protected
+ * @private
*/
- setVisibility: function(show) {
- this.setVisibilibyDomElement_.hidden = !show;
+ setVisibility_: function(show) {
+ this.visibilityDomElement.hidden = !show;
},
};
diff --git a/chrome/browser/ui/profile_reset_bubble.h b/chrome/browser/ui/profile_reset_bubble.h
new file mode 100644
index 0000000..ad0bb2e
--- /dev/null
+++ b/chrome/browser/ui/profile_reset_bubble.h
@@ -0,0 +1,23 @@
+// 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_UI_PROFILE_RESET_BUBBLE_H_
+#define CHROME_BROWSER_UI_PROFILE_RESET_BUBBLE_H_
+
+#include "base/memory/weak_ptr.h"
+
+class Browser;
+class GlobalErrorBubbleViewBase;
+class ProfileResetGlobalError;
+
+// Returns whether or not the profile reset bubble is supported on this
+// platform.
+bool IsProfileResetBubbleSupported();
+
+// Shows the profile reset bubble on the platforms that support it.
+GlobalErrorBubbleViewBase* ShowProfileResetBubble(
+ const base::WeakPtr<ProfileResetGlobalError>& global_error,
+ Browser* browser);
+
+#endif // CHROME_BROWSER_UI_PROFILE_RESET_BUBBLE_H_
diff --git a/chrome/browser/ui/profile_reset_bubble_stub.cc b/chrome/browser/ui/profile_reset_bubble_stub.cc
new file mode 100644
index 0000000..d009660
--- /dev/null
+++ b/chrome/browser/ui/profile_reset_bubble_stub.cc
@@ -0,0 +1,21 @@
+// 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.
+
+// Definitions for chrome/browser/ui/profile_reset_bubble.h, for platforms where
+// the profile reset bubble is not implemented yet.
+
+#include "chrome/browser/ui/profile_reset_bubble.h"
+
+#include "base/logging.h"
+
+bool IsProfileResetBubbleSupported() {
+ return false;
+}
+
+GlobalErrorBubbleViewBase* ShowProfileResetBubble(
+ const base::WeakPtr<ProfileResetGlobalError>& global_error,
+ Browser* browser) {
+ NOTREACHED();
+ return NULL;
+}
diff --git a/chrome/browser/ui/views/profiles/profile_reset_bubble_view.cc b/chrome/browser/ui/views/profiles/profile_reset_bubble_view.cc
new file mode 100644
index 0000000..2b039f2
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_reset_bubble_view.cc
@@ -0,0 +1,447 @@
+// Copyright 2014 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/ui/views/profiles/profile_reset_bubble_view.h"
+
+#include "chrome/app/chrome_command_ids.h"
+#include "chrome/browser/profile_resetter/profile_reset_global_error.h"
+#include "chrome/browser/profile_resetter/resettable_settings_snapshot.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/browser/ui/views/frame/browser_view.h"
+#include "chrome/browser/ui/views/toolbar/toolbar_view.h"
+#include "chrome/common/url_constants.h"
+#include "components/google/core/browser/google_util.h"
+#include "content/public/browser/page_navigator.h"
+#include "content/public/browser/user_metrics.h"
+#include "grit/chromium_strings.h"
+#include "grit/components_strings.h"
+#include "grit/generated_resources.h"
+#include "grit/theme_resources.h"
+#include "ui/base/l10n/l10n_util.h"
+#include "ui/base/resource/resource_bundle.h"
+#include "ui/gfx/image/image_skia_operations.h"
+#include "ui/views/background.h"
+#include "ui/views/controls/button/checkbox.h"
+#include "ui/views/controls/button/image_button.h"
+#include "ui/views/controls/button/label_button.h"
+#include "ui/views/controls/label.h"
+#include "ui/views/controls/link.h"
+#include "ui/views/controls/scroll_view.h"
+#include "ui/views/controls/separator.h"
+#include "ui/views/layout/grid_layout.h"
+#include "ui/views/layout/layout_constants.h"
+
+using views::GridLayout;
+
+namespace {
+
+// Fixed width of the column holding the description label of the bubble.
+const int kWidthOfDescriptionText = 370;
+
+// Margins width for the top rows to compensate for the bottom panel for which
+// we don't want any margin.
+const int kMarginWidth = 12;
+const int kMarginHeight = kMarginWidth;
+
+// Width of a colum in the FeedbackView.
+const int kFeedbackViewColumnWidth = kWidthOfDescriptionText / 2 + kMarginWidth;
+
+// Width of the column used to disaplay the help button.
+const int kHelpButtonColumnWidth = 30;
+
+// Width of the reporting checkbox column.
+const int kReportingCheckboxColumnWidth =
+ kWidthOfDescriptionText + 2 * kMarginWidth;
+
+// Full width including all columns.
+const int kAllColumnsWidth =
+ kReportingCheckboxColumnWidth + kHelpButtonColumnWidth;
+
+// Maximum height of the scrollable feedback view.
+const int kMaxFeedbackViewHeight = 450;
+
+// The vertical padding between two values in the feedback view.
+const int kInterFeedbackValuePadding = 4;
+
+// We subtract 2 to account for the natural button padding, and
+// to bring the separation visually in line with the row separation
+// height.
+const int kButtonPadding = views::kRelatedButtonHSpacing - 2;
+
+// The color of the background of the sub panel to report current settings.
+const SkColor kLightGrayBackgroundColor = 0xFFF5F5F5;
+
+// This view is used to contain the scrollable contents that are shown the user
+// to expose what feedback will be sent back to Google.
+class FeedbackView : public views::View {
+ public:
+ FeedbackView() {}
+
+ // Setup the layout manager of the Feedback view using the content of the
+ // |feedback| ListValue which contains a list of key/value pairs stored in
+ // DictionaryValues. The key is to be displayed right aligned on the left, and
+ // the value as a left aligned multiline text on the right.
+ void SetupLayoutManager(const base::ListValue& feedback) {
+ RemoveAllChildViews(true);
+ set_background(views::Background::CreateSolidBackground(
+ kLightGrayBackgroundColor));
+
+ GridLayout* layout = new GridLayout(this);
+ SetLayoutManager(layout);
+
+ // We only need a single column set for left/right text and middle margin.
+ views::ColumnSet* cs = layout->AddColumnSet(0);
+ cs->AddColumn(GridLayout::FILL, GridLayout::LEADING, 1,
+ GridLayout::FIXED, kFeedbackViewColumnWidth, 0);
+ cs->AddPaddingColumn(0, kMarginWidth);
+ cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
+ GridLayout::FIXED, kFeedbackViewColumnWidth, 0);
+ for (size_t i = 0; i < feedback.GetSize(); ++i) {
+ const base::DictionaryValue* dictionary = NULL;
+ if (!feedback.GetDictionary(i, &dictionary) || !dictionary)
+ continue;
+
+ base::string16 key;
+ if (!dictionary->GetString("key", &key))
+ continue;
+
+ base::string16 value;
+ if (!dictionary->GetString("value", &value))
+ continue;
+
+ // The key is shown on the left, multi-line (required to allow wrapping in
+ // case the key name does not fit), and right-aligned.
+ views::Label* left_text_label = new views::Label(key);
+ left_text_label->SetMultiLine(true);
+ left_text_label->SetEnabledColor(SK_ColorGRAY);
+ left_text_label->SetHorizontalAlignment(gfx::ALIGN_RIGHT);
+
+ // The value is shown on the right, multi-line, left-aligned.
+ views::Label* right_text_label = new views::Label(value);
+ right_text_label->SetMultiLine(true);
+ right_text_label->SetEnabledColor(SK_ColorDKGRAY);
+ right_text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+
+ layout->StartRow(0, 0);
+ layout->AddView(left_text_label);
+ layout->AddView(right_text_label);
+ layout->AddPaddingRow(0, kInterFeedbackValuePadding);
+ }
+
+ // We need to set our size to our preferred size because our parent is a
+ // scroll view and doesn't know which size to set us to. Also since our
+ // parent scrolls, we are not bound to its size. So our size is based on the
+ // size computed by the our layout manager, which is what
+ // SizeToPreferredSize() does.
+ SizeToPreferredSize();
+ }
+
+ private:
+ DISALLOW_COPY_AND_ASSIGN(FeedbackView);
+};
+
+} // namespace
+
+// ProfileResetBubbleView ---------------------------------------------------
+
+// static
+ProfileResetBubbleView* ProfileResetBubbleView::ShowBubble(
+ const base::WeakPtr<ProfileResetGlobalError>& global_error,
+ Browser* browser) {
+ views::View* anchor_view =
+ BrowserView::GetBrowserViewForBrowser(browser)->toolbar()->app_menu();
+ ProfileResetBubbleView* reset_bubble = new ProfileResetBubbleView(
+ global_error, anchor_view, browser, browser->profile());
+ views::BubbleDelegateView::CreateBubble(reset_bubble)->Show();
+ content::RecordAction(base::UserMetricsAction("SettingsResetBubble.Show"));
+ return reset_bubble;
+}
+
+ProfileResetBubbleView::~ProfileResetBubbleView() {}
+
+views::View* ProfileResetBubbleView::GetInitiallyFocusedView() {
+ return controls_.reset_button;
+}
+
+void ProfileResetBubbleView::WindowClosing() {
+ if (global_error_)
+ global_error_->OnBubbleViewDidClose();
+}
+
+ProfileResetBubbleView::ProfileResetBubbleView(
+ const base::WeakPtr<ProfileResetGlobalError>& global_error,
+ views::View* anchor_view,
+ content::PageNavigator* navigator,
+ Profile* profile)
+ : BubbleDelegateView(anchor_view, views::BubbleBorder::TOP_RIGHT),
+ navigator_(navigator),
+ profile_(profile),
+ global_error_(global_error),
+ resetting_(false),
+ chose_to_reset_(false),
+ show_help_pane_(false),
+ weak_factory_(this) {
+}
+
+void ProfileResetBubbleView::ResetAllChildren() {
+ controls_.Reset();
+ SetLayoutManager(NULL);
+ RemoveAllChildViews(true);
+}
+
+void ProfileResetBubbleView::Init() {
+ set_margins(gfx::Insets(kMarginHeight, 0, 0, 0));
+ // Start requesting the feedback data.
+ snapshot_.reset(new ResettableSettingsSnapshot(profile_));
+ snapshot_->RequestShortcuts(
+ base::Bind(&ProfileResetBubbleView::UpdateFeedbackDetails,
+ weak_factory_.GetWeakPtr()));
+ SetupLayoutManager(true);
+}
+
+void ProfileResetBubbleView::SetupLayoutManager(bool report_checked) {
+ ResetAllChildren();
+
+ base::string16 product_name(
+ l10n_util::GetStringUTF16(IDS_SHORT_PRODUCT_NAME));
+ ui::ResourceBundle& rb = ui::ResourceBundle::GetSharedInstance();
+
+ // Bubble title label.
+ views::Label* title_label = new views::Label(
+ l10n_util::GetStringFUTF16(IDS_RESET_BUBBLE_TITLE, product_name),
+ rb.GetFontList(ui::ResourceBundle::BoldFont));
+ title_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+
+ // Description text label.
+ views::Label* text_label = new views::Label(
+ l10n_util::GetStringFUTF16(IDS_RESET_BUBBLE_TEXT, product_name));
+ text_label->SetMultiLine(true);
+ text_label->SetLineHeight(20);
+ text_label->SetEnabledColor(SK_ColorDKGRAY);
+ text_label->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+
+ // Learn more link.
+ views::Link* learn_more_link = new views::Link(
+ l10n_util::GetStringUTF16(IDS_LEARN_MORE));
+ learn_more_link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
+ learn_more_link->set_listener(this);
+ learn_more_link->SetUnderline(false);
+
+ // Reset button's name is based on |resetting_| state.
+ int reset_button_string_id = IDS_RESET_PROFILE_SETTINGS_COMMIT_BUTTON;
+ if (resetting_)
+ reset_button_string_id = IDS_RESETTING;
+ controls_.reset_button = new views::LabelButton(
+ this, l10n_util::GetStringUTF16(reset_button_string_id));
+ controls_.reset_button->SetStyle(views::Button::STYLE_BUTTON);
+ controls_.reset_button->SetIsDefault(true);
+ controls_.reset_button->SetFontList(
+ rb.GetFontList(ui::ResourceBundle::BoldFont));
+ controls_.reset_button->SetEnabled(!resetting_);
+ // For the Resetting... text to fit.
+ gfx::Size reset_button_size = controls_.reset_button->GetPreferredSize();
+ reset_button_size.set_width(100);
+ controls_.reset_button->SetMinSize(reset_button_size);
+
+ // No thanks button.
+ controls_.no_thanks_button = new views::LabelButton(
+ this, l10n_util::GetStringUTF16(IDS_NO_THANKS));
+ controls_.no_thanks_button->SetStyle(views::Button::STYLE_BUTTON);
+ controls_.no_thanks_button->SetEnabled(!resetting_);
+
+ // Checkbox for reporting settings or not.
+ controls_.report_settings_checkbox = new views::Checkbox(
+ l10n_util::GetStringUTF16(IDS_REPORT_BUBBLE_TEXT));
+ controls_.report_settings_checkbox->SetTextColor(
+ views::Button::STATE_NORMAL, SK_ColorGRAY);
+ controls_.report_settings_checkbox->SetChecked(report_checked);
+ controls_.report_settings_checkbox->SetTextMultiLine(true);
+ controls_.report_settings_checkbox->set_background(
+ views::Background::CreateSolidBackground(kLightGrayBackgroundColor));
+ // Have a smaller margin on the right, to have the |controls_.help_button|
+ // closer to the edge.
+ controls_.report_settings_checkbox->SetBorder(
+ views::Border::CreateSolidSidedBorder(kMarginWidth,
+ kMarginWidth,
+ kMarginWidth,
+ kMarginWidth / 2,
+ kLightGrayBackgroundColor));
+
+ // Help button to toggle the bottom panel on or off.
+ controls_.help_button = new views::ImageButton(this);
+ const gfx::ImageSkia* help_image = rb.GetImageSkiaNamed(IDR_QUESTION_MARK);
+ color_utils::HSL hsl_shift = { -1, 0, 0.8 };
+ brighter_help_image_ = gfx::ImageSkiaOperations::CreateHSLShiftedImage(
+ *help_image, hsl_shift);
+ controls_.help_button->SetImage(
+ views::Button::STATE_NORMAL, &brighter_help_image_);
+ controls_.help_button->SetImage(views::Button::STATE_HOVERED, help_image);
+ controls_.help_button->SetImage(views::Button::STATE_PRESSED, help_image);
+ controls_.help_button->set_background(
+ views::Background::CreateSolidBackground(kLightGrayBackgroundColor));
+ controls_.help_button->SetImageAlignment(views::ImageButton::ALIGN_CENTER,
+ views::ImageButton::ALIGN_MIDDLE);
+
+ GridLayout* layout = new GridLayout(this);
+ SetLayoutManager(layout);
+
+ // Title row.
+ const int kTitleColumnSetId = 0;
+ views::ColumnSet* cs = layout->AddColumnSet(kTitleColumnSetId);
+ cs->AddPaddingColumn(0, kMarginWidth);
+ cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
+ GridLayout::USE_PREF, 0, 0);
+ cs->AddPaddingColumn(0, kMarginWidth);
+
+ // Text row.
+ const int kTextColumnSetId = 1;
+ cs = layout->AddColumnSet(kTextColumnSetId);
+ cs->AddPaddingColumn(0, kMarginWidth);
+ cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
+ GridLayout::FIXED, kWidthOfDescriptionText, 0);
+ cs->AddPaddingColumn(0, kMarginWidth);
+
+ // Learn more link & buttons row.
+ const int kButtonsColumnSetId = 2;
+ cs = layout->AddColumnSet(kButtonsColumnSetId);
+ cs->AddPaddingColumn(0, kMarginWidth);
+ cs->AddColumn(GridLayout::LEADING, GridLayout::CENTER, 0,
+ GridLayout::USE_PREF, 0, 0);
+ cs->AddPaddingColumn(1, views::kRelatedControlHorizontalSpacing);
+ cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
+ GridLayout::USE_PREF, 0, 0);
+ cs->AddPaddingColumn(0, kButtonPadding);
+ cs->AddColumn(GridLayout::LEADING, GridLayout::TRAILING, 0,
+ GridLayout::USE_PREF, 0, 0);
+ cs->AddPaddingColumn(0, kMarginWidth);
+
+ // Separator.
+ const int kSeparatorColumnSetId = 3;
+ cs = layout->AddColumnSet(kSeparatorColumnSetId);
+ cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
+ GridLayout::FIXED, kAllColumnsWidth, 0);
+
+ // Reporting row.
+ const int kReportColumnSetId = 4;
+ cs = layout->AddColumnSet(kReportColumnSetId);
+ cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
+ GridLayout::FIXED, kReportingCheckboxColumnWidth, 0);
+ cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 0,
+ GridLayout::FIXED, kHelpButtonColumnWidth, 0);
+
+ layout->StartRow(0, kTitleColumnSetId);
+ layout->AddView(title_label);
+ layout->AddPaddingRow(0, kMarginHeight);
+
+ layout->StartRow(0, kTextColumnSetId);
+ layout->AddView(text_label);
+ layout->AddPaddingRow(0, kMarginHeight);
+
+ layout->StartRow(0, kButtonsColumnSetId);
+ layout->AddView(learn_more_link);
+ layout->AddView(controls_.reset_button);
+ layout->AddView(controls_.no_thanks_button);
+ layout->AddPaddingRow(0, kMarginHeight);
+
+ layout->StartRow(0, kSeparatorColumnSetId);
+ layout->AddView(new views::Separator(views::Separator::HORIZONTAL));
+
+ layout->StartRow(0, kReportColumnSetId);
+ layout->AddView(controls_.report_settings_checkbox);
+ layout->AddView(controls_.help_button);
+
+ if (show_help_pane_ && snapshot_) {
+ // We need a single row to add the scroll view containing the feedback.
+ const int kReportDetailsColumnSetId = 5;
+ cs = layout->AddColumnSet(kReportDetailsColumnSetId);
+ cs->AddColumn(GridLayout::FILL, GridLayout::FILL, 1,
+ GridLayout::USE_PREF, 0, 0);
+
+ FeedbackView* feedback_view = new FeedbackView();
+ scoped_ptr<base::ListValue> feedback_data =
+ GetReadableFeedbackForSnapshot(profile_, *snapshot_);
+ feedback_view->SetupLayoutManager(*feedback_data);
+
+ views::ScrollView* scroll_view = new views::ScrollView();
+ scroll_view->set_background(views::Background::CreateSolidBackground(
+ kLightGrayBackgroundColor));
+ scroll_view->SetContents(feedback_view);
+
+ layout->StartRow(1, kReportDetailsColumnSetId);
+ layout->AddView(scroll_view, 1, 1, GridLayout::FILL,
+ GridLayout::FILL, kAllColumnsWidth,
+ std::min(feedback_view->height() + kMarginHeight,
+ kMaxFeedbackViewHeight));
+ }
+
+ Layout();
+ AddAccelerator(ui::Accelerator(ui::VKEY_RETURN, ui::EF_NONE));
+}
+
+void ProfileResetBubbleView::ButtonPressed(views::Button* sender,
+ const ui::Event& event) {
+ if (sender == controls_.reset_button) {
+ DCHECK(!resetting_);
+ content::RecordAction(
+ base::UserMetricsAction("SettingsResetBubble.Reset"));
+
+ // Remember that the user chose to reset, and that resetting is underway.
+ chose_to_reset_ = true;
+ resetting_ = true;
+
+ controls_.reset_button->SetText(l10n_util::GetStringUTF16(IDS_RESETTING));
+ controls_.reset_button->SetEnabled(false);
+ controls_.no_thanks_button->SetEnabled(false);
+ SchedulePaint();
+
+ if (global_error_) {
+ global_error_->OnBubbleViewResetButtonPressed(
+ controls_.report_settings_checkbox->checked());
+ }
+ } else if (sender == controls_.no_thanks_button) {
+ DCHECK(!resetting_);
+ content::RecordAction(
+ base::UserMetricsAction("SettingsResetBubble.NoThanks"));
+
+ if (global_error_)
+ global_error_->OnBubbleViewNoThanksButtonPressed();
+ GetWidget()->Close();
+ return;
+ } else if (sender == controls_.help_button) {
+ show_help_pane_ = !show_help_pane_;
+
+ SetupLayoutManager(controls_.report_settings_checkbox->checked());
+ SizeToContents();
+ }
+}
+
+void ProfileResetBubbleView::LinkClicked(views::Link* source, int flags) {
+ content::RecordAction(
+ base::UserMetricsAction("SettingsResetBubble.LearnMore"));
+ navigator_->OpenURL(content::OpenURLParams(
+ GURL(chrome::kResetProfileSettingsLearnMoreURL), content::Referrer(),
+ NEW_FOREGROUND_TAB, content::PAGE_TRANSITION_LINK, false));
+}
+
+void ProfileResetBubbleView::CloseBubbleView() {
+ resetting_ = false;
+ GetWidget()->Close();
+}
+
+void ProfileResetBubbleView::UpdateFeedbackDetails() {
+ if (show_help_pane_)
+ SetupLayoutManager(controls_.report_settings_checkbox->checked());
+}
+
+bool IsProfileResetBubbleSupported() {
+ return true;
+}
+
+GlobalErrorBubbleViewBase* ShowProfileResetBubble(
+ const base::WeakPtr<ProfileResetGlobalError>& global_error,
+ Browser* browser) {
+ return ProfileResetBubbleView::ShowBubble(global_error, browser);
+}
diff --git a/chrome/browser/ui/views/profiles/profile_reset_bubble_view.h b/chrome/browser/ui/views/profiles/profile_reset_bubble_view.h
new file mode 100644
index 0000000..454bb2f
--- /dev/null
+++ b/chrome/browser/ui/views/profiles/profile_reset_bubble_view.h
@@ -0,0 +1,138 @@
+// Copyright 2014 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_UI_VIEWS_PROFILES_PROFILE_RESET_BUBBLE_VIEW_H_
+#define CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_RESET_BUBBLE_VIEW_H_
+
+#include "base/callback.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/memory/weak_ptr.h"
+#include "base/values.h"
+#include "chrome/browser/ui/global_error/global_error_bubble_view_base.h"
+#include "ui/gfx/image/image_skia.h"
+#include "ui/views/bubble/bubble_delegate.h"
+#include "ui/views/controls/button/button.h"
+#include "ui/views/controls/link_listener.h"
+
+namespace content {
+class PageNavigator;
+}
+
+namespace views {
+class Checkbox;
+class ImageButton;
+class LabelButton;
+class Link;
+}
+
+class Browser;
+class Profile;
+class ProfileResetGlobalError;
+class ResettableSettingsSnapshot;
+
+// ProfileResetBubbleView warns the user that a settings reset might be needed.
+// It is intended to be used as the content of a bubble anchored off of the
+// Chrome toolbar.
+class ProfileResetBubbleView : public views::BubbleDelegateView,
+ public views::ButtonListener,
+ public views::LinkListener,
+ public GlobalErrorBubbleViewBase {
+ public:
+ static ProfileResetBubbleView* ShowBubble(
+ const base::WeakPtr<ProfileResetGlobalError>& global_error,
+ Browser* browser);
+
+ // views::BubbleDelegateView methods.
+ virtual views::View* GetInitiallyFocusedView() OVERRIDE;
+ virtual void Init() OVERRIDE;
+
+ // views::WidgetDelegate method.
+ virtual void WindowClosing() OVERRIDE;
+
+ // GlobalErrorBubbleViewBase:
+ virtual void CloseBubbleView() OVERRIDE;
+
+ private:
+ ProfileResetBubbleView(
+ const base::WeakPtr<ProfileResetGlobalError>& global_error,
+ views::View* anchor_view,
+ content::PageNavigator* navigator,
+ Profile* profile);
+
+ virtual ~ProfileResetBubbleView();
+
+ // Reset all child views members and remove children from view hierarchy.
+ void ResetAllChildren();
+
+ // Sets up the layout manager and set the report checkbox to the value passed
+ // in |report_checked|.
+ void SetupLayoutManager(bool report_checked);
+
+ // views::ButtonListener method.
+ virtual void ButtonPressed(views::Button* sender,
+ const ui::Event& event) OVERRIDE;
+
+ // views::LinkListener method.
+ virtual void LinkClicked(views::Link* source, int event_flags) OVERRIDE;
+
+ // Sets the fully populated feedback data.
+ void UpdateFeedbackDetails();
+
+ struct Controls {
+ Controls() {
+ Reset();
+ }
+ void Reset() {
+ reset_button = NULL;
+ no_thanks_button = NULL;
+ help_button = NULL;
+ report_settings_checkbox = NULL;
+ }
+
+ // Button for the user to confirm a settings reset.
+ views::LabelButton* reset_button;
+
+ // Button for the user to refuse a settings reset.
+ views::LabelButton* no_thanks_button;
+
+ // Button for the user to get more info about reporting settings.
+ views::ImageButton* help_button;
+
+ // Checkbox for the user to choose to report the settings or not.
+ views::Checkbox* report_settings_checkbox;
+ } controls_;
+
+ // The snapshot is used to show user feedback information.
+ scoped_ptr<ResettableSettingsSnapshot> snapshot_;
+
+ // A version of the help image that is brighter.
+ gfx::ImageSkia brighter_help_image_;
+
+ // Used for opening the learn more link.
+ content::PageNavigator* navigator_;
+
+ // Used to access profile specific stuff like the global error or readable
+ // feedback.
+ Profile* profile_;
+
+ // The GlobalError this Bubble belongs to.
+ base::WeakPtr<ProfileResetGlobalError> global_error_;
+
+ // Remembers if we are currently resetting or not.
+ bool resetting_;
+
+ // Remembers if the reset button was hit before closing the bubble.
+ bool chose_to_reset_;
+
+ // Toggles when the user clicks on the |help_button_| to identify if we should
+ // show the help pane or not.
+ bool show_help_pane_;
+
+ // To cancel pending callbacks after destruction.
+ base::WeakPtrFactory<ProfileResetBubbleView> weak_factory_;
+
+ DISALLOW_COPY_AND_ASSIGN(ProfileResetBubbleView);
+};
+
+#endif // CHROME_BROWSER_UI_VIEWS_PROFILES_PROFILE_RESET_BUBBLE_VIEW_H_
diff --git a/chrome/browser/ui/webui/options/reset_profile_settings_handler.cc b/chrome/browser/ui/webui/options/reset_profile_settings_handler.cc
index cd4db5e..86bacf3 100644
--- a/chrome/browser/ui/webui/options/reset_profile_settings_handler.cc
+++ b/chrome/browser/ui/webui/options/reset_profile_settings_handler.cc
@@ -11,6 +11,8 @@
#include "base/strings/string16.h"
#include "base/values.h"
#include "chrome/browser/google/google_brand.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter.h"
+#include "chrome/browser/profile_resetter/automatic_profile_resetter_factory.h"
#include "chrome/browser/profile_resetter/brandcode_config_fetcher.h"
#include "chrome/browser/profile_resetter/brandcoded_default_settings.h"
#include "chrome/browser/profile_resetter/profile_resetter.h"
@@ -24,7 +26,9 @@
namespace options {
-ResetProfileSettingsHandler::ResetProfileSettingsHandler() {
+ResetProfileSettingsHandler::ResetProfileSettingsHandler()
+ : automatic_profile_resetter_(NULL),
+ has_shown_confirmation_dialog_(false) {
google_brand::GetBrand(&brandcode_);
}
@@ -33,12 +37,25 @@ ResetProfileSettingsHandler::~ResetProfileSettingsHandler() {}
void ResetProfileSettingsHandler::InitializeHandler() {
Profile* profile = Profile::FromWebUI(web_ui());
resetter_.reset(new ProfileResetter(profile));
+ automatic_profile_resetter_ =
+ AutomaticProfileResetterFactory::GetForBrowserContext(profile);
}
void ResetProfileSettingsHandler::InitializePage() {
web_ui()->CallJavascriptFunction(
"ResetProfileSettingsOverlay.setResettingState",
base::FundamentalValue(resetter_->IsActive()));
+ if (automatic_profile_resetter_ &&
+ automatic_profile_resetter_->ShouldShowResetBanner()) {
+ web_ui()->CallJavascriptFunction("ResetProfileSettingsBanner.show");
+ }
+}
+
+void ResetProfileSettingsHandler::Uninitialize() {
+ if (has_shown_confirmation_dialog_ && automatic_profile_resetter_) {
+ automatic_profile_resetter_->NotifyDidCloseWebUIResetDialog(
+ false /*performed_reset*/);
+ }
}
void ResetProfileSettingsHandler::GetLocalizedValues(
@@ -46,6 +63,8 @@ void ResetProfileSettingsHandler::GetLocalizedValues(
DCHECK(localized_strings);
static OptionsStringResource resources[] = {
+ { "resetProfileSettingsBannerText",
+ IDS_RESET_PROFILE_SETTINGS_BANNER_TEXT },
{ "resetProfileSettingsCommit", IDS_RESET_PROFILE_SETTINGS_COMMIT_BUTTON },
{ "resetProfileSettingsExplanation",
IDS_RESET_PROFILE_SETTINGS_EXPLANATION },
@@ -71,6 +90,10 @@ void ResetProfileSettingsHandler::RegisterMessages() {
web_ui()->RegisterMessageCallback("onHideResetProfileDialog",
base::Bind(&ResetProfileSettingsHandler::OnHideResetProfileDialog,
base::Unretained(this)));
+ web_ui()->RegisterMessageCallback("onDismissedResetProfileSettingsBanner",
+ base::Bind(&ResetProfileSettingsHandler::
+ OnDismissedResetProfileSettingsBanner,
+ base::Unretained(this)));
}
void ResetProfileSettingsHandler::HandleResetProfileSettings(
@@ -102,10 +125,17 @@ void ResetProfileSettingsHandler::OnResetProfileSettingsDone(
setting_snapshot_->Subtract(current_snapshot);
std::string report = SerializeSettingsReport(*setting_snapshot_,
difference);
- SendSettingsFeedback(report, profile);
+ bool is_reset_prompt_active = automatic_profile_resetter_ &&
+ automatic_profile_resetter_->IsResetPromptFlowActive();
+ SendSettingsFeedback(report, profile, is_reset_prompt_active ?
+ PROFILE_RESET_PROMPT : PROFILE_RESET_WEBUI);
}
}
setting_snapshot_.reset();
+ if (automatic_profile_resetter_) {
+ automatic_profile_resetter_->NotifyDidCloseWebUIResetDialog(
+ true /*performed_reset*/);
+ }
}
void ResetProfileSettingsHandler::OnShowResetProfileDialog(
@@ -118,6 +148,10 @@ void ResetProfileSettingsHandler::OnShowResetProfileDialog(
UpdateFeedbackUI();
}
+ if (automatic_profile_resetter_)
+ automatic_profile_resetter_->NotifyDidOpenWebUIResetDialog();
+ has_shown_confirmation_dialog_ = true;
+
if (brandcode_.empty())
return;
config_fetcher_.reset(new BrandcodeConfigFetcher(
@@ -133,6 +167,12 @@ void ResetProfileSettingsHandler::OnHideResetProfileDialog(
setting_snapshot_.reset();
}
+void ResetProfileSettingsHandler::OnDismissedResetProfileSettingsBanner(
+ const base::ListValue* args) {
+ if (automatic_profile_resetter_)
+ automatic_profile_resetter_->NotifyDidCloseWebUIResetBanner();
+}
+
void ResetProfileSettingsHandler::OnSettingsFetched() {
DCHECK(config_fetcher_);
DCHECK(!config_fetcher_->IsActive());
diff --git a/chrome/browser/ui/webui/options/reset_profile_settings_handler.h b/chrome/browser/ui/webui/options/reset_profile_settings_handler.h
index 8c1528c..997f681 100644
--- a/chrome/browser/ui/webui/options/reset_profile_settings_handler.h
+++ b/chrome/browser/ui/webui/options/reset_profile_settings_handler.h
@@ -17,6 +17,7 @@ class DictionaryValue;
class ListValue;
} // namespace base
+class AutomaticProfileResetter;
class BrandcodeConfigFetcher;
class ProfileResetter;
class ResettableSettingsSnapshot;
@@ -37,6 +38,7 @@ class ResetProfileSettingsHandler
base::DictionaryValue* localized_strings) OVERRIDE;
virtual void InitializeHandler() OVERRIDE;
virtual void InitializePage() OVERRIDE;
+ virtual void Uninitialize() OVERRIDE;
// WebUIMessageHandler implementation.
virtual void RegisterMessages() OVERRIDE;
@@ -54,6 +56,9 @@ class ResetProfileSettingsHandler
// Called when the confirmation box disappears.
void OnHideResetProfileDialog(const base::ListValue* value);
+ // Called when the reset banner is dismissed from the WebUI.
+ void OnDismissedResetProfileSettingsBanner(const base::ListValue* args);
+
// Called when BrandcodeConfigFetcher completed fetching settings.
void OnSettingsFetched();
@@ -64,6 +69,14 @@ class ResetProfileSettingsHandler
// Sets new values for the feedback area.
void UpdateFeedbackUI();
+ // Destroyed with the Profile, thus it should outlive us. This will be NULL if
+ // the underlying profile is off-the-record (e.g. in Guest mode on Chrome OS).
+ AutomaticProfileResetter* automatic_profile_resetter_;
+
+ // Records whether or not the Profile Reset confirmation dialog was opened at
+ // least once during the lifetime of the settings page.
+ bool has_shown_confirmation_dialog_;
+
scoped_ptr<ProfileResetter> resetter_;
scoped_ptr<BrandcodeConfigFetcher> config_fetcher_;
diff --git a/chrome/chrome_browser.gypi b/chrome/chrome_browser.gypi
index 3dd8d103..bed6356d 100644
--- a/chrome/chrome_browser.gypi
+++ b/chrome/chrome_browser.gypi
@@ -2093,10 +2093,25 @@
'browser/process_singleton_posix.cc',
'browser/process_singleton_startup_lock.cc',
'browser/process_singleton_startup_lock.h',
+ 'browser/profile_resetter/automatic_profile_resetter.cc',
+ 'browser/profile_resetter/automatic_profile_resetter_delegate.cc',
+ 'browser/profile_resetter/automatic_profile_resetter_delegate.h',
+ 'browser/profile_resetter/automatic_profile_resetter_factory.cc',
+ 'browser/profile_resetter/automatic_profile_resetter_factory.h',
+ 'browser/profile_resetter/automatic_profile_resetter.h',
+ 'browser/profile_resetter/automatic_profile_resetter_mementos.cc',
+ 'browser/profile_resetter/automatic_profile_resetter_mementos.h',
'browser/profile_resetter/brandcode_config_fetcher.cc',
'browser/profile_resetter/brandcode_config_fetcher.h',
'browser/profile_resetter/brandcoded_default_settings.cc',
'browser/profile_resetter/brandcoded_default_settings.h',
+ 'browser/profile_resetter/jtl_foundation.cc',
+ 'browser/profile_resetter/jtl_foundation.h',
+ 'browser/profile_resetter/jtl_instructions.h',
+ 'browser/profile_resetter/jtl_interpreter.cc',
+ 'browser/profile_resetter/jtl_interpreter.h',
+ 'browser/profile_resetter/profile_reset_global_error.cc',
+ 'browser/profile_resetter/profile_reset_global_error.h',
'browser/profile_resetter/profile_resetter.cc',
'browser/profile_resetter/profile_resetter.h',
'browser/profile_resetter/resettable_settings_snapshot.cc',
diff --git a/chrome/chrome_browser_ui.gypi b/chrome/chrome_browser_ui.gypi
index 5fddda7..6fc17b9 100644
--- a/chrome/chrome_browser_ui.gypi
+++ b/chrome/chrome_browser_ui.gypi
@@ -864,6 +864,7 @@
'browser/ui/settings_window_manager.cc',
'browser/ui/settings_window_manager.h',
'browser/ui/settings_window_manager_observer.h',
+ 'browser/ui/profile_reset_bubble.h',
'browser/ui/simple_message_box.h',
'browser/ui/status_bubble.h',
'browser/ui/sync/inline_login_dialog.cc',
@@ -2175,6 +2176,8 @@
'browser/ui/views/profiles/new_avatar_button.h',
'browser/ui/views/profiles/profile_chooser_view.cc',
'browser/ui/views/profiles/profile_chooser_view.h',
+ 'browser/ui/views/profiles/profile_reset_bubble_view.cc',
+ 'browser/ui/views/profiles/profile_reset_bubble_view.h',
'browser/ui/views/profiles/user_manager_view.cc',
'browser/ui/views/profiles/user_manager_view.h',
'browser/ui/views/renderer_context_menu/render_view_context_menu_views.cc',
@@ -2313,6 +2316,9 @@
'browser/ui/webui/gesture_config_ui.h',
'browser/ui/window_sizer/window_sizer_aura.cc',
],
+ 'chrome_browser_ui_non_aura_sources': [
+ 'browser/ui/profile_reset_bubble_stub.cc',
+ ],
# Aura but not ChromeOS.
'chrome_browser_ui_aura_non_chromeos': [
'browser/ui/aura/active_desktop_monitor.cc',
@@ -2766,6 +2772,8 @@
'sources': [ '<@(chrome_browser_ui_aura_non_chromeos)' ],
}],
],
+ }, { # else: use_aura==0
+ 'sources': [ '<@(chrome_browser_ui_non_aura_sources)' ],
}],
['ui_compositor_image_transport==1', {
'dependencies': [
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 605eced..7b4222a 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -2528,6 +2528,9 @@
],
}],
['OS!="android" and OS!="ios"', {
+ 'dependencies': [
+ 'tools/profile_reset/jtl_compiler.gyp:jtl_compiler_lib',
+ ],
'sources!': [
'browser/metrics/variations/variations_request_scheduler_mobile_unittest.cc',
'browser/net/spdyproxy/data_reduction_proxy_settings_unittest.cc',
diff --git a/chrome/common/chrome_constants.cc b/chrome/common/chrome_constants.cc
index 523acc7..1b0b5cc 100644
--- a/chrome/common/chrome_constants.cc
+++ b/chrome/common/chrome_constants.cc
@@ -167,6 +167,8 @@ const base::FilePath::CharType kPreferencesFilename[] = FPL("Preferences");
const base::FilePath::CharType kProtectedPreferencesFilenameDeprecated[] =
FPL("Protected Preferences");
const base::FilePath::CharType kReadmeFilename[] = FPL("README");
+const base::FilePath::CharType kResetPromptMementoFilename[] =
+ FPL("Reset Prompt Memento");
const base::FilePath::CharType kSafeBrowsingBaseFilename[] =
FPL("Safe Browsing");
const base::FilePath::CharType kSecurePreferencesFilename[] =
diff --git a/chrome/common/chrome_constants.h b/chrome/common/chrome_constants.h
index a750bed..3d2fd7b5 100644
--- a/chrome/common/chrome_constants.h
+++ b/chrome/common/chrome_constants.h
@@ -86,6 +86,7 @@ extern const base::FilePath::CharType kNewTabThumbnailsFilename[];
extern const base::FilePath::CharType kPreferencesFilename[];
extern const base::FilePath::CharType kProtectedPreferencesFilenameDeprecated[];
extern const base::FilePath::CharType kReadmeFilename[];
+extern const base::FilePath::CharType kResetPromptMementoFilename[];
extern const base::FilePath::CharType kSafeBrowsingBaseFilename[];
extern const base::FilePath::CharType kSecurePreferencesFilename[];
extern const base::FilePath::CharType kServiceStateFileName[];
diff --git a/chrome/common/pref_names.cc b/chrome/common/pref_names.cc
index 8a104aa..52560a9 100644
--- a/chrome/common/pref_names.cc
+++ b/chrome/common/pref_names.cc
@@ -1176,6 +1176,11 @@ const char kSupervisedUserCreationAllowed[] =
// List pref containing the users supervised by this user.
const char kSupervisedUsers[] = "profile.managed_users";
+// String that indicates that the profile reset prompt has already been shown to
+// the user (profile).
+const char kProfileResetPromptMementoInProfilePrefs[] =
+ "profile.reset_prompt_memento";
+
// List pref containing the extension ids which are not allowed to send
// notifications to the message center.
const char kMessageCenterDisabledExtensionIds[] =
@@ -1272,6 +1277,14 @@ const char kProfileCreatedByVersion[] = "profile.created_by_version";
// them.
const char kProfileInfoCache[] = "profile.info_cache";
+// Dictionary that maps profile keys to strings that indicate that the profile
+// reset prompt has already been shown to the corresponding user (profile).
+// This is semantically similar to kProfileResetPromptMementoInProfilePrefs, see
+// chrome/browser/profile_resetter/automatic_profile_resetter_mementos.h for an
+// explanation of why this redundancy is needed.
+const char kProfileResetPromptMementosInLocalState[] =
+ "profile.reset_prompt_mementos";
+
// Prefs for SSLConfigServicePref.
const char kCertRevocationCheckingEnabled[] = "ssl.rev_checking.enabled";
const char kCertRevocationCheckingRequiredLocalAnchors[] =
diff --git a/chrome/common/pref_names.h b/chrome/common/pref_names.h
index 83e6cac..c3f142d 100644
--- a/chrome/common/pref_names.h
+++ b/chrome/common/pref_names.h
@@ -374,6 +374,8 @@ extern const char kDefaultSupervisedUserFilteringBehavior[];
extern const char kSupervisedUserCreationAllowed[];
extern const char kSupervisedUsers[];
+extern const char kProfileResetPromptMementoInProfilePrefs[];
+
extern const char kMessageCenterDisabledExtensionIds[];
extern const char kMessageCenterDisabledSystemComponentIds[];
extern const char kWelcomeNotificationDismissed[];
@@ -436,6 +438,7 @@ extern const char kProfilesLastActive[];
extern const char kProfilesNumCreated[];
extern const char kProfileInfoCache[];
extern const char kProfileCreatedByVersion[];
+extern const char kProfileResetPromptMementosInLocalState[];
extern const char kStabilityPageLoadCount[];
extern const char kStabilityRendererCrashCount[];
diff --git a/chrome/tools/profile_reset/OWNERS b/chrome/tools/profile_reset/OWNERS
new file mode 100644
index 0000000..de6863e
--- /dev/null
+++ b/chrome/tools/profile_reset/OWNERS
@@ -0,0 +1,3 @@
+battre@chromium.org
+engedy@chromium.org
+
diff --git a/chrome/tools/profile_reset/jtl_compiler.cc b/chrome/tools/profile_reset/jtl_compiler.cc
new file mode 100644
index 0000000..ffa5ec2
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_compiler.cc
@@ -0,0 +1,252 @@
+// 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/tools/profile_reset/jtl_compiler.h"
+
+#include <limits>
+#include <map>
+#include <numeric>
+
+#include "base/logging.h"
+#include "chrome/browser/profile_resetter/jtl_foundation.h"
+#include "chrome/tools/profile_reset/jtl_parser.h"
+
+namespace jtl = jtl_foundation;
+
+namespace {
+
+// Serializes symbols into byte-code in a streaming manner.
+class ByteCodeWriter {
+ public:
+ explicit ByteCodeWriter(std::string* output) : output_(output) {}
+ ~ByteCodeWriter() {}
+
+ void WriteUint8(uint8 value) { output_->push_back(static_cast<char>(value)); }
+ void WriteUint32(uint32 value) {
+ for (int i = 0; i < 4; ++i) {
+ output_->push_back(static_cast<char>(value & 0xFFu));
+ value >>= 8;
+ }
+ }
+ void WriteOpCode(uint8 op_code) { WriteUint8(op_code); }
+ void WriteHash(const std::string& hash) {
+ CHECK(jtl::Hasher::IsHash(hash));
+ *output_ += hash;
+ }
+ void WriteBool(bool value) { WriteUint8(value ? 1u : 0u); }
+
+ private:
+ std::string* output_;
+
+ DISALLOW_COPY_AND_ASSIGN(ByteCodeWriter);
+};
+
+// Encapsulates meta-data about all instructions, and is capable of transcoding
+// each instruction from a parsed text-based format to byte-code.
+class InstructionSet {
+ public:
+ InstructionSet() {
+ // Define each instruction in this list.
+ // Note:
+ // - Instructions ending in "hash" will write their 'HashString' arguments
+ // directly into the byte-code.
+ // - Instructions ending in "hashed" will first hash their 'String'
+ // arguments, and will write this hash to the byte-code.
+ Add(Instruction("go", jtl::NAVIGATE, Arguments(String)));
+ Add(Instruction("any", jtl::NAVIGATE_ANY, Arguments()));
+ Add(Instruction("back", jtl::NAVIGATE_BACK, Arguments()));
+ Add(Instruction("store_bool", jtl::STORE_BOOL, Arguments(String, Bool)));
+ Add(Instruction("store_hash",
+ jtl::STORE_HASH, Arguments(String, HashString)));
+ Add(Instruction("store_hashed",
+ jtl::STORE_HASH, Arguments(String, String)));
+ Add(Instruction("store_node_bool",
+ jtl::STORE_NODE_BOOL, Arguments(String)));
+ Add(Instruction("store_node_hash",
+ jtl::STORE_NODE_HASH, Arguments(String)));
+ Add(Instruction("store_node_registerable_domain_hash",
+ jtl::STORE_NODE_REGISTERABLE_DOMAIN_HASH,
+ Arguments(String)));
+ Add(Instruction("compare_bool", jtl::COMPARE_NODE_BOOL, Arguments(Bool)));
+ Add(Instruction("compare_hashed",
+ jtl::COMPARE_NODE_HASH, Arguments(String)));
+ Add(Instruction("compare_hashed_not",
+ jtl::COMPARE_NODE_HASH_NOT, Arguments(String)));
+ Add(Instruction("compare_stored_bool",
+ jtl::COMPARE_STORED_BOOL,
+ Arguments(String, Bool, Bool)));
+ Add(Instruction("compare_stored_hashed",
+ jtl::COMPARE_STORED_HASH,
+ Arguments(String, String, String)));
+ Add(Instruction("compare_to_stored_bool",
+ jtl::COMPARE_NODE_TO_STORED_BOOL,
+ Arguments(String)));
+ Add(Instruction("compare_to_stored_hash",
+ jtl::COMPARE_NODE_TO_STORED_HASH,
+ Arguments(String)));
+ Add(Instruction("compare_substring_hashed",
+ jtl::COMPARE_NODE_SUBSTRING,
+ Arguments(StringPattern)));
+ Add(Instruction("break", jtl::STOP_EXECUTING_SENTENCE, Arguments()));
+ }
+
+ JtlCompiler::CompileError::ErrorCode TranscodeInstruction(
+ const std::string& name,
+ const base::ListValue& arguments,
+ bool ends_sentence,
+ const jtl::Hasher& hasher,
+ ByteCodeWriter* target) const {
+ if (instruction_map_.count(name) == 0)
+ return JtlCompiler::CompileError::INVALID_OPERATION_NAME;
+ const Instruction& instruction(instruction_map_.at(name));
+ if (instruction.argument_types.size() != arguments.GetSize())
+ return JtlCompiler::CompileError::INVALID_ARGUMENT_COUNT;
+ target->WriteOpCode(instruction.op_code);
+ for (size_t i = 0; i < arguments.GetSize(); ++i) {
+ switch (instruction.argument_types[i]) {
+ case Bool: {
+ bool value = false;
+ if (!arguments.GetBoolean(i, &value))
+ return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
+ target->WriteBool(value);
+ break;
+ }
+ case String: {
+ std::string value;
+ if (!arguments.GetString(i, &value))
+ return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
+ target->WriteHash(hasher.GetHash(value));
+ break;
+ }
+ case StringPattern: {
+ std::string value;
+ if (!arguments.GetString(i, &value))
+ return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
+ if (value.empty() ||
+ value.size() > std::numeric_limits<uint32>::max())
+ return JtlCompiler::CompileError::INVALID_ARGUMENT_VALUE;
+ target->WriteHash(hasher.GetHash(value));
+ target->WriteUint32(static_cast<uint32>(value.size()));
+ uint32 pattern_sum = std::accumulate(
+ value.begin(), value.end(), static_cast<uint32>(0u));
+ target->WriteUint32(pattern_sum);
+ break;
+ }
+ case HashString: {
+ std::string hash_value;
+ if (!arguments.GetString(i, &hash_value) ||
+ !jtl::Hasher::IsHash(hash_value))
+ return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
+ target->WriteHash(hash_value);
+ break;
+ }
+ default:
+ NOTREACHED();
+ return JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE;
+ }
+ }
+ if (ends_sentence)
+ target->WriteOpCode(jtl::END_OF_SENTENCE);
+ return JtlCompiler::CompileError::ERROR_NONE;
+ }
+
+ private:
+ // The possible types of an operation's argument.
+ enum ArgumentType {
+ None,
+ Bool,
+ String,
+ StringPattern,
+ HashString
+ };
+
+ // Encapsulates meta-data about one instruction.
+ struct Instruction {
+ Instruction() : op_code(jtl::END_OF_SENTENCE) {}
+ Instruction(const char* name,
+ jtl_foundation::OpCodes op_code,
+ const std::vector<ArgumentType>& argument_types)
+ : name(name), op_code(op_code), argument_types(argument_types) {}
+
+ std::string name;
+ jtl::OpCodes op_code;
+ std::vector<ArgumentType> argument_types;
+ };
+
+ static std::vector<ArgumentType> Arguments(ArgumentType arg1_type = None,
+ ArgumentType arg2_type = None,
+ ArgumentType arg3_type = None) {
+ std::vector<ArgumentType> result;
+ if (arg1_type != None)
+ result.push_back(arg1_type);
+ if (arg2_type != None)
+ result.push_back(arg2_type);
+ if (arg3_type != None)
+ result.push_back(arg3_type);
+ return result;
+ }
+
+ void Add(const Instruction& instruction) {
+ instruction_map_[instruction.name] = instruction;
+ }
+
+ std::map<std::string, Instruction> instruction_map_;
+
+ DISALLOW_COPY_AND_ASSIGN(InstructionSet);
+};
+
+} // namespace
+
+bool JtlCompiler::Compile(const std::string& source_code,
+ const std::string& hash_seed,
+ std::string* output_bytecode,
+ CompileError* error_details) {
+ DCHECK(output_bytecode);
+ InstructionSet instruction_set;
+ ByteCodeWriter bytecode_writer(output_bytecode);
+ jtl::Hasher hasher(hash_seed);
+
+ std::string compacted_source_code;
+ std::vector<size_t> newline_indices;
+ size_t mismatched_quotes_line;
+ if (!JtlParser::RemoveCommentsAndAllWhitespace(source_code,
+ &compacted_source_code,
+ &newline_indices,
+ &mismatched_quotes_line)) {
+ if (error_details) {
+ error_details->context = ""; // No meaningful intra-line context here.
+ error_details->line_number = mismatched_quotes_line;
+ error_details->error_code = CompileError::MISMATCHED_DOUBLE_QUOTES;
+ }
+ return false;
+ }
+
+ JtlParser parser(compacted_source_code, newline_indices);
+ while (!parser.HasFinished()) {
+ std::string operation_name;
+ base::ListValue arguments;
+ bool ends_sentence = false;
+ if (!parser.ParseNextOperation(
+ &operation_name, &arguments, &ends_sentence)) {
+ if (error_details) {
+ error_details->context = parser.GetLastContext();
+ error_details->line_number = parser.GetLastLineNumber();
+ error_details->error_code = CompileError::PARSING_ERROR;
+ }
+ return false;
+ }
+ CompileError::ErrorCode error_code = instruction_set.TranscodeInstruction(
+ operation_name, arguments, ends_sentence, hasher, &bytecode_writer);
+ if (error_code != CompileError::ERROR_NONE) {
+ if (error_details) {
+ error_details->context = parser.GetLastContext();
+ error_details->line_number = parser.GetLastLineNumber();
+ error_details->error_code = error_code;
+ }
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/chrome/tools/profile_reset/jtl_compiler.gyp b/chrome/tools/profile_reset/jtl_compiler.gyp
new file mode 100644
index 0000000..6d21015
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_compiler.gyp
@@ -0,0 +1,38 @@
+{
+ 'variables': {
+ 'chromium_code': 1,
+ },
+ 'targets': [
+ {
+ 'target_name': 'jtl_compiler',
+ 'type': 'executable',
+ 'dependencies': [
+ '../../../base/base.gyp:base',
+ '../../../crypto/crypto.gyp:crypto',
+ 'jtl_compiler_lib',
+ ],
+ 'sources': [
+ '../../browser/profile_resetter/jtl_foundation.cc',
+ '../../browser/profile_resetter/jtl_foundation.h',
+ 'jtl_compiler_frontend.cc',
+ ],
+ },
+ {
+ 'target_name': 'jtl_compiler_lib',
+ 'type': 'static_library',
+ 'product_name': 'jtl_compiler',
+ 'dependencies': [
+ '../../../third_party/re2/re2.gyp:re2',
+ '../../../base/base.gyp:base',
+ ],
+ 'sources': [
+ '../../browser/profile_resetter/jtl_foundation.h',
+ '../../browser/profile_resetter/jtl_instructions.h',
+ 'jtl_compiler.h',
+ 'jtl_compiler.cc',
+ 'jtl_parser.h',
+ 'jtl_parser.cc',
+ ],
+ },
+ ],
+}
diff --git a/chrome/tools/profile_reset/jtl_compiler.h b/chrome/tools/profile_reset/jtl_compiler.h
new file mode 100644
index 0000000..cd81b81
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_compiler.h
@@ -0,0 +1,74 @@
+// 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_TOOLS_PROFILE_RESET_JTL_COMPILER_H_
+#define CHROME_TOOLS_PROFILE_RESET_JTL_COMPILER_H_
+
+#include <string>
+
+#include "base/basictypes.h"
+
+// Compiles text-based JTL source code into JTL byte-code.
+//
+// For an overview of JTL (JSON Traversal Language), and the exhaustive list of
+// instructions, please see "chrome/browser/profile_resetter/jtl_foundation.h".
+//
+// The text-based JTL syntax itself much resembles C/C++. A program consists of
+// zero or more sentences. Each sentence is terminated by a semi-colon (;), and
+// is composed of *one* or more operations, separated by periods (.).
+//
+// Each operation resembles a C/C++ function call and consists of an instruction
+// name, and an optional argument list, which takes Boolean values and/or string
+// literals. The text-based instruction names are defined in "jtl_compiler.cc".
+//
+// Whitespace does not matter, except for inside string literals. C++-style,
+// double-slash-introduced comments are also supported.
+//
+// Example source code:
+//
+// // Store "x"=true if path "foo.bar" is found.
+// go("foo").go("bar").store_bool("x", true);
+//
+// // Store "y"="1" if the above value is set.
+// compare_stored_bool("x", true, false).store_hash("y", "1");
+//
+class JtlCompiler {
+ public:
+ struct CompileError {
+ enum ErrorCode {
+ ERROR_NONE,
+ MISMATCHED_DOUBLE_QUOTES,
+ PARSING_ERROR,
+ INVALID_OPERATION_NAME,
+ INVALID_ARGUMENT_COUNT,
+ INVALID_ARGUMENT_TYPE,
+ INVALID_ARGUMENT_VALUE
+ };
+
+ CompileError() : line_number(0), error_code(ERROR_NONE) {}
+ CompileError(size_t line_number,
+ const std::string& context,
+ ErrorCode error_code)
+ : line_number(line_number), context(context), error_code(error_code) {}
+
+ size_t line_number; // 0-based.
+ std::string context;
+ ErrorCode error_code;
+ };
+
+ // Compiles text-based JTL source code contained in |source_code| into JTL
+ // byte-code to |output_bytecode|. Variable, node names, and string literals
+ // will be hashed using the seed in |hash_seed|.
+ // On success, returns true. Otherwise, returns false and fills |error| with
+ // more information (if it is non-NULL).
+ static bool Compile(const std::string& source_code,
+ const std::string& hash_seed,
+ std::string* output_bytecode,
+ CompileError* error);
+
+ private:
+ DISALLOW_IMPLICIT_CONSTRUCTORS(JtlCompiler);
+};
+
+#endif // CHROME_TOOLS_PROFILE_RESET_JTL_COMPILER_H_
diff --git a/chrome/tools/profile_reset/jtl_compiler_frontend.cc b/chrome/tools/profile_reset/jtl_compiler_frontend.cc
new file mode 100644
index 0000000..b172205
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_compiler_frontend.cc
@@ -0,0 +1,112 @@
+// 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.
+//
+// A simple command-line compiler for JTL (JSON Traversal Language).
+//
+// Translates rules from a text-based, human-readable format to an easy-to-parse
+// byte-code format, which then can be interpreted by JtlInterpreter.
+//
+// Example usage:
+// jtl_compiler --input=blah.txt --hash-seed="foobar" --output=blah.dat
+
+#include <iostream>
+#include <string>
+
+#include "base/command_line.h"
+#include "base/file_util.h"
+#include "base/files/file_path.h"
+#include "chrome/tools/profile_reset/jtl_compiler.h"
+
+namespace {
+
+// Command-line argument name: path to the input text-based JTL source code.
+const char kInputPath[] = "input";
+
+// Command-line argument name: path to the output byte-code.
+const char kOutputPath[] = "output";
+
+// Command-line argument name: the hash seed to use.
+const char kHashSeed[] = "hash-seed";
+
+// Error codes.
+const char kMismatchedDoubleQuotes[] = "Mismatched double-quotes before EOL.";
+const char kParsingError[] = "Parsing error. Input is ill-formed.";
+const char kArgumentCountError[] = "Wrong number of arguments for operation.";
+const char kArgumentTypeError[] = "Wrong argument type(s) for operation.";
+const char kArgumentValueError[] = "Wrong argument value(s) for operation.";
+const char kUnknownOperationError[] = "No operation by this name.";
+const char kUnknownError[] = "Unknown error.";
+
+const char* ResolveErrorCode(JtlCompiler::CompileError::ErrorCode code) {
+ switch (code) {
+ case JtlCompiler::CompileError::MISMATCHED_DOUBLE_QUOTES:
+ return kMismatchedDoubleQuotes;
+ case JtlCompiler::CompileError::PARSING_ERROR:
+ return kParsingError;
+ case JtlCompiler::CompileError::INVALID_ARGUMENT_COUNT:
+ return kArgumentCountError;
+ case JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE:
+ return kArgumentTypeError;
+ case JtlCompiler::CompileError::INVALID_ARGUMENT_VALUE:
+ return kArgumentValueError;
+ case JtlCompiler::CompileError::INVALID_OPERATION_NAME:
+ return kUnknownOperationError;
+ default:
+ return kUnknownError;
+ }
+}
+
+} // namespace
+
+int main(int argc, char* argv[]) {
+ CommandLine::Init(argc, argv);
+ CommandLine* cmd_line = CommandLine::ForCurrentProcess();
+ if (!cmd_line->HasSwitch(kInputPath) || !cmd_line->HasSwitch(kHashSeed) ||
+ !cmd_line->HasSwitch(kOutputPath)) {
+ std::cerr << "Usage: " << argv[0] << " <required switches>" << std::endl;
+ std::cerr << "\nRequired switches are:" << std::endl;
+ std::cerr << " --" << kInputPath << "=<file>"
+ << "\t\tPath to the input text-based JTL source code."
+ << std::endl;
+ std::cerr << " --" << kOutputPath << "=<file>"
+ << "\t\tPath to the output byte-code." << std::endl;
+ std::cerr << " --" << kHashSeed << "=<value>"
+ << "\t\tThe hash seed to use." << std::endl;
+ return -1;
+ }
+
+ base::FilePath source_code_path =
+ MakeAbsoluteFilePath(cmd_line->GetSwitchValuePath(kInputPath));
+ std::string source_code;
+ if (!base::ReadFileToString(source_code_path, &source_code)) {
+ std::cerr << "ERROR: Cannot read input file." << std::endl;
+ return -3;
+ }
+
+ std::string bytecode;
+ JtlCompiler::CompileError error;
+ std::string hash_seed = cmd_line->GetSwitchValueASCII(kHashSeed);
+ if (!JtlCompiler::Compile(source_code, hash_seed, &bytecode, &error)) {
+ std::cerr << "COMPILE ERROR: " << ResolveErrorCode(error.error_code)
+ << std::endl;
+ std::cerr << " Line number: " << (error.line_number + 1) << std::endl;
+ std::cerr << " Context: " << (error.context.size() > 63
+ ? error.context.substr(0, 60) + "..."
+ : error.context) << std::endl;
+ return -2;
+ }
+
+ base::FilePath bytecode_path =
+ MakeAbsoluteFilePath(cmd_line->GetSwitchValuePath(kOutputPath));
+ int bytes_written =
+ base::WriteFile(cmd_line->GetSwitchValuePath(kOutputPath),
+ bytecode.data(),
+ static_cast<int>(bytecode.size()));
+ if (bytes_written != static_cast<int>(bytecode.size())) {
+ std::cerr << "ERROR: Cannot write output file." << std::endl;
+ return -3;
+ }
+
+ return 0;
+}
diff --git a/chrome/tools/profile_reset/jtl_compiler_unittest.cc b/chrome/tools/profile_reset/jtl_compiler_unittest.cc
new file mode 100644
index 0000000..3dbf886
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_compiler_unittest.cc
@@ -0,0 +1,217 @@
+// 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/tools/profile_reset/jtl_compiler.h"
+
+#include <string>
+
+#include "base/values.h"
+#include "chrome/browser/profile_resetter/jtl_foundation.h"
+#include "chrome/browser/profile_resetter/jtl_instructions.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+const char kTestHashSeed[] = "test-hash-seed";
+
+// Helpers -------------------------------------------------------------------
+
+std::string GetHash(const std::string& input) {
+ return jtl_foundation::Hasher(kTestHashSeed).GetHash(input);
+}
+
+static 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;
+}
+
+// Tests ---------------------------------------------------------------------
+
+// Note: Parsing and parsing-related errors are unit-tested separately in more
+// detail in "jtl_parser_unittest.cc". Here, most of the time, we assume that
+// creating the parse tree works.
+
+TEST(JtlCompiler, CompileSingleInstructions) {
+ struct TestCase {
+ std::string source_code;
+ std::string expected_bytecode;
+ } cases[] = {
+ {"go(\"foo\").", OP_NAVIGATE(GetHash("foo"))},
+ {"go(\"has whitespace\t\").", OP_NAVIGATE(GetHash("has whitespace\t"))},
+ {"any.", OP_NAVIGATE_ANY},
+ {"back.", OP_NAVIGATE_BACK},
+ {"store_bool(\"name\", true).",
+ OP_STORE_BOOL(GetHash("name"), VALUE_TRUE)},
+ {"compare_stored_bool(\"name\", true, false).",
+ OP_COMPARE_STORED_BOOL(GetHash("name"), VALUE_TRUE, VALUE_FALSE)},
+ {"store_hash(\"name\", \"" + GetHash("value") + "\").",
+ OP_STORE_HASH(GetHash("name"), GetHash("value"))},
+ {"store_hashed(\"name\", \"value\").",
+ OP_STORE_HASH(GetHash("name"), GetHash("value"))},
+ {"store_node_bool(\"name\").", OP_STORE_NODE_BOOL(GetHash("name"))},
+ {"store_node_hash(\"name\").", OP_STORE_NODE_HASH(GetHash("name"))},
+ {"store_node_registerable_domain_hash(\"name\").",
+ OP_STORE_NODE_REGISTERABLE_DOMAIN_HASH(GetHash("name"))},
+ {"compare_stored_hashed(\"name\", \"value\", \"default\").",
+ OP_COMPARE_STORED_HASH(
+ GetHash("name"), GetHash("value"), GetHash("default"))},
+ {"compare_bool(false).", OP_COMPARE_NODE_BOOL(VALUE_FALSE)},
+ {"compare_bool(true).", OP_COMPARE_NODE_BOOL(VALUE_TRUE)},
+ {"compare_hashed(\"foo\").", OP_COMPARE_NODE_HASH(GetHash("foo"))},
+ {"compare_hashed_not(\"foo\").",
+ OP_COMPARE_NODE_HASH_NOT(GetHash("foo"))},
+ {"compare_to_stored_bool(\"name\").",
+ OP_COMPARE_NODE_TO_STORED_BOOL(GetHash("name"))},
+ {"compare_to_stored_hash(\"name\").",
+ OP_COMPARE_NODE_TO_STORED_HASH(GetHash("name"))},
+ {"compare_substring_hashed(\"pattern\").",
+ OP_COMPARE_NODE_SUBSTRING(
+ GetHash("pattern"), EncodeUint32(7), EncodeUint32(766))},
+ {"break.", OP_STOP_EXECUTING_SENTENCE},
+ {"break;", OP_STOP_EXECUTING_SENTENCE + OP_END_OF_SENTENCE}};
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].source_code);
+ std::string bytecode;
+ EXPECT_TRUE(JtlCompiler::Compile(
+ cases[i].source_code, kTestHashSeed, &bytecode, NULL));
+ EXPECT_EQ(cases[i].expected_bytecode, bytecode);
+ }
+}
+
+TEST(JtlCompiler, CompileEntireProgram) {
+ const char kSourceCode[] =
+ "// Store \"x\"=true if path is found.\n"
+ "go(\"foo\").go(\"bar\").store_bool(\"x\", true);\n"
+ "// ...\n"
+ "// Store \"y\"=\"1\" if above value is set.\n"
+ "compare_stored_bool(\"x\", true, false).store_hashed(\"y\", \"1\");\n";
+
+ std::string expected_bytecode =
+ OP_NAVIGATE(GetHash("foo")) +
+ OP_NAVIGATE(GetHash("bar")) +
+ OP_STORE_BOOL(GetHash("x"), VALUE_TRUE) + OP_END_OF_SENTENCE +
+ OP_COMPARE_STORED_BOOL(GetHash("x"), VALUE_TRUE, VALUE_FALSE) +
+ OP_STORE_HASH(GetHash("y"), GetHash("1")) + OP_END_OF_SENTENCE;
+
+ std::string bytecode;
+ EXPECT_TRUE(
+ JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, NULL));
+ EXPECT_EQ(expected_bytecode, bytecode);
+}
+
+TEST(JtlCompiler, InvalidOperationName) {
+ const char kSourceCode[] = "any()\n.\nnon_existent_instruction\n(\n)\n;\n";
+
+ std::string bytecode;
+ JtlCompiler::CompileError error;
+ EXPECT_FALSE(
+ JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, &error));
+ EXPECT_THAT(error.context, testing::StartsWith("non_existent_instruction"));
+ EXPECT_EQ(2u, error.line_number);
+ EXPECT_EQ(JtlCompiler::CompileError::INVALID_OPERATION_NAME,
+ error.error_code);
+}
+
+TEST(JtlCompiler, InvalidArgumentsCount) {
+ const char* kSourceCodes[] = {
+ "any().\nstore_bool(\"name\", true, \"superfluous argument\");\n",
+ "any().\nstore_bool(\"name\");"}; // missing argument
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kSourceCodes); ++i) {
+ SCOPED_TRACE(kSourceCodes[i]);
+ std::string bytecode;
+ JtlCompiler::CompileError error;
+ EXPECT_FALSE(JtlCompiler::Compile(
+ kSourceCodes[i], kTestHashSeed, &bytecode, &error));
+ EXPECT_THAT(error.context, testing::StartsWith("store_bool"));
+ EXPECT_EQ(1u, error.line_number);
+ EXPECT_EQ(JtlCompiler::CompileError::INVALID_ARGUMENT_COUNT,
+ error.error_code);
+ }
+}
+
+TEST(JtlCompiler, InvalidArgumentType) {
+ struct TestCase {
+ std::string expected_context_prefix;
+ std::string source_code;
+ } cases[] = {
+ {"compare_bool", "any()\n.\ncompare_bool(\"foo\");"},
+ {"compare_bool",
+ "any()\n.\ncompare_bool(\"01234567890123456789012345678901\");"},
+ {"compare_hashed", "any()\n.\ncompare_hashed(false);"},
+ {"store_hash", "any()\n.\nstore_hash(\"name\", false);"},
+ {"store_hash", "any()\n.\nstore_hash(\"name\", \"foo\");"},
+ {"compare_stored_bool",
+ "any()\n.\ncompare_stored_bool(\"name\", \"need a bool\", false);"},
+ {"compare_stored_bool",
+ "any()\n.\ncompare_stored_bool(\"name\", false, \"need a bool\");"},
+ {"compare_substring_hashed",
+ "any()\n.\ncompare_substring_hashed(true);"}};
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].source_code);
+ std::string bytecode;
+ JtlCompiler::CompileError error;
+ EXPECT_FALSE(JtlCompiler::Compile(
+ cases[i].source_code, kTestHashSeed, &bytecode, &error));
+ EXPECT_THAT(error.context,
+ testing::StartsWith(cases[i].expected_context_prefix));
+ EXPECT_EQ(2u, error.line_number);
+ EXPECT_EQ(JtlCompiler::CompileError::INVALID_ARGUMENT_TYPE,
+ error.error_code);
+ }
+}
+
+TEST(JtlCompiler, InvalidArgumentValue) {
+ struct TestCase {
+ std::string expected_context_prefix;
+ std::string source_code;
+ } cases[] = {
+ {"compare_substring_hashed", "compare_substring_hashed(\"\");"}};
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].source_code);
+ std::string bytecode;
+ JtlCompiler::CompileError error;
+ EXPECT_FALSE(JtlCompiler::Compile(
+ cases[i].source_code, kTestHashSeed, &bytecode, &error));
+ EXPECT_THAT(error.context,
+ testing::StartsWith(cases[i].expected_context_prefix));
+ EXPECT_EQ(0u, error.line_number);
+ EXPECT_EQ(JtlCompiler::CompileError::INVALID_ARGUMENT_VALUE,
+ error.error_code);
+ }
+}
+
+TEST(JtlCompiler, MistmatchedDoubleQuotes) {
+ const char kSourceCode[] = "any().\ngo(\"ok\", \"stray quote).break();";
+
+ std::string bytecode;
+ JtlCompiler::CompileError error;
+ EXPECT_FALSE(
+ JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, &error));
+ EXPECT_EQ(1u, error.line_number);
+ EXPECT_EQ(JtlCompiler::CompileError::MISMATCHED_DOUBLE_QUOTES,
+ error.error_code);
+}
+
+TEST(JtlCompiler, ParsingError) {
+ const char kSourceCode[] = "any().\ngo()missing_separator();";
+
+ std::string bytecode;
+ JtlCompiler::CompileError error;
+ EXPECT_FALSE(
+ JtlCompiler::Compile(kSourceCode, kTestHashSeed, &bytecode, &error));
+ EXPECT_THAT(error.context, testing::StartsWith("go"));
+ EXPECT_EQ(1u, error.line_number);
+ EXPECT_EQ(JtlCompiler::CompileError::PARSING_ERROR, error.error_code);
+}
+
+} // namespace
diff --git a/chrome/tools/profile_reset/jtl_parser.cc b/chrome/tools/profile_reset/jtl_parser.cc
new file mode 100644
index 0000000..c1d9c15
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_parser.cc
@@ -0,0 +1,167 @@
+// 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/tools/profile_reset/jtl_parser.h"
+
+#include <algorithm>
+
+#include "base/logging.h"
+#include "third_party/re2/re2/re2.h"
+
+namespace {
+
+// RegEx that matches the first line of a text. Will throw away any potential
+// double-slash-introduced comments and the potential trailing EOL character.
+// Note: will fail in case the first line contains an unmatched double-quote
+// outside of comments.
+const char kSingleLineWithMaybeCommentsRE[] =
+ // Non-greedily match and capture sequences of 1.) string literals inside
+ // correctly matched double-quotes, or 2.) any other character.
+ "^((?:\"[^\"\\n]*\"|[^\"\\n])*?)"
+ // Greedily match and throw away the potential comment.
+ "(?://.*)?"
+ // Match and throw away EOL, or match end-of-string.
+ "(?:\n|$)";
+
+// RegEx to match either a double-quote-enclosed string literal or a whitespace.
+// Applied repeatedly and without overlapping, can be used to remove whitespace
+// outside of string literals.
+const char kRemoveWhitespaceRE[] = "(\"[^\"]*\")|\\s";
+
+// The substitution pattern to use together with the above when replacing. As
+// the whitespace is not back-referenced here, it will get removed.
+const char kRemoveWhitespaceRewrite[] = "\\1";
+
+// Separator to terminate a sentence.
+const char kEndOfSentenceSeparator[] = ";";
+
+// The 'true' Boolean keyword.
+const char kTrueKeyword[] = "true";
+
+// RegEx that matches and captures one argument, which is either a double-quote
+// enclosed string, or a Boolean value. Will throw away a trailing comma.
+const char kSingleArgumentRE[] = "(?:(?:\"([^\"]*)\"|(true|false))(?:,|$))";
+
+// RegEx-es that, when concatenated, will match a single operation, and capture
+// the: operation name, the optional arguments, and the separator that follows.
+const char kOperationNameRE[] = "([[:word:]]+)";
+const char kMaybeArgumentListRE[] =
+ "(?:\\(" // Opening parenthesis.
+ "((?:\"[^\"]*\"|[^\")])*)" // Capture: anything inside, quote-aware.
+ "\\))?"; // Closing parenthesis + everything optional.
+const char kOperationSeparatorRE[] = "(;|\\.)";
+
+} // namespace
+
+struct JtlParser::ParsingState {
+ explicit ParsingState(const re2::StringPiece& compacted_source)
+ : single_operation_regex(std::string(kOperationNameRE) +
+ kMaybeArgumentListRE +
+ kOperationSeparatorRE),
+ single_argument_regex(kSingleArgumentRE),
+ remaining_compacted_source(compacted_source),
+ last_line_number(0) {}
+
+ RE2 single_operation_regex;
+ RE2 single_argument_regex;
+ re2::StringPiece remaining_compacted_source;
+ re2::StringPiece last_context;
+ size_t last_line_number;
+};
+
+JtlParser::JtlParser(const std::string& compacted_source_code,
+ const std::vector<size_t>& newline_indices)
+ : compacted_source_(compacted_source_code),
+ newline_indices_(newline_indices) {
+ state_.reset(new ParsingState(compacted_source_));
+}
+
+JtlParser::~JtlParser() {}
+
+// static
+bool JtlParser::RemoveCommentsAndAllWhitespace(
+ const std::string& verbose_text,
+ std::string* compacted_text,
+ std::vector<size_t>* newline_indices,
+ size_t* error_line_number) {
+ DCHECK(compacted_text);
+ DCHECK(newline_indices);
+ std::string line;
+ RE2 single_line_regex(kSingleLineWithMaybeCommentsRE);
+ RE2 remove_whitespace_regex(kRemoveWhitespaceRE);
+ re2::StringPiece verbose_text_piece(verbose_text);
+ compacted_text->clear();
+ newline_indices->clear();
+ while (!verbose_text_piece.empty()) {
+ if (!RE2::Consume(&verbose_text_piece, single_line_regex, &line)) {
+ if (error_line_number)
+ *error_line_number = newline_indices->size();
+ return false;
+ }
+ RE2::GlobalReplace(
+ &line, remove_whitespace_regex, kRemoveWhitespaceRewrite);
+ *compacted_text += line;
+ newline_indices->push_back(compacted_text->size());
+ }
+ return true;
+}
+
+bool JtlParser::HasFinished() {
+ return state_->remaining_compacted_source.empty();
+}
+
+bool JtlParser::ParseNextOperation(std::string* name,
+ base::ListValue* argument_list,
+ bool* ends_sentence) {
+ DCHECK(name);
+ DCHECK(argument_list);
+ DCHECK(ends_sentence);
+
+ state_->last_context = state_->remaining_compacted_source;
+ state_->last_line_number = GetOriginalLineNumber(
+ compacted_source_.size() - state_->remaining_compacted_source.length());
+
+ std::string arguments, separator;
+ if (!RE2::Consume(&state_->remaining_compacted_source,
+ state_->single_operation_regex,
+ name,
+ &arguments,
+ &separator))
+ return false;
+
+ *ends_sentence = (separator == kEndOfSentenceSeparator);
+ state_->last_context.remove_suffix(state_->remaining_compacted_source.size());
+
+ re2::StringPiece arguments_piece(arguments);
+ std::string string_value, boolean_value;
+ while (!arguments_piece.empty()) {
+ if (!RE2::Consume(&arguments_piece,
+ state_->single_argument_regex,
+ &string_value,
+ &boolean_value))
+ return false;
+
+ if (!boolean_value.empty()) {
+ argument_list->Append(
+ new base::FundamentalValue(boolean_value == kTrueKeyword));
+ } else {
+ // |string_value| might be empty for an empty string
+ argument_list->Append(new base::StringValue(string_value));
+ }
+ }
+ return true;
+}
+
+size_t JtlParser::GetOriginalLineNumber(size_t compacted_index) const {
+ return static_cast<size_t>(std::upper_bound(newline_indices_.begin(),
+ newline_indices_.end(),
+ compacted_index) -
+ newline_indices_.begin());
+}
+
+size_t JtlParser::GetLastLineNumber() const { return state_->last_line_number; }
+
+std::string JtlParser::GetLastContext() const {
+ return state_->last_context.ToString();
+}
diff --git a/chrome/tools/profile_reset/jtl_parser.h b/chrome/tools/profile_reset/jtl_parser.h
new file mode 100644
index 0000000..e758211
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_parser.h
@@ -0,0 +1,83 @@
+// 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_TOOLS_PROFILE_RESET_JTL_PARSER_H_
+#define CHROME_TOOLS_PROFILE_RESET_JTL_PARSER_H_
+
+#include <string>
+#include <vector>
+
+#include "base/basictypes.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+
+// Parses text-based JTL source code into a stream of operation names, arguments
+// and separator kinds.
+class JtlParser {
+ public:
+ // Creates a new parser to parse |compacted_source_code|, which should already
+ // be stripped of all comments and whitespace (except inside string literals).
+ // Use RemoveCommentsAndAllWhitespace() to manufacture these arguments, also
+ // see its comments for a description of |newline_indices|.
+ JtlParser(const std::string& compacted_source_code,
+ const std::vector<size_t>& newline_indices);
+ ~JtlParser();
+
+ // Removes comments from |verbose_text| and compacts it into whitespace-free
+ // format (except inside string literals). Elements in |newline_indices| will
+ // be monotonically increasing and will refer to positions in |compacted_text|
+ // such that a new line has been removed before that position.
+ // Example:
+ // verbose_text = "H e l l o // my\n"
+ // " dear \n"
+ // "\n"
+ // "world\" ! \""
+ // compacted_text = "Hellodearworld\" ! \""
+ // 01234567890123...
+ // newline_indices = {5, 9, 9}
+ // Returns true on success, false if there were unmatched quotes in a line, in
+ // which case |error_line_number| will be set accordingly if it is non-NULL.
+ static bool RemoveCommentsAndAllWhitespace(
+ const std::string& verbose_text,
+ std::string* compacted_text,
+ std::vector<size_t>* newline_indices,
+ size_t* error_line_number);
+
+ // Returns true if the entire input has been successfully consumed. Note that
+ // even when this returns false, a subsequent call to ParseNextOperation()
+ // might still fail if the next operation cannot be parsed.
+ bool HasFinished();
+
+ // Fetches the |name| and the |argument_list| of the next operation, and also
+ // whether or not it |ends_the_sentence|, i.e. it is followed by the
+ // end-of-sentence separator.
+ // Returns false if there is a parsing error, in which case the values for the
+ // output parameters are undefined, and |this| parser shall no longer be used.
+ bool ParseNextOperation(std::string* name,
+ base::ListValue* argument_list,
+ bool* ends_the_sentence);
+
+ // Returns the compacted source code that was passed in to the constructor.
+ const std::string& compacted_source() const { return compacted_source_; }
+
+ // Returns at which line the character at position |compacted_index| in the
+ // |compacted_source()| was originally located.
+ size_t GetOriginalLineNumber(size_t compacted_index) const;
+
+ size_t GetLastLineNumber() const;
+ std::string GetLastContext() const;
+
+ private:
+ // Contains pre-compiled regular expressions and related state. Factored out
+ // to avoid this header depending on RE2 headers.
+ struct ParsingState;
+
+ std::string compacted_source_;
+ std::vector<size_t> newline_indices_;
+ scoped_ptr<ParsingState> state_;
+
+ DISALLOW_COPY_AND_ASSIGN(JtlParser);
+};
+
+#endif // CHROME_TOOLS_PROFILE_RESET_JTL_PARSER_H_
diff --git a/chrome/tools/profile_reset/jtl_parser_unittest.cc b/chrome/tools/profile_reset/jtl_parser_unittest.cc
new file mode 100644
index 0000000..1868df8
--- /dev/null
+++ b/chrome/tools/profile_reset/jtl_parser_unittest.cc
@@ -0,0 +1,346 @@
+// 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/tools/profile_reset/jtl_parser.h"
+
+#include "base/json/json_writer.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/values.h"
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace {
+
+// Helpers -------------------------------------------------------------------
+
+void ExpectNextOperation(JtlParser* parser,
+ const char* expected_name,
+ const char* expected_args_json,
+ bool expected_ends_sentence) {
+ std::string actual_name;
+ base::ListValue actual_args;
+ std::string actual_args_json;
+ bool actual_ends_sentence;
+
+ EXPECT_FALSE(parser->HasFinished());
+ EXPECT_TRUE(parser->ParseNextOperation(
+ &actual_name, &actual_args, &actual_ends_sentence));
+ EXPECT_EQ(expected_name, actual_name);
+ base::JSONWriter::Write(&actual_args, &actual_args_json);
+ EXPECT_EQ(expected_args_json, actual_args_json);
+ EXPECT_EQ(expected_ends_sentence, actual_ends_sentence);
+}
+
+void ExpectNextOperationToFail(JtlParser* parser,
+ size_t expected_line_number,
+ const char* expected_context_prefix) {
+ std::string actual_name;
+ base::ListValue actual_args;
+ bool actual_ends_sentence;
+
+ EXPECT_FALSE(parser->HasFinished());
+ EXPECT_FALSE(parser->ParseNextOperation(
+ &actual_name, &actual_args, &actual_ends_sentence));
+ EXPECT_THAT(parser->GetLastContext(),
+ testing::StartsWith(expected_context_prefix));
+ EXPECT_EQ(expected_line_number, parser->GetLastLineNumber());
+}
+
+JtlParser* CreateParserFromVerboseText(const std::string& verbose_text) {
+ std::string compacted_source_code;
+ std::vector<size_t> newline_indices;
+ EXPECT_TRUE(JtlParser::RemoveCommentsAndAllWhitespace(
+ verbose_text, &compacted_source_code, &newline_indices, NULL));
+ return new JtlParser(compacted_source_code, newline_indices);
+}
+
+// Tests ---------------------------------------------------------------------
+
+TEST(JtlParser, CompactingEmpty) {
+ const char kSourceCode[] = "";
+ const char kCompactedSourceCode[] = "";
+
+ scoped_ptr<JtlParser> parser(CreateParserFromVerboseText(kSourceCode));
+ EXPECT_EQ(kCompactedSourceCode, parser->compacted_source());
+}
+
+TEST(JtlParser, CompactingTrivial) {
+ const char kSourceCode[] = "foo";
+ const char kCompactedSourceCode[] = "foo";
+
+ scoped_ptr<JtlParser> parser(CreateParserFromVerboseText(kSourceCode));
+ EXPECT_EQ(kCompactedSourceCode, parser->compacted_source());
+}
+
+TEST(JtlParser, CompactingOneLine) {
+ const char kSourceCode[] = " \r f\to o ( true ) ";
+ const char kCompactedSourceCode[] = "foo(true)";
+
+ scoped_ptr<JtlParser> parser(CreateParserFromVerboseText(kSourceCode));
+ EXPECT_EQ(kCompactedSourceCode, parser->compacted_source());
+ for (size_t i = 0; i < arraysize(kCompactedSourceCode) - 1; ++i) {
+ SCOPED_TRACE(testing::Message("Position ") << i);
+ EXPECT_EQ(0u, parser->GetOriginalLineNumber(i));
+ }
+}
+
+TEST(JtlParser, CompactingMultipleLines) {
+ const char kSourceCode[] = "a\nbb\n \nccc \n\n d( \n e \n )";
+ const char kCompactedSourceCode[] = "abbcccd(e)";
+ const size_t kLineNumbers[] = {0u, 1u, 1u, 3u, 3u, 3u, 5u, 5u, 6u, 7u};
+ COMPILE_ASSERT(arraysize(kCompactedSourceCode) == arraysize(kLineNumbers) + 1,
+ mismatched_test_data);
+
+ scoped_ptr<JtlParser> parser(CreateParserFromVerboseText(kSourceCode));
+ EXPECT_EQ(kCompactedSourceCode, parser->compacted_source());
+ for (size_t i = 0; i < arraysize(kLineNumbers); ++i) {
+ SCOPED_TRACE(testing::Message("Position ") << i);
+ EXPECT_EQ(kLineNumbers[i], parser->GetOriginalLineNumber(i));
+ }
+}
+
+TEST(JtlParser, CompactingMultipleLinesWithComments) {
+ const char kSourceCode[] =
+ "a/ /b//Comment \n"
+ "//\n"
+ "// Full line comment\n"
+ " cd //";
+ const char kCompactedSourceCode[] = "a//bcd";
+ const size_t kLineNumbers[] = {0u, 0u, 0u, 0u, 3u, 3u};
+ COMPILE_ASSERT(arraysize(kCompactedSourceCode) == arraysize(kLineNumbers) + 1,
+ mismatched_test_data);
+
+ scoped_ptr<JtlParser> parser(CreateParserFromVerboseText(kSourceCode));
+ EXPECT_EQ(kCompactedSourceCode, parser->compacted_source());
+ for (size_t i = 0; i < arraysize(kLineNumbers); ++i) {
+ SCOPED_TRACE(testing::Message("Position ") << i);
+ EXPECT_EQ(kLineNumbers[i], parser->GetOriginalLineNumber(i));
+ }
+}
+
+TEST(JtlParser, HandlingCommentsAndStringLiterals) {
+ struct TestCase {
+ const char* source_code;
+ const char* compacted_source_code;
+ } cases[] = {
+ {"//", ""},
+ {"//comment", ""},
+ {"foo // comment", "foo"},
+ {"foo // // comment", "foo"},
+ {"foo //", "foo"},
+ {"\"literal\"", "\"literal\""},
+ {"\"literal with space\"", "\"literal with space\""},
+ {"\"\"", "\"\""},
+ {"\"\"\"\"", "\"\"\"\""},
+ {"\"\" // comment", "\"\""},
+ {"\"literal\" // comment", "\"literal\""},
+ {"\"literal\" \"literal\" // comment", "\"literal\"\"literal\""},
+ {"foo // \"not a literal\"", "foo"},
+ {"foo // \"not even matched", "foo"},
+ {"foo // \"not a literal\" \"not even matched", "foo"},
+ {"\"literal\" // \"not a literal\"", "\"literal\""},
+ {"\"literal\" // \"not even matched", "\"literal\""},
+ {"\"//not a comment//\"", "\"//not a comment//\""},
+ {"\"//not a comment//\" // comment", "\"//not a comment//\""},
+ {"// \"//not a literal//\" // comment", ""},
+ {"\"literal\" // \"//not a literal//\" // comment", "\"literal\""},
+ {"\"//not a comment//\" // \"//not a literal//\" // comment",
+ "\"//not a comment//\""},
+ {"\"literal // \"not a literal nor a comment",
+ "\"literal // \"notaliteralnoracomment"},
+ {"\"literal // \"not a literal nor a comment//\"",
+ "\"literal // \"notaliteralnoracomment"}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].source_code);
+ scoped_ptr<JtlParser> parser(
+ CreateParserFromVerboseText(cases[i].source_code));
+ EXPECT_EQ(cases[i].compacted_source_code, parser->compacted_source());
+ }
+}
+
+TEST(JtlParser, MismatchedDoubleQuotesBeforeEndOfLine) {
+ struct TestCase {
+ const char* source_code;
+ size_t error_line_number;
+ } cases[] = {
+ {"\"", 0},
+ {"\"mismatched literal", 0},
+ {"\n\"", 1},
+ {"\"\n\"", 0},
+ {"\"\"\"", 0},
+ {"\"\"\n\"", 1},
+ {"\"\"\n\"\n\"", 1},
+ {"\" // not a comment", 0},
+ {"\" // not a comment\n\"", 0},
+ {"\"\" // comment\n\"", 1},
+ {"\"\"\" // not a comment\n\"", 0},
+ {"\"\"\" // \" neither a literal nor a comment\"\n\"", 0},
+ {"\"\" // comment\n\"// not a comment", 1},
+ {"\" // not a comment\"\n\"// not a comment", 1},
+ {"foo(\"bar\");\nfoo(\"mismatched);", 1},
+ {"foo(\n\"bar\", \"mismatched);", 1},
+ {"foo(\n\"bar\", \"mismatched); //comment", 1},
+ {"foo(\n\"bar\", \"mismatched);\ngood(\"bar\")", 1}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].source_code);
+ std::string compacted_source_code;
+ std::vector<size_t> newline_indices;
+ size_t error_line_number;
+ EXPECT_FALSE(JtlParser::RemoveCommentsAndAllWhitespace(
+ cases[i].source_code,
+ &compacted_source_code,
+ &newline_indices,
+ &error_line_number));
+ EXPECT_EQ(cases[i].error_line_number, error_line_number);
+ }
+}
+
+TEST(JtlParser, ParsingEmpty) {
+ const char kSourceCode[] = "";
+
+ scoped_ptr<JtlParser> parser(CreateParserFromVerboseText(kSourceCode));
+ EXPECT_TRUE(parser->HasFinished());
+}
+
+TEST(JtlParser, ParsingOneWellFormedOperation) {
+ struct TestCase {
+ const char* source_code;
+ const char* expected_name;
+ const char* expected_args;
+ const bool expected_ends_sentence;
+ } cases[] = {
+ {"foo1;", "foo1", "[]", true},
+ {"foo2().", "foo2", "[]", false},
+ {"foo3(true);", "foo3", "[true]", true},
+ {"foo4(false).", "foo4", "[false]", false},
+ {"foo5(\"bar\").", "foo5", "[\"bar\"]", false},
+ {"foo6(\" b a r \").", "foo6", "[\" b a r \"]", false},
+ {"foo7(true, \"bar\").", "foo7", "[true,\"bar\"]", false},
+ {"foo8(\"bar\", false, true);", "foo8", "[\"bar\",false,true]", true},
+ {"foo9(\"bar\", \" b a r \");", "foo9", "[\"bar\",\" b a r \"]", true}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].expected_name);
+ scoped_ptr<JtlParser> parser(
+ CreateParserFromVerboseText(cases[i].source_code));
+ ExpectNextOperation(parser.get(),
+ cases[i].expected_name,
+ cases[i].expected_args,
+ cases[i].expected_ends_sentence);
+ EXPECT_TRUE(parser->HasFinished());
+ }
+}
+
+TEST(JtlParser, ParsingMultipleWellFormedOperations) {
+ const char kSourceCode[] =
+ "foo1(true).foo2.foo3(\"bar\");"
+ "foo4(\"bar\", false);";
+
+ scoped_ptr<JtlParser> parser(CreateParserFromVerboseText(kSourceCode));
+ ExpectNextOperation(parser.get(), "foo1", "[true]", false);
+ ExpectNextOperation(parser.get(), "foo2", "[]", false);
+ ExpectNextOperation(parser.get(), "foo3", "[\"bar\"]", true);
+ ExpectNextOperation(parser.get(), "foo4", "[\"bar\",false]", true);
+ EXPECT_TRUE(parser->HasFinished());
+}
+
+TEST(JtlParser, ParsingTrickyStringLiterals) {
+ struct TestCase {
+ const char* source_code;
+ const char* expected_name;
+ const char* expected_args;
+ const bool expected_ends_sentence;
+ } cases[] = {
+ {"prev().foo1(\"\");next(true);", "foo1", "[\"\"]", true},
+ {"prev().foo2(\" \");next(true);", "foo2", "[\" \"]", true},
+ {"prev().foo3(\",\",true);next(true);", "foo3", "[\",\",true]", true},
+ {"prev().foo4(\")\",true);next(true);", "foo4", "[\")\",true]", true},
+ {"prev().foo5(\";\",true);next(true);", "foo5", "[\";\",true]", true},
+ {"prev().foo6(\"/\",true).next(true);", "foo6", "[\"/\",true]", false},
+ {"prev().foo7(\"//\",true).next(true);", "foo7", "[\"//\",true]", false},
+ {"prev().foo8(\".\",true).next(true);", "foo8", "[\".\",true]", false},
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].expected_name);
+ scoped_ptr<JtlParser> parser(
+ CreateParserFromVerboseText(cases[i].source_code));
+ ExpectNextOperation(parser.get(), "prev", "[]", false);
+ ExpectNextOperation(parser.get(),
+ cases[i].expected_name,
+ cases[i].expected_args,
+ cases[i].expected_ends_sentence);
+ ExpectNextOperation(parser.get(), "next", "[true]", true);
+ EXPECT_TRUE(parser->HasFinished());
+ }
+}
+
+TEST(JtlParser, FirstOperationIsIllFormed) {
+ struct TestCase {
+ const char* source_code;
+ const char* operation_name;
+ } cases[] = {
+ {";;", ";"},
+ {"bad_args1(not a boolean value);", "bad_args1"},
+ {"bad_args2(,);", "bad_args2"},
+ {"bad_args3(...);", "bad_args3"},
+ {"bad_args4(1);", "bad_args4"},
+ {"bad_args5(1.2);", "bad_args5"},
+ {"bad_args6([\"bar\"]);", "bad_args6"},
+ {"bad_args7(False);", "bad_args7"},
+ {"bad_args8(True);", "bad_args8"},
+ {"bad_quotes1(missing both, true).good();", "bad_quotes1"},
+ {"bad_quotes2(true, \"missing one).good(); //\"", "bad_quotes2"},
+ {"bad_quotes3(\"too\" \"much\", true).good();", "bad_quotes3"},
+ {"bad_missing_separator1", "bad_missing_separator1"},
+ {"bad_missing_separator2()good();", "bad_missing_separator2"},
+ {"bad_parenthesis1(true.good();", "bad_parenthesis1"},
+ {"bad_parenthesis2(true.good());", "bad_parenthesis2"},
+ {"bad_parenthesis3).good();", "bad_parenthesis3"}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].operation_name);
+ scoped_ptr<JtlParser> parser(
+ CreateParserFromVerboseText(cases[i].source_code));
+ ExpectNextOperationToFail(parser.get(), 0, cases[i].operation_name);
+ }
+}
+
+TEST(JtlParser, SecondOperationIsIllFormed) {
+ struct TestCase {
+ const char* source_code;
+ const char* bad_operation_name;
+ } cases[] = {
+ {"\ngood(true,false)\n.bad_args(,);", "bad_args"},
+ {"\ngood(true,false)\n.bad_quotes1(missing both, true).good();",
+ "bad_quotes1"},
+ {"\ngood(true,false)\n.bad_quotes2(\"missing one, true).good(); //\"",
+ "bad_quotes2"},
+ {"\ngood(true,false)\n.bad_quotes3(\"too\" \"many\", true).good();",
+ "bad_quotes3"},
+ {"\ngood(true,false)\n.bad_separator1()/good();", "bad_separator1"},
+ {"\ngood(true,false)\n.missing_separator1", "missing_separator1"},
+ {"\ngood(true,false)\n.missing_separator2()good();",
+ "missing_separator2"},
+ {"\ngood(true,false)\n.bad_parens1(true.good();", "bad_parens1"},
+ {"\ngood(true,false)\n.bad_parens2(true.good());", "bad_parens2"},
+ {"\ngood(true,false)\n.bad_parens3).good();", "bad_parens3"}
+ };
+
+ for (size_t i = 0; i < ARRAYSIZE_UNSAFE(cases); ++i) {
+ SCOPED_TRACE(cases[i].bad_operation_name);
+ scoped_ptr<JtlParser> parser(
+ CreateParserFromVerboseText(cases[i].source_code));
+ ExpectNextOperation(parser.get(), "good", "[true,false]", false);
+ ExpectNextOperationToFail(parser.get(), 2, cases[i].bad_operation_name);
+ }
+}
+
+} // namespace
diff --git a/tools/gritsettings/resource_ids b/tools/gritsettings/resource_ids
index 1c20615..9cd707c 100644
--- a/tools/gritsettings/resource_ids
+++ b/tools/gritsettings/resource_ids
@@ -17,7 +17,7 @@
"SRCDIR": "../..",
"chrome/browser/browser_resources.grd": {
- "includes": [500],
+ "includes": [400],
"structures": [750],
},
"chrome/browser/resources/component_extension_resources.grd": {
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index dc8dd6a..365d7b2 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -51739,7 +51739,7 @@ To add a new entry, add it with any value and run test to compute valid value.
<int value="11" label="prefs::kPinnedTabs"/>
<int value="12"
label="extensions::pref_names::kKnownDisabled (Obsolete 07/2014)"/>
- <int value="13" label="prefs::kProfileResetPromptMemento (Obsolete 07/2014)"/>
+ <int value="13" label="prefs::kProfileResetPromptMemento"/>
<int value="14"
label="DefaultSearchManager::kDefaultSearchProviderDataPrefName"/>
<int value="15" label="prefs::kPreferenceResetTime"/>