1 // Copyright (c) 2011 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 cr.define('options', function() { 6 const OptionsPage = options.OptionsPage; 7 8 // Variable to track if a captcha challenge was issued. If this gets set to 9 // true, it stays that way until we are told about successful login from 10 // the browser. This means subsequent errors (like invalid password) are 11 // rendered in the captcha state, which is basically identical except we 12 // don't show the top error blurb "Error Signing in" or the "Create 13 // account" link. 14 var captchaChallengeActive_ = false; 15 16 // True if the synced account uses a custom passphrase. 17 var usePassphrase_ = false; 18 19 /** 20 * SyncSetupOverlay class 21 * Encapsulated handling of the 'Sync Setup' overlay page. 22 * @class 23 */ 24 function SyncSetupOverlay() { 25 OptionsPage.call(this, 'syncSetup', 26 templateData.syncSetupOverlayTitle, 27 'sync-setup-overlay'); 28 } 29 30 cr.addSingletonGetter(SyncSetupOverlay); 31 32 SyncSetupOverlay.prototype = { 33 __proto__: OptionsPage.prototype, 34 35 /** 36 * Initializes the page. 37 */ 38 initializePage: function() { 39 OptionsPage.prototype.initializePage.call(this); 40 41 var acct_text = $('gaia-account-text'); 42 var translated_text = acct_text.textContent; 43 var posGoogle = translated_text.indexOf('Google'); 44 if (posGoogle != -1) { 45 var ltr = templateData['textdirection'] == 'ltr'; 46 var googleIsAtEndOfSentence = posGoogle != 0; 47 if (googleIsAtEndOfSentence == ltr) { 48 // We're in ltr and in the translation the word 'Google' is AFTER the 49 // word 'Account' OR we're in rtl and 'Google' is BEFORE 'Account'. 50 var logo_td = $('gaia-logo'); 51 logo_td.parentNode.appendChild(logo_td); 52 } 53 acct_text.textContent = translated_text.replace('Google',''); 54 } 55 56 var self = this; 57 $('gaia-login-form').onsubmit = function() { 58 self.sendCredentialsAndClose_(); 59 return false; 60 }; 61 $('chooseDataTypesForm').onsubmit = function() { 62 self.sendConfiguration_(); 63 return false; 64 }; 65 $('google-option').onchange = $('explicit-option').onchange = function() { 66 self.onRadioChange_(); 67 }; 68 $('choose-datatypes-cancel').onclick = $('sync-setup-cancel').onclick = 69 $('confirm-everything-cancel').onclick = function() { 70 self.closeOverlay_(); 71 }; 72 $('customize-link').onclick = function() { 73 self.showCustomizePage_(false); 74 }; 75 $('confirm-everything-ok').onclick = function() { 76 self.sendConfiguration_(); 77 }; 78 $('use-default-link').onclick = function() { 79 self.showSyncEverythingPage_(); 80 }; 81 $('cancel-no-button').onclick = function() { 82 self.hideCancelWarning_(); 83 return false; 84 }; 85 $('cancel-yes-button').onclick = function() { 86 chrome.send('PassphraseCancel', ['']); 87 return false; 88 }; 89 $('passphraseForm').onsubmit = $('passphrase-ok').onclick = function() { 90 self.sendPassphraseAndClose_(); 91 return false; 92 }; 93 $('passphrase-cancel').onclick = function() { 94 self.showCancelWarning_(); 95 return false; 96 }; 97 }, 98 99 closeOverlay_: function() { 100 OptionsPage.closeOverlay(); 101 }, 102 103 /** @inheritDoc */ 104 didShowPage: function() { 105 chrome.send('didShowPage'); 106 }, 107 108 /** @inheritDoc */ 109 didClosePage: function() { 110 chrome.send('didClosePage'); 111 }, 112 113 showCancelWarning_: function() { 114 $('cancel-warning-box').hidden = false; 115 $('passphrase-ok').disabled = true; 116 $('passphrase-cancel').disabled = true; 117 $('cancel-no-button').focus(); 118 }, 119 120 sendPassphraseAndClose_: function() { 121 var f = $('passphraseForm'); 122 var result = JSON.stringify({"passphrase": f.passphrase.value}); 123 chrome.send("Passphrase", [result]); 124 }, 125 126 getRadioCheckedValue_: function() { 127 var f = $('chooseDataTypesForm'); 128 for (var i = 0; i < f.option.length; ++i) { 129 if (f.option[i].checked) { 130 return f.option[i].value; 131 } 132 } 133 134 return undefined; 135 }, 136 137 // TODO(jhawkins): Remove this method. 138 switchToMode_: function(mode) { 139 if (mode == "google") 140 $('sync-custom-passphrase').hidden = true; 141 else if (mode =="explicit") 142 $('sync-custom-passphrase').hidden = false; 143 }, 144 145 onRadioChange_: function() { 146 this.switchToMode_(this.getRadioCheckedValue_()); 147 }, 148 149 checkAllDataTypeCheckboxes_: function() { 150 var checkboxes = document.getElementsByName("dataTypeCheckbox"); 151 for (var i = 0; i < checkboxes.length; i++) { 152 // Only check the visible ones (since there's no way to uncheck 153 // the invisible ones). 154 if (checkboxes[i].parentElement.className == "sync-item-show") { 155 checkboxes[i].checked = true; 156 } 157 } 158 }, 159 160 setDataTypeCheckboxesEnabled_: function(enabled) { 161 var checkboxes = document.getElementsByName("dataTypeCheckbox"); 162 var labels = document.getElementsByName("dataTypeLabel"); 163 for (var i = 0; i < checkboxes.length; i++) { 164 checkboxes[i].disabled = !enabled; 165 if (checkboxes[i].disabled) { 166 labels[i].className = "sync-label-inactive"; 167 } else { 168 labels[i].className = "sync-label-active"; 169 } 170 } 171 }, 172 173 setCheckboxesToKeepEverythingSynced_: function(value) { 174 this.setDataTypeCheckboxesEnabled_(!value); 175 if (value) 176 this.checkAllDataTypeCheckboxes_(); 177 }, 178 179 // Returns true if at least one data type is enabled and no data types are 180 // checked. (If all data type checkboxes are disabled, it's because "keep 181 // everything synced" is checked.) 182 noDataTypesChecked_: function() { 183 var checkboxes = document.getElementsByName("dataTypeCheckbox"); 184 var atLeastOneChecked = false; 185 var atLeastOneEnabled = false; 186 for (var i = 0; i < checkboxes.length; i++) { 187 if (!checkboxes[i].disabled && 188 checkboxes[i].parentElement.className == "sync-item-show") { 189 atLeastOneEnabled = true; 190 if (checkboxes[i].checked) { 191 atLeastOneChecked = true; 192 } 193 } 194 } 195 196 return atLeastOneEnabled && !atLeastOneChecked; 197 }, 198 199 checkPassphraseMatch_: function() { 200 var emptyError = $('emptyerror'); 201 var mismatchError = $('mismatcherror'); 202 emptyError.style.display = "none"; 203 mismatchError.style.display = "none"; 204 205 var f = $('chooseDataTypesForm'); 206 if (this.getRadioCheckedValue_() != "explicit" || f.option[0].disabled) 207 return true; 208 209 var customPassphrase = $('custom-passphrase'); 210 if (customPassphrase.value.length == 0) { 211 emptyError.style.display = "block"; 212 return false; 213 } 214 215 var confirmPassphrase = $('confirm-passphrase'); 216 if (confirmPassphrase.value != customPassphrase.value) { 217 mismatchError.style.display = "block"; 218 return false; 219 } 220 221 return true; 222 }, 223 224 hideCancelWarning_: function() { 225 $('cancel-warning-box').hidden = true; 226 $('passphrase-ok').disabled = false; 227 $('passphrase-cancel').disabled = false; 228 }, 229 230 sendConfiguration_: function() { 231 // Trying to submit, so hide previous errors. 232 $('aborted-text').className = "sync-error-hide"; 233 $('error-text').className = "sync-error-hide"; 234 235 if (this.noDataTypesChecked_()) { 236 $('error-text').className = "sync-error-show"; 237 return; 238 } 239 240 var f = $('chooseDataTypesForm'); 241 if (!this.checkPassphraseMatch_()) 242 return; 243 244 // Don't allow the user to tweak the settings once we send the 245 // configuration to the backend. 246 this.disableConfigureElements_(); 247 248 var syncAll = 249 document.getElementById('sync-select-datatypes').selectedIndex == 0; 250 var customPassphrase = $('custom-passphrase'); 251 252 // These values need to be kept in sync with where they are read in 253 // SyncSetupFlow::GetDataTypeChoiceData(). 254 var result = JSON.stringify({ 255 "keepEverythingSynced": syncAll, 256 "syncBookmarks": syncAll || f.bookmarksCheckbox.checked, 257 "syncPreferences": syncAll || f.preferencesCheckbox.checked, 258 "syncThemes": syncAll || f.themesCheckbox.checked, 259 "syncPasswords": syncAll || f.passwordsCheckbox.checked, 260 "syncAutofill": syncAll || f.autofillCheckbox.checked, 261 "syncExtensions": syncAll || f.extensionsCheckbox.checked, 262 "syncTypedUrls": syncAll || f.typedUrlsCheckbox.checked, 263 "syncApps": syncAll || f.appsCheckbox.checked, 264 "syncSessions": syncAll || f.sessionsCheckbox.checked, 265 "usePassphrase": (this.getRadioCheckedValue_() == 'explicit'), 266 "passphrase": customPassphrase.value 267 }); 268 chrome.send("Configure", [result]); 269 }, 270 271 /** 272 * Disables all input elements within the 'Customize Sync Preferences' 273 * screen. This is used to prohibit the user from changing the inputs after 274 * confirming the customized sync preferences. 275 * @private 276 */ 277 disableConfigureElements_: function() { 278 var configureElements = 279 $('customize-sync-preferences').querySelectorAll('input'); 280 for (var i = 0; i < configureElements.length; i++) 281 configureElements[i].disabled = true; 282 }, 283 284 setChooseDataTypesCheckboxes_: function(args) { 285 // If this frame is on top, the focus should be on it, so pressing enter 286 // submits this form. 287 if (args.iframeToShow == 'configure') { 288 $('choose-datatypes-ok').focus(); 289 } 290 291 var datatypeSelect = document.getElementById('sync-select-datatypes'); 292 datatypeSelect.selectedIndex = args.keepEverythingSynced ? 0 : 1; 293 294 $('bookmarksCheckbox').checked = args.syncBookmarks; 295 $('preferencesCheckbox').checked = args.syncPreferences; 296 $('themesCheckbox').checked = args.syncThemes; 297 298 if (args.passwordsRegistered) { 299 $('passwordsCheckbox').checked = args.syncPasswords; 300 $('passwordsItem').className = "sync-item-show"; 301 } else { 302 $('passwordsItem').className = "sync-item-hide"; 303 } 304 if (args.autofillRegistered) { 305 $('autofillCheckbox').checked = args.syncAutofill; 306 $('autofillItem').className = "sync-item-show"; 307 } else { 308 $('autofillItem').className = "sync-item-hide"; 309 } 310 if (args.extensionsRegistered) { 311 $('extensionsCheckbox').checked = args.syncExtensions; 312 $('extensionsItem').className = "sync-item-show"; 313 } else { 314 $('extensionsItem').className = "sync-item-hide"; 315 } 316 if (args.typedUrlsRegistered) { 317 $('typedUrlsCheckbox').checked = args.syncTypedUrls; 318 $('omniboxItem').className = "sync-item-show"; 319 } else { 320 $('omniboxItem').className = "sync-item-hide"; 321 } 322 if (args.appsRegistered) { 323 $('appsCheckbox').checked = args.syncApps; 324 $('appsItem').className = "sync-item-show"; 325 } else { 326 $('appsItem').className = "sync-item-hide"; 327 } 328 329 this.setCheckboxesToKeepEverythingSynced_(args.keepEverythingSynced); 330 if (args.sessionsRegistered) { 331 $('sessionsCheckbox').checked = args.syncSessions; 332 $('sessionsItem').className = "sync-item-show"; 333 } else { 334 $('sessionsItem').className = "sync-item-hide"; 335 } 336 }, 337 338 setEncryptionCheckboxes_: function(args) { 339 if (args["usePassphrase"]) { 340 $('explicit-option').checked = true; 341 342 // The passphrase, once set, cannot be unset, but we show a reset link. 343 $('explicit-option').disabled = true; 344 $('google-option').disabled = true; 345 $('sync-custom-passphrase').hidden = true; 346 } else { 347 $('google-option').checked = true; 348 } 349 350 this.switchToMode_(""); 351 }, 352 353 setErrorState_: function(args) { 354 if (!args.was_aborted) 355 return; 356 357 $('aborted-text').className = "sync-error-show"; 358 $('choose-datatypes-ok').disabled = true; 359 $('keepEverythingSyncedRadio').disabled = true; 360 $('chooseDataTypesRadio').disabled = true; 361 }, 362 363 setCheckboxesAndErrors_: function(args) { 364 this.setChooseDataTypesCheckboxes_(args); 365 this.setEncryptionCheckboxes_(args); 366 this.setErrorState_(args); 367 }, 368 369 // Called once, when this html/js is loaded. 370 showConfigure_: function(args) { 371 var datatypeSelect = document.getElementById('sync-select-datatypes'); 372 var self = this; 373 datatypeSelect.onchange = function() { 374 var syncAll = this.selectedIndex == 0; 375 self.setCheckboxesToKeepEverythingSynced_(syncAll); 376 }; 377 378 $('sync-setup-configure').classList.remove('hidden'); 379 380 if (args) { 381 this.setCheckboxesAndErrors_(args); 382 383 // Whether to display the 'Sync everything' confirmation screen or the 384 // customize data types screen. 385 // TODO(jhawkins): Rename |keepEverythingSynced| to |syncAllDataTypes|. 386 var syncEverything = args['syncEverything']; 387 var syncAllDataTypes = args['keepEverythingSynced']; 388 this.usePassphrase_ = args['usePassphrase']; 389 if (syncEverything == false || syncAllDataTypes == false || 390 this.usePassphrase_) { 391 this.showCustomizePage_(syncAllDataTypes); 392 } else { 393 this.showSyncEverythingPage_(); 394 } 395 } 396 }, 397 398 showSyncEverythingPage_: function() { 399 $('confirm-sync-preferences').hidden = false; 400 $('customize-sync-preferences').hidden = true; 401 402 // Reset the selection to 'Sync everything'. 403 $('sync-select-datatypes').selectedIndex = 0; 404 405 // The default state is to sync everything. 406 this.setCheckboxesToKeepEverythingSynced_(true); 407 408 // If the account is not synced with a custom passphrase, reset the 409 // passphrase radio when switching to the 'Sync everything' page. 410 if (!this.usePassphrase_) { 411 $('google-option').checked = true; 412 this.switchToMode_("google"); 413 } 414 415 $('confirm-everything-ok').focus(); 416 }, 417 418 showCustomizePage_: function(syncEverything) { 419 document.getElementById('confirm-sync-preferences').hidden = true; 420 document.getElementById('customize-sync-preferences').hidden = false; 421 422 // If the user has selected the 'Customize' page on initial set up, it's 423 // likely he intends to change the data types. Select the 424 // 'Choose data types' option in this case. 425 var index = syncEverything ? 0 : 1; 426 document.getElementById('sync-select-datatypes').selectedIndex = index; 427 this.setDataTypeCheckboxesEnabled_(!syncEverything); 428 $('choose-datatypes-ok').focus(); 429 }, 430 431 showSyncSetupPage_: function(page, args) { 432 if (page == 'settingUp') { 433 this.setThrobbersVisible_(true); 434 return; 435 } else { 436 this.setThrobbersVisible_(false); 437 } 438 439 // Hide an existing visible overlay. 440 var overlay = $('sync-setup-overlay'); 441 for (var i = 0; i < overlay.children.length; i++) 442 overlay.children[i].classList.add('hidden'); 443 444 if (page == 'login') 445 this.showGaiaLogin_(args); 446 else if (page == 'configure') 447 this.showConfigure_(args); 448 else if (page == 'passphrase') 449 this.showPassphrase_(args); 450 else if (page == 'done') 451 this.closeOverlay_(); 452 }, 453 454 setThrobbersVisible_: function(visible) { 455 var throbbers = document.getElementsByClassName("throbber"); 456 for (var i = 0; i < throbbers.length; i++) 457 throbbers[i].style.visibility = visible ? "visible" : "hidden"; 458 }, 459 460 showPassphrase_: function(args) { 461 $('sync-setup-passphrase').classList.remove('hidden'); 462 463 $('passphraseRejectedBody').style.display = "none"; 464 $('normalBody').style.display = "none"; 465 $('incorrectPassphrase').style.display = "none"; 466 467 if (args["passphrase_creation_rejected"]) { 468 $('passphraseRejectedBody').style.display = "block"; 469 } else { 470 $('normalBody').style.display = "block"; 471 } 472 473 if (args["passphrase_setting_rejected"]) { 474 $('incorrectPassphrase').style.display = "block"; 475 } 476 477 $('passphrase').focus(); 478 }, 479 480 setElementDisplay_: function(id, display) { 481 var d = document.getElementById(id); 482 if (d) 483 d.style.display = display; 484 }, 485 486 loginSetFocus_: function() { 487 var email = $('gaia-email'); 488 var passwd = $('gaia-passwd'); 489 if (email && (email.value == null || email.value == "")) { 490 email.focus(); 491 } else if (passwd) { 492 passwd.focus(); 493 } 494 }, 495 496 showAccessCodeRequired_: function() { 497 this.setElementDisplay_("password-row", "none"); 498 this.setElementDisplay_("email-row", "none"); 499 $('create-account-cell').style.visibility = "hidden"; 500 501 this.setElementDisplay_("access-code-label-row", "table-row"); 502 this.setElementDisplay_("access-code-input-row", "table-row"); 503 this.setElementDisplay_("access-code-help-row", "table-row"); 504 document.getElementById('access-code').disabled = false; 505 }, 506 507 showCaptcha_: function(args) { 508 this.captchaChallengeActive_ = true; 509 510 // The captcha takes up lots of space, so make room. 511 this.setElementDisplay_("top-blurb", "none"); 512 this.setElementDisplay_("top-blurb-error", "none"); 513 this.setElementDisplay_("create-account-div", "none"); 514 document.getElementById('create-account-cell').height = 0; 515 516 // It's showtime for the captcha now. 517 this.setElementDisplay_("captcha-div", "block"); 518 document.getElementById('gaia-email').disabled = true; 519 document.getElementById('gaia-passwd').disabled = false; 520 document.getElementById('captcha-value').disabled = false; 521 document.getElementById('captcha-wrapper').style.backgroundImage = 522 url(args.captchaUrl); 523 }, 524 525 showGaiaLogin_: function(args) { 526 $('sync-setup-login').classList.remove('hidden'); 527 528 document.getElementById('gaia-email').disabled = false; 529 document.getElementById('gaia-passwd').disabled = false; 530 531 var f = $('gaia-login-form'); 532 var email = $('gaia-email'); 533 var passwd = $('gaia-passwd'); 534 if (f) { 535 if (args.user != undefined) { 536 if (email.value != args.user) 537 passwd.value = ""; // Reset the password field 538 email.value = args.user; 539 } 540 541 if (!args.editable_user) { 542 email.style.display = 'none'; 543 var span = document.getElementById('email-readonly'); 544 span.appendChild(document.createTextNode(email.value)); 545 span.style.display = 'inline'; 546 this.setElementDisplay_("create-account-div", "none"); 547 } 548 549 f.accessCode.disabled = true; 550 } 551 552 if (1 == args.error) { 553 var access_code = document.getElementById('access-code'); 554 if (access_code.value && access_code.value != "") { 555 this.setElementDisplay_("errormsg-0-access-code", 'block'); 556 this.showAccessCodeRequired_(); 557 } else { 558 this.setElementDisplay_("errormsg-1-password", 'table-row'); 559 } 560 this.setBlurbError_(args.error_message); 561 } else if (3 == args.error) { 562 this.setElementDisplay_("errormsg-0-connection", 'table-row'); 563 this.setBlurbError_(args.error_message); 564 } else if (4 == args.error) { 565 this.showCaptcha_(args); 566 } else if (8 == args.error) { 567 this.showAccessCodeRequired_(); 568 } else if (args.error_message) { 569 this.setBlurbError_(args.error_message); 570 } 571 572 $('sign-in').disabled = false; 573 $('sign-in').value = templateData['signin']; 574 this.loginSetFocus_(); 575 }, 576 577 resetErrorVisibility_: function() { 578 this.setElementDisplay_("errormsg-0-email", 'none'); 579 this.setElementDisplay_("errormsg-0-password", 'none'); 580 this.setElementDisplay_("errormsg-1-password", 'none'); 581 this.setElementDisplay_("errormsg-0-connection", 'none'); 582 this.setElementDisplay_("errormsg-0-access-code", 'none'); 583 }, 584 585 setBlurbError_: function(error_message) { 586 if (this.captchaChallengeActive_) 587 return; // No blurb in captcha challenge mode. 588 589 if (error_message) { 590 document.getElementById('error-signing-in').style.display = 'none'; 591 document.getElementById('error-custom').style.display = 'inline'; 592 document.getElementById('error-custom').textContent = error_message; 593 } else { 594 document.getElementById('error-signing-in').style.display = 'inline'; 595 document.getElementById('error-custom').style.display = 'none'; 596 } 597 598 $('top-blurb-error').style.visibility = "visible"; 599 document.getElementById('gaia-email').disabled = false; 600 document.getElementById('gaia-passwd').disabled = false; 601 }, 602 603 setErrorVisibility_: function() { 604 this.resetErrorVisibility_(); 605 var f = $('gaia-login-form'); 606 var email = $('gaia-email'); 607 var passwd = $('gaia-passwd'); 608 if (null == email.value || "" == email.value) { 609 this.setElementDisplay_("errormsg-0-email", 'table-row'); 610 this.setBlurbError_(); 611 return false; 612 } 613 if (null == passwd.value || "" == passwd.value) { 614 this.setElementDisplay_("errormsg-0-password", 'table-row'); 615 this.setBlurbError_(); 616 return false; 617 } 618 if (!f.accessCode.disabled && (null == f.accessCode.value || 619 "" == f.accessCode.value)) { 620 this.setElementDisplay_("errormsg-0-password", 'table-row'); 621 return false; 622 } 623 return true; 624 }, 625 626 sendCredentialsAndClose_: function() { 627 if (!this.setErrorVisibility_()) { 628 return false; 629 } 630 631 $('gaia-email').disabled = true; 632 $('gaia-passwd').disabled = true; 633 $('captcha-value').disabled = true; 634 $('access-code').disabled = true; 635 636 $('logging-in-throbber').style.visibility = "visible"; 637 638 var f = $('gaia-login-form'); 639 var email = $('gaia-email'); 640 var passwd = $('gaia-passwd'); 641 var result = JSON.stringify({"user" : email.value, 642 "pass" : passwd.value, 643 "captcha" : f.captchaValue.value, 644 "access_code" : f.accessCode.value}); 645 $('sign-in').disabled = true; 646 chrome.send("SubmitAuth", [result]); 647 }, 648 649 showGaiaSuccessAndClose_: function() { 650 $('sign-in').value = localStrings.getString('loginSuccess'); 651 setTimeout(this.closeOverlay_, 1600); 652 }, 653 654 showSuccessAndSettingUp_: function() { 655 $('sign-in').value = localStrings.getString('settingup'); 656 }, 657 658 /** @inheritDoc */ 659 shouldClose: function() { 660 if (!$('cancel-warning-box').hidden) { 661 chrome.send('PassphraseCancel', ['']); 662 return true; 663 } else if (!$('sync-setup-passphrase').classList.contains('hidden')) { 664 // The Passphrase page is showing, and the use has pressed escape. 665 // Activate the cancel logic in this case. 666 this.showCancelWarning_(); 667 return false; 668 } 669 670 return true; 671 }, 672 }; 673 674 SyncSetupOverlay.showSyncSetupPage = function(page, args) { 675 SyncSetupOverlay.getInstance().showSyncSetupPage_(page, args); 676 }; 677 678 SyncSetupOverlay.showSuccessAndClose = function() { 679 SyncSetupOverlay.getInstance().showSuccessAndClose_(); 680 }; 681 682 SyncSetupOverlay.showSuccessAndSettingUp = function() { 683 SyncSetupOverlay.getInstance().showSuccessAndSettingUp_(); 684 }; 685 686 // Export 687 return { 688 SyncSetupOverlay: SyncSetupOverlay 689 }; 690 }); 691