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