browser/base/content/sync/setup.js

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

     1 // -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
     2 /* This Source Code Form is subject to the terms of the Mozilla Public
     3  * License, v. 2.0. If a copy of the MPL was not distributed with this
     4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     6 const Ci = Components.interfaces;
     7 const Cc = Components.classes;
     8 const Cr = Components.results;
     9 const Cu = Components.utils;
    11 // page consts
    13 const PAIR_PAGE                     = 0;
    14 const INTRO_PAGE                    = 1;
    15 const NEW_ACCOUNT_START_PAGE        = 2;
    16 const EXISTING_ACCOUNT_CONNECT_PAGE = 3;
    17 const EXISTING_ACCOUNT_LOGIN_PAGE   = 4;
    18 const OPTIONS_PAGE                  = 5;
    19 const OPTIONS_CONFIRM_PAGE          = 6;
    21 // Broader than we'd like, but after this changed from api-secure.recaptcha.net
    22 // we had no choice. At least we only do this for the duration of setup.
    23 // See discussion in Bugs 508112 and 653307.
    24 const RECAPTCHA_DOMAIN = "https://www.google.com";
    26 const PIN_PART_LENGTH = 4;
    28 Cu.import("resource://services-sync/main.js");
    29 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    30 Cu.import("resource://gre/modules/Services.jsm");
    31 Cu.import("resource://gre/modules/PlacesUtils.jsm");
    32 Cu.import("resource://gre/modules/PluralForm.jsm");
    35 function setVisibility(element, visible) {
    36   element.style.visibility = visible ? "visible" : "hidden";
    37 }
    39 var gSyncSetup = {
    40   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
    41                                          Ci.nsIWebProgressListener,
    42                                          Ci.nsISupportsWeakReference]),
    44   captchaBrowser: null,
    45   wizard: null,
    46   _disabledSites: [],
    48   status: {
    49     password: false,
    50     email: false,
    51     server: false
    52   },
    54   get _remoteSites() [Weave.Service.serverURL, RECAPTCHA_DOMAIN],
    56   get _usingMainServers() {
    57     if (this._settingUpNew)
    58       return document.getElementById("server").selectedIndex == 0;
    59     return document.getElementById("existingServer").selectedIndex == 0;
    60   },
    62   init: function () {
    63     let obs = [
    64       ["weave:service:change-passphrase", "onResetPassphrase"],
    65       ["weave:service:login:start",       "onLoginStart"],
    66       ["weave:service:login:error",       "onLoginEnd"],
    67       ["weave:service:login:finish",      "onLoginEnd"]];
    69     // Add the observers now and remove them on unload
    70     let self = this;
    71     let addRem = function(add) {
    72       obs.forEach(function([topic, func]) {
    73         //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
    74         //        of `this`. Fix in a followup. (bug 583347)
    75         if (add)
    76           Weave.Svc.Obs.add(topic, self[func], self);
    77         else
    78           Weave.Svc.Obs.remove(topic, self[func], self);
    79       });
    80     };
    81     addRem(true);
    82     window.addEventListener("unload", function() addRem(false), false);
    84     window.setTimeout(function () {
    85       // Force Service to be loaded so that engines are registered.
    86       // See Bug 670082.
    87       Weave.Service;
    88     }, 0);
    90     this.captchaBrowser = document.getElementById("captcha");
    92     this.wizardType = null;
    93     if (window.arguments && window.arguments[0]) {
    94       this.wizardType = window.arguments[0];
    95     }
    96     switch (this.wizardType) {
    97       case null:
    98         this.wizard.pageIndex = INTRO_PAGE;
    99         // Fall through!
   100       case "pair":
   101         this.captchaBrowser.addProgressListener(this);
   102         Weave.Svc.Prefs.set("firstSync", "notReady");
   103         break;
   104       case "reset":
   105         this._resettingSync = true;
   106         this.wizard.pageIndex = OPTIONS_PAGE;
   107         break;
   108     }
   110     this.wizard.getButton("extra1").label =
   111       this._stringBundle.GetStringFromName("button.syncOptions.label");
   113     // Remember these values because the options pages change them temporarily.
   114     this._nextButtonLabel = this.wizard.getButton("next").label;
   115     this._nextButtonAccesskey = this.wizard.getButton("next")
   116                                            .getAttribute("accesskey");
   117     this._backButtonLabel = this.wizard.getButton("back").label;
   118     this._backButtonAccesskey = this.wizard.getButton("back")
   119                                            .getAttribute("accesskey");
   120   },
   122   startNewAccountSetup: function () {
   123     if (!Weave.Utils.ensureMPUnlocked())
   124       return false;
   125     this._settingUpNew = true;
   126     this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE;
   127   },
   129   useExistingAccount: function () {
   130     if (!Weave.Utils.ensureMPUnlocked())
   131       return false;
   132     this._settingUpNew = false;
   133     if (this.wizardType == "pair") {
   134       // We're already pairing, so there's no point in pairing again.
   135       // Go straight to the manual login page.
   136       this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
   137     } else {
   138       this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE;
   139     }
   140   },
   142   resetPassphrase: function resetPassphrase() {
   143     // Apply the existing form fields so that
   144     // Weave.Service.changePassphrase() has the necessary credentials.
   145     Weave.Service.identity.account = document.getElementById("existingAccountName").value;
   146     Weave.Service.identity.basicPassword = document.getElementById("existingPassword").value;
   148     // Generate a new passphrase so that Weave.Service.login() will
   149     // actually do something.
   150     let passphrase = Weave.Utils.generatePassphrase();
   151     Weave.Service.identity.syncKey = passphrase;
   153     // Only open the dialog if username + password are actually correct.
   154     Weave.Service.login();
   155     if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE,
   156          Weave.LOGIN_FAILED_NO_PASSPHRASE,
   157          Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) {
   158       return;
   159     }
   161     // Hide any errors about the passphrase, we know it's not right.
   162     let feedback = document.getElementById("existingPassphraseFeedbackRow");
   163     feedback.hidden = true;
   164     let el = document.getElementById("existingPassphrase");
   165     el.value = Weave.Utils.hyphenatePassphrase(passphrase);
   167     // changePassphrase() will sync, make sure we set the "firstSync" pref
   168     // according to the user's pref.
   169     Weave.Svc.Prefs.reset("firstSync");
   170     this.setupInitialSync();
   171     gSyncUtils.resetPassphrase(true);
   172   },
   174   onResetPassphrase: function () {
   175     document.getElementById("existingPassphrase").value =
   176       Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
   177     this.checkFields();
   178     this.wizard.advance();
   179   },
   181   onLoginStart: function () {
   182     this.toggleLoginFeedback(false);
   183   },
   185   onLoginEnd: function () {
   186     this.toggleLoginFeedback(true);
   187   },
   189   sendCredentialsAfterSync: function () {
   190     let send = function() {
   191       Services.obs.removeObserver("weave:service:sync:finish", send);
   192       Services.obs.removeObserver("weave:service:sync:error", send);
   193       let credentials = {account:   Weave.Service.identity.account,
   194                          password:  Weave.Service.identity.basicPassword,
   195                          synckey:   Weave.Service.identity.syncKey,
   196                          serverURL: Weave.Service.serverURL};
   197       this._jpakeclient.sendAndComplete(credentials);
   198     }.bind(this);
   199     Services.obs.addObserver("weave:service:sync:finish", send, false);
   200     Services.obs.addObserver("weave:service:sync:error", send, false);
   201   },
   203   toggleLoginFeedback: function (stop) {
   204     document.getElementById("login-throbber").hidden = stop;
   205     let password = document.getElementById("existingPasswordFeedbackRow");
   206     let server = document.getElementById("existingServerFeedbackRow");
   207     let passphrase = document.getElementById("existingPassphraseFeedbackRow");
   209     if (!stop || (Weave.Status.login == Weave.LOGIN_SUCCEEDED)) {
   210       password.hidden = server.hidden = passphrase.hidden = true;
   211       return;
   212     }
   214     let feedback;
   215     switch (Weave.Status.login) {
   216       case Weave.LOGIN_FAILED_NETWORK_ERROR:
   217       case Weave.LOGIN_FAILED_SERVER_ERROR:
   218         feedback = server;
   219         break;
   220       case Weave.LOGIN_FAILED_LOGIN_REJECTED:
   221       case Weave.LOGIN_FAILED_NO_USERNAME:
   222       case Weave.LOGIN_FAILED_NO_PASSWORD:
   223         feedback = password;
   224         break;
   225       case Weave.LOGIN_FAILED_INVALID_PASSPHRASE:
   226         feedback = passphrase;
   227         break;
   228     }
   229     this._setFeedbackMessage(feedback, false, Weave.Status.login);
   230   },
   232   setupInitialSync: function () {
   233     let action = document.getElementById("mergeChoiceRadio").selectedItem.id;
   234     switch (action) {
   235       case "resetClient":
   236         // if we're not resetting sync, we don't need to explicitly
   237         // call resetClient
   238         if (!this._resettingSync)
   239           return;
   240         // otherwise, fall through
   241       case "wipeClient":
   242       case "wipeRemote":
   243         Weave.Svc.Prefs.set("firstSync", action);
   244         break;
   245     }
   246   },
   248   // fun with validation!
   249   checkFields: function () {
   250     this.wizard.canAdvance = this.readyToAdvance();
   251   },
   253   readyToAdvance: function () {
   254     switch (this.wizard.pageIndex) {
   255       case INTRO_PAGE:
   256         return false;
   257       case NEW_ACCOUNT_START_PAGE:
   258         for (let i in this.status) {
   259           if (!this.status[i])
   260             return false;
   261         }
   262         if (this._usingMainServers)
   263           return document.getElementById("tos").checked;
   265         return true;
   266       case EXISTING_ACCOUNT_LOGIN_PAGE:
   267         let hasUser = document.getElementById("existingAccountName").value != "";
   268         let hasPass = document.getElementById("existingPassword").value != "";
   269         let hasKey = document.getElementById("existingPassphrase").value != "";
   271         if (hasUser && hasPass && hasKey) {
   272           if (this._usingMainServers)
   273             return true;
   275           if (this._validateServer(document.getElementById("existingServer"))) {
   276             return true;
   277           }
   278         }
   279         return false;
   280     }
   281     // Default, e.g. wizard's special page -1 etc.
   282     return true;
   283   },
   285   onPINInput: function onPINInput(textbox) {
   286     if (textbox && textbox.value.length == PIN_PART_LENGTH) {
   287       this.nextFocusEl[textbox.id].focus();
   288     }
   289     this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH &&
   290                               this.pin2.value.length == PIN_PART_LENGTH &&
   291                               this.pin3.value.length == PIN_PART_LENGTH);
   292   },
   294   onEmailInput: function () {
   295     // Check account validity when the user stops typing for 1 second.
   296     if (this._checkAccountTimer)
   297       window.clearTimeout(this._checkAccountTimer);
   298     this._checkAccountTimer = window.setTimeout(function () {
   299       gSyncSetup.checkAccount();
   300     }, 1000);
   301   },
   303   checkAccount: function() {
   304     delete this._checkAccountTimer;
   305     let value = Weave.Utils.normalizeAccount(
   306       document.getElementById("weaveEmail").value);
   307     if (!value) {
   308       this.status.email = false;
   309       this.checkFields();
   310       return;
   311     }
   313     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,}))$/;
   314     let feedback = document.getElementById("emailFeedbackRow");
   315     let valid = re.test(value);
   317     let str = "";
   318     if (!valid) {
   319       str = "invalidEmail.label";
   320     } else {
   321       let availCheck = Weave.Service.checkAccount(value);
   322       valid = availCheck == "available";
   323       if (!valid) {
   324         if (availCheck == "notAvailable")
   325           str = "usernameNotAvailable.label";
   326         else
   327           str = availCheck;
   328       }
   329     }
   331     this._setFeedbackMessage(feedback, valid, str);
   332     this.status.email = valid;
   333     if (valid)
   334       Weave.Service.identity.account = value;
   335     this.checkFields();
   336   },
   338   onPasswordChange: function () {
   339     let password = document.getElementById("weavePassword");
   340     let pwconfirm = document.getElementById("weavePasswordConfirm");
   341     let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm);
   343     let feedback = document.getElementById("passwordFeedbackRow");
   344     this._setFeedback(feedback, valid, errorString);
   346     this.status.password = valid;
   347     this.checkFields();
   348   },
   350   onPageShow: function() {
   351     switch (this.wizard.pageIndex) {
   352       case PAIR_PAGE:
   353         this.wizard.getButton("back").hidden = true;
   354         this.wizard.getButton("extra1").hidden = true;
   355         this.onPINInput();
   356         this.pin1.focus();
   357         break;
   358       case INTRO_PAGE:
   359         // We may not need the captcha in the Existing Account branch of the
   360         // wizard. However, we want to preload it to avoid any flickering while
   361         // the Create Account page is shown.
   362         this.loadCaptcha();
   363         this.wizard.getButton("next").hidden = true;
   364         this.wizard.getButton("back").hidden = true;
   365         this.wizard.getButton("extra1").hidden = true;
   366         this.checkFields();
   367         break;
   368       case NEW_ACCOUNT_START_PAGE:
   369         this.wizard.getButton("extra1").hidden = false;
   370         this.wizard.getButton("next").hidden = false;
   371         this.wizard.getButton("back").hidden = false;
   372         this.onServerCommand();
   373         this.wizard.canRewind = true;
   374         this.checkFields();
   375         break;
   376       case EXISTING_ACCOUNT_CONNECT_PAGE:
   377         Weave.Svc.Prefs.set("firstSync", "existingAccount");
   378         this.wizard.getButton("next").hidden = false;
   379         this.wizard.getButton("back").hidden = false;
   380         this.wizard.getButton("extra1").hidden = false;
   381         this.wizard.canAdvance = false;
   382         this.wizard.canRewind = true;
   383         this.startEasySetup();
   384         break;
   385       case EXISTING_ACCOUNT_LOGIN_PAGE:
   386         this.wizard.getButton("next").hidden = false;
   387         this.wizard.getButton("back").hidden = false;
   388         this.wizard.getButton("extra1").hidden = false;
   389         this.wizard.canRewind = true;
   390         this.checkFields();
   391         break;
   392       case OPTIONS_PAGE:
   393         this.wizard.canRewind = false;
   394         this.wizard.canAdvance = true;
   395         if (!this._resettingSync) {
   396           this.wizard.getButton("next").label =
   397             this._stringBundle.GetStringFromName("button.syncOptionsDone.label");
   398           this.wizard.getButton("next").removeAttribute("accesskey");
   399         }
   400         this.wizard.getButton("next").hidden = false;
   401         this.wizard.getButton("back").hidden = true;
   402         this.wizard.getButton("cancel").hidden = !this._resettingSync;
   403         this.wizard.getButton("extra1").hidden = true;
   404         document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
   405         document.getElementById("syncOptions").collapsed = this._resettingSync;
   406         document.getElementById("mergeOptions").collapsed = this._settingUpNew;
   407         break;
   408       case OPTIONS_CONFIRM_PAGE:
   409         this.wizard.canRewind = true;
   410         this.wizard.canAdvance = true;
   411         this.wizard.getButton("back").label =
   412           this._stringBundle.GetStringFromName("button.syncOptionsCancel.label");
   413         this.wizard.getButton("back").removeAttribute("accesskey");
   414         this.wizard.getButton("back").hidden = this._resettingSync;
   415         this.wizard.getButton("next").hidden = false;
   416         this.wizard.getButton("finish").hidden = true;
   417         break;
   418     }
   419   },
   421   onWizardAdvance: function () {
   422     // Check pageIndex so we don't prompt before the Sync setup wizard appears.
   423     // This is a fallback in case the Master Password gets locked mid-wizard.
   424     if ((this.wizard.pageIndex >= 0) &&
   425         !Weave.Utils.ensureMPUnlocked()) {
   426       return false;
   427     }
   429     switch (this.wizard.pageIndex) {
   430       case PAIR_PAGE:
   431         this.startPairing();
   432         return false;
   433       case NEW_ACCOUNT_START_PAGE:
   434         // If the user selects Next (e.g. by hitting enter) when we haven't
   435         // executed the delayed checks yet, execute them immediately.
   436         if (this._checkAccountTimer) {
   437           this.checkAccount();
   438         }
   439         if (this._checkServerTimer) {
   440           this.checkServer();
   441         }
   442         if (!this.wizard.canAdvance) {
   443           return false;
   444         }
   446         let doc = this.captchaBrowser.contentDocument;
   447         let getField = function getField(field) {
   448           let node = doc.getElementById("recaptcha_" + field + "_field");
   449           return node && node.value;
   450         };
   452         // Display throbber
   453         let feedback = document.getElementById("captchaFeedback");
   454         let image = feedback.firstChild;
   455         let label = image.nextSibling;
   456         image.setAttribute("status", "active");
   457         label.value = this._stringBundle.GetStringFromName("verifying.label");
   458         setVisibility(feedback, true);
   460         let password = document.getElementById("weavePassword").value;
   461         let email = Weave.Utils.normalizeAccount(
   462           document.getElementById("weaveEmail").value);
   463         let challenge = getField("challenge");
   464         let response = getField("response");
   466         let error = Weave.Service.createAccount(email, password,
   467                                                 challenge, response);
   469         if (error == null) {
   470           Weave.Service.identity.account = email;
   471           Weave.Service.identity.basicPassword = password;
   472           Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase();
   473           this._handleNoScript(false);
   474           Weave.Svc.Prefs.set("firstSync", "newAccount");
   475 #ifdef XP_WIN
   476 #ifdef MOZ_METRO
   477           if (document.getElementById("metroSetupCheckbox").checked) {
   478             Services.metro.storeSyncInfo(email, password, Weave.Service.identity.syncKey);
   479           }
   480 #endif
   481 #endif
   482           this.wizardFinish();
   483           return false;
   484         }
   486         image.setAttribute("status", "error");
   487         label.value = Weave.Utils.getErrorString(error);
   488         return false;
   489       case EXISTING_ACCOUNT_LOGIN_PAGE:
   490         Weave.Service.identity.account = Weave.Utils.normalizeAccount(
   491           document.getElementById("existingAccountName").value);
   492         Weave.Service.identity.basicPassword =
   493           document.getElementById("existingPassword").value;
   494         let pp = document.getElementById("existingPassphrase").value;
   495         Weave.Service.identity.syncKey = Weave.Utils.normalizePassphrase(pp);
   496         if (Weave.Service.login()) {
   497           this.wizardFinish();
   498         }
   499         return false;
   500       case OPTIONS_PAGE:
   501         let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
   502         // No confirmation needed on new account setup or merge option
   503         // with existing account.
   504         if (this._settingUpNew || (!this._resettingSync && desc == 0))
   505           return this.returnFromOptions();
   506         return this._handleChoice();
   507       case OPTIONS_CONFIRM_PAGE:
   508         if (this._resettingSync) {
   509           this.wizardFinish();
   510           return false;
   511         }
   512         return this.returnFromOptions();
   513     }
   514     return true;
   515   },
   517   onWizardBack: function () {
   518     switch (this.wizard.pageIndex) {
   519       case NEW_ACCOUNT_START_PAGE:
   520       case EXISTING_ACCOUNT_LOGIN_PAGE:
   521         this.wizard.pageIndex = INTRO_PAGE;
   522         return false;
   523       case EXISTING_ACCOUNT_CONNECT_PAGE:
   524         this.abortEasySetup();
   525         this.wizard.pageIndex = INTRO_PAGE;
   526         return false;
   527       case EXISTING_ACCOUNT_LOGIN_PAGE:
   528         // If we were already pairing on entry, we went straight to the manual
   529         // login page. If subsequently we go back, return to the page that lets
   530         // us choose whether we already have an account.
   531         if (this.wizardType == "pair") {
   532           this.wizard.pageIndex = INTRO_PAGE;
   533           return false;
   534         }
   535         return true;
   536       case OPTIONS_CONFIRM_PAGE:
   537         // Backing up from the confirmation page = resetting first sync to merge.
   538         document.getElementById("mergeChoiceRadio").selectedIndex = 0;
   539         return this.returnFromOptions();
   540     }
   541     return true;
   542   },
   544   wizardFinish: function () {
   545     this.setupInitialSync();
   547     if (this.wizardType == "pair") {
   548       this.completePairing();
   549     }
   551     if (!this._resettingSync) {
   552       function isChecked(element) {
   553         return document.getElementById(element).hasAttribute("checked");
   554       }
   556       let prefs = ["engine.bookmarks", "engine.passwords", "engine.history",
   557                    "engine.tabs", "engine.prefs", "engine.addons"];
   558       for (let i = 0;i < prefs.length;i++) {
   559         Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i]));
   560       }
   561       this._handleNoScript(false);
   562       if (Weave.Svc.Prefs.get("firstSync", "") == "notReady")
   563         Weave.Svc.Prefs.reset("firstSync");
   565       Weave.Service.persistLogin();
   566       Weave.Svc.Obs.notify("weave:service:setup-complete");
   568       gSyncUtils.openFirstSyncProgressPage();
   569     }
   570     Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
   571     window.close();
   572   },
   574   onWizardCancel: function () {
   575     if (this._resettingSync)
   576       return;
   578     this.abortEasySetup();
   579     this._handleNoScript(false);
   580     Weave.Service.startOver();
   581   },
   583   onSyncOptions: function () {
   584     this._beforeOptionsPage = this.wizard.pageIndex;
   585     this.wizard.pageIndex = OPTIONS_PAGE;
   586   },
   588   returnFromOptions: function() {
   589     this.wizard.getButton("next").label = this._nextButtonLabel;
   590     this.wizard.getButton("next").setAttribute("accesskey",
   591                                                this._nextButtonAccesskey);
   592     this.wizard.getButton("back").label = this._backButtonLabel;
   593     this.wizard.getButton("back").setAttribute("accesskey",
   594                                                this._backButtonAccesskey);
   595     this.wizard.getButton("cancel").hidden = false;
   596     this.wizard.getButton("extra1").hidden = false;
   597     this.wizard.pageIndex = this._beforeOptionsPage;
   598     return false;
   599   },
   601   startPairing: function startPairing() {
   602     this.pairDeviceErrorRow.hidden = true;
   603     // When onAbort is called, Weave may already be gone.
   604     const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
   606     let self = this;
   607     let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
   608       onPaired: function onPaired() {
   609         self.wizard.pageIndex = INTRO_PAGE;
   610       },
   611       onComplete: function onComplete() {
   612         // This method will never be called since SendCredentialsController
   613         // will take over after the wizard completes.
   614       },
   615       onAbort: function onAbort(error) {
   616         delete self._jpakeclient;
   618         // Aborted by user, ignore. The window is almost certainly going to close
   619         // or is already closed.
   620         if (error == JPAKE_ERROR_USERABORT) {
   621           return;
   622         }
   624         self.pairDeviceErrorRow.hidden = false;
   625         self.pairDeviceThrobber.hidden = true;
   626         self.pin1.value = self.pin2.value = self.pin3.value = "";
   627         self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
   628         if (self.wizard.pageIndex == PAIR_PAGE) {
   629           self.pin1.focus();
   630         }
   631       }
   632     });
   633     this.pairDeviceThrobber.hidden = false;
   634     this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
   635     this.wizard.canAdvance = false;
   637     let pin = this.pin1.value + this.pin2.value + this.pin3.value;
   638     let expectDelay = true;
   639     jpakeclient.pairWithPIN(pin, expectDelay);
   640   },
   642   completePairing: function completePairing() {
   643     if (!this._jpakeclient) {
   644       // The channel was aborted while we were setting up the account
   645       // locally. XXX TODO should we do anything here, e.g. tell
   646       // the user on the last wizard page that it's ok, they just
   647       // have to pair again?
   648       return;
   649     }
   650     let controller = new Weave.SendCredentialsController(this._jpakeclient,
   651                                                          Weave.Service);
   652     this._jpakeclient.controller = controller;
   653   },
   655   startEasySetup: function () {
   656     // Don't do anything if we have a client already (e.g. we went to
   657     // Sync Options and just came back).
   658     if (this._jpakeclient)
   659       return;
   661     // When onAbort is called, Weave may already be gone
   662     const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
   664     let self = this;
   665     this._jpakeclient = new Weave.JPAKEClient({
   666       displayPIN: function displayPIN(pin) {
   667         document.getElementById("easySetupPIN1").value = pin.slice(0, 4);
   668         document.getElementById("easySetupPIN2").value = pin.slice(4, 8);
   669         document.getElementById("easySetupPIN3").value = pin.slice(8);
   670       },
   672       onPairingStart: function onPairingStart() {},
   674       onComplete: function onComplete(credentials) {
   675         Weave.Service.identity.account = credentials.account;
   676         Weave.Service.identity.basicPassword = credentials.password;
   677         Weave.Service.identity.syncKey = credentials.synckey;
   678         Weave.Service.serverURL = credentials.serverURL;
   679         gSyncSetup.wizardFinish();
   680       },
   682       onAbort: function onAbort(error) {
   683         delete self._jpakeclient;
   685         // Ignore if wizard is aborted.
   686         if (error == JPAKE_ERROR_USERABORT)
   687           return;
   689         // Automatically go to manual setup if we couldn't acquire a channel.
   690         if (error == Weave.JPAKE_ERROR_CHANNEL) {
   691           self.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
   692           return;
   693         }
   695         // Restart on all other errors.
   696         self.startEasySetup();
   697       }
   698     });
   699     this._jpakeclient.receiveNoPIN();
   700   },
   702   abortEasySetup: function () {
   703     document.getElementById("easySetupPIN1").value = "";
   704     document.getElementById("easySetupPIN2").value = "";
   705     document.getElementById("easySetupPIN3").value = "";
   706     if (!this._jpakeclient)
   707       return;
   709     this._jpakeclient.abort();
   710     delete this._jpakeclient;
   711   },
   713   manualSetup: function () {
   714     this.abortEasySetup();
   715     this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
   716   },
   718   // _handleNoScript is needed because it blocks the captcha. So we temporarily
   719   // allow the necessary sites so that we can verify the user is in fact a human.
   720   // This was done with the help of Giorgio (NoScript author). See bug 508112.
   721   _handleNoScript: function (addExceptions) {
   722     // if NoScript isn't installed, or is disabled, bail out.
   723     let ns = Cc["@maone.net/noscript-service;1"];
   724     if (ns == null)
   725       return;
   727     ns = ns.getService().wrappedJSObject;
   728     if (addExceptions) {
   729       this._remoteSites.forEach(function(site) {
   730         site = ns.getSite(site);
   731         if (!ns.isJSEnabled(site)) {
   732           this._disabledSites.push(site); // save status
   733           ns.setJSEnabled(site, true); // allow site
   734         }
   735       }, this);
   736     }
   737     else {
   738       this._disabledSites.forEach(function(site) {
   739         ns.setJSEnabled(site, false);
   740       });
   741       this._disabledSites = [];
   742     }
   743   },
   745   onExistingServerCommand: function () {
   746     let control = document.getElementById("existingServer");
   747     if (control.selectedIndex == 0) {
   748       control.removeAttribute("editable");
   749       Weave.Svc.Prefs.reset("serverURL");
   750     } else {
   751       control.setAttribute("editable", "true");
   752       // Force a style flush to ensure that the binding is attached.
   753       control.clientTop;
   754       control.value = "";
   755       control.inputField.focus();
   756     }
   757     document.getElementById("existingServerFeedbackRow").hidden = true;
   758     this.checkFields();
   759   },
   761   onExistingServerInput: function () {
   762     // Check custom server validity when the user stops typing for 1 second.
   763     if (this._existingServerTimer)
   764       window.clearTimeout(this._existingServerTimer);
   765     this._existingServerTimer = window.setTimeout(function () {
   766       gSyncSetup.checkFields();
   767     }, 1000);
   768   },
   770   onServerCommand: function () {
   771     setVisibility(document.getElementById("TOSRow"), this._usingMainServers);
   772     let control = document.getElementById("server");
   773     if (!this._usingMainServers) {
   774       control.setAttribute("editable", "true");
   775       // Force a style flush to ensure that the binding is attached.
   776       control.clientTop;
   777       control.value = "";
   778       control.inputField.focus();
   779       // checkServer() will call checkAccount() and checkFields().
   780       this.checkServer();
   781       return;
   782     }
   783     control.removeAttribute("editable");
   784     Weave.Svc.Prefs.reset("serverURL");
   785     if (this._settingUpNew) {
   786       this.loadCaptcha();
   787     }
   788     this.checkAccount();
   789     this.status.server = true;
   790     document.getElementById("serverFeedbackRow").hidden = true;
   791     this.checkFields();
   792   },
   794   onServerInput: function () {
   795     // Check custom server validity when the user stops typing for 1 second.
   796     if (this._checkServerTimer)
   797       window.clearTimeout(this._checkServerTimer);
   798     this._checkServerTimer = window.setTimeout(function () {
   799       gSyncSetup.checkServer();
   800     }, 1000);
   801   },
   803   checkServer: function () {
   804     delete this._checkServerTimer;
   805     let el = document.getElementById("server");
   806     let valid = false;
   807     let feedback = document.getElementById("serverFeedbackRow");
   808     let str = "";
   809     if (el.value) {
   810       valid = this._validateServer(el);
   811       let str = valid ? "" : "serverInvalid.label";
   812       this._setFeedbackMessage(feedback, valid, str);
   813     }
   814     else
   815       this._setFeedbackMessage(feedback, true);
   817     // Recheck account against the new server.
   818     if (valid)
   819       this.checkAccount();
   821     this.status.server = valid;
   822     this.checkFields();
   823   },
   825   _validateServer: function (element) {
   826     let valid = false;
   827     let val = element.value;
   828     if (!val)
   829       return false;
   831     let uri = Weave.Utils.makeURI(val);
   833     if (!uri)
   834       uri = Weave.Utils.makeURI("https://" + val);
   836     if (uri && this._settingUpNew) {
   837       function isValid(uri) {
   838         Weave.Service.serverURL = uri.spec;
   839         let check = Weave.Service.checkAccount("a");
   840         return (check == "available" || check == "notAvailable");
   841       }
   843       if (uri.schemeIs("http")) {
   844         uri.scheme = "https";
   845         if (isValid(uri))
   846           valid = true;
   847         else
   848           // setting the scheme back to http
   849           uri.scheme = "http";
   850       }
   851       if (!valid)
   852         valid = isValid(uri);
   854       if (valid) {
   855         this.loadCaptcha();
   856       }
   857     }
   858     else if (uri) {
   859       valid = true;
   860       Weave.Service.serverURL = uri.spec;
   861     }
   863     if (valid)
   864       element.value = Weave.Service.serverURL;
   865     else
   866       Weave.Svc.Prefs.reset("serverURL");
   868     return valid;
   869   },
   871   _handleChoice: function () {
   872     let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
   873     document.getElementById("chosenActionDeck").selectedIndex = desc;
   874     switch (desc) {
   875       case 1:
   876         if (this._case1Setup)
   877           break;
   879         let places_db = PlacesUtils.history
   880                                    .QueryInterface(Ci.nsPIPlacesDatabase)
   881                                    .DBConnection;
   882         if (Weave.Service.engineManager.get("history").enabled) {
   883           let daysOfHistory = 0;
   884           let stm = places_db.createStatement(
   885             "SELECT ROUND(( " +
   886               "strftime('%s','now','localtime','utc') - " +
   887               "( " +
   888                 "SELECT visit_date FROM moz_historyvisits " +
   889                 "ORDER BY visit_date ASC LIMIT 1 " +
   890                 ")/1000000 " +
   891               ")/86400) AS daysOfHistory ");
   893           if (stm.step())
   894             daysOfHistory = stm.getInt32(0);
   895           // Support %S for historical reasons (see bug 600141)
   896           document.getElementById("historyCount").value =
   897             PluralForm.get(daysOfHistory,
   898                            this._stringBundle.GetStringFromName("historyDaysCount.label"))
   899                       .replace("%S", daysOfHistory)
   900                       .replace("#1", daysOfHistory);
   901         } else {
   902           document.getElementById("historyCount").hidden = true;
   903         }
   905         if (Weave.Service.engineManager.get("bookmarks").enabled) {
   906           let bookmarks = 0;
   907           let stm = places_db.createStatement(
   908             "SELECT count(*) AS bookmarks " +
   909             "FROM moz_bookmarks b " +
   910             "LEFT JOIN moz_bookmarks t ON " +
   911             "b.parent = t.id WHERE b.type = 1 AND t.parent <> :tag");
   912           stm.params.tag = PlacesUtils.tagsFolderId;
   913           if (stm.executeStep())
   914             bookmarks = stm.row.bookmarks;
   915           // Support %S for historical reasons (see bug 600141)
   916           document.getElementById("bookmarkCount").value =
   917             PluralForm.get(bookmarks,
   918                            this._stringBundle.GetStringFromName("bookmarksCount.label"))
   919                       .replace("%S", bookmarks)
   920                       .replace("#1", bookmarks);
   921         } else {
   922           document.getElementById("bookmarkCount").hidden = true;
   923         }
   925         if (Weave.Service.engineManager.get("passwords").enabled) {
   926           let logins = Services.logins.getAllLogins({});
   927           // Support %S for historical reasons (see bug 600141)
   928           document.getElementById("passwordCount").value =
   929             PluralForm.get(logins.length,
   930                            this._stringBundle.GetStringFromName("passwordsCount.label"))
   931                       .replace("%S", logins.length)
   932                       .replace("#1", logins.length);
   933         } else {
   934           document.getElementById("passwordCount").hidden = true;
   935         }
   937         if (!Weave.Service.engineManager.get("prefs").enabled) {
   938           document.getElementById("prefsWipe").hidden = true;
   939         }
   941         let addonsEngine = Weave.Service.engineManager.get("addons");
   942         if (addonsEngine.enabled) {
   943           let ids = addonsEngine._store.getAllIDs();
   944           let blessedcount = 0;
   945           for each (let i in ids) {
   946             if (i) {
   947               blessedcount++;
   948             }
   949           }
   950           // bug 600141 does not apply, as this does not have to support existing strings
   951           document.getElementById("addonCount").value =
   952             PluralForm.get(blessedcount,
   953                            this._stringBundle.GetStringFromName("addonsCount.label"))
   954                       .replace("#1", blessedcount);
   955         } else {
   956           document.getElementById("addonCount").hidden = true;
   957         }
   959         this._case1Setup = true;
   960         break;
   961       case 2:
   962         if (this._case2Setup)
   963           break;
   964         let count = 0;
   965         function appendNode(label) {
   966           let box = document.getElementById("clientList");
   967           let node = document.createElement("label");
   968           node.setAttribute("value", label);
   969           node.setAttribute("class", "data indent");
   970           box.appendChild(node);
   971         }
   973         for each (let name in Weave.Service.clientsEngine.stats.names) {
   974           // Don't list the current client
   975           if (name == Weave.Service.clientsEngine.localName)
   976             continue;
   978           // Only show the first several client names
   979           if (++count <= 5)
   980             appendNode(name);
   981         }
   982         if (count > 5) {
   983           // Support %S for historical reasons (see bug 600141)
   984           let label =
   985             PluralForm.get(count - 5,
   986                            this._stringBundle.GetStringFromName("additionalClientCount.label"))
   987                       .replace("%S", count - 5)
   988                       .replace("#1", count - 5);
   989           appendNode(label);
   990         }
   991         this._case2Setup = true;
   992         break;
   993     }
   995     return true;
   996   },
   998   // sets class and string on a feedback element
   999   // if no property string is passed in, we clear label/style
  1000   _setFeedback: function (element, success, string) {
  1001     element.hidden = success || !string;
  1002     let classname = success ? "success" : "error";
  1003     let image = element.getElementsByAttribute("class", "statusIcon")[0];
  1004     image.setAttribute("status", classname);
  1005     let label = element.getElementsByAttribute("class", "status")[0];
  1006     label.value = string;
  1007   },
  1009   // shim
  1010   _setFeedbackMessage: function (element, success, string) {
  1011     let str = "";
  1012     if (string) {
  1013       try {
  1014         str = this._stringBundle.GetStringFromName(string);
  1015       } catch(e) {}
  1017       if (!str)
  1018         str = Weave.Utils.getErrorString(string);
  1020     this._setFeedback(element, success, str);
  1021   },
  1023   loadCaptcha: function loadCaptcha() {
  1024     let captchaURI = Weave.Service.miscAPI + "captcha_html";
  1025     // First check for NoScript and whitelist the right sites.
  1026     this._handleNoScript(true);
  1027     if (this.captchaBrowser.currentURI.spec != captchaURI) {
  1028       this.captchaBrowser.loadURI(captchaURI);
  1030   },
  1032   onStateChange: function(webProgress, request, stateFlags, status) {
  1033     // We're only looking for the end of the frame load
  1034     if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0)
  1035       return;
  1036     if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) == 0)
  1037       return;
  1038     if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) == 0)
  1039       return;
  1041     // If we didn't find a captcha, assume it's not needed and don't show it.
  1042     let responseStatus = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
  1043     setVisibility(this.captchaBrowser, responseStatus != 404);
  1044     //XXX TODO we should really log any responseStatus other than 200
  1045   },
  1046   onProgressChange: function() {},
  1047   onStatusChange: function() {},
  1048   onSecurityChange: function() {},
  1049   onLocationChange: function () {}
  1050 };
  1052 // Define lazy getters for various XUL elements.
  1053 //
  1054 // onWizardAdvance() and onPageShow() are run before init(), so we'll even
  1055 // define things that will almost certainly be used (like 'wizard') as a lazy
  1056 // getter here.
  1057 ["wizard",
  1058  "pin1",
  1059  "pin2",
  1060  "pin3",
  1061  "pairDeviceErrorRow",
  1062  "pairDeviceThrobber"].forEach(function (id) {
  1063   XPCOMUtils.defineLazyGetter(gSyncSetup, id, function() {
  1064     return document.getElementById(id);
  1065   });
  1066 });
  1067 XPCOMUtils.defineLazyGetter(gSyncSetup, "nextFocusEl", function () {
  1068   return {pin1: this.pin2,
  1069           pin2: this.pin3,
  1070           pin3: this.wizard.getButton("next")};
  1071 });
  1072 XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() {
  1073   return Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
  1074 });

mercurial