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.
5 #include "chrome/browser/ui/webui/sync_setup_handler.h"
7 #include "base/basictypes.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/browser_process.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/profile_oauth2_token_service_factory.h"
27 #include "chrome/browser/signin/signin_header_helper.h"
28 #include "chrome/browser/signin/signin_manager_factory.h"
29 #include "chrome/browser/signin/signin_promo.h"
30 #include "chrome/browser/sync/profile_sync_service.h"
31 #include "chrome/browser/sync/profile_sync_service_factory.h"
32 #include "chrome/browser/ui/browser_finder.h"
33 #include "chrome/browser/ui/browser_navigator.h"
34 #include "chrome/browser/ui/browser_window.h"
35 #include "chrome/browser/ui/singleton_tabs.h"
36 #include "chrome/browser/ui/sync/signin_histogram.h"
37 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
38 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
39 #include "chrome/common/chrome_switches.h"
40 #include "chrome/common/pref_names.h"
41 #include "chrome/common/url_constants.h"
42 #include "components/google/core/browser/google_util.h"
43 #include "components/signin/core/browser/profile_oauth2_token_service.h"
44 #include "components/signin/core/browser/signin_error_controller.h"
45 #include "components/signin/core/common/profile_management_switches.h"
46 #include "components/sync_driver/sync_prefs.h"
47 #include "content/public/browser/render_view_host.h"
48 #include "content/public/browser/web_contents.h"
49 #include "content/public/browser/web_contents_delegate.h"
50 #include "google_apis/gaia/gaia_auth_util.h"
51 #include "google_apis/gaia/gaia_constants.h"
52 #include "grit/chromium_strings.h"
53 #include "grit/generated_resources.h"
54 #include "grit/locale_settings.h"
55 #include "net/base/url_util.h"
56 #include "ui/base/l10n/l10n_util.h"
58 #if defined(OS_CHROMEOS)
59 #include "components/signin/core/browser/signin_manager_base.h"
61 #include "components/signin/core/browser/signin_manager.h"
64 using content::WebContents;
65 using l10n_util::GetStringFUTF16;
66 using l10n_util::GetStringUTF16;
70 // A structure which contains all the configuration information for sync.
71 struct SyncConfigInfo {
78 syncer::ModelTypeSet data_types;
79 std::string passphrase;
80 bool passphrase_is_gaia;
83 SyncConfigInfo::SyncConfigInfo()
85 sync_everything(false),
87 passphrase_is_gaia(false) {
90 SyncConfigInfo::~SyncConfigInfo() {}
92 // Note: The order of these types must match the ordering of
93 // the respective types in ModelType
94 const char* kDataTypeNames[] = {
106 COMPILE_ASSERT(32 == syncer::MODEL_TYPE_COUNT,
107 update_kDataTypeNames_to_match_UserSelectableTypes);
109 typedef std::map<syncer::ModelType, const char*> ModelTypeNameMap;
111 ModelTypeNameMap GetSelectableTypeNameMap() {
112 ModelTypeNameMap type_names;
113 syncer::ModelTypeSet type_set = syncer::UserSelectableTypes();
114 syncer::ModelTypeSet::Iterator it = type_set.First();
115 DCHECK_EQ(arraysize(kDataTypeNames), type_set.Size());
116 for (size_t i = 0; i < arraysize(kDataTypeNames) && it.Good();
118 type_names[it.Get()] = kDataTypeNames[i];
123 bool GetConfiguration(const std::string& json, SyncConfigInfo* config) {
124 scoped_ptr<base::Value> parsed_value(base::JSONReader::Read(json));
125 base::DictionaryValue* result;
126 if (!parsed_value || !parsed_value->GetAsDictionary(&result)) {
127 DLOG(ERROR) << "GetConfiguration() not passed a Dictionary";
131 if (!result->GetBoolean("syncAllDataTypes", &config->sync_everything)) {
132 DLOG(ERROR) << "GetConfiguration() not passed a syncAllDataTypes value";
136 if (!result->GetBoolean("syncNothing", &config->sync_nothing)) {
137 DLOG(ERROR) << "GetConfiguration() not passed a syncNothing value";
141 DCHECK(!(config->sync_everything && config->sync_nothing))
142 << "syncAllDataTypes and syncNothing cannot both be true";
144 ModelTypeNameMap type_names = GetSelectableTypeNameMap();
146 for (ModelTypeNameMap::const_iterator it = type_names.begin();
147 it != type_names.end(); ++it) {
148 std::string key_name = it->second + std::string("Synced");
150 if (!result->GetBoolean(key_name, &sync_value)) {
151 DLOG(ERROR) << "GetConfiguration() not passed a value for " << key_name;
155 config->data_types.Put(it->first);
158 // Encryption settings.
159 if (!result->GetBoolean("encryptAllData", &config->encrypt_all)) {
160 DLOG(ERROR) << "GetConfiguration() not passed a value for encryptAllData";
164 // Passphrase settings.
165 bool have_passphrase;
166 if (!result->GetBoolean("usePassphrase", &have_passphrase)) {
167 DLOG(ERROR) << "GetConfiguration() not passed a usePassphrase value";
171 if (have_passphrase) {
172 if (!result->GetBoolean("isGooglePassphrase",
173 &config->passphrase_is_gaia)) {
174 DLOG(ERROR) << "GetConfiguration() not passed isGooglePassphrase value";
177 if (!result->GetString("passphrase", &config->passphrase)) {
178 DLOG(ERROR) << "GetConfiguration() not passed a passphrase value";
187 SyncSetupHandler::SyncSetupHandler(ProfileManager* profile_manager)
188 : configuring_sync_(false),
189 profile_manager_(profile_manager) {
192 SyncSetupHandler::~SyncSetupHandler() {
193 // Just exit if running unit tests (no actual WebUI is attached).
197 // This case is hit when the user performs a back navigation.
201 void SyncSetupHandler::GetLocalizedValues(
202 base::DictionaryValue* localized_strings) {
203 GetStaticLocalizedValues(localized_strings, web_ui());
206 void SyncSetupHandler::GetStaticLocalizedValues(
207 base::DictionaryValue* localized_strings,
208 content::WebUI* web_ui) {
209 DCHECK(localized_strings);
211 base::string16 product_name(GetStringUTF16(IDS_PRODUCT_NAME));
212 localized_strings->SetString(
213 "chooseDataTypesInstructions",
214 GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS, product_name));
215 localized_strings->SetString(
216 "encryptionInstructions",
217 GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS, product_name));
218 localized_strings->SetString(
219 "encryptionHelpURL", chrome::kSyncEncryptionHelpURL);
220 localized_strings->SetString(
221 "encryptionSectionMessage",
222 GetStringFUTF16(IDS_SYNC_ENCRYPTION_SECTION_MESSAGE, product_name));
223 localized_strings->SetString(
226 IDS_SYNC_PASSPHRASE_RECOVER,
228 google_util::AppendGoogleLocaleParam(
229 GURL(chrome::kSyncGoogleDashboardURL),
230 g_browser_process->GetApplicationLocale()).spec())));
231 localized_strings->SetString(
232 "stopSyncingExplanation",
233 l10n_util::GetStringFUTF16(
234 IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL,
235 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME),
237 google_util::AppendGoogleLocaleParam(
238 GURL(chrome::kSyncGoogleDashboardURL),
239 g_browser_process->GetApplicationLocale()).spec())));
240 localized_strings->SetString("deleteProfileLabel",
241 l10n_util::GetStringUTF16(IDS_SYNC_STOP_DELETE_PROFILE_LABEL));
242 localized_strings->SetString("stopSyncingTitle",
243 l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE));
244 localized_strings->SetString("stopSyncingConfirm",
245 l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL));
247 localized_strings->SetString(
248 "syncEverythingHelpURL", chrome::kSyncEverythingLearnMoreURL);
249 localized_strings->SetString(
250 "syncErrorHelpURL", chrome::kSyncErrorsHelpURL);
252 static OptionsStringResource resources[] = {
253 { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE },
254 { "syncSetupSpinnerTitle", IDS_SYNC_SETUP_SPINNER_TITLE },
255 { "syncSetupTimeoutTitle", IDS_SYNC_SETUP_TIME_OUT_TITLE },
256 { "syncSetupTimeoutContent", IDS_SYNC_SETUP_TIME_OUT_CONTENT },
257 { "errorLearnMore", IDS_LEARN_MORE },
258 { "cancel", IDS_CANCEL },
259 { "loginSuccess", IDS_SYNC_SUCCESS },
260 { "settingUp", IDS_SYNC_LOGIN_SETTING_UP },
261 { "syncAllDataTypes", IDS_SYNC_EVERYTHING },
262 { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES },
263 { "syncNothing", IDS_SYNC_NOTHING },
264 { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS },
265 { "preferences", IDS_SYNC_DATATYPE_PREFERENCES },
266 { "autofill", IDS_SYNC_DATATYPE_AUTOFILL },
267 { "themes", IDS_SYNC_DATATYPE_THEMES },
268 { "passwords", IDS_SYNC_DATATYPE_PASSWORDS },
269 { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS },
270 { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS },
271 { "apps", IDS_SYNC_DATATYPE_APPS },
272 { "openTabs", IDS_SYNC_DATATYPE_TABS },
273 { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR },
274 { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL },
275 { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR },
276 { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR },
277 { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL },
278 { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES },
279 { "syncEverything", IDS_SYNC_SYNC_EVERYTHING },
280 { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS },
281 { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY },
282 { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY },
283 { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL },
284 { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE },
285 { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING },
286 { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES },
287 { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO },
288 { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX },
289 { "sectionExplicitMessagePostfix",
290 IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX },
291 // TODO(rogerta): browser/resource/sync_promo/sync_promo.html and related
292 // file may not be needed any more. If not, then the following promo
293 // strings can also be removed.
294 { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE },
295 { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON },
296 { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED },
297 { "promoLearnMore", IDS_LEARN_MORE },
298 { "promoTitleShort", IDS_SYNC_PROMO_MESSAGE_TITLE_SHORT },
299 { "encryptionSectionTitle", IDS_SYNC_ENCRYPTION_SECTION_TITLE },
300 { "basicEncryptionOption", IDS_SYNC_BASIC_ENCRYPTION_DATA },
301 { "fullEncryptionOption", IDS_SYNC_FULL_ENCRYPTION_DATA },
304 RegisterStrings(localized_strings, resources, arraysize(resources));
305 RegisterTitle(localized_strings, "syncSetupOverlay", IDS_SYNC_SETUP_TITLE);
308 void SyncSetupHandler::DisplayConfigureSync(bool show_advanced,
309 bool passphrase_failed) {
310 // Should never call this when we are not signed in.
311 DCHECK(!SigninManagerFactory::GetForProfile(
312 GetProfile())->GetAuthenticatedUsername().empty());
313 ProfileSyncService* service = GetSyncService();
315 if (!service->sync_initialized()) {
316 service->UnsuppressAndStart();
318 // See if it's even possible to bring up the sync backend - if not
319 // (unrecoverable error?), don't bother displaying a spinner that will be
320 // immediately closed because this leads to some ugly infinite UI loop (see
321 // http://crbug.com/244769).
322 if (SyncStartupTracker::GetSyncServiceState(GetProfile()) !=
323 SyncStartupTracker::SYNC_STARTUP_ERROR) {
327 // Start SyncSetupTracker to wait for sync to initialize.
328 sync_startup_tracker_.reset(
329 new SyncStartupTracker(GetProfile(), this));
333 // Should only get here if user is signed in and sync is initialized, so no
334 // longer need a SyncStartupTracker.
335 sync_startup_tracker_.reset();
336 configuring_sync_ = true;
337 DCHECK(service->sync_initialized()) <<
338 "Cannot configure sync until the sync backend is initialized";
340 // Setup args for the sync configure screen:
341 // showSyncEverythingPage: false to skip directly to the configure screen
342 // syncAllDataTypes: true if the user wants to sync everything
343 // syncNothing: true if the user wants to sync nothing
344 // <data_type>Registered: true if the associated data type is supported
345 // <data_type>Synced: true if the user wants to sync that specific data type
346 // encryptionEnabled: true if sync supports encryption
347 // encryptAllData: true if user wants to encrypt all data (not just
349 // usePassphrase: true if the data is encrypted with a secondary passphrase
350 // show_passphrase: true if a passphrase is needed to decrypt the sync data
351 base::DictionaryValue args;
353 // Tell the UI layer which data types are registered/enabled by the user.
354 const syncer::ModelTypeSet registered_types =
355 service->GetRegisteredDataTypes();
356 const syncer::ModelTypeSet preferred_types =
357 service->GetPreferredDataTypes();
358 ModelTypeNameMap type_names = GetSelectableTypeNameMap();
359 for (ModelTypeNameMap::const_iterator it = type_names.begin();
360 it != type_names.end(); ++it) {
361 syncer::ModelType sync_type = it->first;
362 const std::string key_name = it->second;
363 args.SetBoolean(key_name + "Registered",
364 registered_types.Has(sync_type));
365 args.SetBoolean(key_name + "Synced", preferred_types.Has(sync_type));
367 sync_driver::SyncPrefs sync_prefs(GetProfile()->GetPrefs());
368 args.SetBoolean("passphraseFailed", passphrase_failed);
369 args.SetBoolean("showSyncEverythingPage", !show_advanced);
370 args.SetBoolean("syncAllDataTypes", sync_prefs.HasKeepEverythingSynced());
371 args.SetBoolean("syncNothing", false); // Always false during initial setup.
372 args.SetBoolean("encryptAllData", service->EncryptEverythingEnabled());
374 // We call IsPassphraseRequired() here, instead of calling
375 // IsPassphraseRequiredForDecryption(), because we want to show the passphrase
376 // UI even if no encrypted data types are enabled.
377 args.SetBoolean("showPassphrase", service->IsPassphraseRequired());
379 // To distinguish between FROZEN_IMPLICIT_PASSPHRASE and CUSTOM_PASSPHRASE
380 // we only set usePassphrase for CUSTOM_PASSPHRASE.
381 args.SetBoolean("usePassphrase",
382 service->GetPassphraseType() == syncer::CUSTOM_PASSPHRASE);
383 base::Time passphrase_time = service->GetExplicitPassphraseTime();
384 syncer::PassphraseType passphrase_type = service->GetPassphraseType();
385 if (!passphrase_time.is_null()) {
386 base::string16 passphrase_time_str =
387 base::TimeFormatShortDate(passphrase_time);
389 "enterPassphraseBody",
390 GetStringFUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE,
391 passphrase_time_str));
393 "enterGooglePassphraseBody",
394 GetStringFUTF16(IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY_WITH_DATE,
395 passphrase_time_str));
396 switch (passphrase_type) {
397 case syncer::FROZEN_IMPLICIT_PASSPHRASE:
399 "fullEncryptionBody",
400 GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_GOOGLE_WITH_DATE,
401 passphrase_time_str));
403 case syncer::CUSTOM_PASSPHRASE:
405 "fullEncryptionBody",
406 GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM_WITH_DATE,
407 passphrase_time_str));
411 "fullEncryptionBody",
412 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
415 } else if (passphrase_type == syncer::CUSTOM_PASSPHRASE) {
417 "fullEncryptionBody",
418 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM));
421 "fullEncryptionBody",
422 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_DATA));
425 base::StringValue page("configure");
426 web_ui()->CallJavascriptFunction(
427 "SyncSetupOverlay.showSyncSetupPage", page, args);
429 // Make sure the tab used for the Gaia sign in does not cover the settings
434 void SyncSetupHandler::ConfigureSyncDone() {
435 base::StringValue page("done");
436 web_ui()->CallJavascriptFunction(
437 "SyncSetupOverlay.showSyncSetupPage", page);
439 // Suppress the sign in promo once the user starts sync. This way the user
440 // doesn't see the sign in promo even if they sign out later on.
441 signin::SetUserSkippedPromo(GetProfile());
443 ProfileSyncService* service = GetSyncService();
445 if (!service->HasSyncSetupCompleted()) {
446 // This is the first time configuring sync, so log it.
447 base::FilePath profile_file_path = GetProfile()->GetPath();
448 ProfileMetrics::LogProfileSyncSignIn(profile_file_path);
450 // We're done configuring, so notify ProfileSyncService that it is OK to
452 service->SetSetupInProgress(false);
453 service->SetSyncSetupCompleted();
457 bool SyncSetupHandler::IsActiveLogin() const {
458 // LoginUIService can be NULL if page is brought up in incognito mode
459 // (i.e. if the user is running in guest mode in cros and brings up settings).
460 LoginUIService* service = GetLoginUIService();
461 return service && (service->current_login_ui() == this);
464 void SyncSetupHandler::RegisterMessages() {
465 web_ui()->RegisterMessageCallback(
466 "SyncSetupDidClosePage",
467 base::Bind(&SyncSetupHandler::OnDidClosePage,
468 base::Unretained(this)));
469 web_ui()->RegisterMessageCallback(
470 "SyncSetupConfigure",
471 base::Bind(&SyncSetupHandler::HandleConfigure,
472 base::Unretained(this)));
473 web_ui()->RegisterMessageCallback(
474 "SyncSetupShowSetupUI",
475 base::Bind(&SyncSetupHandler::HandleShowSetupUI,
476 base::Unretained(this)));
477 web_ui()->RegisterMessageCallback("CloseTimeout",
478 base::Bind(&SyncSetupHandler::HandleCloseTimeout,
479 base::Unretained(this)));
480 #if defined(OS_CHROMEOS)
481 web_ui()->RegisterMessageCallback(
482 "SyncSetupDoSignOutOnAuthError",
483 base::Bind(&SyncSetupHandler::HandleDoSignOutOnAuthError,
484 base::Unretained(this)));
486 web_ui()->RegisterMessageCallback("SyncSetupStopSyncing",
487 base::Bind(&SyncSetupHandler::HandleStopSyncing,
488 base::Unretained(this)));
489 web_ui()->RegisterMessageCallback("SyncSetupStartSignIn",
490 base::Bind(&SyncSetupHandler::HandleStartSignin,
491 base::Unretained(this)));
495 #if !defined(OS_CHROMEOS)
496 void SyncSetupHandler::DisplayGaiaLogin() {
497 DCHECK(!sync_startup_tracker_);
498 // Advanced options are no longer being configured if the login screen is
499 // visible. If the user exits the signin wizard after this without
500 // configuring sync, CloseSyncSetup() will ensure they are logged out.
501 configuring_sync_ = false;
502 DisplayGaiaLoginInNewTabOrWindow();
505 void SyncSetupHandler::DisplayGaiaLoginInNewTabOrWindow() {
506 Browser* browser = chrome::FindBrowserWithWebContents(
507 web_ui()->GetWebContents());
509 // Settings is not displayed in a browser window. Open a new window.
510 browser = new Browser(Browser::CreateParams(
511 Browser::TYPE_TABBED, GetProfile(), chrome::GetActiveDesktop()));
514 // If the signin manager already has an authenticated username, this is a
515 // re-auth scenario, and we need to ensure that the user signs in with the
516 // same email address.
518 std::string email = SigninManagerFactory::GetForProfile(
519 browser->profile())->GetAuthenticatedUsername();
520 if (!email.empty()) {
521 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
522 signin::HISTOGRAM_SHOWN,
523 signin::HISTOGRAM_MAX);
525 SigninErrorController* error_controller =
526 ProfileOAuth2TokenServiceFactory::GetForProfile(browser->profile())->
527 signin_error_controller();
528 DCHECK(error_controller->HasError());
529 if (switches::IsNewProfileManagement()) {
530 browser->window()->ShowAvatarBubbleFromAvatarButton(
531 BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH,
532 signin::ManageAccountsParams());
534 url = signin::GetReauthURL(browser->profile(),
535 error_controller->error_account_id());
538 if (switches::IsNewProfileManagement()) {
539 browser->window()->ShowAvatarBubbleFromAvatarButton(
540 BrowserWindow::AVATAR_BUBBLE_MODE_SIGNIN,
541 signin::ManageAccountsParams());
543 url = signin::GetPromoURL(signin::SOURCE_SETTINGS, true);
548 chrome::ShowSingletonTab(browser, url);
552 bool SyncSetupHandler::PrepareSyncSetup() {
554 // If the wizard is already visible, just focus that one.
555 if (FocusExistingWizardIfPresent()) {
556 if (!IsActiveLogin())
561 // Notify services that login UI is now active.
562 GetLoginUIService()->SetLoginUI(this);
564 ProfileSyncService* service = GetSyncService();
566 service->SetSetupInProgress(true);
571 void SyncSetupHandler::DisplaySpinner() {
572 configuring_sync_ = true;
573 base::StringValue page("spinner");
574 base::DictionaryValue args;
576 const int kTimeoutSec = 30;
577 DCHECK(!backend_start_timer_);
578 backend_start_timer_.reset(new base::OneShotTimer<SyncSetupHandler>());
579 backend_start_timer_->Start(FROM_HERE,
580 base::TimeDelta::FromSeconds(kTimeoutSec),
581 this, &SyncSetupHandler::DisplayTimeout);
583 web_ui()->CallJavascriptFunction(
584 "SyncSetupOverlay.showSyncSetupPage", page, args);
587 // TODO(kochi): Handle error conditions other than timeout.
588 // http://crbug.com/128692
589 void SyncSetupHandler::DisplayTimeout() {
590 // Stop a timer to handle timeout in waiting for checking network connection.
591 backend_start_timer_.reset();
593 // Do not listen to sync startup events.
594 sync_startup_tracker_.reset();
596 base::StringValue page("timeout");
597 base::DictionaryValue args;
598 web_ui()->CallJavascriptFunction(
599 "SyncSetupOverlay.showSyncSetupPage", page, args);
602 void SyncSetupHandler::OnDidClosePage(const base::ListValue* args) {
606 void SyncSetupHandler::SyncStartupFailed() {
607 // Stop a timer to handle timeout in waiting for checking network connection.
608 backend_start_timer_.reset();
610 // Just close the sync overlay (the idea is that the base settings page will
611 // display the current error.)
615 void SyncSetupHandler::SyncStartupCompleted() {
616 ProfileSyncService* service = GetSyncService();
617 DCHECK(service->sync_initialized());
619 // Stop a timer to handle timeout in waiting for checking network connection.
620 backend_start_timer_.reset();
622 DisplayConfigureSync(true, false);
625 Profile* SyncSetupHandler::GetProfile() const {
626 return Profile::FromWebUI(web_ui());
629 ProfileSyncService* SyncSetupHandler::GetSyncService() const {
630 Profile* profile = GetProfile();
631 return profile->IsSyncAccessible() ?
632 ProfileSyncServiceFactory::GetForProfile(GetProfile()) : NULL;
635 void SyncSetupHandler::HandleConfigure(const base::ListValue* args) {
636 DCHECK(!sync_startup_tracker_);
638 if (!args->GetString(0, &json)) {
639 NOTREACHED() << "Could not read JSON argument";
647 SyncConfigInfo configuration;
648 if (!GetConfiguration(json, &configuration)) {
649 // The page sent us something that we didn't understand.
650 // This probably indicates a programming error.
655 // Start configuring the ProfileSyncService using the configuration passed
656 // to us from the JS layer.
657 ProfileSyncService* service = GetSyncService();
659 // If the sync engine has shutdown for some reason, just close the sync
661 if (!service || !service->sync_initialized()) {
666 // Disable sync, but remain signed in if the user selected "Sync nothing" in
667 // the advanced settings dialog. Note: In order to disable sync across
668 // restarts on Chrome OS, we must call StopSyncingPermanently(), which
669 // suppresses sync startup in addition to disabling it.
670 if (configuration.sync_nothing) {
671 ProfileSyncService::SyncEvent(
672 ProfileSyncService::STOP_FROM_ADVANCED_DIALOG);
674 service->StopSyncingPermanently();
675 service->SetSetupInProgress(false);
679 // Note: Data encryption will not occur until configuration is complete
680 // (when the PSS receives its CONFIGURE_DONE notification from the sync
681 // backend), so the user still has a chance to cancel out of the operation
682 // if (for example) some kind of passphrase error is encountered.
683 if (configuration.encrypt_all)
684 service->EnableEncryptEverything();
686 bool passphrase_failed = false;
687 if (!configuration.passphrase.empty()) {
688 // We call IsPassphraseRequired() here (instead of
689 // IsPassphraseRequiredForDecryption()) because the user may try to enter
690 // a passphrase even though no encrypted data types are enabled.
691 if (service->IsPassphraseRequired()) {
692 // If we have pending keys, try to decrypt them with the provided
693 // passphrase. We track if this succeeds or fails because a failed
694 // decryption should result in an error even if there aren't any encrypted
697 !service->SetDecryptionPassphrase(configuration.passphrase);
699 // OK, the user sent us a passphrase, but we don't have pending keys. So
700 // it either means that the pending keys were resolved somehow since the
701 // time the UI was displayed (re-encryption, pending passphrase change,
702 // etc) or the user wants to re-encrypt.
703 if (!configuration.passphrase_is_gaia &&
704 !service->IsUsingSecondaryPassphrase()) {
705 // User passed us a secondary passphrase, and the data is encrypted
706 // with a GAIA passphrase so they must want to encrypt.
707 service->SetEncryptionPassphrase(configuration.passphrase,
708 ProfileSyncService::EXPLICIT);
713 bool user_was_prompted_for_passphrase =
714 service->IsPassphraseRequiredForDecryption();
715 service->OnUserChoseDatatypes(configuration.sync_everything,
716 configuration.data_types);
718 // Need to call IsPassphraseRequiredForDecryption() *after* calling
719 // OnUserChoseDatatypes() because the user may have just disabled the
720 // encrypted datatypes (in which case we just want to exit, not prompt the
721 // user for a passphrase).
722 if (passphrase_failed || service->IsPassphraseRequiredForDecryption()) {
723 // We need a passphrase, or the user's attempt to set a passphrase failed -
724 // prompt them again. This covers a few subtle cases:
725 // 1) The user enters an incorrect passphrase *and* disabled the encrypted
726 // data types. In that case we want to notify the user that the
727 // passphrase was incorrect even though there are no longer any encrypted
728 // types enabled (IsPassphraseRequiredForDecryption() == false).
729 // 2) The user doesn't enter any passphrase. In this case, we won't call
730 // SetDecryptionPassphrase() (passphrase_failed == false), but we still
731 // want to display an error message to let the user know that their
732 // blank passphrase entry is not acceptable.
733 // 3) The user just enabled an encrypted data type - in this case we don't
734 // want to display an "invalid passphrase" error, since it's the first
735 // time the user is seeing the prompt.
736 DisplayConfigureSync(
737 true, passphrase_failed || user_was_prompted_for_passphrase);
739 // No passphrase is required from the user so mark the configuration as
740 // complete and close the sync setup overlay.
744 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE);
745 if (configuration.encrypt_all)
746 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT);
747 if (configuration.passphrase_is_gaia && !configuration.passphrase.empty())
748 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE);
749 if (!configuration.sync_everything)
750 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE);
753 void SyncSetupHandler::HandleShowSetupUI(const base::ListValue* args) {
754 if (!GetSyncService()) {
755 DLOG(WARNING) << "Cannot display sync UI when sync is disabled";
760 SigninManagerBase* signin =
761 SigninManagerFactory::GetForProfile(GetProfile());
762 if (signin->GetAuthenticatedUsername().empty()) {
763 // For web-based signin, the signin page is not displayed in an overlay
764 // on the settings page. So if we get here, it must be due to the user
765 // cancelling signin (by reloading the sync settings page during initial
766 // signin) or by directly navigating to settings/syncSetup
767 // (http://crbug.com/229836). So just exit and go back to the settings page.
768 DLOG(WARNING) << "Cannot display sync setup UI when not signed in";
773 // If a setup wizard is already present, but not on this page, close the
774 // blank setup overlay on this page by showing the "done" page. This can
775 // happen if the user navigates to chrome://settings/syncSetup in more than
776 // one tab. See crbug.com/261566.
777 // Note: The following block will transfer focus to the existing wizard.
778 if (IsExistingWizardPresent() && !IsActiveLogin()) {
782 // If a setup wizard is present on this page or another, bring it to focus.
783 // Otherwise, display a new one on this page.
784 if (!FocusExistingWizardIfPresent())
788 #if defined(OS_CHROMEOS)
789 // On ChromeOS, we need to sign out the user session to fix an auth error, so
790 // the user goes through the real signin flow to generate a new auth token.
791 void SyncSetupHandler::HandleDoSignOutOnAuthError(const base::ListValue* args) {
792 DVLOG(1) << "Signing out the user to fix a sync error.";
793 chrome::AttemptUserExit();
797 #if !defined(OS_CHROMEOS)
798 void SyncSetupHandler::HandleStartSignin(const base::ListValue* args) {
799 // Should only be called if the user is not already signed in.
800 DCHECK(SigninManagerFactory::GetForProfile(GetProfile())->
801 GetAuthenticatedUsername().empty());
805 void SyncSetupHandler::HandleStopSyncing(const base::ListValue* args) {
806 if (GetSyncService())
807 ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS);
808 SigninManagerFactory::GetForProfile(GetProfile())->SignOut();
810 bool delete_profile = false;
811 if (args->GetBoolean(0, &delete_profile) && delete_profile) {
812 web_ui()->CallJavascriptFunction(
813 "BrowserOptions.deleteCurrentProfile");
818 void SyncSetupHandler::HandleCloseTimeout(const base::ListValue* args) {
822 void SyncSetupHandler::CloseSyncSetup() {
823 // Stop a timer to handle timeout in waiting for checking network connection.
824 backend_start_timer_.reset();
826 // Clear the sync startup tracker, since the setup wizard is being closed.
827 sync_startup_tracker_.reset();
829 ProfileSyncService* sync_service = GetSyncService();
830 if (IsActiveLogin()) {
831 // Don't log a cancel event if the sync setup dialog is being
832 // automatically closed due to an auth error.
833 if (!sync_service || (!sync_service->HasSyncSetupCompleted() &&
834 sync_service->GetAuthError().state() == GoogleServiceAuthError::NONE)) {
835 if (configuring_sync_) {
836 ProfileSyncService::SyncEvent(
837 ProfileSyncService::CANCEL_DURING_CONFIGURE);
839 // If the user clicked "Cancel" while setting up sync, disable sync
840 // because we don't want the sync backend to remain in the
841 // first-setup-incomplete state.
842 // Note: In order to disable sync across restarts on Chrome OS,
843 // we must call StopSyncingPermanently(), which suppresses sync startup
844 // in addition to disabling it.
846 DVLOG(1) << "Sync setup aborted by user action";
847 sync_service->StopSyncingPermanently();
848 #if !defined(OS_CHROMEOS)
849 // Sign out the user on desktop Chrome if they click cancel during
851 // TODO(rsimha): Revisit this for M30. See http://crbug.com/252049.
852 if (sync_service->FirstSetupInProgress())
853 SigninManagerFactory::GetForProfile(GetProfile())->SignOut();
859 GetLoginUIService()->LoginUIClosed(this);
862 // Alert the sync service anytime the sync setup dialog is closed. This can
863 // happen due to the user clicking the OK or Cancel button, or due to the
864 // dialog being closed by virtue of sync being disabled in the background.
866 sync_service->SetSetupInProgress(false);
868 configuring_sync_ = false;
871 void SyncSetupHandler::OpenSyncSetup() {
872 if (!PrepareSyncSetup())
875 // There are several different UI flows that can bring the user here:
877 // 2) Normal signin through settings page (GetAuthenticatedUsername() is
879 // 3) Previously working credentials have expired.
880 // 4) User is signed in, but has stopped sync via the google dashboard, and
881 // signout is prohibited by policy so we need to force a re-auth.
882 // 5) User clicks [Advanced Settings] button on options page while already
884 // 6) One-click signin (credentials are already available, so should display
885 // sync configure UI, not login UI).
886 // 7) User re-enables sync after disabling it via advanced settings.
887 #if !defined(OS_CHROMEOS)
888 SigninManagerBase* signin =
889 SigninManagerFactory::GetForProfile(GetProfile());
891 if (signin->GetAuthenticatedUsername().empty() ||
892 ProfileOAuth2TokenServiceFactory::GetForProfile(GetProfile())->
893 signin_error_controller()->HasError()) {
894 // User is not logged in (cases 1-2), or login has been specially requested
895 // because previously working credentials have expired (case 3). Close sync
896 // setup including any visible overlays, and display the gaia auth page.
897 // Control will be returned to the sync settings page once auth is complete.
903 if (!GetSyncService()) {
904 // This can happen if the user directly navigates to /settings/syncSetup.
905 DLOG(WARNING) << "Cannot display sync UI when sync is disabled";
910 // User is already logged in. They must have brought up the config wizard
911 // via the "Advanced..." button or through One-Click signin (cases 4-6), or
912 // they are re-enabling sync after having disabled it (case 7).
913 DisplayConfigureSync(true, false);
916 void SyncSetupHandler::OpenConfigureSync() {
917 if (!PrepareSyncSetup())
920 DisplayConfigureSync(true, false);
923 void SyncSetupHandler::FocusUI() {
924 DCHECK(IsActiveLogin());
925 WebContents* web_contents = web_ui()->GetWebContents();
926 web_contents->GetDelegate()->ActivateContents(web_contents);
929 void SyncSetupHandler::CloseUI() {
931 base::StringValue page("done");
932 web_ui()->CallJavascriptFunction(
933 "SyncSetupOverlay.showSyncSetupPage", page);
936 bool SyncSetupHandler::IsExistingWizardPresent() {
937 LoginUIService* service = GetLoginUIService();
939 return service->current_login_ui() != NULL;
942 bool SyncSetupHandler::FocusExistingWizardIfPresent() {
943 if (!IsExistingWizardPresent())
946 LoginUIService* service = GetLoginUIService();
948 service->current_login_ui()->FocusUI();
952 LoginUIService* SyncSetupHandler::GetLoginUIService() const {
953 return LoginUIServiceFactory::GetForProfile(GetProfile());