michael@0: // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const Ci = Components.interfaces; michael@0: const Cc = Components.classes; michael@0: const Cr = Components.results; michael@0: const Cu = Components.utils; michael@0: michael@0: // page consts michael@0: michael@0: const PAIR_PAGE = 0; michael@0: const INTRO_PAGE = 1; michael@0: const NEW_ACCOUNT_START_PAGE = 2; michael@0: const EXISTING_ACCOUNT_CONNECT_PAGE = 3; michael@0: const EXISTING_ACCOUNT_LOGIN_PAGE = 4; michael@0: const OPTIONS_PAGE = 5; michael@0: const OPTIONS_CONFIRM_PAGE = 6; michael@0: michael@0: // Broader than we'd like, but after this changed from api-secure.recaptcha.net michael@0: // we had no choice. At least we only do this for the duration of setup. michael@0: // See discussion in Bugs 508112 and 653307. michael@0: const RECAPTCHA_DOMAIN = "https://www.google.com"; michael@0: michael@0: const PIN_PART_LENGTH = 4; michael@0: michael@0: Cu.import("resource://services-sync/main.js"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/PlacesUtils.jsm"); michael@0: Cu.import("resource://gre/modules/PluralForm.jsm"); michael@0: michael@0: michael@0: function setVisibility(element, visible) { michael@0: element.style.visibility = visible ? "visible" : "hidden"; michael@0: } michael@0: michael@0: var gSyncSetup = { michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, michael@0: Ci.nsIWebProgressListener, michael@0: Ci.nsISupportsWeakReference]), michael@0: michael@0: captchaBrowser: null, michael@0: wizard: null, michael@0: _disabledSites: [], michael@0: michael@0: status: { michael@0: password: false, michael@0: email: false, michael@0: server: false michael@0: }, michael@0: michael@0: get _remoteSites() [Weave.Service.serverURL, RECAPTCHA_DOMAIN], michael@0: michael@0: get _usingMainServers() { michael@0: if (this._settingUpNew) michael@0: return document.getElementById("server").selectedIndex == 0; michael@0: return document.getElementById("existingServer").selectedIndex == 0; michael@0: }, michael@0: michael@0: init: function () { michael@0: let obs = [ michael@0: ["weave:service:change-passphrase", "onResetPassphrase"], michael@0: ["weave:service:login:start", "onLoginStart"], michael@0: ["weave:service:login:error", "onLoginEnd"], michael@0: ["weave:service:login:finish", "onLoginEnd"]]; michael@0: michael@0: // Add the observers now and remove them on unload michael@0: let self = this; michael@0: let addRem = function(add) { michael@0: obs.forEach(function([topic, func]) { michael@0: //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling michael@0: // of `this`. Fix in a followup. (bug 583347) michael@0: if (add) michael@0: Weave.Svc.Obs.add(topic, self[func], self); michael@0: else michael@0: Weave.Svc.Obs.remove(topic, self[func], self); michael@0: }); michael@0: }; michael@0: addRem(true); michael@0: window.addEventListener("unload", function() addRem(false), false); michael@0: michael@0: window.setTimeout(function () { michael@0: // Force Service to be loaded so that engines are registered. michael@0: // See Bug 670082. michael@0: Weave.Service; michael@0: }, 0); michael@0: michael@0: this.captchaBrowser = document.getElementById("captcha"); michael@0: michael@0: this.wizardType = null; michael@0: if (window.arguments && window.arguments[0]) { michael@0: this.wizardType = window.arguments[0]; michael@0: } michael@0: switch (this.wizardType) { michael@0: case null: michael@0: this.wizard.pageIndex = INTRO_PAGE; michael@0: // Fall through! michael@0: case "pair": michael@0: this.captchaBrowser.addProgressListener(this); michael@0: Weave.Svc.Prefs.set("firstSync", "notReady"); michael@0: break; michael@0: case "reset": michael@0: this._resettingSync = true; michael@0: this.wizard.pageIndex = OPTIONS_PAGE; michael@0: break; michael@0: } michael@0: michael@0: this.wizard.getButton("extra1").label = michael@0: this._stringBundle.GetStringFromName("button.syncOptions.label"); michael@0: michael@0: // Remember these values because the options pages change them temporarily. michael@0: this._nextButtonLabel = this.wizard.getButton("next").label; michael@0: this._nextButtonAccesskey = this.wizard.getButton("next") michael@0: .getAttribute("accesskey"); michael@0: this._backButtonLabel = this.wizard.getButton("back").label; michael@0: this._backButtonAccesskey = this.wizard.getButton("back") michael@0: .getAttribute("accesskey"); michael@0: }, michael@0: michael@0: startNewAccountSetup: function () { michael@0: if (!Weave.Utils.ensureMPUnlocked()) michael@0: return false; michael@0: this._settingUpNew = true; michael@0: this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE; michael@0: }, michael@0: michael@0: useExistingAccount: function () { michael@0: if (!Weave.Utils.ensureMPUnlocked()) michael@0: return false; michael@0: this._settingUpNew = false; michael@0: if (this.wizardType == "pair") { michael@0: // We're already pairing, so there's no point in pairing again. michael@0: // Go straight to the manual login page. michael@0: this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE; michael@0: } else { michael@0: this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE; michael@0: } michael@0: }, michael@0: michael@0: resetPassphrase: function resetPassphrase() { michael@0: // Apply the existing form fields so that michael@0: // Weave.Service.changePassphrase() has the necessary credentials. michael@0: Weave.Service.identity.account = document.getElementById("existingAccountName").value; michael@0: Weave.Service.identity.basicPassword = document.getElementById("existingPassword").value; michael@0: michael@0: // Generate a new passphrase so that Weave.Service.login() will michael@0: // actually do something. michael@0: let passphrase = Weave.Utils.generatePassphrase(); michael@0: Weave.Service.identity.syncKey = passphrase; michael@0: michael@0: // Only open the dialog if username + password are actually correct. michael@0: Weave.Service.login(); michael@0: if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE, michael@0: Weave.LOGIN_FAILED_NO_PASSPHRASE, michael@0: Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) { michael@0: return; michael@0: } michael@0: michael@0: // Hide any errors about the passphrase, we know it's not right. michael@0: let feedback = document.getElementById("existingPassphraseFeedbackRow"); michael@0: feedback.hidden = true; michael@0: let el = document.getElementById("existingPassphrase"); michael@0: el.value = Weave.Utils.hyphenatePassphrase(passphrase); michael@0: michael@0: // changePassphrase() will sync, make sure we set the "firstSync" pref michael@0: // according to the user's pref. michael@0: Weave.Svc.Prefs.reset("firstSync"); michael@0: this.setupInitialSync(); michael@0: gSyncUtils.resetPassphrase(true); michael@0: }, michael@0: michael@0: onResetPassphrase: function () { michael@0: document.getElementById("existingPassphrase").value = michael@0: Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey); michael@0: this.checkFields(); michael@0: this.wizard.advance(); michael@0: }, michael@0: michael@0: onLoginStart: function () { michael@0: this.toggleLoginFeedback(false); michael@0: }, michael@0: michael@0: onLoginEnd: function () { michael@0: this.toggleLoginFeedback(true); michael@0: }, michael@0: michael@0: sendCredentialsAfterSync: function () { michael@0: let send = function() { michael@0: Services.obs.removeObserver("weave:service:sync:finish", send); michael@0: Services.obs.removeObserver("weave:service:sync:error", send); michael@0: let credentials = {account: Weave.Service.identity.account, michael@0: password: Weave.Service.identity.basicPassword, michael@0: synckey: Weave.Service.identity.syncKey, michael@0: serverURL: Weave.Service.serverURL}; michael@0: this._jpakeclient.sendAndComplete(credentials); michael@0: }.bind(this); michael@0: Services.obs.addObserver("weave:service:sync:finish", send, false); michael@0: Services.obs.addObserver("weave:service:sync:error", send, false); michael@0: }, michael@0: michael@0: toggleLoginFeedback: function (stop) { michael@0: document.getElementById("login-throbber").hidden = stop; michael@0: let password = document.getElementById("existingPasswordFeedbackRow"); michael@0: let server = document.getElementById("existingServerFeedbackRow"); michael@0: let passphrase = document.getElementById("existingPassphraseFeedbackRow"); michael@0: michael@0: if (!stop || (Weave.Status.login == Weave.LOGIN_SUCCEEDED)) { michael@0: password.hidden = server.hidden = passphrase.hidden = true; michael@0: return; michael@0: } michael@0: michael@0: let feedback; michael@0: switch (Weave.Status.login) { michael@0: case Weave.LOGIN_FAILED_NETWORK_ERROR: michael@0: case Weave.LOGIN_FAILED_SERVER_ERROR: michael@0: feedback = server; michael@0: break; michael@0: case Weave.LOGIN_FAILED_LOGIN_REJECTED: michael@0: case Weave.LOGIN_FAILED_NO_USERNAME: michael@0: case Weave.LOGIN_FAILED_NO_PASSWORD: michael@0: feedback = password; michael@0: break; michael@0: case Weave.LOGIN_FAILED_INVALID_PASSPHRASE: michael@0: feedback = passphrase; michael@0: break; michael@0: } michael@0: this._setFeedbackMessage(feedback, false, Weave.Status.login); michael@0: }, michael@0: michael@0: setupInitialSync: function () { michael@0: let action = document.getElementById("mergeChoiceRadio").selectedItem.id; michael@0: switch (action) { michael@0: case "resetClient": michael@0: // if we're not resetting sync, we don't need to explicitly michael@0: // call resetClient michael@0: if (!this._resettingSync) michael@0: return; michael@0: // otherwise, fall through michael@0: case "wipeClient": michael@0: case "wipeRemote": michael@0: Weave.Svc.Prefs.set("firstSync", action); michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: // fun with validation! michael@0: checkFields: function () { michael@0: this.wizard.canAdvance = this.readyToAdvance(); michael@0: }, michael@0: michael@0: readyToAdvance: function () { michael@0: switch (this.wizard.pageIndex) { michael@0: case INTRO_PAGE: michael@0: return false; michael@0: case NEW_ACCOUNT_START_PAGE: michael@0: for (let i in this.status) { michael@0: if (!this.status[i]) michael@0: return false; michael@0: } michael@0: if (this._usingMainServers) michael@0: return document.getElementById("tos").checked; michael@0: michael@0: return true; michael@0: case EXISTING_ACCOUNT_LOGIN_PAGE: michael@0: let hasUser = document.getElementById("existingAccountName").value != ""; michael@0: let hasPass = document.getElementById("existingPassword").value != ""; michael@0: let hasKey = document.getElementById("existingPassphrase").value != ""; michael@0: michael@0: if (hasUser && hasPass && hasKey) { michael@0: if (this._usingMainServers) michael@0: return true; michael@0: michael@0: if (this._validateServer(document.getElementById("existingServer"))) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: // Default, e.g. wizard's special page -1 etc. michael@0: return true; michael@0: }, michael@0: michael@0: onPINInput: function onPINInput(textbox) { michael@0: if (textbox && textbox.value.length == PIN_PART_LENGTH) { michael@0: this.nextFocusEl[textbox.id].focus(); michael@0: } michael@0: this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH && michael@0: this.pin2.value.length == PIN_PART_LENGTH && michael@0: this.pin3.value.length == PIN_PART_LENGTH); michael@0: }, michael@0: michael@0: onEmailInput: function () { michael@0: // Check account validity when the user stops typing for 1 second. michael@0: if (this._checkAccountTimer) michael@0: window.clearTimeout(this._checkAccountTimer); michael@0: this._checkAccountTimer = window.setTimeout(function () { michael@0: gSyncSetup.checkAccount(); michael@0: }, 1000); michael@0: }, michael@0: michael@0: checkAccount: function() { michael@0: delete this._checkAccountTimer; michael@0: let value = Weave.Utils.normalizeAccount( michael@0: document.getElementById("weaveEmail").value); michael@0: if (!value) { michael@0: this.status.email = false; michael@0: this.checkFields(); michael@0: return; michael@0: } michael@0: michael@0: let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/; michael@0: let feedback = document.getElementById("emailFeedbackRow"); michael@0: let valid = re.test(value); michael@0: michael@0: let str = ""; michael@0: if (!valid) { michael@0: str = "invalidEmail.label"; michael@0: } else { michael@0: let availCheck = Weave.Service.checkAccount(value); michael@0: valid = availCheck == "available"; michael@0: if (!valid) { michael@0: if (availCheck == "notAvailable") michael@0: str = "usernameNotAvailable.label"; michael@0: else michael@0: str = availCheck; michael@0: } michael@0: } michael@0: michael@0: this._setFeedbackMessage(feedback, valid, str); michael@0: this.status.email = valid; michael@0: if (valid) michael@0: Weave.Service.identity.account = value; michael@0: this.checkFields(); michael@0: }, michael@0: michael@0: onPasswordChange: function () { michael@0: let password = document.getElementById("weavePassword"); michael@0: let pwconfirm = document.getElementById("weavePasswordConfirm"); michael@0: let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm); michael@0: michael@0: let feedback = document.getElementById("passwordFeedbackRow"); michael@0: this._setFeedback(feedback, valid, errorString); michael@0: michael@0: this.status.password = valid; michael@0: this.checkFields(); michael@0: }, michael@0: michael@0: onPageShow: function() { michael@0: switch (this.wizard.pageIndex) { michael@0: case PAIR_PAGE: michael@0: this.wizard.getButton("back").hidden = true; michael@0: this.wizard.getButton("extra1").hidden = true; michael@0: this.onPINInput(); michael@0: this.pin1.focus(); michael@0: break; michael@0: case INTRO_PAGE: michael@0: // We may not need the captcha in the Existing Account branch of the michael@0: // wizard. However, we want to preload it to avoid any flickering while michael@0: // the Create Account page is shown. michael@0: this.loadCaptcha(); michael@0: this.wizard.getButton("next").hidden = true; michael@0: this.wizard.getButton("back").hidden = true; michael@0: this.wizard.getButton("extra1").hidden = true; michael@0: this.checkFields(); michael@0: break; michael@0: case NEW_ACCOUNT_START_PAGE: michael@0: this.wizard.getButton("extra1").hidden = false; michael@0: this.wizard.getButton("next").hidden = false; michael@0: this.wizard.getButton("back").hidden = false; michael@0: this.onServerCommand(); michael@0: this.wizard.canRewind = true; michael@0: this.checkFields(); michael@0: break; michael@0: case EXISTING_ACCOUNT_CONNECT_PAGE: michael@0: Weave.Svc.Prefs.set("firstSync", "existingAccount"); michael@0: this.wizard.getButton("next").hidden = false; michael@0: this.wizard.getButton("back").hidden = false; michael@0: this.wizard.getButton("extra1").hidden = false; michael@0: this.wizard.canAdvance = false; michael@0: this.wizard.canRewind = true; michael@0: this.startEasySetup(); michael@0: break; michael@0: case EXISTING_ACCOUNT_LOGIN_PAGE: michael@0: this.wizard.getButton("next").hidden = false; michael@0: this.wizard.getButton("back").hidden = false; michael@0: this.wizard.getButton("extra1").hidden = false; michael@0: this.wizard.canRewind = true; michael@0: this.checkFields(); michael@0: break; michael@0: case OPTIONS_PAGE: michael@0: this.wizard.canRewind = false; michael@0: this.wizard.canAdvance = true; michael@0: if (!this._resettingSync) { michael@0: this.wizard.getButton("next").label = michael@0: this._stringBundle.GetStringFromName("button.syncOptionsDone.label"); michael@0: this.wizard.getButton("next").removeAttribute("accesskey"); michael@0: } michael@0: this.wizard.getButton("next").hidden = false; michael@0: this.wizard.getButton("back").hidden = true; michael@0: this.wizard.getButton("cancel").hidden = !this._resettingSync; michael@0: this.wizard.getButton("extra1").hidden = true; michael@0: document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName; michael@0: document.getElementById("syncOptions").collapsed = this._resettingSync; michael@0: document.getElementById("mergeOptions").collapsed = this._settingUpNew; michael@0: break; michael@0: case OPTIONS_CONFIRM_PAGE: michael@0: this.wizard.canRewind = true; michael@0: this.wizard.canAdvance = true; michael@0: this.wizard.getButton("back").label = michael@0: this._stringBundle.GetStringFromName("button.syncOptionsCancel.label"); michael@0: this.wizard.getButton("back").removeAttribute("accesskey"); michael@0: this.wizard.getButton("back").hidden = this._resettingSync; michael@0: this.wizard.getButton("next").hidden = false; michael@0: this.wizard.getButton("finish").hidden = true; michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: onWizardAdvance: function () { michael@0: // Check pageIndex so we don't prompt before the Sync setup wizard appears. michael@0: // This is a fallback in case the Master Password gets locked mid-wizard. michael@0: if ((this.wizard.pageIndex >= 0) && michael@0: !Weave.Utils.ensureMPUnlocked()) { michael@0: return false; michael@0: } michael@0: michael@0: switch (this.wizard.pageIndex) { michael@0: case PAIR_PAGE: michael@0: this.startPairing(); michael@0: return false; michael@0: case NEW_ACCOUNT_START_PAGE: michael@0: // If the user selects Next (e.g. by hitting enter) when we haven't michael@0: // executed the delayed checks yet, execute them immediately. michael@0: if (this._checkAccountTimer) { michael@0: this.checkAccount(); michael@0: } michael@0: if (this._checkServerTimer) { michael@0: this.checkServer(); michael@0: } michael@0: if (!this.wizard.canAdvance) { michael@0: return false; michael@0: } michael@0: michael@0: let doc = this.captchaBrowser.contentDocument; michael@0: let getField = function getField(field) { michael@0: let node = doc.getElementById("recaptcha_" + field + "_field"); michael@0: return node && node.value; michael@0: }; michael@0: michael@0: // Display throbber michael@0: let feedback = document.getElementById("captchaFeedback"); michael@0: let image = feedback.firstChild; michael@0: let label = image.nextSibling; michael@0: image.setAttribute("status", "active"); michael@0: label.value = this._stringBundle.GetStringFromName("verifying.label"); michael@0: setVisibility(feedback, true); michael@0: michael@0: let password = document.getElementById("weavePassword").value; michael@0: let email = Weave.Utils.normalizeAccount( michael@0: document.getElementById("weaveEmail").value); michael@0: let challenge = getField("challenge"); michael@0: let response = getField("response"); michael@0: michael@0: let error = Weave.Service.createAccount(email, password, michael@0: challenge, response); michael@0: michael@0: if (error == null) { michael@0: Weave.Service.identity.account = email; michael@0: Weave.Service.identity.basicPassword = password; michael@0: Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase(); michael@0: this._handleNoScript(false); michael@0: Weave.Svc.Prefs.set("firstSync", "newAccount"); michael@0: #ifdef XP_WIN michael@0: #ifdef MOZ_METRO michael@0: if (document.getElementById("metroSetupCheckbox").checked) { michael@0: Services.metro.storeSyncInfo(email, password, Weave.Service.identity.syncKey); michael@0: } michael@0: #endif michael@0: #endif michael@0: this.wizardFinish(); michael@0: return false; michael@0: } michael@0: michael@0: image.setAttribute("status", "error"); michael@0: label.value = Weave.Utils.getErrorString(error); michael@0: return false; michael@0: case EXISTING_ACCOUNT_LOGIN_PAGE: michael@0: Weave.Service.identity.account = Weave.Utils.normalizeAccount( michael@0: document.getElementById("existingAccountName").value); michael@0: Weave.Service.identity.basicPassword = michael@0: document.getElementById("existingPassword").value; michael@0: let pp = document.getElementById("existingPassphrase").value; michael@0: Weave.Service.identity.syncKey = Weave.Utils.normalizePassphrase(pp); michael@0: if (Weave.Service.login()) { michael@0: this.wizardFinish(); michael@0: } michael@0: return false; michael@0: case OPTIONS_PAGE: michael@0: let desc = document.getElementById("mergeChoiceRadio").selectedIndex; michael@0: // No confirmation needed on new account setup or merge option michael@0: // with existing account. michael@0: if (this._settingUpNew || (!this._resettingSync && desc == 0)) michael@0: return this.returnFromOptions(); michael@0: return this._handleChoice(); michael@0: case OPTIONS_CONFIRM_PAGE: michael@0: if (this._resettingSync) { michael@0: this.wizardFinish(); michael@0: return false; michael@0: } michael@0: return this.returnFromOptions(); michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: onWizardBack: function () { michael@0: switch (this.wizard.pageIndex) { michael@0: case NEW_ACCOUNT_START_PAGE: michael@0: case EXISTING_ACCOUNT_LOGIN_PAGE: michael@0: this.wizard.pageIndex = INTRO_PAGE; michael@0: return false; michael@0: case EXISTING_ACCOUNT_CONNECT_PAGE: michael@0: this.abortEasySetup(); michael@0: this.wizard.pageIndex = INTRO_PAGE; michael@0: return false; michael@0: case EXISTING_ACCOUNT_LOGIN_PAGE: michael@0: // If we were already pairing on entry, we went straight to the manual michael@0: // login page. If subsequently we go back, return to the page that lets michael@0: // us choose whether we already have an account. michael@0: if (this.wizardType == "pair") { michael@0: this.wizard.pageIndex = INTRO_PAGE; michael@0: return false; michael@0: } michael@0: return true; michael@0: case OPTIONS_CONFIRM_PAGE: michael@0: // Backing up from the confirmation page = resetting first sync to merge. michael@0: document.getElementById("mergeChoiceRadio").selectedIndex = 0; michael@0: return this.returnFromOptions(); michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: wizardFinish: function () { michael@0: this.setupInitialSync(); michael@0: michael@0: if (this.wizardType == "pair") { michael@0: this.completePairing(); michael@0: } michael@0: michael@0: if (!this._resettingSync) { michael@0: function isChecked(element) { michael@0: return document.getElementById(element).hasAttribute("checked"); michael@0: } michael@0: michael@0: let prefs = ["engine.bookmarks", "engine.passwords", "engine.history", michael@0: "engine.tabs", "engine.prefs", "engine.addons"]; michael@0: for (let i = 0;i < prefs.length;i++) { michael@0: Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i])); michael@0: } michael@0: this._handleNoScript(false); michael@0: if (Weave.Svc.Prefs.get("firstSync", "") == "notReady") michael@0: Weave.Svc.Prefs.reset("firstSync"); michael@0: michael@0: Weave.Service.persistLogin(); michael@0: Weave.Svc.Obs.notify("weave:service:setup-complete"); michael@0: michael@0: gSyncUtils.openFirstSyncProgressPage(); michael@0: } michael@0: Weave.Utils.nextTick(Weave.Service.sync, Weave.Service); michael@0: window.close(); michael@0: }, michael@0: michael@0: onWizardCancel: function () { michael@0: if (this._resettingSync) michael@0: return; michael@0: michael@0: this.abortEasySetup(); michael@0: this._handleNoScript(false); michael@0: Weave.Service.startOver(); michael@0: }, michael@0: michael@0: onSyncOptions: function () { michael@0: this._beforeOptionsPage = this.wizard.pageIndex; michael@0: this.wizard.pageIndex = OPTIONS_PAGE; michael@0: }, michael@0: michael@0: returnFromOptions: function() { michael@0: this.wizard.getButton("next").label = this._nextButtonLabel; michael@0: this.wizard.getButton("next").setAttribute("accesskey", michael@0: this._nextButtonAccesskey); michael@0: this.wizard.getButton("back").label = this._backButtonLabel; michael@0: this.wizard.getButton("back").setAttribute("accesskey", michael@0: this._backButtonAccesskey); michael@0: this.wizard.getButton("cancel").hidden = false; michael@0: this.wizard.getButton("extra1").hidden = false; michael@0: this.wizard.pageIndex = this._beforeOptionsPage; michael@0: return false; michael@0: }, michael@0: michael@0: startPairing: function startPairing() { michael@0: this.pairDeviceErrorRow.hidden = true; michael@0: // When onAbort is called, Weave may already be gone. michael@0: const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT; michael@0: michael@0: let self = this; michael@0: let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({ michael@0: onPaired: function onPaired() { michael@0: self.wizard.pageIndex = INTRO_PAGE; michael@0: }, michael@0: onComplete: function onComplete() { michael@0: // This method will never be called since SendCredentialsController michael@0: // will take over after the wizard completes. michael@0: }, michael@0: onAbort: function onAbort(error) { michael@0: delete self._jpakeclient; michael@0: michael@0: // Aborted by user, ignore. The window is almost certainly going to close michael@0: // or is already closed. michael@0: if (error == JPAKE_ERROR_USERABORT) { michael@0: return; michael@0: } michael@0: michael@0: self.pairDeviceErrorRow.hidden = false; michael@0: self.pairDeviceThrobber.hidden = true; michael@0: self.pin1.value = self.pin2.value = self.pin3.value = ""; michael@0: self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false; michael@0: if (self.wizard.pageIndex == PAIR_PAGE) { michael@0: self.pin1.focus(); michael@0: } michael@0: } michael@0: }); michael@0: this.pairDeviceThrobber.hidden = false; michael@0: this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true; michael@0: this.wizard.canAdvance = false; michael@0: michael@0: let pin = this.pin1.value + this.pin2.value + this.pin3.value; michael@0: let expectDelay = true; michael@0: jpakeclient.pairWithPIN(pin, expectDelay); michael@0: }, michael@0: michael@0: completePairing: function completePairing() { michael@0: if (!this._jpakeclient) { michael@0: // The channel was aborted while we were setting up the account michael@0: // locally. XXX TODO should we do anything here, e.g. tell michael@0: // the user on the last wizard page that it's ok, they just michael@0: // have to pair again? michael@0: return; michael@0: } michael@0: let controller = new Weave.SendCredentialsController(this._jpakeclient, michael@0: Weave.Service); michael@0: this._jpakeclient.controller = controller; michael@0: }, michael@0: michael@0: startEasySetup: function () { michael@0: // Don't do anything if we have a client already (e.g. we went to michael@0: // Sync Options and just came back). michael@0: if (this._jpakeclient) michael@0: return; michael@0: michael@0: // When onAbort is called, Weave may already be gone michael@0: const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT; michael@0: michael@0: let self = this; michael@0: this._jpakeclient = new Weave.JPAKEClient({ michael@0: displayPIN: function displayPIN(pin) { michael@0: document.getElementById("easySetupPIN1").value = pin.slice(0, 4); michael@0: document.getElementById("easySetupPIN2").value = pin.slice(4, 8); michael@0: document.getElementById("easySetupPIN3").value = pin.slice(8); michael@0: }, michael@0: michael@0: onPairingStart: function onPairingStart() {}, michael@0: michael@0: onComplete: function onComplete(credentials) { michael@0: Weave.Service.identity.account = credentials.account; michael@0: Weave.Service.identity.basicPassword = credentials.password; michael@0: Weave.Service.identity.syncKey = credentials.synckey; michael@0: Weave.Service.serverURL = credentials.serverURL; michael@0: gSyncSetup.wizardFinish(); michael@0: }, michael@0: michael@0: onAbort: function onAbort(error) { michael@0: delete self._jpakeclient; michael@0: michael@0: // Ignore if wizard is aborted. michael@0: if (error == JPAKE_ERROR_USERABORT) michael@0: return; michael@0: michael@0: // Automatically go to manual setup if we couldn't acquire a channel. michael@0: if (error == Weave.JPAKE_ERROR_CHANNEL) { michael@0: self.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE; michael@0: return; michael@0: } michael@0: michael@0: // Restart on all other errors. michael@0: self.startEasySetup(); michael@0: } michael@0: }); michael@0: this._jpakeclient.receiveNoPIN(); michael@0: }, michael@0: michael@0: abortEasySetup: function () { michael@0: document.getElementById("easySetupPIN1").value = ""; michael@0: document.getElementById("easySetupPIN2").value = ""; michael@0: document.getElementById("easySetupPIN3").value = ""; michael@0: if (!this._jpakeclient) michael@0: return; michael@0: michael@0: this._jpakeclient.abort(); michael@0: delete this._jpakeclient; michael@0: }, michael@0: michael@0: manualSetup: function () { michael@0: this.abortEasySetup(); michael@0: this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE; michael@0: }, michael@0: michael@0: // _handleNoScript is needed because it blocks the captcha. So we temporarily michael@0: // allow the necessary sites so that we can verify the user is in fact a human. michael@0: // This was done with the help of Giorgio (NoScript author). See bug 508112. michael@0: _handleNoScript: function (addExceptions) { michael@0: // if NoScript isn't installed, or is disabled, bail out. michael@0: let ns = Cc["@maone.net/noscript-service;1"]; michael@0: if (ns == null) michael@0: return; michael@0: michael@0: ns = ns.getService().wrappedJSObject; michael@0: if (addExceptions) { michael@0: this._remoteSites.forEach(function(site) { michael@0: site = ns.getSite(site); michael@0: if (!ns.isJSEnabled(site)) { michael@0: this._disabledSites.push(site); // save status michael@0: ns.setJSEnabled(site, true); // allow site michael@0: } michael@0: }, this); michael@0: } michael@0: else { michael@0: this._disabledSites.forEach(function(site) { michael@0: ns.setJSEnabled(site, false); michael@0: }); michael@0: this._disabledSites = []; michael@0: } michael@0: }, michael@0: michael@0: onExistingServerCommand: function () { michael@0: let control = document.getElementById("existingServer"); michael@0: if (control.selectedIndex == 0) { michael@0: control.removeAttribute("editable"); michael@0: Weave.Svc.Prefs.reset("serverURL"); michael@0: } else { michael@0: control.setAttribute("editable", "true"); michael@0: // Force a style flush to ensure that the binding is attached. michael@0: control.clientTop; michael@0: control.value = ""; michael@0: control.inputField.focus(); michael@0: } michael@0: document.getElementById("existingServerFeedbackRow").hidden = true; michael@0: this.checkFields(); michael@0: }, michael@0: michael@0: onExistingServerInput: function () { michael@0: // Check custom server validity when the user stops typing for 1 second. michael@0: if (this._existingServerTimer) michael@0: window.clearTimeout(this._existingServerTimer); michael@0: this._existingServerTimer = window.setTimeout(function () { michael@0: gSyncSetup.checkFields(); michael@0: }, 1000); michael@0: }, michael@0: michael@0: onServerCommand: function () { michael@0: setVisibility(document.getElementById("TOSRow"), this._usingMainServers); michael@0: let control = document.getElementById("server"); michael@0: if (!this._usingMainServers) { michael@0: control.setAttribute("editable", "true"); michael@0: // Force a style flush to ensure that the binding is attached. michael@0: control.clientTop; michael@0: control.value = ""; michael@0: control.inputField.focus(); michael@0: // checkServer() will call checkAccount() and checkFields(). michael@0: this.checkServer(); michael@0: return; michael@0: } michael@0: control.removeAttribute("editable"); michael@0: Weave.Svc.Prefs.reset("serverURL"); michael@0: if (this._settingUpNew) { michael@0: this.loadCaptcha(); michael@0: } michael@0: this.checkAccount(); michael@0: this.status.server = true; michael@0: document.getElementById("serverFeedbackRow").hidden = true; michael@0: this.checkFields(); michael@0: }, michael@0: michael@0: onServerInput: function () { michael@0: // Check custom server validity when the user stops typing for 1 second. michael@0: if (this._checkServerTimer) michael@0: window.clearTimeout(this._checkServerTimer); michael@0: this._checkServerTimer = window.setTimeout(function () { michael@0: gSyncSetup.checkServer(); michael@0: }, 1000); michael@0: }, michael@0: michael@0: checkServer: function () { michael@0: delete this._checkServerTimer; michael@0: let el = document.getElementById("server"); michael@0: let valid = false; michael@0: let feedback = document.getElementById("serverFeedbackRow"); michael@0: let str = ""; michael@0: if (el.value) { michael@0: valid = this._validateServer(el); michael@0: let str = valid ? "" : "serverInvalid.label"; michael@0: this._setFeedbackMessage(feedback, valid, str); michael@0: } michael@0: else michael@0: this._setFeedbackMessage(feedback, true); michael@0: michael@0: // Recheck account against the new server. michael@0: if (valid) michael@0: this.checkAccount(); michael@0: michael@0: this.status.server = valid; michael@0: this.checkFields(); michael@0: }, michael@0: michael@0: _validateServer: function (element) { michael@0: let valid = false; michael@0: let val = element.value; michael@0: if (!val) michael@0: return false; michael@0: michael@0: let uri = Weave.Utils.makeURI(val); michael@0: michael@0: if (!uri) michael@0: uri = Weave.Utils.makeURI("https://" + val); michael@0: michael@0: if (uri && this._settingUpNew) { michael@0: function isValid(uri) { michael@0: Weave.Service.serverURL = uri.spec; michael@0: let check = Weave.Service.checkAccount("a"); michael@0: return (check == "available" || check == "notAvailable"); michael@0: } michael@0: michael@0: if (uri.schemeIs("http")) { michael@0: uri.scheme = "https"; michael@0: if (isValid(uri)) michael@0: valid = true; michael@0: else michael@0: // setting the scheme back to http michael@0: uri.scheme = "http"; michael@0: } michael@0: if (!valid) michael@0: valid = isValid(uri); michael@0: michael@0: if (valid) { michael@0: this.loadCaptcha(); michael@0: } michael@0: } michael@0: else if (uri) { michael@0: valid = true; michael@0: Weave.Service.serverURL = uri.spec; michael@0: } michael@0: michael@0: if (valid) michael@0: element.value = Weave.Service.serverURL; michael@0: else michael@0: Weave.Svc.Prefs.reset("serverURL"); michael@0: michael@0: return valid; michael@0: }, michael@0: michael@0: _handleChoice: function () { michael@0: let desc = document.getElementById("mergeChoiceRadio").selectedIndex; michael@0: document.getElementById("chosenActionDeck").selectedIndex = desc; michael@0: switch (desc) { michael@0: case 1: michael@0: if (this._case1Setup) michael@0: break; michael@0: michael@0: let places_db = PlacesUtils.history michael@0: .QueryInterface(Ci.nsPIPlacesDatabase) michael@0: .DBConnection; michael@0: if (Weave.Service.engineManager.get("history").enabled) { michael@0: let daysOfHistory = 0; michael@0: let stm = places_db.createStatement( michael@0: "SELECT ROUND(( " + michael@0: "strftime('%s','now','localtime','utc') - " + michael@0: "( " + michael@0: "SELECT visit_date FROM moz_historyvisits " + michael@0: "ORDER BY visit_date ASC LIMIT 1 " + michael@0: ")/1000000 " + michael@0: ")/86400) AS daysOfHistory "); michael@0: michael@0: if (stm.step()) michael@0: daysOfHistory = stm.getInt32(0); michael@0: // Support %S for historical reasons (see bug 600141) michael@0: document.getElementById("historyCount").value = michael@0: PluralForm.get(daysOfHistory, michael@0: this._stringBundle.GetStringFromName("historyDaysCount.label")) michael@0: .replace("%S", daysOfHistory) michael@0: .replace("#1", daysOfHistory); michael@0: } else { michael@0: document.getElementById("historyCount").hidden = true; michael@0: } michael@0: michael@0: if (Weave.Service.engineManager.get("bookmarks").enabled) { michael@0: let bookmarks = 0; michael@0: let stm = places_db.createStatement( michael@0: "SELECT count(*) AS bookmarks " + michael@0: "FROM moz_bookmarks b " + michael@0: "LEFT JOIN moz_bookmarks t ON " + michael@0: "b.parent = t.id WHERE b.type = 1 AND t.parent <> :tag"); michael@0: stm.params.tag = PlacesUtils.tagsFolderId; michael@0: if (stm.executeStep()) michael@0: bookmarks = stm.row.bookmarks; michael@0: // Support %S for historical reasons (see bug 600141) michael@0: document.getElementById("bookmarkCount").value = michael@0: PluralForm.get(bookmarks, michael@0: this._stringBundle.GetStringFromName("bookmarksCount.label")) michael@0: .replace("%S", bookmarks) michael@0: .replace("#1", bookmarks); michael@0: } else { michael@0: document.getElementById("bookmarkCount").hidden = true; michael@0: } michael@0: michael@0: if (Weave.Service.engineManager.get("passwords").enabled) { michael@0: let logins = Services.logins.getAllLogins({}); michael@0: // Support %S for historical reasons (see bug 600141) michael@0: document.getElementById("passwordCount").value = michael@0: PluralForm.get(logins.length, michael@0: this._stringBundle.GetStringFromName("passwordsCount.label")) michael@0: .replace("%S", logins.length) michael@0: .replace("#1", logins.length); michael@0: } else { michael@0: document.getElementById("passwordCount").hidden = true; michael@0: } michael@0: michael@0: if (!Weave.Service.engineManager.get("prefs").enabled) { michael@0: document.getElementById("prefsWipe").hidden = true; michael@0: } michael@0: michael@0: let addonsEngine = Weave.Service.engineManager.get("addons"); michael@0: if (addonsEngine.enabled) { michael@0: let ids = addonsEngine._store.getAllIDs(); michael@0: let blessedcount = 0; michael@0: for each (let i in ids) { michael@0: if (i) { michael@0: blessedcount++; michael@0: } michael@0: } michael@0: // bug 600141 does not apply, as this does not have to support existing strings michael@0: document.getElementById("addonCount").value = michael@0: PluralForm.get(blessedcount, michael@0: this._stringBundle.GetStringFromName("addonsCount.label")) michael@0: .replace("#1", blessedcount); michael@0: } else { michael@0: document.getElementById("addonCount").hidden = true; michael@0: } michael@0: michael@0: this._case1Setup = true; michael@0: break; michael@0: case 2: michael@0: if (this._case2Setup) michael@0: break; michael@0: let count = 0; michael@0: function appendNode(label) { michael@0: let box = document.getElementById("clientList"); michael@0: let node = document.createElement("label"); michael@0: node.setAttribute("value", label); michael@0: node.setAttribute("class", "data indent"); michael@0: box.appendChild(node); michael@0: } michael@0: michael@0: for each (let name in Weave.Service.clientsEngine.stats.names) { michael@0: // Don't list the current client michael@0: if (name == Weave.Service.clientsEngine.localName) michael@0: continue; michael@0: michael@0: // Only show the first several client names michael@0: if (++count <= 5) michael@0: appendNode(name); michael@0: } michael@0: if (count > 5) { michael@0: // Support %S for historical reasons (see bug 600141) michael@0: let label = michael@0: PluralForm.get(count - 5, michael@0: this._stringBundle.GetStringFromName("additionalClientCount.label")) michael@0: .replace("%S", count - 5) michael@0: .replace("#1", count - 5); michael@0: appendNode(label); michael@0: } michael@0: this._case2Setup = true; michael@0: break; michael@0: } michael@0: michael@0: return true; michael@0: }, michael@0: michael@0: // sets class and string on a feedback element michael@0: // if no property string is passed in, we clear label/style michael@0: _setFeedback: function (element, success, string) { michael@0: element.hidden = success || !string; michael@0: let classname = success ? "success" : "error"; michael@0: let image = element.getElementsByAttribute("class", "statusIcon")[0]; michael@0: image.setAttribute("status", classname); michael@0: let label = element.getElementsByAttribute("class", "status")[0]; michael@0: label.value = string; michael@0: }, michael@0: michael@0: // shim michael@0: _setFeedbackMessage: function (element, success, string) { michael@0: let str = ""; michael@0: if (string) { michael@0: try { michael@0: str = this._stringBundle.GetStringFromName(string); michael@0: } catch(e) {} michael@0: michael@0: if (!str) michael@0: str = Weave.Utils.getErrorString(string); michael@0: } michael@0: this._setFeedback(element, success, str); michael@0: }, michael@0: michael@0: loadCaptcha: function loadCaptcha() { michael@0: let captchaURI = Weave.Service.miscAPI + "captcha_html"; michael@0: // First check for NoScript and whitelist the right sites. michael@0: this._handleNoScript(true); michael@0: if (this.captchaBrowser.currentURI.spec != captchaURI) { michael@0: this.captchaBrowser.loadURI(captchaURI); michael@0: } michael@0: }, michael@0: michael@0: onStateChange: function(webProgress, request, stateFlags, status) { michael@0: // We're only looking for the end of the frame load michael@0: if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0) michael@0: return; michael@0: if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) == 0) michael@0: return; michael@0: if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) == 0) michael@0: return; michael@0: michael@0: // If we didn't find a captcha, assume it's not needed and don't show it. michael@0: let responseStatus = request.QueryInterface(Ci.nsIHttpChannel).responseStatus; michael@0: setVisibility(this.captchaBrowser, responseStatus != 404); michael@0: //XXX TODO we should really log any responseStatus other than 200 michael@0: }, michael@0: onProgressChange: function() {}, michael@0: onStatusChange: function() {}, michael@0: onSecurityChange: function() {}, michael@0: onLocationChange: function () {} michael@0: }; michael@0: michael@0: // Define lazy getters for various XUL elements. michael@0: // michael@0: // onWizardAdvance() and onPageShow() are run before init(), so we'll even michael@0: // define things that will almost certainly be used (like 'wizard') as a lazy michael@0: // getter here. michael@0: ["wizard", michael@0: "pin1", michael@0: "pin2", michael@0: "pin3", michael@0: "pairDeviceErrorRow", michael@0: "pairDeviceThrobber"].forEach(function (id) { michael@0: XPCOMUtils.defineLazyGetter(gSyncSetup, id, function() { michael@0: return document.getElementById(id); michael@0: }); michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(gSyncSetup, "nextFocusEl", function () { michael@0: return {pin1: this.pin2, michael@0: pin2: this.pin3, michael@0: pin3: this.wizard.getButton("next")}; michael@0: }); michael@0: XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() { michael@0: return Services.strings.createBundle("chrome://browser/locale/syncSetup.properties"); michael@0: });