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/options/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_metrics.h"
25 #include "chrome/browser/signin/signin_error_controller_factory.h"
26 #include "chrome/browser/signin/signin_header_helper.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/browser_window.h"
34 #include "chrome/browser/ui/singleton_tabs.h"
35 #include "chrome/browser/ui/webui/options/options_handlers_helper.h"
36 #include "chrome/browser/ui/webui/signin/login_ui_service.h"
37 #include "chrome/browser/ui/webui/signin/login_ui_service_factory.h"
38 #include "chrome/common/chrome_switches.h"
39 #include "chrome/common/url_constants.h"
40 #include "chrome/grit/chromium_strings.h"
41 #include "chrome/grit/generated_resources.h"
42 #include "chrome/grit/locale_settings.h"
43 #include "components/google/core/browser/google_util.h"
44 #include "components/signin/core/browser/signin_error_controller.h"
45 #include "components/signin/core/browser/signin_metrics.h"
46 #include "components/signin/core/common/profile_management_switches.h"
47 #include "components/sync_driver/sync_prefs.h"
48 #include "content/public/browser/render_view_host.h"
49 #include "content/public/browser/web_contents.h"
50 #include "content/public/browser/web_contents_delegate.h"
51 #include "google_apis/gaia/gaia_auth_util.h"
52 #include "google_apis/gaia/gaia_constants.h"
53 #include "grit/components_strings.h"
54 #include "net/base/url_util.h"
55 #include "ui/base/l10n/l10n_util.h"
57 #if defined(OS_CHROMEOS)
58 #include "components/signin/core/browser/signin_manager_base.h"
60 #include "components/signin/core/browser/signin_manager.h"
63 using content::WebContents
;
64 using l10n_util::GetStringFUTF16
;
65 using l10n_util::GetStringUTF16
;
69 // A structure which contains all the configuration information for sync.
70 struct SyncConfigInfo
{
77 syncer::ModelTypeSet data_types
;
78 std::string passphrase
;
79 bool passphrase_is_gaia
;
82 SyncConfigInfo::SyncConfigInfo()
84 sync_everything(false),
86 passphrase_is_gaia(false) {
89 SyncConfigInfo::~SyncConfigInfo() {}
91 bool GetConfiguration(const std::string
& json
, SyncConfigInfo
* config
) {
92 scoped_ptr
<base::Value
> parsed_value
= base::JSONReader::Read(json
);
93 base::DictionaryValue
* result
;
94 if (!parsed_value
|| !parsed_value
->GetAsDictionary(&result
)) {
95 DLOG(ERROR
) << "GetConfiguration() not passed a Dictionary";
99 if (!result
->GetBoolean("syncAllDataTypes", &config
->sync_everything
)) {
100 DLOG(ERROR
) << "GetConfiguration() not passed a syncAllDataTypes value";
104 if (!result
->GetBoolean("syncNothing", &config
->sync_nothing
)) {
105 DLOG(ERROR
) << "GetConfiguration() not passed a syncNothing value";
109 DCHECK(!(config
->sync_everything
&& config
->sync_nothing
))
110 << "syncAllDataTypes and syncNothing cannot both be true";
112 syncer::ModelTypeNameMap type_names
= syncer::GetUserSelectableTypeNameMap();
114 for (syncer::ModelTypeNameMap::const_iterator it
= type_names
.begin();
115 it
!= type_names
.end(); ++it
) {
116 std::string key_name
= it
->second
+ std::string("Synced");
118 if (!result
->GetBoolean(key_name
, &sync_value
)) {
119 DLOG(ERROR
) << "GetConfiguration() not passed a value for " << key_name
;
123 config
->data_types
.Put(it
->first
);
126 // Encryption settings.
127 if (!result
->GetBoolean("encryptAllData", &config
->encrypt_all
)) {
128 DLOG(ERROR
) << "GetConfiguration() not passed a value for encryptAllData";
132 // Passphrase settings.
133 bool have_passphrase
;
134 if (!result
->GetBoolean("usePassphrase", &have_passphrase
)) {
135 DLOG(ERROR
) << "GetConfiguration() not passed a usePassphrase value";
139 if (have_passphrase
) {
140 if (!result
->GetBoolean("isGooglePassphrase",
141 &config
->passphrase_is_gaia
)) {
142 DLOG(ERROR
) << "GetConfiguration() not passed isGooglePassphrase value";
145 if (!result
->GetString("passphrase", &config
->passphrase
)) {
146 DLOG(ERROR
) << "GetConfiguration() not passed a passphrase value";
155 SyncSetupHandler::SyncSetupHandler()
156 : configuring_sync_(false) {
159 SyncSetupHandler::~SyncSetupHandler() {
160 // Just exit if running unit tests (no actual WebUI is attached).
164 // This case is hit when the user performs a back navigation.
168 void SyncSetupHandler::GetLocalizedValues(
169 base::DictionaryValue
* localized_strings
) {
170 GetStaticLocalizedValues(localized_strings
, web_ui());
173 void SyncSetupHandler::GetStaticLocalizedValues(
174 base::DictionaryValue
* localized_strings
,
175 content::WebUI
* web_ui
) {
176 DCHECK(localized_strings
);
178 base::string16
product_name(GetStringUTF16(IDS_PRODUCT_NAME
));
179 localized_strings
->SetString(
180 "chooseDataTypesInstructions",
181 GetStringFUTF16(IDS_SYNC_CHOOSE_DATATYPES_INSTRUCTIONS
, product_name
));
182 localized_strings
->SetString(
183 "encryptionInstructions",
184 GetStringFUTF16(IDS_SYNC_ENCRYPTION_INSTRUCTIONS
, product_name
));
185 localized_strings
->SetString(
186 "encryptionHelpURL", chrome::kSyncEncryptionHelpURL
);
187 localized_strings
->SetString(
188 "encryptionSectionMessage",
189 GetStringFUTF16(IDS_SYNC_ENCRYPTION_SECTION_MESSAGE
, product_name
));
190 localized_strings
->SetString(
193 IDS_SYNC_PASSPHRASE_RECOVER
,
195 google_util::AppendGoogleLocaleParam(
196 GURL(chrome::kSyncGoogleDashboardURL
),
197 g_browser_process
->GetApplicationLocale()).spec())));
198 localized_strings
->SetString(
199 "stopSyncingExplanation",
200 l10n_util::GetStringFUTF16(
201 IDS_SYNC_STOP_SYNCING_EXPLANATION_LABEL
,
202 l10n_util::GetStringUTF16(IDS_PRODUCT_NAME
),
204 google_util::AppendGoogleLocaleParam(
205 GURL(chrome::kSyncGoogleDashboardURL
),
206 g_browser_process
->GetApplicationLocale()).spec())));
207 localized_strings
->SetString("deleteProfileLabel",
208 l10n_util::GetStringUTF16(IDS_SYNC_STOP_DELETE_PROFILE_LABEL
));
209 localized_strings
->SetString("stopSyncingTitle",
210 l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_DIALOG_TITLE
));
211 localized_strings
->SetString("stopSyncingConfirm",
212 l10n_util::GetStringUTF16(IDS_SYNC_STOP_SYNCING_CONFIRM_BUTTON_LABEL
));
214 localized_strings
->SetString(
215 "syncEverythingHelpURL", chrome::kSyncEverythingLearnMoreURL
);
216 localized_strings
->SetString(
217 "syncErrorHelpURL", chrome::kSyncErrorsHelpURL
);
219 static OptionsStringResource resources
[] = {
220 { "syncSetupConfigureTitle", IDS_SYNC_SETUP_CONFIGURE_TITLE
},
221 { "syncSetupSpinnerTitle", IDS_SYNC_SETUP_SPINNER_TITLE
},
222 { "syncSetupTimeoutTitle", IDS_SYNC_SETUP_TIME_OUT_TITLE
},
223 { "syncSetupTimeoutContent", IDS_SYNC_SETUP_TIME_OUT_CONTENT
},
224 { "errorLearnMore", IDS_LEARN_MORE
},
225 { "cancel", IDS_CANCEL
},
226 { "loginSuccess", IDS_SYNC_SUCCESS
},
227 { "settingUp", IDS_SYNC_LOGIN_SETTING_UP
},
228 { "syncAllDataTypes", IDS_SYNC_EVERYTHING
},
229 { "chooseDataTypes", IDS_SYNC_CHOOSE_DATATYPES
},
230 { "syncNothing", IDS_SYNC_NOTHING
},
231 { "bookmarks", IDS_SYNC_DATATYPE_BOOKMARKS
},
232 { "preferences", IDS_SYNC_DATATYPE_PREFERENCES
},
233 { "autofill", IDS_SYNC_DATATYPE_AUTOFILL
},
234 { "themes", IDS_SYNC_DATATYPE_THEMES
},
235 { "passwords", IDS_SYNC_DATATYPE_PASSWORDS
},
236 { "extensions", IDS_SYNC_DATATYPE_EXTENSIONS
},
237 { "typedURLs", IDS_SYNC_DATATYPE_TYPED_URLS
},
238 { "apps", IDS_SYNC_DATATYPE_APPS
},
239 { "wifiCredentials", IDS_SYNC_DATATYPE_WIFI_CREDENTIALS
},
240 { "openTabs", IDS_SYNC_DATATYPE_TABS
},
241 { "serviceUnavailableError", IDS_SYNC_SETUP_ABORTED_BY_PENDING_CLEAR
},
242 { "confirmLabel", IDS_SYNC_CONFIRM_PASSPHRASE_LABEL
},
243 { "emptyErrorMessage", IDS_SYNC_EMPTY_PASSPHRASE_ERROR
},
244 { "mismatchErrorMessage", IDS_SYNC_PASSPHRASE_MISMATCH_ERROR
},
245 { "customizeLinkLabel", IDS_SYNC_CUSTOMIZE_LINK_LABEL
},
246 { "confirmSyncPreferences", IDS_SYNC_CONFIRM_SYNC_PREFERENCES
},
247 { "syncEverything", IDS_SYNC_SYNC_EVERYTHING
},
248 { "useDefaultSettings", IDS_SYNC_USE_DEFAULT_SETTINGS
},
249 { "enterPassphraseBody", IDS_SYNC_ENTER_PASSPHRASE_BODY
},
250 { "enterGooglePassphraseBody", IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY
},
251 { "passphraseLabel", IDS_SYNC_PASSPHRASE_LABEL
},
252 { "incorrectPassphrase", IDS_SYNC_INCORRECT_PASSPHRASE
},
253 { "passphraseWarning", IDS_SYNC_PASSPHRASE_WARNING
},
254 { "yes", IDS_SYNC_PASSPHRASE_CANCEL_YES
},
255 { "no", IDS_SYNC_PASSPHRASE_CANCEL_NO
},
256 { "sectionExplicitMessagePrefix", IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_PREFIX
},
257 { "sectionExplicitMessagePostfix",
258 IDS_SYNC_PASSPHRASE_MSG_EXPLICIT_POSTFIX
},
259 // TODO(rogerta): browser/resource/sync_promo/sync_promo.html and related
260 // file may not be needed any more. If not, then the following promo
261 // strings can also be removed.
262 { "promoPageTitle", IDS_SYNC_PROMO_TAB_TITLE
},
263 { "promoSkipButton", IDS_SYNC_PROMO_SKIP_BUTTON
},
264 { "promoAdvanced", IDS_SYNC_PROMO_ADVANCED
},
265 { "promoLearnMore", IDS_LEARN_MORE
},
266 { "promoTitleShort", IDS_SYNC_PROMO_MESSAGE_TITLE_SHORT
},
267 { "encryptionSectionTitle", IDS_SYNC_ENCRYPTION_SECTION_TITLE
},
268 { "basicEncryptionOption", IDS_SYNC_BASIC_ENCRYPTION_DATA
},
269 { "fullEncryptionOption", IDS_SYNC_FULL_ENCRYPTION_DATA
},
272 RegisterStrings(localized_strings
, resources
, arraysize(resources
));
273 RegisterTitle(localized_strings
, "syncSetupOverlay", IDS_SYNC_SETUP_TITLE
);
276 void SyncSetupHandler::ConfigureSyncDone() {
277 base::StringValue
page("done");
278 web_ui()->CallJavascriptFunction(
279 "SyncSetupOverlay.showSyncSetupPage", page
);
281 // Suppress the sign in promo once the user starts sync. This way the user
282 // doesn't see the sign in promo even if they sign out later on.
283 signin::SetUserSkippedPromo(GetProfile());
285 ProfileSyncService
* service
= GetSyncService();
287 if (!service
->HasSyncSetupCompleted()) {
288 // This is the first time configuring sync, so log it.
289 base::FilePath profile_file_path
= GetProfile()->GetPath();
290 ProfileMetrics::LogProfileSyncSignIn(profile_file_path
);
292 // We're done configuring, so notify ProfileSyncService that it is OK to
294 service
->SetSetupInProgress(false);
295 service
->SetSyncSetupCompleted();
299 bool SyncSetupHandler::IsActiveLogin() const {
300 // LoginUIService can be NULL if page is brought up in incognito mode
301 // (i.e. if the user is running in guest mode in cros and brings up settings).
302 LoginUIService
* service
= GetLoginUIService();
303 return service
&& (service
->current_login_ui() == this);
306 void SyncSetupHandler::RegisterMessages() {
307 web_ui()->RegisterMessageCallback(
308 "SyncSetupDidClosePage",
309 base::Bind(&SyncSetupHandler::OnDidClosePage
,
310 base::Unretained(this)));
311 web_ui()->RegisterMessageCallback(
312 "SyncSetupConfigure",
313 base::Bind(&SyncSetupHandler::HandleConfigure
,
314 base::Unretained(this)));
315 web_ui()->RegisterMessageCallback(
316 "SyncSetupShowSetupUI",
317 base::Bind(&SyncSetupHandler::HandleShowSetupUI
,
318 base::Unretained(this)));
319 web_ui()->RegisterMessageCallback("CloseTimeout",
320 base::Bind(&SyncSetupHandler::HandleCloseTimeout
,
321 base::Unretained(this)));
322 #if defined(OS_CHROMEOS)
323 web_ui()->RegisterMessageCallback(
324 "SyncSetupDoSignOutOnAuthError",
325 base::Bind(&SyncSetupHandler::HandleDoSignOutOnAuthError
,
326 base::Unretained(this)));
328 web_ui()->RegisterMessageCallback("SyncSetupStopSyncing",
329 base::Bind(&SyncSetupHandler::HandleStopSyncing
,
330 base::Unretained(this)));
331 web_ui()->RegisterMessageCallback("SyncSetupStartSignIn",
332 base::Bind(&SyncSetupHandler::HandleStartSignin
,
333 base::Unretained(this)));
337 #if !defined(OS_CHROMEOS)
338 void SyncSetupHandler::DisplayGaiaLogin() {
339 DCHECK(!sync_startup_tracker_
);
340 // Advanced options are no longer being configured if the login screen is
341 // visible. If the user exits the signin wizard after this without
342 // configuring sync, CloseSyncSetup() will ensure they are logged out.
343 configuring_sync_
= false;
344 DisplayGaiaLoginInNewTabOrWindow();
347 void SyncSetupHandler::DisplayGaiaLoginInNewTabOrWindow() {
348 Browser
* browser
= chrome::FindBrowserWithWebContents(
349 web_ui()->GetWebContents());
350 bool force_new_tab
= false;
352 // Settings is not displayed in a browser window. Open a new window.
353 browser
= new Browser(Browser::CreateParams(
354 Browser::TYPE_TABBED
, GetProfile(), chrome::GetActiveDesktop()));
355 force_new_tab
= true;
358 // If the signin manager already has an authenticated username, this is a
359 // re-auth scenario, and we need to ensure that the user signs in with the
360 // same email address.
362 if (SigninManagerFactory::GetForProfile(
363 browser
->profile())->IsAuthenticated()) {
364 UMA_HISTOGRAM_ENUMERATION("Signin.Reauth",
365 signin_metrics::HISTOGRAM_REAUTH_SHOWN
,
366 signin_metrics::HISTOGRAM_REAUTH_MAX
);
368 SigninErrorController
* error_controller
=
369 SigninErrorControllerFactory::GetForProfile(browser
->profile());
370 DCHECK(error_controller
->HasError());
371 if (switches::IsNewAvatarMenu() && !force_new_tab
) {
372 browser
->window()->ShowAvatarBubbleFromAvatarButton(
373 BrowserWindow::AVATAR_BUBBLE_MODE_REAUTH
,
374 signin::ManageAccountsParams());
376 url
= signin::GetReauthURL(browser
->profile(),
377 error_controller
->error_account_id());
380 signin_metrics::LogSigninSource(signin_metrics::SOURCE_SETTINGS
);
381 if (switches::IsNewAvatarMenu() && !force_new_tab
) {
382 browser
->window()->ShowAvatarBubbleFromAvatarButton(
383 BrowserWindow::AVATAR_BUBBLE_MODE_SIGNIN
,
384 signin::ManageAccountsParams());
386 url
= signin::GetPromoURL(signin_metrics::SOURCE_SETTINGS
, true);
391 chrome::ShowSingletonTab(browser
, url
);
395 bool SyncSetupHandler::PrepareSyncSetup() {
396 // If the wizard is already visible, just focus that one.
397 if (FocusExistingWizardIfPresent()) {
398 if (!IsActiveLogin())
403 // Notify services that login UI is now active.
404 GetLoginUIService()->SetLoginUI(this);
406 ProfileSyncService
* service
= GetSyncService();
408 service
->SetSetupInProgress(true);
413 void SyncSetupHandler::DisplaySpinner() {
414 configuring_sync_
= true;
415 base::StringValue
page("spinner");
416 base::DictionaryValue args
;
418 const int kTimeoutSec
= 30;
419 DCHECK(!backend_start_timer_
);
420 backend_start_timer_
.reset(new base::OneShotTimer
<SyncSetupHandler
>());
421 backend_start_timer_
->Start(FROM_HERE
,
422 base::TimeDelta::FromSeconds(kTimeoutSec
),
423 this, &SyncSetupHandler::DisplayTimeout
);
425 web_ui()->CallJavascriptFunction(
426 "SyncSetupOverlay.showSyncSetupPage", page
, args
);
429 // TODO(kochi): Handle error conditions other than timeout.
430 // http://crbug.com/128692
431 void SyncSetupHandler::DisplayTimeout() {
432 // Stop a timer to handle timeout in waiting for checking network connection.
433 backend_start_timer_
.reset();
435 // Do not listen to sync startup events.
436 sync_startup_tracker_
.reset();
438 base::StringValue
page("timeout");
439 base::DictionaryValue args
;
440 web_ui()->CallJavascriptFunction(
441 "SyncSetupOverlay.showSyncSetupPage", page
, args
);
444 void SyncSetupHandler::OnDidClosePage(const base::ListValue
* args
) {
448 void SyncSetupHandler::SyncStartupFailed() {
449 // Stop a timer to handle timeout in waiting for checking network connection.
450 backend_start_timer_
.reset();
452 // Just close the sync overlay (the idea is that the base settings page will
453 // display the current error.)
457 void SyncSetupHandler::SyncStartupCompleted() {
458 ProfileSyncService
* service
= GetSyncService();
459 DCHECK(service
->backend_initialized());
461 // Stop a timer to handle timeout in waiting for checking network connection.
462 backend_start_timer_
.reset();
464 DisplayConfigureSync(false);
467 Profile
* SyncSetupHandler::GetProfile() const {
468 return Profile::FromWebUI(web_ui());
471 ProfileSyncService
* SyncSetupHandler::GetSyncService() const {
472 Profile
* profile
= GetProfile();
473 return profile
->IsSyncAccessible() ?
474 ProfileSyncServiceFactory::GetForProfile(GetProfile()) : NULL
;
477 void SyncSetupHandler::HandleConfigure(const base::ListValue
* args
) {
478 DCHECK(!sync_startup_tracker_
);
480 if (!args
->GetString(0, &json
)) {
481 NOTREACHED() << "Could not read JSON argument";
489 SyncConfigInfo configuration
;
490 if (!GetConfiguration(json
, &configuration
)) {
491 // The page sent us something that we didn't understand.
492 // This probably indicates a programming error.
497 // Start configuring the ProfileSyncService using the configuration passed
498 // to us from the JS layer.
499 ProfileSyncService
* service
= GetSyncService();
501 // If the sync engine has shutdown for some reason, just close the sync
503 if (!service
|| !service
->backend_initialized()) {
508 // Disable sync, but remain signed in if the user selected "Sync nothing" in
509 // the advanced settings dialog. Note: In order to disable sync across
510 // restarts on Chrome OS, we must call StopSyncingPermanently(), which
511 // suppresses sync startup in addition to disabling it.
512 if (configuration
.sync_nothing
) {
513 ProfileSyncService::SyncEvent(
514 ProfileSyncService::STOP_FROM_ADVANCED_DIALOG
);
516 service
->StopSyncingPermanently();
517 service
->SetSetupInProgress(false);
521 // Don't allow "encrypt all" if the ProfileSyncService doesn't allow it.
522 // The UI is hidden, but the user may have enabled it e.g. by fiddling with
523 // the web inspector.
524 if (!service
->EncryptEverythingAllowed())
525 configuration
.encrypt_all
= false;
527 // Note: Data encryption will not occur until configuration is complete
528 // (when the PSS receives its CONFIGURE_DONE notification from the sync
529 // backend), so the user still has a chance to cancel out of the operation
530 // if (for example) some kind of passphrase error is encountered.
531 if (configuration
.encrypt_all
)
532 service
->EnableEncryptEverything();
534 bool passphrase_failed
= false;
535 if (!configuration
.passphrase
.empty()) {
536 // We call IsPassphraseRequired() here (instead of
537 // IsPassphraseRequiredForDecryption()) because the user may try to enter
538 // a passphrase even though no encrypted data types are enabled.
539 if (service
->IsPassphraseRequired()) {
540 // If we have pending keys, try to decrypt them with the provided
541 // passphrase. We track if this succeeds or fails because a failed
542 // decryption should result in an error even if there aren't any encrypted
545 !service
->SetDecryptionPassphrase(configuration
.passphrase
);
547 // OK, the user sent us a passphrase, but we don't have pending keys. So
548 // it either means that the pending keys were resolved somehow since the
549 // time the UI was displayed (re-encryption, pending passphrase change,
550 // etc) or the user wants to re-encrypt.
551 if (!configuration
.passphrase_is_gaia
&&
552 !service
->IsUsingSecondaryPassphrase()) {
553 // User passed us a secondary passphrase, and the data is encrypted
554 // with a GAIA passphrase so they must want to encrypt.
555 service
->SetEncryptionPassphrase(configuration
.passphrase
,
556 ProfileSyncService::EXPLICIT
);
561 bool user_was_prompted_for_passphrase
=
562 service
->IsPassphraseRequiredForDecryption();
563 service
->OnUserChoseDatatypes(configuration
.sync_everything
,
564 configuration
.data_types
);
566 // Need to call IsPassphraseRequiredForDecryption() *after* calling
567 // OnUserChoseDatatypes() because the user may have just disabled the
568 // encrypted datatypes (in which case we just want to exit, not prompt the
569 // user for a passphrase).
570 if (passphrase_failed
|| service
->IsPassphraseRequiredForDecryption()) {
571 // We need a passphrase, or the user's attempt to set a passphrase failed -
572 // prompt them again. This covers a few subtle cases:
573 // 1) The user enters an incorrect passphrase *and* disabled the encrypted
574 // data types. In that case we want to notify the user that the
575 // passphrase was incorrect even though there are no longer any encrypted
576 // types enabled (IsPassphraseRequiredForDecryption() == false).
577 // 2) The user doesn't enter any passphrase. In this case, we won't call
578 // SetDecryptionPassphrase() (passphrase_failed == false), but we still
579 // want to display an error message to let the user know that their
580 // blank passphrase entry is not acceptable.
581 // 3) The user just enabled an encrypted data type - in this case we don't
582 // want to display an "invalid passphrase" error, since it's the first
583 // time the user is seeing the prompt.
584 DisplayConfigureSync(passphrase_failed
|| user_was_prompted_for_passphrase
);
586 // No passphrase is required from the user so mark the configuration as
587 // complete and close the sync setup overlay.
591 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CUSTOMIZE
);
592 if (configuration
.encrypt_all
)
593 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_ENCRYPT
);
594 if (configuration
.passphrase_is_gaia
&& !configuration
.passphrase
.empty())
595 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_PASSPHRASE
);
596 if (!configuration
.sync_everything
)
597 ProfileMetrics::LogProfileSyncInfo(ProfileMetrics::SYNC_CHOOSE
);
600 void SyncSetupHandler::HandleShowSetupUI(const base::ListValue
* args
) {
601 if (!GetSyncService()) {
602 DLOG(WARNING
) << "Cannot display sync UI when sync is disabled";
607 SigninManagerBase
* signin
=
608 SigninManagerFactory::GetForProfile(GetProfile());
609 if (!signin
->IsAuthenticated()) {
610 // For web-based signin, the signin page is not displayed in an overlay
611 // on the settings page. So if we get here, it must be due to the user
612 // cancelling signin (by reloading the sync settings page during initial
613 // signin) or by directly navigating to settings/syncSetup
614 // (http://crbug.com/229836). So just exit and go back to the settings page.
615 DLOG(WARNING
) << "Cannot display sync setup UI when not signed in";
620 // If a setup wizard is already present, but not on this page, close the
621 // blank setup overlay on this page by showing the "done" page. This can
622 // happen if the user navigates to chrome://settings/syncSetup in more than
623 // one tab. See crbug.com/261566.
624 // Note: The following block will transfer focus to the existing wizard.
625 if (IsExistingWizardPresent() && !IsActiveLogin())
628 // If a setup wizard is present on this page or another, bring it to focus.
629 // Otherwise, display a new one on this page.
630 if (!FocusExistingWizardIfPresent())
634 #if defined(OS_CHROMEOS)
635 // On ChromeOS, we need to sign out the user session to fix an auth error, so
636 // the user goes through the real signin flow to generate a new auth token.
637 void SyncSetupHandler::HandleDoSignOutOnAuthError(const base::ListValue
* args
) {
638 DVLOG(1) << "Signing out the user to fix a sync error.";
639 chrome::AttemptUserExit();
643 #if !defined(OS_CHROMEOS)
644 void SyncSetupHandler::HandleStartSignin(const base::ListValue
* args
) {
645 // Should only be called if the user is not already signed in.
646 DCHECK(!SigninManagerFactory::GetForProfile(GetProfile())->
651 void SyncSetupHandler::HandleStopSyncing(const base::ListValue
* args
) {
652 if (GetSyncService())
653 ProfileSyncService::SyncEvent(ProfileSyncService::STOP_FROM_OPTIONS
);
654 SigninManagerFactory::GetForProfile(GetProfile())->SignOut(
655 signin_metrics::USER_CLICKED_SIGNOUT_SETTINGS
);
657 bool delete_profile
= false;
658 if (args
->GetBoolean(0, &delete_profile
) && delete_profile
) {
659 // Do as BrowserOptionsHandler::DeleteProfile().
660 options::helper::DeleteProfileAtPath(GetProfile()->GetPath(), web_ui());
665 void SyncSetupHandler::HandleCloseTimeout(const base::ListValue
* args
) {
669 void SyncSetupHandler::CloseSyncSetup() {
670 // Stop a timer to handle timeout in waiting for checking network connection.
671 backend_start_timer_
.reset();
673 // Clear the sync startup tracker, since the setup wizard is being closed.
674 sync_startup_tracker_
.reset();
676 ProfileSyncService
* sync_service
= GetSyncService();
677 if (IsActiveLogin()) {
678 // Don't log a cancel event if the sync setup dialog is being
679 // automatically closed due to an auth error.
680 if (!sync_service
|| (!sync_service
->HasSyncSetupCompleted() &&
681 sync_service
->GetAuthError().state() == GoogleServiceAuthError::NONE
)) {
682 if (configuring_sync_
) {
683 ProfileSyncService::SyncEvent(
684 ProfileSyncService::CANCEL_DURING_CONFIGURE
);
686 // If the user clicked "Cancel" while setting up sync, disable sync
687 // because we don't want the sync backend to remain in the
688 // first-setup-incomplete state.
689 // Note: In order to disable sync across restarts on Chrome OS,
690 // we must call StopSyncingPermanently(), which suppresses sync startup
691 // in addition to disabling it.
693 DVLOG(1) << "Sync setup aborted by user action";
694 sync_service
->StopSyncingPermanently();
695 #if !defined(OS_CHROMEOS)
696 // Sign out the user on desktop Chrome if they click cancel during
698 // TODO(rsimha): Revisit this for M30. See http://crbug.com/252049.
699 if (sync_service
->FirstSetupInProgress()) {
700 SigninManagerFactory::GetForProfile(GetProfile())->SignOut(
701 signin_metrics::ABORT_SIGNIN
);
708 GetLoginUIService()->LoginUIClosed(this);
711 // Alert the sync service anytime the sync setup dialog is closed. This can
712 // happen due to the user clicking the OK or Cancel button, or due to the
713 // dialog being closed by virtue of sync being disabled in the background.
715 sync_service
->SetSetupInProgress(false);
717 configuring_sync_
= false;
720 void SyncSetupHandler::OpenSyncSetup() {
721 if (!PrepareSyncSetup())
724 // There are several different UI flows that can bring the user here:
726 // 2) Normal signin through settings page (IsAuthenticated() is false).
727 // 3) Previously working credentials have expired.
728 // 4) User is signed in, but has stopped sync via the google dashboard, and
729 // signout is prohibited by policy so we need to force a re-auth.
730 // 5) User clicks [Advanced Settings] button on options page while already
732 // 6) One-click signin (credentials are already available, so should display
733 // sync configure UI, not login UI).
734 // 7) User re-enables sync after disabling it via advanced settings.
735 #if !defined(OS_CHROMEOS)
736 SigninManagerBase
* signin
=
737 SigninManagerFactory::GetForProfile(GetProfile());
739 if (!signin
->IsAuthenticated() ||
740 SigninErrorControllerFactory::GetForProfile(GetProfile())->HasError()) {
741 // User is not logged in (cases 1-2), or login has been specially requested
742 // because previously working credentials have expired (case 3). Close sync
743 // setup including any visible overlays, and display the gaia auth page.
744 // Control will be returned to the sync settings page once auth is complete.
750 if (!GetSyncService()) {
751 // This can happen if the user directly navigates to /settings/syncSetup.
752 DLOG(WARNING
) << "Cannot display sync UI when sync is disabled";
757 // User is already logged in. They must have brought up the config wizard
758 // via the "Advanced..." button or through One-Click signin (cases 4-6), or
759 // they are re-enabling sync after having disabled it (case 7).
760 DisplayConfigureSync(false);
763 void SyncSetupHandler::OpenConfigureSync() {
764 if (!PrepareSyncSetup())
767 DisplayConfigureSync(false);
770 void SyncSetupHandler::FocusUI() {
771 DCHECK(IsActiveLogin());
772 WebContents
* web_contents
= web_ui()->GetWebContents();
773 web_contents
->GetDelegate()->ActivateContents(web_contents
);
776 void SyncSetupHandler::CloseUI() {
778 base::StringValue
page("done");
779 web_ui()->CallJavascriptFunction(
780 "SyncSetupOverlay.showSyncSetupPage", page
);
783 bool SyncSetupHandler::IsExistingWizardPresent() {
784 LoginUIService
* service
= GetLoginUIService();
786 return service
->current_login_ui() != NULL
;
789 bool SyncSetupHandler::FocusExistingWizardIfPresent() {
790 if (!IsExistingWizardPresent())
793 LoginUIService
* service
= GetLoginUIService();
795 service
->current_login_ui()->FocusUI();
799 void SyncSetupHandler::DisplayConfigureSync(bool passphrase_failed
) {
800 // Should never call this when we are not signed in.
801 DCHECK(SigninManagerFactory::GetForProfile(
802 GetProfile())->IsAuthenticated());
803 ProfileSyncService
* service
= GetSyncService();
805 if (!service
->backend_initialized()) {
806 service
->UnsuppressAndStart();
808 // See if it's even possible to bring up the sync backend - if not
809 // (unrecoverable error?), don't bother displaying a spinner that will be
810 // immediately closed because this leads to some ugly infinite UI loop (see
811 // http://crbug.com/244769).
812 if (SyncStartupTracker::GetSyncServiceState(GetProfile()) !=
813 SyncStartupTracker::SYNC_STARTUP_ERROR
) {
817 // Start SyncSetupTracker to wait for sync to initialize.
818 sync_startup_tracker_
.reset(
819 new SyncStartupTracker(GetProfile(), this));
823 // Should only get here if user is signed in and sync is initialized, so no
824 // longer need a SyncStartupTracker.
825 sync_startup_tracker_
.reset();
826 configuring_sync_
= true;
827 DCHECK(service
->backend_initialized()) <<
828 "Cannot configure sync until the sync backend is initialized";
830 // Setup args for the sync configure screen:
831 // syncAllDataTypes: true if the user wants to sync everything
832 // syncNothing: true if the user wants to sync nothing
833 // <data_type>Registered: true if the associated data type is supported
834 // <data_type>Synced: true if the user wants to sync that specific data type
835 // encryptionEnabled: true if sync supports encryption
836 // encryptAllData: true if user wants to encrypt all data (not just
838 // usePassphrase: true if the data is encrypted with a secondary passphrase
839 // show_passphrase: true if a passphrase is needed to decrypt the sync data
840 base::DictionaryValue args
;
842 // Tell the UI layer which data types are registered/enabled by the user.
843 const syncer::ModelTypeSet registered_types
=
844 service
->GetRegisteredDataTypes();
845 const syncer::ModelTypeSet preferred_types
= service
->GetPreferredDataTypes();
846 const syncer::ModelTypeSet enforced_types
= service
->GetForcedDataTypes();
847 syncer::ModelTypeNameMap type_names
= syncer::GetUserSelectableTypeNameMap();
848 for (syncer::ModelTypeNameMap::const_iterator it
= type_names
.begin();
849 it
!= type_names
.end(); ++it
) {
850 syncer::ModelType sync_type
= it
->first
;
851 const std::string key_name
= it
->second
;
852 args
.SetBoolean(key_name
+ "Registered", registered_types
.Has(sync_type
));
853 args
.SetBoolean(key_name
+ "Synced", preferred_types
.Has(sync_type
));
854 args
.SetBoolean(key_name
+ "Enforced", enforced_types
.Has(sync_type
));
855 // TODO(treib): How do we want to handle pref groups, i.e. when only some of
856 // the sync types behind a checkbox are force-enabled? crbug.com/403326
858 sync_driver::SyncPrefs
sync_prefs(GetProfile()->GetPrefs());
859 args
.SetBoolean("passphraseFailed", passphrase_failed
);
860 args
.SetBoolean("syncAllDataTypes", sync_prefs
.HasKeepEverythingSynced());
861 args
.SetBoolean("syncNothing", false); // Always false during initial setup.
862 args
.SetBoolean("encryptAllData", service
->EncryptEverythingEnabled());
863 args
.SetBoolean("encryptAllDataAllowed", service
->EncryptEverythingAllowed());
865 // We call IsPassphraseRequired() here, instead of calling
866 // IsPassphraseRequiredForDecryption(), because we want to show the passphrase
867 // UI even if no encrypted data types are enabled.
868 args
.SetBoolean("showPassphrase", service
->IsPassphraseRequired());
870 // To distinguish between FROZEN_IMPLICIT_PASSPHRASE and CUSTOM_PASSPHRASE
871 // we only set usePassphrase for CUSTOM_PASSPHRASE.
872 args
.SetBoolean("usePassphrase",
873 service
->GetPassphraseType() == syncer::CUSTOM_PASSPHRASE
);
874 base::Time passphrase_time
= service
->GetExplicitPassphraseTime();
875 syncer::PassphraseType passphrase_type
= service
->GetPassphraseType();
876 if (!passphrase_time
.is_null()) {
877 base::string16 passphrase_time_str
=
878 base::TimeFormatShortDate(passphrase_time
);
880 "enterPassphraseBody",
881 GetStringFUTF16(IDS_SYNC_ENTER_PASSPHRASE_BODY_WITH_DATE
,
882 passphrase_time_str
));
884 "enterGooglePassphraseBody",
885 GetStringFUTF16(IDS_SYNC_ENTER_GOOGLE_PASSPHRASE_BODY_WITH_DATE
,
886 passphrase_time_str
));
887 switch (passphrase_type
) {
888 case syncer::FROZEN_IMPLICIT_PASSPHRASE
:
890 "fullEncryptionBody",
891 GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_GOOGLE_WITH_DATE
,
892 passphrase_time_str
));
894 case syncer::CUSTOM_PASSPHRASE
:
896 "fullEncryptionBody",
897 GetStringFUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM_WITH_DATE
,
898 passphrase_time_str
));
902 "fullEncryptionBody",
903 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM
));
906 } else if (passphrase_type
== syncer::CUSTOM_PASSPHRASE
) {
908 "fullEncryptionBody",
909 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_BODY_CUSTOM
));
912 "fullEncryptionBody",
913 GetStringUTF16(IDS_SYNC_FULL_ENCRYPTION_DATA
));
916 base::StringValue
page("configure");
917 web_ui()->CallJavascriptFunction(
918 "SyncSetupOverlay.showSyncSetupPage", page
, args
);
920 // Make sure the tab used for the Gaia sign in does not cover the settings
925 LoginUIService
* SyncSetupHandler::GetLoginUIService() const {
926 return LoginUIServiceFactory::GetForProfile(GetProfile());