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