Home | History | Annotate | Download | only in webui
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 #include "chrome/browser/ui/webui/sync_setup_handler.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/bind.h"
      9 #include "base/bind_helpers.h"
     10 #include "base/command_line.h"
     11 #include "base/compiler_specific.h"
     12 #include "base/i18n/time_formatting.h"
     13 #include "base/json/json_reader.h"
     14 #include "base/json/json_writer.h"
     15 #include "base/metrics/histogram.h"
     16 #include "base/prefs/pref_service.h"
     17 #include "base/strings/utf_string_conversions.h"
     18 #include "base/values.h"
     19 #include "chrome/app/chrome_command_ids.h"
     20 #include "chrome/browser/google/google_util.h"
     21 #include "chrome/browser/lifetime/application_lifetime.h"
     22 #include "chrome/browser/profiles/profile.h"
     23 #include "chrome/browser/profiles/profile_info_cache.h"
     24 #include "chrome/browser/profiles/profile_manager.h"
     25 #include "chrome/browser/profiles/profile_metrics.h"
     26 #include "chrome/browser/signin/signin_global_error.h"
     27 #include "chrome/browser/signin/signin_manager_factory.h"
     28 #include "chrome/browser/signin/signin_promo.h"
     29 #include "chrome/browser/sync/profile_sync_service.h"
     30 #include "chrome/browser/sync/profile_sync_service_factory.h"
     31 #include "chrome/browser/ui/browser_finder.h"
     32 #include "chrome/browser/ui/browser_navigator.h"
     33 #include "chrome/browser/ui/sync/signin_histogram.h"
     34 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
     35 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
     36 #include "chrome/common/chrome_switches.h"
     37 #include "chrome/common/pref_names.h"
     38 #include "chrome/common/url_constants.h"
     39 #include "content/public/browser/render_view_host.h"
     40 #include "content/public/browser/web_contents.h"
     41 #include "content/public/browser/web_contents_delegate.h"
     42 #include "google_apis/gaia/gaia_auth_util.h"
     43 #include "google_apis/gaia/gaia_constants.h"
     44 #include "grit/chromium_strings.h"
     45 #include "grit/generated_resources.h"
     46 #include "grit/locale_settings.h"
     47 #include "ui/base/l10n/l10n_util.h"
     48 
     49 #if defined(OS_CHROMEOS)
     50 #include "chrome/browser/signin/signin_manager_base.h"
     51 #else
     52 #include "chrome/browser/signin/signin_manager.h"
     53 #endif
     54 
     55 using content::WebContents;
     56 using l10n_util::GetStringFUTF16;
     57 using l10n_util::GetStringUTF16;
     58 
     59 namespace {
     60 
     61 // A structure which contains all the configuration information for sync.
     62 struct SyncConfigInfo {
     63   SyncConfigInfo();
     64   ~SyncConfigInfo();
     65 
     66   bool encrypt_all;
     67   bool sync_everything;
     68   bool sync_nothing;
     69   syncer::ModelTypeSet data_types;
     70   std::string passphrase;
     71   bool passphrase_is_gaia;
     72 };
     73 
     74 SyncConfigInfo::SyncConfigInfo()
     75     : encrypt_all(false),
     76       sync_everything(false),
     77       sync_nothing(false),
     78       passphrase_is_gaia(false) {
     79 }
     80 
     81 SyncConfigInfo::~SyncConfigInfo() {}
     82 
     83 // Note: The order of these types must match the ordering of
     84 // the respective types in ModelType
     85 const char* kDataTypeNames[] = {
     86   "bookmarks",
     87   "preferences",
     88   "passwords",
     89   "autofill",
     90   "themes",
     91   "typedUrls",
     92   "extensions",
     93   "apps",
     94   "tabs"
     95 };
     96 
     97 COMPILE_ASSERT(28 == syncer::MODEL_TYPE_COUNT,
     98                update_kDataTypeNames_to_match_UserSelectableTypes);
     99 
    100 typedef std::map<syncer::ModelType, const char*> ModelTypeNameMap;
    101 
    102 ModelTypeNameMap GetSelectableTypeNameMap() {
    103   ModelTypeNameMap type_names;
    104   syncer::ModelTypeSet type_set = syncer::UserSelectableTypes();
    105   syncer::ModelTypeSet::Iterator it = type_set.First();
    106   DCHECK_EQ(arraysize(kDataTypeNames), type_set.Size());
    107   for (size_t i = 0; i < arraysize(kDataTypeNames) && it.Good();
    108        ++i, it.Inc()) {
    109     type_names[it.Get()] = kDataTypeNames[i];
    110   }
    111   return type_names;
    112 }
    113 
    114 bool GetConfiguration(const std::string& json, SyncConfigInfo* config) {
    115   scoped_ptr<Value> parsed_value(base::JSONReader::Read(json));
    116   DictionaryValue* result;
    117   if (!parsed_value || !parsed_value->GetAsDictionary(&result)) {
    118     DLOG(ERROR) << "GetConfiguration() not passed a Dictionary";
    119     return false;
    120   }
    121 
    122   if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything)) {
    123     DLOG(ERROR) << "GetConfiguration() not passed a syncAllDataTypes value";
    124     return false;
    125   }
    126 
    127   if (!result->GetBoolean("syncNothing", &config->sync_nothing)) {
    128     DLOG(ERROR) << "GetConfiguration() not passed a syncNothing value";
    129     return false;
    130   }
    131 
    132   DCHECK(!(config->sync_everything && config->sync_nothing))
    133       << "syncAllDataTypes and syncNothing cannot both be true";
    134 
    135   ModelTypeNameMap type_names = GetSelectableTypeNameMap();
    136 
    137   for (ModelTypeNameMap::const_iterator it = type_names.begin();
    138        it != type_names.end(); ++it) {
    139     std::string key_name = it->second + std::string("Synced");
    140     bool sync_value;
    141     if (!result->GetBoolean(key_name, &sync_value)) {
    142       DLOG(ERROR) << "GetConfiguration() not passed a value for " << key_name;
    143       return false;
    144     }
    145     if (sync_value)
    146       config->data_types.Put(it->first);
    147   }
    148 
    149   // Encryption settings.
    150   if (!result->GetBoolean("encryptAllData", &config->encrypt_all)) {
    151     DLOG(ERROR) << "GetConfiguration() not passed a value for encryptAllData";
    152     return false;
    153   }
    154 
    155   // Passphrase settings.
    156   bool have_passphrase;
    157   if (!result->GetBoolean("usePassphrase", &have_passphrase)) {
    158     DLOG(ERROR) << "GetConfiguration() not passed a usePassphrase value";
    159     return false;
    160   }
    161 
    162   if (have_passphrase) {
    163     if (!result->GetBoolean("isGooglePassphrase",
    164                             &config->passphrase_is_gaia)) {
    165       DLOG(ERROR) << "GetConfiguration() not passed isGooglePassphrase value";
    166       return false;
    167     }
    168     if (!result->GetString("passphrase", &config->passphrase)) {
    169       DLOG(ERROR) << "GetConfiguration() not passed a passphrase value";
    170       return false;
    171     }
    172   }
    173   return true;
    174 }
    175 
    176 }  // namespace
    177 
    178 SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager)
    179     : configuring_sync_(false),
    180       profile_manager_(profile_manager) {
    181 }
    182 
    183 SyncSetupHandler::~SyncSetupHandler() {
    184   // Just exit if running unit tests (no actual WebUI is attached).
    185   if (!web_ui())
    186     return;
    187 
    188   // This case is hit when the user performs a back navigation.
    189   CloseSyncSetup();
    190 }
    191 
    192 void SyncSetupHandler::GetLocalizedValues(DictionaryValue* localized_strings) {
    193   GetStaticLocalizedValues(localized_strings, web_ui());
    194 }
    195 
    196 void SyncSetupHandler::GetStaticLocalizedValues(
    197     DictionaryValue* localized_strings,
    198     content::WebUI* web_ui) {
    199   DCHECK(localized_strings);
    200 
    201   string16 product_name(GetStringUTF16(IDS_PRODUCT_NAME));
    202   localized_strings->SetString(
    203       "chooseDataTypesInstructions",
    204       GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, product_name));
    205   localized_strings->SetString(
    206       "encryptionInstructions",
    207       GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, product_name));
    208   localized_strings->SetString(
    209       "encryptionHelpURL", chrome::kSyncEncryptionHelpURL);
    210   localized_strings->SetString(
    211       "encryptionSectionMessage",
    212       GetStringFUTF16(IDS_SYNC_ENCRYPTION_SECTION_MESSAGE, product_name));
    213   localized_strings->SetString(
    214       "passphraseRecover",
    215       GetStringFUTF16(IDS_SYNC_PASSPHRASE_RECOVER,
    216                       ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
    217                           chrome::kSyncGoogleDashboardURL))));
    218   localized_strings->SetString("stopSyncingExplanation",
    219       l10n_util::GetStringFUTF16(
    220           IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL,
    221           l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
    222           ASCIIToUTF16(google_util::StringAppendGoogleLocaleParam(
    223               chrome::kSyncGoogleDashboardURL))));
    224   localized_strings->SetString("stopSyncingTitle",
    225       l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE));
    226   localized_strings->SetString("stopSyncingConfirm",
    227         l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL));
    228 
    229   localized_strings->SetString(
    230       "syncEverythingHelpURL", chrome::kSyncEverythingLearnMoreURL);
    231   localized_strings->SetString(
    232       "syncErrorHelpURL", chrome::kSyncErrorsHelpURL);
    233 
    234   static OptionsStringResource resources[] = {
    235     { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE },
    236     { "syncSetupSpinnerTitle", IDS_SYNC_SETUP_SPINNER_TITLE },
    237     { "syncSetupTimeoutTitle", IDS_SYNC_SETUP_TIME_OUT_TITLE },
    238     { "syncSetupTimeoutContent", IDS_SYNC_SETUP_TIME_OUT_CONTENT },
    239     { "errorLearnMore", IDS_LEARN_MORE },
    240     { "cancel", IDS_CANCEL },
    241     { "loginSuccess", IDS_SYNC_SUCCESS },
    242     { "settingUp", IDS_SYNC_LOGIN_SETTING_UP },
    243     { "syncAllDataTypes", IDS_SYNC_EVERYTHING },
    244     { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES },
    245     { "syncNothing", IDS_SYNC_NOTHING },
    246     { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS },
    247     { "preferences", IDS_SYNC_DATATYPE_PREFERENCES },
    248     { "autofill", IDS_SYNC_DATATYPE_AUTOFILL },
    249     { "themes", IDS_SYNC_DATATYPE_THEMES },
    250     { "passwords", IDS_SYNC_DATATYPE_PASSWORDS },
    251     { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS },
    252     { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS },
    253     { "apps", IDS_SYNC_DATATYPE_APPS },
    254     { "openTabs", IDS_SYNC_DATATYPE_TABS },
    255     { "syncZeroDataTypesError", IDS_SYNC_ZERO_DATA_TYPES_ERROR },
    256     { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR },
    257     { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL },
    258     { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR },
    259     { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR },
    260     { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL },
    261     { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES },
    262     { "syncEverything", IDS_SYNC_SYNC_EVERYTHING },
    263     { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS },
    264     { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY },
    265     { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY },
    266     { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
    267     { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE },
    268     { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
    269     { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES },
    270     { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO },
    271     { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX },
    272     { "sectionExplicitMessagePostfix",
    273         IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX },
    274     // TODO(rogerta): browser/resource/sync_promo/sync_promo.html and related
    275     // file may not be needed any more.  If not, then the following promo
    276     // strings can also be removed.
    277     { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE },
    278     { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON },
    279     { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED },
    280     { "promoLearnMore", IDS_LEARN_MORE },
    281     { "promoTitleShort", IDS_SYNC_PROMO_MESSAGE_TITLE_SHORT },
    282     { "encryptionSectionTitle", IDS_SYNC_ENCRYPTION_SECTION_TITLE },
    283     { "basicEncryptionOption", IDS_SYNC_BASIC_ENCRYPTION_DATA },
    284     { "fullEncryptionOption", IDS_SYNC_FULL_ENCRYPTION_DATA },
    285   };
    286 
    287   RegisterStrings(localized_strings, resources, arraysize(resources));
    288   RegisterTitle(localized_strings, "syncSetupOverlay", IDS_SYNC_SETUP_TITLE);
    289 }
    290 
    291 void SyncSetupHandler::DisplayConfigureSync(bool show_advanced,
    292                                             bool passphrase_failed) {
    293   // Should never call this when we are not signed in.
    294   DCHECK(!SigninManagerFactory::GetForProfile(
    295       GetProfile())->GetAuthenticatedUsername().empty());
    296   ProfileSyncService* service = GetSyncService();
    297   DCHECK(service);
    298   if (!service->sync_initialized()) {
    299     service->UnsuppressAndStart();
    300 
    301     // See if it's even possible to bring up the sync backend - if not
    302     // (unrecoverable error?), don't bother displaying a spinner that will be
    303     // immediately closed because this leads to some ugly infinite UI loop (see
    304     // http://crbug.com/244769).
    305     if (SyncStartupTracker::GetSyncServiceState(GetProfile()) !=
    306         SyncStartupTracker::SYNC_STARTUP_ERROR) {
    307       DisplaySpinner();
    308     }
    309 
    310     // Start SyncSetupTracker to wait for sync to initialize.
    311     sync_startup_tracker_.reset(
    312         new SyncStartupTracker(GetProfile(), this));
    313     return;
    314   }
    315 
    316   // Should only get here if user is signed in and sync is initialized, so no
    317   // longer need a SyncStartupTracker.
    318   sync_startup_tracker_.reset();
    319   configuring_sync_ = true;
    320   DCHECK(service->sync_initialized()) <<
    321       "Cannot configure sync until the sync backend is initialized";
    322 
    323   // Setup args for the sync configure screen:
    324   //   showSyncEverythingPage: false to skip directly to the configure screen
    325   //   syncAllDataTypes: true if the user wants to sync everything
    326   //   syncNothing: true if the user wants to sync nothing
    327   //   <data_type>Registered: true if the associated data type is supported
    328   //   <data_type>Synced: true if the user wants to sync that specific data type
    329   //   encryptionEnabled: true if sync supports encryption
    330   //   encryptAllData: true if user wants to encrypt all data (not just
    331   //       passwords)
    332   //   usePassphrase: true if the data is encrypted with a secondary passphrase
    333   //   show_passphrase: true if a passphrase is needed to decrypt the sync data
    334   DictionaryValue args;
    335 
    336   // Tell the UI layer which data types are registered/enabled by the user.
    337   const syncer::ModelTypeSet registered_types =
    338       service->GetRegisteredDataTypes();
    339   const syncer::ModelTypeSet preferred_types =
    340       service->GetPreferredDataTypes();
    341   ModelTypeNameMap type_names = GetSelectableTypeNameMap();
    342   for (ModelTypeNameMap::const_iterator it = type_names.begin();
    343        it != type_names.end(); ++it) {
    344     syncer::ModelType sync_type = it->first;
    345     const std::string key_name = it->second;
    346     args.SetBoolean(key_name + "Registered",
    347                     registered_types.Has(sync_type));
    348     args.SetBoolean(key_name + "Synced", preferred_types.Has(sync_type));
    349   }
    350   browser_sync::SyncPrefs sync_prefs(GetProfile()->GetPrefs());
    351   args.SetBoolean("passphraseFailed", passphrase_failed);
    352   args.SetBoolean("showSyncEverythingPage", !show_advanced);
    353   args.SetBoolean("syncAllDataTypes", sync_prefs.HasKeepEverythingSynced());
    354   args.SetBoolean("syncNothing", false);  // Always false during initial setup.
    355   args.SetBoolean("encryptAllData", service->EncryptEverythingEnabled());
    356 
    357   // We call IsPassphraseRequired() here, instead of calling
    358   // IsPassphraseRequiredForDecryption(), because we want to show the passphrase
    359   // UI even if no encrypted data types are enabled.
    360   args.SetBoolean("showPassphrase", service->IsPassphraseRequired());
    361 
    362   // To distinguish between FROZEN_IMPLICIT_PASSPHRASE and CUSTOM_PASSPHRASE
    363   // we only set usePassphrase for CUSTOM_PASSPHRASE.
    364   args.SetBoolean("usePassphrase",
    365                   service->GetPassphraseType() == syncer::CUSTOM_PASSPHRASE);
    366   base::Time passphrase_time = service->GetExplicitPassphraseTime();
    367   syncer::PassphraseType passphrase_type = service->GetPassphraseType();
    368   if (!passphrase_time.is_null()) {
    369     string16 passphrase_time_str = base::TimeFormatShortDate(passphrase_time);
    370     args.SetString(
    371         "enterPassphraseBody",
    372         GetStringFUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE,
    373                         passphrase_time_str));
    374     args.SetString(
    375         "enterGooglePassphraseBody",
    376         GetStringFUTF16(IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY_WITH_DATE,
    377                         passphrase_time_str));
    378     switch (passphrase_type) {
    379       case syncer::FROZEN_IMPLICIT_PASSPHRASE:
    380         args.SetString(
    381             "fullEncryptionBody",
    382             GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_GOOGLE_WITH_DATE,
    383                             passphrase_time_str));
    384         break;
    385       case syncer::CUSTOM_PASSPHRASE:
    386         args.SetString(
    387             "fullEncryptionBody",
    388             GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM_WITH_DATE,
    389                             passphrase_time_str));
    390         break;
    391       default:
    392         args.SetString(
    393             "fullEncryptionBody",
    394             GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
    395         break;
    396     }
    397   } else if (passphrase_type == syncer::CUSTOM_PASSPHRASE) {
    398     args.SetString(
    399         "fullEncryptionBody",
    400         GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
    401   } else {
    402     args.SetString(
    403         "fullEncryptionBody",
    404         GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_DATA));
    405   }
    406 
    407   StringValue page("configure");
    408   web_ui()->CallJavascriptFunction(
    409       "SyncSetupOverlay.showSyncSetupPage", page, args);
    410 
    411   // Make sure the tab used for the Gaia sign in does not cover the settings
    412   // tab.
    413   FocusUI();
    414 }
    415 
    416 void SyncSetupHandler::ConfigureSyncDone() {
    417   StringValue page("done");
    418   web_ui()->CallJavascriptFunction(
    419       "SyncSetupOverlay.showSyncSetupPage", page);
    420 
    421   // Suppress the sign in promo once the user starts sync. This way the user
    422   // doesn't see the sign in promo even if they sign out later on.
    423   signin::SetUserSkippedPromo(GetProfile());
    424 
    425   ProfileSyncService* service = GetSyncService();
    426   DCHECK(service);
    427   if (!service->HasSyncSetupCompleted()) {
    428     // This is the first time configuring sync, so log it.
    429     base::FilePath profile_file_path = GetProfile()->GetPath();
    430     ProfileMetrics::LogProfileSyncSignIn(profile_file_path);
    431 
    432     // We're done configuring, so notify ProfileSyncService that it is OK to
    433     // start syncing.
    434     service->SetSetupInProgress(false);
    435     service->SetSyncSetupCompleted();
    436   }
    437 }
    438 
    439 bool SyncSetupHandler::IsActiveLogin() const {
    440   // LoginUIService can be NULL if page is brought up in incognito mode
    441   // (i.e. if the user is running in guest mode in cros and brings up settings).
    442   LoginUIService* service = GetLoginUIService();
    443   return service && (service->current_login_ui() == this);
    444 }
    445 
    446 void SyncSetupHandler::RegisterMessages() {
    447   web_ui()->RegisterMessageCallback(
    448       "SyncSetupDidClosePage",
    449       base::Bind(&SyncSetupHandler::OnDidClosePage,
    450                  base::Unretained(this)));
    451   web_ui()->RegisterMessageCallback(
    452       "SyncSetupConfigure",
    453       base::Bind(&SyncSetupHandler::HandleConfigure,
    454                  base::Unretained(this)));
    455   web_ui()->RegisterMessageCallback(
    456       "SyncSetupShowSetupUI",
    457       base::Bind(&SyncSetupHandler::HandleShowSetupUI,
    458                  base::Unretained(this)));
    459   web_ui()->RegisterMessageCallback("CloseTimeout",
    460       base::Bind(&SyncSetupHandler::HandleCloseTimeout,
    461                  base::Unretained(this)));
    462 #if defined(OS_CHROMEOS)
    463   web_ui()->RegisterMessageCallback(
    464       "SyncSetupDoSignOutOnAuthError",
    465       base::Bind(&SyncSetupHandler::HandleDoSignOutOnAuthError,
    466                  base::Unretained(this)));
    467 #else
    468   web_ui()->RegisterMessageCallback("SyncSetupStopSyncing",
    469       base::Bind(&SyncSetupHandler::HandleStopSyncing,
    470                  base::Unretained(this)));
    471   web_ui()->RegisterMessageCallback("SyncSetupStartSignIn",
    472       base::Bind(&SyncSetupHandler::HandleStartSignin,
    473                  base::Unretained(this)));
    474 #endif
    475 }
    476 
    477 #if !defined(OS_CHROMEOS)
    478 void SyncSetupHandler::DisplayGaiaLogin() {
    479   DCHECK(!sync_startup_tracker_);
    480   // Advanced options are no longer being configured if the login screen is
    481   // visible. If the user exits the signin wizard after this without
    482   // configuring sync, CloseSyncSetup() will ensure they are logged out.
    483   configuring_sync_ = false;
    484   DisplayGaiaLoginInNewTabOrWindow();
    485 }
    486 
    487 void SyncSetupHandler::DisplayGaiaLoginInNewTabOrWindow() {
    488   GURL url(signin::GetPromoURL(signin::SOURCE_SETTINGS,
    489                                true));  // auto close after success.
    490   Browser* browser = chrome::FindBrowserWithWebContents(
    491       web_ui()->GetWebContents());
    492   if (!browser) {
    493     // Settings is not displayed in a browser window. Open a new window.
    494     browser = new Browser(Browser::CreateParams(
    495         Browser::TYPE_TABBED, GetProfile(), chrome::GetActiveDesktop()));
    496   }
    497 
    498   // If the signin manager already has an authenticated username, this is a
    499   // re-auth scenario, and we need to ensure that the user signs in with the
    500   // same email address.
    501   std::string email = SigninManagerFactory::GetForProfile(
    502       browser->profile())->GetAuthenticatedUsername();
    503   if (!email.empty()) {
    504     UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
    505                               signin::HISTOGRAM_SHOWN,
    506                               signin::HISTOGRAM_MAX);
    507     std::string fragment("Email=");
    508     fragment += email;
    509     GURL::Replacements replacements;
    510     replacements.SetRefStr(fragment);
    511     url = url.ReplaceComponents(replacements);
    512   }
    513 
    514   browser->OpenURL(
    515       content::OpenURLParams(url, content::Referrer(), SINGLETON_TAB,
    516                              content::PAGE_TRANSITION_AUTO_BOOKMARK, false));
    517 }
    518 #endif
    519 
    520 bool SyncSetupHandler::PrepareSyncSetup() {
    521 
    522   // If the wizard is already visible, just focus that one.
    523   if (FocusExistingWizardIfPresent()) {
    524     if (!IsActiveLogin())
    525       CloseSyncSetup();
    526     return false;
    527   }
    528 
    529   // Notify services that login UI is now active.
    530   GetLoginUIService()->SetLoginUI(this);
    531 
    532   ProfileSyncService* service = GetSyncService();
    533   if (service)
    534     service->SetSetupInProgress(true);
    535 
    536   return true;
    537 }
    538 
    539 void SyncSetupHandler::DisplaySpinner() {
    540   configuring_sync_ = true;
    541   StringValue page("spinner");
    542   DictionaryValue args;
    543 
    544   const int kTimeoutSec = 30;
    545   DCHECK(!backend_start_timer_);
    546   backend_start_timer_.reset(new base::OneShotTimer<SyncSetupHandler>());
    547   backend_start_timer_->Start(FROM_HERE,
    548                               base::TimeDelta::FromSeconds(kTimeoutSec),
    549                               this, &SyncSetupHandler::DisplayTimeout);
    550 
    551   web_ui()->CallJavascriptFunction(
    552       "SyncSetupOverlay.showSyncSetupPage", page, args);
    553 }
    554 
    555 // TODO(kochi): Handle error conditions other than timeout.
    556 // http://crbug.com/128692
    557 void SyncSetupHandler::DisplayTimeout() {
    558   // Stop a timer to handle timeout in waiting for checking network connection.
    559   backend_start_timer_.reset();
    560 
    561   // Do not listen to sync startup events.
    562   sync_startup_tracker_.reset();
    563 
    564   StringValue page("timeout");
    565   DictionaryValue args;
    566   web_ui()->CallJavascriptFunction(
    567       "SyncSetupOverlay.showSyncSetupPage", page, args);
    568 }
    569 
    570 void SyncSetupHandler::OnDidClosePage(const ListValue* args) {
    571   CloseSyncSetup();
    572 }
    573 
    574 void SyncSetupHandler::SyncStartupFailed() {
    575   // Stop a timer to handle timeout in waiting for checking network connection.
    576   backend_start_timer_.reset();
    577 
    578   // Just close the sync overlay (the idea is that the base settings page will
    579   // display the current err
    580   CloseSyncSetup();
    581 }
    582 
    583 void SyncSetupHandler::SyncStartupCompleted() {
    584   ProfileSyncService* service = GetSyncService();
    585   DCHECK(service->sync_initialized());
    586 
    587   // Stop a timer to handle timeout in waiting for checking network connection.
    588   backend_start_timer_.reset();
    589 
    590   DisplayConfigureSync(true, false);
    591 }
    592 
    593 Profile* SyncSetupHandler::GetProfile() const {
    594   return Profile::FromWebUI(web_ui());
    595 }
    596 
    597 ProfileSyncService* SyncSetupHandler::GetSyncService() const {
    598   Profile* profile = GetProfile();
    599   return profile->IsSyncAccessible() ?
    600       ProfileSyncServiceFactory::GetForProfile(GetProfile()) : NULL;
    601 }
    602 
    603 void SyncSetupHandler::HandleConfigure(const ListValue* args) {
    604   DCHECK(!sync_startup_tracker_);
    605   std::string json;
    606   if (!args->GetString(0, &json)) {
    607     NOTREACHED() << "Could not read JSON argument";
    608     return;
    609   }
    610   if (json.empty()) {
    611     NOTREACHED();
    612     return;
    613   }
    614 
    615   SyncConfigInfo configuration;
    616   if (!GetConfiguration(json, &configuration)) {
    617     // The page sent us something that we didn't understand.
    618     // This probably indicates a programming error.
    619     NOTREACHED();
    620     return;
    621   }
    622 
    623   // Start configuring the ProfileSyncService using the configuration passed
    624   // to us from the JS layer.
    625   ProfileSyncService* service = GetSyncService();
    626 
    627   // If the sync engine has shutdown for some reason, just close the sync
    628   // dialog.
    629   if (!service || !service->sync_initialized()) {
    630     CloseSyncSetup();
    631     return;
    632   }
    633 
    634   // Disable sync, but remain signed in if the user selected "Sync nothing" in
    635   // the advanced settings dialog. Note: In order to disable sync across
    636   // restarts on Chrome OS, we must call OnStopSyncingPermanently(), which
    637   // suppresses sync startup in addition to disabling it.
    638   if (configuration.sync_nothing) {
    639     ProfileSyncService::SyncEvent(
    640         ProfileSyncService::STOP_FROM_ADVANCED_DIALOG);
    641     CloseSyncSetup();
    642     service->OnStopSyncingPermanently();
    643     service->SetSetupInProgress(false);
    644     return;
    645   }
    646 
    647   // Note: Data encryption will not occur until configuration is complete
    648   // (when the PSS receives its CONFIGURE_DONE notification from the sync
    649   // backend), so the user still has a chance to cancel out of the operation
    650   // if (for example) some kind of passphrase error is encountered.
    651   if (configuration.encrypt_all)
    652     service->EnableEncryptEverything();
    653 
    654   bool passphrase_failed = false;
    655   if (!configuration.passphrase.empty()) {
    656     // We call IsPassphraseRequired() here (instead of
    657     // IsPassphraseRequiredForDecryption()) because the user may try to enter
    658     // a passphrase even though no encrypted data types are enabled.
    659     if (service->IsPassphraseRequired()) {
    660       // If we have pending keys, try to decrypt them with the provided
    661       // passphrase. We track if this succeeds or fails because a failed
    662       // decryption should result in an error even if there aren't any encrypted
    663       // data types.
    664       passphrase_failed =
    665           !service->SetDecryptionPassphrase(configuration.passphrase);
    666     } else {
    667       // OK, the user sent us a passphrase, but we don't have pending keys. So
    668       // it either means that the pending keys were resolved somehow since the
    669       // time the UI was displayed (re-encryption, pending passphrase change,
    670       // etc) or the user wants to re-encrypt.
    671       if (!configuration.passphrase_is_gaia &&
    672           !service->IsUsingSecondaryPassphrase()) {
    673         // User passed us a secondary passphrase, and the data is encrypted
    674         // with a GAIA passphrase so they must want to encrypt.
    675         service->SetEncryptionPassphrase(configuration.passphrase,
    676                                          ProfileSyncService::EXPLICIT);
    677       }
    678     }
    679   }
    680 
    681   bool user_was_prompted_for_passphrase =
    682       service->IsPassphraseRequiredForDecryption();
    683   service->OnUserChoseDatatypes(configuration.sync_everything,
    684                                 configuration.data_types);
    685 
    686   // Need to call IsPassphraseRequiredForDecryption() *after* calling
    687   // OnUserChoseDatatypes() because the user may have just disabled the
    688   // encrypted datatypes (in which case we just want to exit, not prompt the
    689   // user for a passphrase).
    690   if (passphrase_failed || service->IsPassphraseRequiredForDecryption()) {
    691     // We need a passphrase, or the user's attempt to set a passphrase failed -
    692     // prompt them again. This covers a few subtle cases:
    693     // 1) The user enters an incorrect passphrase *and* disabled the encrypted
    694     //    data types. In that case we want to notify the user that the
    695     //    passphrase was incorrect even though there are no longer any encrypted
    696     //    types enabled (IsPassphraseRequiredForDecryption() == false).
    697     // 2) The user doesn't enter any passphrase. In this case, we won't call
    698     //    SetDecryptionPassphrase() (passphrase_failed == false), but we still
    699     //    want to display an error message to let the user know that their
    700     //    blank passphrase entry is not acceptable.
    701     // 3) The user just enabled an encrypted data type - in this case we don't
    702     //    want to display an "invalid passphrase" error, since it's the first
    703     //    time the user is seeing the prompt.
    704     DisplayConfigureSync(
    705         true, passphrase_failed || user_was_prompted_for_passphrase);
    706   } else {
    707     // No passphrase is required from the user so mark the configuration as
    708     // complete and close the sync setup overlay.
    709     ConfigureSyncDone();
    710   }
    711 
    712   ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE);
    713   if (configuration.encrypt_all)
    714     ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT);
    715   if (configuration.passphrase_is_gaia && !configuration.passphrase.empty())
    716     ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE);
    717   if (!configuration.sync_everything)
    718     ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE);
    719 }
    720 
    721 void SyncSetupHandler::HandleShowSetupUI(const ListValue* args) {
    722   ProfileSyncService* service = GetSyncService();
    723   DCHECK(service);
    724 
    725   SigninManagerBase* signin =
    726       SigninManagerFactory::GetForProfile(GetProfile());
    727   if (signin->GetAuthenticatedUsername().empty()) {
    728     // For web-based signin, the signin page is not displayed in an overlay
    729     // on the settings page. So if we get here, it must be due to the user
    730     // cancelling signin (by reloading the sync settings page during initial
    731     // signin) or by directly navigating to settings/syncSetup
    732     // (http://crbug.com/229836). So just exit and go back to the settings page.
    733     DLOG(WARNING) << "Cannot display sync setup UI when not signed in";
    734     CloseSyncSetup();
    735     StringValue page("done");
    736     web_ui()->CallJavascriptFunction(
    737         "SyncSetupOverlay.showSyncSetupPage", page);
    738     return;
    739   }
    740 
    741   // If a setup wizard is already present, but not on this page, close the
    742   // blank setup overlay on this page by showing the "done" page. This can
    743   // happen if the user navigates to chrome://settings/syncSetup in more than
    744   // one tab. See crbug.com/261566.
    745   // Note: The following block will transfer focus to the existing wizard.
    746   if (IsExistingWizardPresent() && !IsActiveLogin()) {
    747     CloseSyncSetup();
    748     StringValue page("done");
    749     web_ui()->CallJavascriptFunction(
    750         "SyncSetupOverlay.showSyncSetupPage", page);
    751   }
    752 
    753   // If a setup wizard is present on this page or another, bring it to focus.
    754   // Otherwise, display a new one on this page.
    755   if (!FocusExistingWizardIfPresent())
    756     OpenSyncSetup();
    757 }
    758 
    759 #if defined(OS_CHROMEOS)
    760 // On ChromeOS, we need to sign out the user session to fix an auth error, so
    761 // the user goes through the real signin flow to generate a new auth token.
    762 void SyncSetupHandler::HandleDoSignOutOnAuthError(const ListValue* args) {
    763   DLOG(INFO) << "Signing out the user to fix a sync error.";
    764   chrome::AttemptUserExit();
    765 }
    766 #endif
    767 
    768 #if !defined(OS_CHROMEOS)
    769 void SyncSetupHandler::HandleStartSignin(const ListValue* args) {
    770   // Should only be called if the user is not already signed in.
    771   DCHECK(SigninManagerFactory::GetForProfile(GetProfile())->
    772       GetAuthenticatedUsername().empty());
    773   OpenSyncSetup();
    774 }
    775 
    776 void SyncSetupHandler::HandleStopSyncing(const ListValue* args) {
    777   if (GetSyncService())
    778     ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS);
    779 #if !defined(OS_CHROMEOS)
    780   SigninManagerFactory::GetForProfile(GetProfile())->SignOut();
    781 #endif
    782 }
    783 #endif
    784 
    785 void SyncSetupHandler::HandleCloseTimeout(const ListValue* args) {
    786   CloseSyncSetup();
    787 }
    788 
    789 void SyncSetupHandler::CloseSyncSetup() {
    790   // Stop a timer to handle timeout in waiting for checking network connection.
    791   backend_start_timer_.reset();
    792 
    793   // Clear the sync startup tracker, since the setup wizard is being closed.
    794   sync_startup_tracker_.reset();
    795 
    796   ProfileSyncService* sync_service = GetSyncService();
    797   if (IsActiveLogin()) {
    798     // Don't log a cancel event if the sync setup dialog is being
    799     // automatically closed due to an auth error.
    800     if (!sync_service || (!sync_service->HasSyncSetupCompleted() &&
    801         sync_service->GetAuthError().state() == GoogleServiceAuthError::NONE)) {
    802       if (configuring_sync_) {
    803         ProfileSyncService::SyncEvent(
    804             ProfileSyncService::CANCEL_DURING_CONFIGURE);
    805       }
    806 
    807       // If the user clicked "Cancel" while setting up sync, disable sync
    808       // because we don't want the sync backend to remain in the initialized
    809       // state. Note: In order to disable sync across restarts on Chrome OS, we
    810       // must call OnStopSyncingPermanently(), which suppresses sync startup in
    811       // addition to disabling it.
    812       if (sync_service) {
    813         DVLOG(1) << "Sync setup aborted by user action";
    814         sync_service->OnStopSyncingPermanently();
    815 #if !defined(OS_CHROMEOS)
    816         // Sign out the user on desktop Chrome if they click cancel during
    817         // initial setup.
    818         // TODO(rsimha): Revisit this for M30. See http://crbug.com/252049.
    819         if (sync_service->FirstSetupInProgress())
    820           SigninManagerFactory::GetForProfile(GetProfile())->SignOut();
    821 #endif
    822       }
    823     }
    824 
    825     GetLoginUIService()->LoginUIClosed(this);
    826   }
    827 
    828   // Alert the sync service anytime the sync setup dialog is closed. This can
    829   // happen due to the user clicking the OK or Cancel button, or due to the
    830   // dialog being closed by virtue of sync being disabled in the background.
    831   if (sync_service)
    832     sync_service->SetSetupInProgress(false);
    833 
    834   configuring_sync_ = false;
    835 }
    836 
    837 void SyncSetupHandler::OpenSyncSetup() {
    838   if (!PrepareSyncSetup())
    839     return;
    840 
    841   // There are several different UI flows that can bring the user here:
    842   // 1) Signin promo.
    843   // 2) Normal signin through settings page (GetAuthenticatedUsername() is
    844   //    empty).
    845   // 3) Previously working credentials have expired.
    846   // 4) User is signed in, but has stopped sync via the google dashboard, and
    847   //    signout is prohibited by policy so we need to force a re-auth.
    848   // 5) User clicks [Advanced Settings] button on options page while already
    849   //    logged in.
    850   // 6) One-click signin (credentials are already available, so should display
    851   //    sync configure UI, not login UI).
    852   // 7) User re-enables sync after disabling it via advanced settings.
    853 #if !defined(OS_CHROMEOS)
    854   SigninManagerBase* signin =
    855       SigninManagerFactory::GetForProfile(GetProfile());
    856 
    857   if (signin->GetAuthenticatedUsername().empty() ||
    858       SigninGlobalError::GetForProfile(GetProfile())->HasMenuItem()) {
    859     // User is not logged in (cases 1-2), or login has been specially requested
    860     // because previously working credentials have expired (case 3). Close sync
    861     // setup including any visible overlays, and display the gaia auth page.
    862     // Control will be returned to the sync settings page once auth is complete.
    863     CloseSyncSetup();
    864     DisplayGaiaLogin();
    865     return;
    866   }
    867 #endif
    868   if (!GetSyncService()) {
    869     // This can happen if the user directly navigates to /settings/syncSetup.
    870     DLOG(WARNING) << "Cannot display sync UI when sync is disabled";
    871     CloseSyncSetup();
    872     return;
    873   }
    874 
    875   // User is already logged in. They must have brought up the config wizard
    876   // via the "Advanced..." button or through One-Click signin (cases 4-6), or
    877   // they are re-enabling sync after having disabled it (case 7).
    878   DisplayConfigureSync(true, false);
    879 }
    880 
    881 void SyncSetupHandler::OpenConfigureSync() {
    882   if (!PrepareSyncSetup())
    883     return;
    884 
    885   DisplayConfigureSync(true, false);
    886 }
    887 
    888 void SyncSetupHandler::FocusUI() {
    889   DCHECK(IsActiveLogin());
    890   WebContents* web_contents = web_ui()->GetWebContents();
    891   web_contents->GetDelegate()->ActivateContents(web_contents);
    892 }
    893 
    894 void SyncSetupHandler::CloseUI() {
    895   DCHECK(IsActiveLogin());
    896   CloseSyncSetup();
    897 }
    898 
    899 bool SyncSetupHandler::IsExistingWizardPresent() {
    900   LoginUIService* service = GetLoginUIService();
    901   DCHECK(service);
    902   return service->current_login_ui() != NULL;
    903 }
    904 
    905 bool SyncSetupHandler::FocusExistingWizardIfPresent() {
    906   if (!IsExistingWizardPresent())
    907     return false;
    908 
    909   LoginUIService* service = GetLoginUIService();
    910   DCHECK(service);
    911   service->current_login_ui()->FocusUI();
    912   return true;
    913 }
    914 
    915 LoginUIService* SyncSetupHandler::GetLoginUIService() const {
    916   return LoginUIServiceFactory::GetForProfile(GetProfile());
    917 }
    918