1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/browser/base/content/sync/setup.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1074 @@ 1.4 +// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*- 1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.8 + 1.9 +const Ci = Components.interfaces; 1.10 +const Cc = Components.classes; 1.11 +const Cr = Components.results; 1.12 +const Cu = Components.utils; 1.13 + 1.14 +// page consts 1.15 + 1.16 +const PAIR_PAGE = 0; 1.17 +const INTRO_PAGE = 1; 1.18 +const NEW_ACCOUNT_START_PAGE = 2; 1.19 +const EXISTING_ACCOUNT_CONNECT_PAGE = 3; 1.20 +const EXISTING_ACCOUNT_LOGIN_PAGE = 4; 1.21 +const OPTIONS_PAGE = 5; 1.22 +const OPTIONS_CONFIRM_PAGE = 6; 1.23 + 1.24 +// Broader than we'd like, but after this changed from api-secure.recaptcha.net 1.25 +// we had no choice. At least we only do this for the duration of setup. 1.26 +// See discussion in Bugs 508112 and 653307. 1.27 +const RECAPTCHA_DOMAIN = "https://www.google.com"; 1.28 + 1.29 +const PIN_PART_LENGTH = 4; 1.30 + 1.31 +Cu.import("resource://services-sync/main.js"); 1.32 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.33 +Cu.import("resource://gre/modules/Services.jsm"); 1.34 +Cu.import("resource://gre/modules/PlacesUtils.jsm"); 1.35 +Cu.import("resource://gre/modules/PluralForm.jsm"); 1.36 + 1.37 + 1.38 +function setVisibility(element, visible) { 1.39 + element.style.visibility = visible ? "visible" : "hidden"; 1.40 +} 1.41 + 1.42 +var gSyncSetup = { 1.43 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports, 1.44 + Ci.nsIWebProgressListener, 1.45 + Ci.nsISupportsWeakReference]), 1.46 + 1.47 + captchaBrowser: null, 1.48 + wizard: null, 1.49 + _disabledSites: [], 1.50 + 1.51 + status: { 1.52 + password: false, 1.53 + email: false, 1.54 + server: false 1.55 + }, 1.56 + 1.57 + get _remoteSites() [Weave.Service.serverURL, RECAPTCHA_DOMAIN], 1.58 + 1.59 + get _usingMainServers() { 1.60 + if (this._settingUpNew) 1.61 + return document.getElementById("server").selectedIndex == 0; 1.62 + return document.getElementById("existingServer").selectedIndex == 0; 1.63 + }, 1.64 + 1.65 + init: function () { 1.66 + let obs = [ 1.67 + ["weave:service:change-passphrase", "onResetPassphrase"], 1.68 + ["weave:service:login:start", "onLoginStart"], 1.69 + ["weave:service:login:error", "onLoginEnd"], 1.70 + ["weave:service:login:finish", "onLoginEnd"]]; 1.71 + 1.72 + // Add the observers now and remove them on unload 1.73 + let self = this; 1.74 + let addRem = function(add) { 1.75 + obs.forEach(function([topic, func]) { 1.76 + //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling 1.77 + // of `this`. Fix in a followup. (bug 583347) 1.78 + if (add) 1.79 + Weave.Svc.Obs.add(topic, self[func], self); 1.80 + else 1.81 + Weave.Svc.Obs.remove(topic, self[func], self); 1.82 + }); 1.83 + }; 1.84 + addRem(true); 1.85 + window.addEventListener("unload", function() addRem(false), false); 1.86 + 1.87 + window.setTimeout(function () { 1.88 + // Force Service to be loaded so that engines are registered. 1.89 + // See Bug 670082. 1.90 + Weave.Service; 1.91 + }, 0); 1.92 + 1.93 + this.captchaBrowser = document.getElementById("captcha"); 1.94 + 1.95 + this.wizardType = null; 1.96 + if (window.arguments && window.arguments[0]) { 1.97 + this.wizardType = window.arguments[0]; 1.98 + } 1.99 + switch (this.wizardType) { 1.100 + case null: 1.101 + this.wizard.pageIndex = INTRO_PAGE; 1.102 + // Fall through! 1.103 + case "pair": 1.104 + this.captchaBrowser.addProgressListener(this); 1.105 + Weave.Svc.Prefs.set("firstSync", "notReady"); 1.106 + break; 1.107 + case "reset": 1.108 + this._resettingSync = true; 1.109 + this.wizard.pageIndex = OPTIONS_PAGE; 1.110 + break; 1.111 + } 1.112 + 1.113 + this.wizard.getButton("extra1").label = 1.114 + this._stringBundle.GetStringFromName("button.syncOptions.label"); 1.115 + 1.116 + // Remember these values because the options pages change them temporarily. 1.117 + this._nextButtonLabel = this.wizard.getButton("next").label; 1.118 + this._nextButtonAccesskey = this.wizard.getButton("next") 1.119 + .getAttribute("accesskey"); 1.120 + this._backButtonLabel = this.wizard.getButton("back").label; 1.121 + this._backButtonAccesskey = this.wizard.getButton("back") 1.122 + .getAttribute("accesskey"); 1.123 + }, 1.124 + 1.125 + startNewAccountSetup: function () { 1.126 + if (!Weave.Utils.ensureMPUnlocked()) 1.127 + return false; 1.128 + this._settingUpNew = true; 1.129 + this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE; 1.130 + }, 1.131 + 1.132 + useExistingAccount: function () { 1.133 + if (!Weave.Utils.ensureMPUnlocked()) 1.134 + return false; 1.135 + this._settingUpNew = false; 1.136 + if (this.wizardType == "pair") { 1.137 + // We're already pairing, so there's no point in pairing again. 1.138 + // Go straight to the manual login page. 1.139 + this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE; 1.140 + } else { 1.141 + this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE; 1.142 + } 1.143 + }, 1.144 + 1.145 + resetPassphrase: function resetPassphrase() { 1.146 + // Apply the existing form fields so that 1.147 + // Weave.Service.changePassphrase() has the necessary credentials. 1.148 + Weave.Service.identity.account = document.getElementById("existingAccountName").value; 1.149 + Weave.Service.identity.basicPassword = document.getElementById("existingPassword").value; 1.150 + 1.151 + // Generate a new passphrase so that Weave.Service.login() will 1.152 + // actually do something. 1.153 + let passphrase = Weave.Utils.generatePassphrase(); 1.154 + Weave.Service.identity.syncKey = passphrase; 1.155 + 1.156 + // Only open the dialog if username + password are actually correct. 1.157 + Weave.Service.login(); 1.158 + if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE, 1.159 + Weave.LOGIN_FAILED_NO_PASSPHRASE, 1.160 + Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) { 1.161 + return; 1.162 + } 1.163 + 1.164 + // Hide any errors about the passphrase, we know it's not right. 1.165 + let feedback = document.getElementById("existingPassphraseFeedbackRow"); 1.166 + feedback.hidden = true; 1.167 + let el = document.getElementById("existingPassphrase"); 1.168 + el.value = Weave.Utils.hyphenatePassphrase(passphrase); 1.169 + 1.170 + // changePassphrase() will sync, make sure we set the "firstSync" pref 1.171 + // according to the user's pref. 1.172 + Weave.Svc.Prefs.reset("firstSync"); 1.173 + this.setupInitialSync(); 1.174 + gSyncUtils.resetPassphrase(true); 1.175 + }, 1.176 + 1.177 + onResetPassphrase: function () { 1.178 + document.getElementById("existingPassphrase").value = 1.179 + Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey); 1.180 + this.checkFields(); 1.181 + this.wizard.advance(); 1.182 + }, 1.183 + 1.184 + onLoginStart: function () { 1.185 + this.toggleLoginFeedback(false); 1.186 + }, 1.187 + 1.188 + onLoginEnd: function () { 1.189 + this.toggleLoginFeedback(true); 1.190 + }, 1.191 + 1.192 + sendCredentialsAfterSync: function () { 1.193 + let send = function() { 1.194 + Services.obs.removeObserver("weave:service:sync:finish", send); 1.195 + Services.obs.removeObserver("weave:service:sync:error", send); 1.196 + let credentials = {account: Weave.Service.identity.account, 1.197 + password: Weave.Service.identity.basicPassword, 1.198 + synckey: Weave.Service.identity.syncKey, 1.199 + serverURL: Weave.Service.serverURL}; 1.200 + this._jpakeclient.sendAndComplete(credentials); 1.201 + }.bind(this); 1.202 + Services.obs.addObserver("weave:service:sync:finish", send, false); 1.203 + Services.obs.addObserver("weave:service:sync:error", send, false); 1.204 + }, 1.205 + 1.206 + toggleLoginFeedback: function (stop) { 1.207 + document.getElementById("login-throbber").hidden = stop; 1.208 + let password = document.getElementById("existingPasswordFeedbackRow"); 1.209 + let server = document.getElementById("existingServerFeedbackRow"); 1.210 + let passphrase = document.getElementById("existingPassphraseFeedbackRow"); 1.211 + 1.212 + if (!stop || (Weave.Status.login == Weave.LOGIN_SUCCEEDED)) { 1.213 + password.hidden = server.hidden = passphrase.hidden = true; 1.214 + return; 1.215 + } 1.216 + 1.217 + let feedback; 1.218 + switch (Weave.Status.login) { 1.219 + case Weave.LOGIN_FAILED_NETWORK_ERROR: 1.220 + case Weave.LOGIN_FAILED_SERVER_ERROR: 1.221 + feedback = server; 1.222 + break; 1.223 + case Weave.LOGIN_FAILED_LOGIN_REJECTED: 1.224 + case Weave.LOGIN_FAILED_NO_USERNAME: 1.225 + case Weave.LOGIN_FAILED_NO_PASSWORD: 1.226 + feedback = password; 1.227 + break; 1.228 + case Weave.LOGIN_FAILED_INVALID_PASSPHRASE: 1.229 + feedback = passphrase; 1.230 + break; 1.231 + } 1.232 + this._setFeedbackMessage(feedback, false, Weave.Status.login); 1.233 + }, 1.234 + 1.235 + setupInitialSync: function () { 1.236 + let action = document.getElementById("mergeChoiceRadio").selectedItem.id; 1.237 + switch (action) { 1.238 + case "resetClient": 1.239 + // if we're not resetting sync, we don't need to explicitly 1.240 + // call resetClient 1.241 + if (!this._resettingSync) 1.242 + return; 1.243 + // otherwise, fall through 1.244 + case "wipeClient": 1.245 + case "wipeRemote": 1.246 + Weave.Svc.Prefs.set("firstSync", action); 1.247 + break; 1.248 + } 1.249 + }, 1.250 + 1.251 + // fun with validation! 1.252 + checkFields: function () { 1.253 + this.wizard.canAdvance = this.readyToAdvance(); 1.254 + }, 1.255 + 1.256 + readyToAdvance: function () { 1.257 + switch (this.wizard.pageIndex) { 1.258 + case INTRO_PAGE: 1.259 + return false; 1.260 + case NEW_ACCOUNT_START_PAGE: 1.261 + for (let i in this.status) { 1.262 + if (!this.status[i]) 1.263 + return false; 1.264 + } 1.265 + if (this._usingMainServers) 1.266 + return document.getElementById("tos").checked; 1.267 + 1.268 + return true; 1.269 + case EXISTING_ACCOUNT_LOGIN_PAGE: 1.270 + let hasUser = document.getElementById("existingAccountName").value != ""; 1.271 + let hasPass = document.getElementById("existingPassword").value != ""; 1.272 + let hasKey = document.getElementById("existingPassphrase").value != ""; 1.273 + 1.274 + if (hasUser && hasPass && hasKey) { 1.275 + if (this._usingMainServers) 1.276 + return true; 1.277 + 1.278 + if (this._validateServer(document.getElementById("existingServer"))) { 1.279 + return true; 1.280 + } 1.281 + } 1.282 + return false; 1.283 + } 1.284 + // Default, e.g. wizard's special page -1 etc. 1.285 + return true; 1.286 + }, 1.287 + 1.288 + onPINInput: function onPINInput(textbox) { 1.289 + if (textbox && textbox.value.length == PIN_PART_LENGTH) { 1.290 + this.nextFocusEl[textbox.id].focus(); 1.291 + } 1.292 + this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH && 1.293 + this.pin2.value.length == PIN_PART_LENGTH && 1.294 + this.pin3.value.length == PIN_PART_LENGTH); 1.295 + }, 1.296 + 1.297 + onEmailInput: function () { 1.298 + // Check account validity when the user stops typing for 1 second. 1.299 + if (this._checkAccountTimer) 1.300 + window.clearTimeout(this._checkAccountTimer); 1.301 + this._checkAccountTimer = window.setTimeout(function () { 1.302 + gSyncSetup.checkAccount(); 1.303 + }, 1000); 1.304 + }, 1.305 + 1.306 + checkAccount: function() { 1.307 + delete this._checkAccountTimer; 1.308 + let value = Weave.Utils.normalizeAccount( 1.309 + document.getElementById("weaveEmail").value); 1.310 + if (!value) { 1.311 + this.status.email = false; 1.312 + this.checkFields(); 1.313 + return; 1.314 + } 1.315 + 1.316 + 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,}))$/; 1.317 + let feedback = document.getElementById("emailFeedbackRow"); 1.318 + let valid = re.test(value); 1.319 + 1.320 + let str = ""; 1.321 + if (!valid) { 1.322 + str = "invalidEmail.label"; 1.323 + } else { 1.324 + let availCheck = Weave.Service.checkAccount(value); 1.325 + valid = availCheck == "available"; 1.326 + if (!valid) { 1.327 + if (availCheck == "notAvailable") 1.328 + str = "usernameNotAvailable.label"; 1.329 + else 1.330 + str = availCheck; 1.331 + } 1.332 + } 1.333 + 1.334 + this._setFeedbackMessage(feedback, valid, str); 1.335 + this.status.email = valid; 1.336 + if (valid) 1.337 + Weave.Service.identity.account = value; 1.338 + this.checkFields(); 1.339 + }, 1.340 + 1.341 + onPasswordChange: function () { 1.342 + let password = document.getElementById("weavePassword"); 1.343 + let pwconfirm = document.getElementById("weavePasswordConfirm"); 1.344 + let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm); 1.345 + 1.346 + let feedback = document.getElementById("passwordFeedbackRow"); 1.347 + this._setFeedback(feedback, valid, errorString); 1.348 + 1.349 + this.status.password = valid; 1.350 + this.checkFields(); 1.351 + }, 1.352 + 1.353 + onPageShow: function() { 1.354 + switch (this.wizard.pageIndex) { 1.355 + case PAIR_PAGE: 1.356 + this.wizard.getButton("back").hidden = true; 1.357 + this.wizard.getButton("extra1").hidden = true; 1.358 + this.onPINInput(); 1.359 + this.pin1.focus(); 1.360 + break; 1.361 + case INTRO_PAGE: 1.362 + // We may not need the captcha in the Existing Account branch of the 1.363 + // wizard. However, we want to preload it to avoid any flickering while 1.364 + // the Create Account page is shown. 1.365 + this.loadCaptcha(); 1.366 + this.wizard.getButton("next").hidden = true; 1.367 + this.wizard.getButton("back").hidden = true; 1.368 + this.wizard.getButton("extra1").hidden = true; 1.369 + this.checkFields(); 1.370 + break; 1.371 + case NEW_ACCOUNT_START_PAGE: 1.372 + this.wizard.getButton("extra1").hidden = false; 1.373 + this.wizard.getButton("next").hidden = false; 1.374 + this.wizard.getButton("back").hidden = false; 1.375 + this.onServerCommand(); 1.376 + this.wizard.canRewind = true; 1.377 + this.checkFields(); 1.378 + break; 1.379 + case EXISTING_ACCOUNT_CONNECT_PAGE: 1.380 + Weave.Svc.Prefs.set("firstSync", "existingAccount"); 1.381 + this.wizard.getButton("next").hidden = false; 1.382 + this.wizard.getButton("back").hidden = false; 1.383 + this.wizard.getButton("extra1").hidden = false; 1.384 + this.wizard.canAdvance = false; 1.385 + this.wizard.canRewind = true; 1.386 + this.startEasySetup(); 1.387 + break; 1.388 + case EXISTING_ACCOUNT_LOGIN_PAGE: 1.389 + this.wizard.getButton("next").hidden = false; 1.390 + this.wizard.getButton("back").hidden = false; 1.391 + this.wizard.getButton("extra1").hidden = false; 1.392 + this.wizard.canRewind = true; 1.393 + this.checkFields(); 1.394 + break; 1.395 + case OPTIONS_PAGE: 1.396 + this.wizard.canRewind = false; 1.397 + this.wizard.canAdvance = true; 1.398 + if (!this._resettingSync) { 1.399 + this.wizard.getButton("next").label = 1.400 + this._stringBundle.GetStringFromName("button.syncOptionsDone.label"); 1.401 + this.wizard.getButton("next").removeAttribute("accesskey"); 1.402 + } 1.403 + this.wizard.getButton("next").hidden = false; 1.404 + this.wizard.getButton("back").hidden = true; 1.405 + this.wizard.getButton("cancel").hidden = !this._resettingSync; 1.406 + this.wizard.getButton("extra1").hidden = true; 1.407 + document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName; 1.408 + document.getElementById("syncOptions").collapsed = this._resettingSync; 1.409 + document.getElementById("mergeOptions").collapsed = this._settingUpNew; 1.410 + break; 1.411 + case OPTIONS_CONFIRM_PAGE: 1.412 + this.wizard.canRewind = true; 1.413 + this.wizard.canAdvance = true; 1.414 + this.wizard.getButton("back").label = 1.415 + this._stringBundle.GetStringFromName("button.syncOptionsCancel.label"); 1.416 + this.wizard.getButton("back").removeAttribute("accesskey"); 1.417 + this.wizard.getButton("back").hidden = this._resettingSync; 1.418 + this.wizard.getButton("next").hidden = false; 1.419 + this.wizard.getButton("finish").hidden = true; 1.420 + break; 1.421 + } 1.422 + }, 1.423 + 1.424 + onWizardAdvance: function () { 1.425 + // Check pageIndex so we don't prompt before the Sync setup wizard appears. 1.426 + // This is a fallback in case the Master Password gets locked mid-wizard. 1.427 + if ((this.wizard.pageIndex >= 0) && 1.428 + !Weave.Utils.ensureMPUnlocked()) { 1.429 + return false; 1.430 + } 1.431 + 1.432 + switch (this.wizard.pageIndex) { 1.433 + case PAIR_PAGE: 1.434 + this.startPairing(); 1.435 + return false; 1.436 + case NEW_ACCOUNT_START_PAGE: 1.437 + // If the user selects Next (e.g. by hitting enter) when we haven't 1.438 + // executed the delayed checks yet, execute them immediately. 1.439 + if (this._checkAccountTimer) { 1.440 + this.checkAccount(); 1.441 + } 1.442 + if (this._checkServerTimer) { 1.443 + this.checkServer(); 1.444 + } 1.445 + if (!this.wizard.canAdvance) { 1.446 + return false; 1.447 + } 1.448 + 1.449 + let doc = this.captchaBrowser.contentDocument; 1.450 + let getField = function getField(field) { 1.451 + let node = doc.getElementById("recaptcha_" + field + "_field"); 1.452 + return node && node.value; 1.453 + }; 1.454 + 1.455 + // Display throbber 1.456 + let feedback = document.getElementById("captchaFeedback"); 1.457 + let image = feedback.firstChild; 1.458 + let label = image.nextSibling; 1.459 + image.setAttribute("status", "active"); 1.460 + label.value = this._stringBundle.GetStringFromName("verifying.label"); 1.461 + setVisibility(feedback, true); 1.462 + 1.463 + let password = document.getElementById("weavePassword").value; 1.464 + let email = Weave.Utils.normalizeAccount( 1.465 + document.getElementById("weaveEmail").value); 1.466 + let challenge = getField("challenge"); 1.467 + let response = getField("response"); 1.468 + 1.469 + let error = Weave.Service.createAccount(email, password, 1.470 + challenge, response); 1.471 + 1.472 + if (error == null) { 1.473 + Weave.Service.identity.account = email; 1.474 + Weave.Service.identity.basicPassword = password; 1.475 + Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase(); 1.476 + this._handleNoScript(false); 1.477 + Weave.Svc.Prefs.set("firstSync", "newAccount"); 1.478 +#ifdef XP_WIN 1.479 +#ifdef MOZ_METRO 1.480 + if (document.getElementById("metroSetupCheckbox").checked) { 1.481 + Services.metro.storeSyncInfo(email, password, Weave.Service.identity.syncKey); 1.482 + } 1.483 +#endif 1.484 +#endif 1.485 + this.wizardFinish(); 1.486 + return false; 1.487 + } 1.488 + 1.489 + image.setAttribute("status", "error"); 1.490 + label.value = Weave.Utils.getErrorString(error); 1.491 + return false; 1.492 + case EXISTING_ACCOUNT_LOGIN_PAGE: 1.493 + Weave.Service.identity.account = Weave.Utils.normalizeAccount( 1.494 + document.getElementById("existingAccountName").value); 1.495 + Weave.Service.identity.basicPassword = 1.496 + document.getElementById("existingPassword").value; 1.497 + let pp = document.getElementById("existingPassphrase").value; 1.498 + Weave.Service.identity.syncKey = Weave.Utils.normalizePassphrase(pp); 1.499 + if (Weave.Service.login()) { 1.500 + this.wizardFinish(); 1.501 + } 1.502 + return false; 1.503 + case OPTIONS_PAGE: 1.504 + let desc = document.getElementById("mergeChoiceRadio").selectedIndex; 1.505 + // No confirmation needed on new account setup or merge option 1.506 + // with existing account. 1.507 + if (this._settingUpNew || (!this._resettingSync && desc == 0)) 1.508 + return this.returnFromOptions(); 1.509 + return this._handleChoice(); 1.510 + case OPTIONS_CONFIRM_PAGE: 1.511 + if (this._resettingSync) { 1.512 + this.wizardFinish(); 1.513 + return false; 1.514 + } 1.515 + return this.returnFromOptions(); 1.516 + } 1.517 + return true; 1.518 + }, 1.519 + 1.520 + onWizardBack: function () { 1.521 + switch (this.wizard.pageIndex) { 1.522 + case NEW_ACCOUNT_START_PAGE: 1.523 + case EXISTING_ACCOUNT_LOGIN_PAGE: 1.524 + this.wizard.pageIndex = INTRO_PAGE; 1.525 + return false; 1.526 + case EXISTING_ACCOUNT_CONNECT_PAGE: 1.527 + this.abortEasySetup(); 1.528 + this.wizard.pageIndex = INTRO_PAGE; 1.529 + return false; 1.530 + case EXISTING_ACCOUNT_LOGIN_PAGE: 1.531 + // If we were already pairing on entry, we went straight to the manual 1.532 + // login page. If subsequently we go back, return to the page that lets 1.533 + // us choose whether we already have an account. 1.534 + if (this.wizardType == "pair") { 1.535 + this.wizard.pageIndex = INTRO_PAGE; 1.536 + return false; 1.537 + } 1.538 + return true; 1.539 + case OPTIONS_CONFIRM_PAGE: 1.540 + // Backing up from the confirmation page = resetting first sync to merge. 1.541 + document.getElementById("mergeChoiceRadio").selectedIndex = 0; 1.542 + return this.returnFromOptions(); 1.543 + } 1.544 + return true; 1.545 + }, 1.546 + 1.547 + wizardFinish: function () { 1.548 + this.setupInitialSync(); 1.549 + 1.550 + if (this.wizardType == "pair") { 1.551 + this.completePairing(); 1.552 + } 1.553 + 1.554 + if (!this._resettingSync) { 1.555 + function isChecked(element) { 1.556 + return document.getElementById(element).hasAttribute("checked"); 1.557 + } 1.558 + 1.559 + let prefs = ["engine.bookmarks", "engine.passwords", "engine.history", 1.560 + "engine.tabs", "engine.prefs", "engine.addons"]; 1.561 + for (let i = 0;i < prefs.length;i++) { 1.562 + Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i])); 1.563 + } 1.564 + this._handleNoScript(false); 1.565 + if (Weave.Svc.Prefs.get("firstSync", "") == "notReady") 1.566 + Weave.Svc.Prefs.reset("firstSync"); 1.567 + 1.568 + Weave.Service.persistLogin(); 1.569 + Weave.Svc.Obs.notify("weave:service:setup-complete"); 1.570 + 1.571 + gSyncUtils.openFirstSyncProgressPage(); 1.572 + } 1.573 + Weave.Utils.nextTick(Weave.Service.sync, Weave.Service); 1.574 + window.close(); 1.575 + }, 1.576 + 1.577 + onWizardCancel: function () { 1.578 + if (this._resettingSync) 1.579 + return; 1.580 + 1.581 + this.abortEasySetup(); 1.582 + this._handleNoScript(false); 1.583 + Weave.Service.startOver(); 1.584 + }, 1.585 + 1.586 + onSyncOptions: function () { 1.587 + this._beforeOptionsPage = this.wizard.pageIndex; 1.588 + this.wizard.pageIndex = OPTIONS_PAGE; 1.589 + }, 1.590 + 1.591 + returnFromOptions: function() { 1.592 + this.wizard.getButton("next").label = this._nextButtonLabel; 1.593 + this.wizard.getButton("next").setAttribute("accesskey", 1.594 + this._nextButtonAccesskey); 1.595 + this.wizard.getButton("back").label = this._backButtonLabel; 1.596 + this.wizard.getButton("back").setAttribute("accesskey", 1.597 + this._backButtonAccesskey); 1.598 + this.wizard.getButton("cancel").hidden = false; 1.599 + this.wizard.getButton("extra1").hidden = false; 1.600 + this.wizard.pageIndex = this._beforeOptionsPage; 1.601 + return false; 1.602 + }, 1.603 + 1.604 + startPairing: function startPairing() { 1.605 + this.pairDeviceErrorRow.hidden = true; 1.606 + // When onAbort is called, Weave may already be gone. 1.607 + const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT; 1.608 + 1.609 + let self = this; 1.610 + let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({ 1.611 + onPaired: function onPaired() { 1.612 + self.wizard.pageIndex = INTRO_PAGE; 1.613 + }, 1.614 + onComplete: function onComplete() { 1.615 + // This method will never be called since SendCredentialsController 1.616 + // will take over after the wizard completes. 1.617 + }, 1.618 + onAbort: function onAbort(error) { 1.619 + delete self._jpakeclient; 1.620 + 1.621 + // Aborted by user, ignore. The window is almost certainly going to close 1.622 + // or is already closed. 1.623 + if (error == JPAKE_ERROR_USERABORT) { 1.624 + return; 1.625 + } 1.626 + 1.627 + self.pairDeviceErrorRow.hidden = false; 1.628 + self.pairDeviceThrobber.hidden = true; 1.629 + self.pin1.value = self.pin2.value = self.pin3.value = ""; 1.630 + self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false; 1.631 + if (self.wizard.pageIndex == PAIR_PAGE) { 1.632 + self.pin1.focus(); 1.633 + } 1.634 + } 1.635 + }); 1.636 + this.pairDeviceThrobber.hidden = false; 1.637 + this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true; 1.638 + this.wizard.canAdvance = false; 1.639 + 1.640 + let pin = this.pin1.value + this.pin2.value + this.pin3.value; 1.641 + let expectDelay = true; 1.642 + jpakeclient.pairWithPIN(pin, expectDelay); 1.643 + }, 1.644 + 1.645 + completePairing: function completePairing() { 1.646 + if (!this._jpakeclient) { 1.647 + // The channel was aborted while we were setting up the account 1.648 + // locally. XXX TODO should we do anything here, e.g. tell 1.649 + // the user on the last wizard page that it's ok, they just 1.650 + // have to pair again? 1.651 + return; 1.652 + } 1.653 + let controller = new Weave.SendCredentialsController(this._jpakeclient, 1.654 + Weave.Service); 1.655 + this._jpakeclient.controller = controller; 1.656 + }, 1.657 + 1.658 + startEasySetup: function () { 1.659 + // Don't do anything if we have a client already (e.g. we went to 1.660 + // Sync Options and just came back). 1.661 + if (this._jpakeclient) 1.662 + return; 1.663 + 1.664 + // When onAbort is called, Weave may already be gone 1.665 + const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT; 1.666 + 1.667 + let self = this; 1.668 + this._jpakeclient = new Weave.JPAKEClient({ 1.669 + displayPIN: function displayPIN(pin) { 1.670 + document.getElementById("easySetupPIN1").value = pin.slice(0, 4); 1.671 + document.getElementById("easySetupPIN2").value = pin.slice(4, 8); 1.672 + document.getElementById("easySetupPIN3").value = pin.slice(8); 1.673 + }, 1.674 + 1.675 + onPairingStart: function onPairingStart() {}, 1.676 + 1.677 + onComplete: function onComplete(credentials) { 1.678 + Weave.Service.identity.account = credentials.account; 1.679 + Weave.Service.identity.basicPassword = credentials.password; 1.680 + Weave.Service.identity.syncKey = credentials.synckey; 1.681 + Weave.Service.serverURL = credentials.serverURL; 1.682 + gSyncSetup.wizardFinish(); 1.683 + }, 1.684 + 1.685 + onAbort: function onAbort(error) { 1.686 + delete self._jpakeclient; 1.687 + 1.688 + // Ignore if wizard is aborted. 1.689 + if (error == JPAKE_ERROR_USERABORT) 1.690 + return; 1.691 + 1.692 + // Automatically go to manual setup if we couldn't acquire a channel. 1.693 + if (error == Weave.JPAKE_ERROR_CHANNEL) { 1.694 + self.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE; 1.695 + return; 1.696 + } 1.697 + 1.698 + // Restart on all other errors. 1.699 + self.startEasySetup(); 1.700 + } 1.701 + }); 1.702 + this._jpakeclient.receiveNoPIN(); 1.703 + }, 1.704 + 1.705 + abortEasySetup: function () { 1.706 + document.getElementById("easySetupPIN1").value = ""; 1.707 + document.getElementById("easySetupPIN2").value = ""; 1.708 + document.getElementById("easySetupPIN3").value = ""; 1.709 + if (!this._jpakeclient) 1.710 + return; 1.711 + 1.712 + this._jpakeclient.abort(); 1.713 + delete this._jpakeclient; 1.714 + }, 1.715 + 1.716 + manualSetup: function () { 1.717 + this.abortEasySetup(); 1.718 + this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE; 1.719 + }, 1.720 + 1.721 + // _handleNoScript is needed because it blocks the captcha. So we temporarily 1.722 + // allow the necessary sites so that we can verify the user is in fact a human. 1.723 + // This was done with the help of Giorgio (NoScript author). See bug 508112. 1.724 + _handleNoScript: function (addExceptions) { 1.725 + // if NoScript isn't installed, or is disabled, bail out. 1.726 + let ns = Cc["@maone.net/noscript-service;1"]; 1.727 + if (ns == null) 1.728 + return; 1.729 + 1.730 + ns = ns.getService().wrappedJSObject; 1.731 + if (addExceptions) { 1.732 + this._remoteSites.forEach(function(site) { 1.733 + site = ns.getSite(site); 1.734 + if (!ns.isJSEnabled(site)) { 1.735 + this._disabledSites.push(site); // save status 1.736 + ns.setJSEnabled(site, true); // allow site 1.737 + } 1.738 + }, this); 1.739 + } 1.740 + else { 1.741 + this._disabledSites.forEach(function(site) { 1.742 + ns.setJSEnabled(site, false); 1.743 + }); 1.744 + this._disabledSites = []; 1.745 + } 1.746 + }, 1.747 + 1.748 + onExistingServerCommand: function () { 1.749 + let control = document.getElementById("existingServer"); 1.750 + if (control.selectedIndex == 0) { 1.751 + control.removeAttribute("editable"); 1.752 + Weave.Svc.Prefs.reset("serverURL"); 1.753 + } else { 1.754 + control.setAttribute("editable", "true"); 1.755 + // Force a style flush to ensure that the binding is attached. 1.756 + control.clientTop; 1.757 + control.value = ""; 1.758 + control.inputField.focus(); 1.759 + } 1.760 + document.getElementById("existingServerFeedbackRow").hidden = true; 1.761 + this.checkFields(); 1.762 + }, 1.763 + 1.764 + onExistingServerInput: function () { 1.765 + // Check custom server validity when the user stops typing for 1 second. 1.766 + if (this._existingServerTimer) 1.767 + window.clearTimeout(this._existingServerTimer); 1.768 + this._existingServerTimer = window.setTimeout(function () { 1.769 + gSyncSetup.checkFields(); 1.770 + }, 1000); 1.771 + }, 1.772 + 1.773 + onServerCommand: function () { 1.774 + setVisibility(document.getElementById("TOSRow"), this._usingMainServers); 1.775 + let control = document.getElementById("server"); 1.776 + if (!this._usingMainServers) { 1.777 + control.setAttribute("editable", "true"); 1.778 + // Force a style flush to ensure that the binding is attached. 1.779 + control.clientTop; 1.780 + control.value = ""; 1.781 + control.inputField.focus(); 1.782 + // checkServer() will call checkAccount() and checkFields(). 1.783 + this.checkServer(); 1.784 + return; 1.785 + } 1.786 + control.removeAttribute("editable"); 1.787 + Weave.Svc.Prefs.reset("serverURL"); 1.788 + if (this._settingUpNew) { 1.789 + this.loadCaptcha(); 1.790 + } 1.791 + this.checkAccount(); 1.792 + this.status.server = true; 1.793 + document.getElementById("serverFeedbackRow").hidden = true; 1.794 + this.checkFields(); 1.795 + }, 1.796 + 1.797 + onServerInput: function () { 1.798 + // Check custom server validity when the user stops typing for 1 second. 1.799 + if (this._checkServerTimer) 1.800 + window.clearTimeout(this._checkServerTimer); 1.801 + this._checkServerTimer = window.setTimeout(function () { 1.802 + gSyncSetup.checkServer(); 1.803 + }, 1000); 1.804 + }, 1.805 + 1.806 + checkServer: function () { 1.807 + delete this._checkServerTimer; 1.808 + let el = document.getElementById("server"); 1.809 + let valid = false; 1.810 + let feedback = document.getElementById("serverFeedbackRow"); 1.811 + let str = ""; 1.812 + if (el.value) { 1.813 + valid = this._validateServer(el); 1.814 + let str = valid ? "" : "serverInvalid.label"; 1.815 + this._setFeedbackMessage(feedback, valid, str); 1.816 + } 1.817 + else 1.818 + this._setFeedbackMessage(feedback, true); 1.819 + 1.820 + // Recheck account against the new server. 1.821 + if (valid) 1.822 + this.checkAccount(); 1.823 + 1.824 + this.status.server = valid; 1.825 + this.checkFields(); 1.826 + }, 1.827 + 1.828 + _validateServer: function (element) { 1.829 + let valid = false; 1.830 + let val = element.value; 1.831 + if (!val) 1.832 + return false; 1.833 + 1.834 + let uri = Weave.Utils.makeURI(val); 1.835 + 1.836 + if (!uri) 1.837 + uri = Weave.Utils.makeURI("https://" + val); 1.838 + 1.839 + if (uri && this._settingUpNew) { 1.840 + function isValid(uri) { 1.841 + Weave.Service.serverURL = uri.spec; 1.842 + let check = Weave.Service.checkAccount("a"); 1.843 + return (check == "available" || check == "notAvailable"); 1.844 + } 1.845 + 1.846 + if (uri.schemeIs("http")) { 1.847 + uri.scheme = "https"; 1.848 + if (isValid(uri)) 1.849 + valid = true; 1.850 + else 1.851 + // setting the scheme back to http 1.852 + uri.scheme = "http"; 1.853 + } 1.854 + if (!valid) 1.855 + valid = isValid(uri); 1.856 + 1.857 + if (valid) { 1.858 + this.loadCaptcha(); 1.859 + } 1.860 + } 1.861 + else if (uri) { 1.862 + valid = true; 1.863 + Weave.Service.serverURL = uri.spec; 1.864 + } 1.865 + 1.866 + if (valid) 1.867 + element.value = Weave.Service.serverURL; 1.868 + else 1.869 + Weave.Svc.Prefs.reset("serverURL"); 1.870 + 1.871 + return valid; 1.872 + }, 1.873 + 1.874 + _handleChoice: function () { 1.875 + let desc = document.getElementById("mergeChoiceRadio").selectedIndex; 1.876 + document.getElementById("chosenActionDeck").selectedIndex = desc; 1.877 + switch (desc) { 1.878 + case 1: 1.879 + if (this._case1Setup) 1.880 + break; 1.881 + 1.882 + let places_db = PlacesUtils.history 1.883 + .QueryInterface(Ci.nsPIPlacesDatabase) 1.884 + .DBConnection; 1.885 + if (Weave.Service.engineManager.get("history").enabled) { 1.886 + let daysOfHistory = 0; 1.887 + let stm = places_db.createStatement( 1.888 + "SELECT ROUND(( " + 1.889 + "strftime('%s','now','localtime','utc') - " + 1.890 + "( " + 1.891 + "SELECT visit_date FROM moz_historyvisits " + 1.892 + "ORDER BY visit_date ASC LIMIT 1 " + 1.893 + ")/1000000 " + 1.894 + ")/86400) AS daysOfHistory "); 1.895 + 1.896 + if (stm.step()) 1.897 + daysOfHistory = stm.getInt32(0); 1.898 + // Support %S for historical reasons (see bug 600141) 1.899 + document.getElementById("historyCount").value = 1.900 + PluralForm.get(daysOfHistory, 1.901 + this._stringBundle.GetStringFromName("historyDaysCount.label")) 1.902 + .replace("%S", daysOfHistory) 1.903 + .replace("#1", daysOfHistory); 1.904 + } else { 1.905 + document.getElementById("historyCount").hidden = true; 1.906 + } 1.907 + 1.908 + if (Weave.Service.engineManager.get("bookmarks").enabled) { 1.909 + let bookmarks = 0; 1.910 + let stm = places_db.createStatement( 1.911 + "SELECT count(*) AS bookmarks " + 1.912 + "FROM moz_bookmarks b " + 1.913 + "LEFT JOIN moz_bookmarks t ON " + 1.914 + "b.parent = t.id WHERE b.type = 1 AND t.parent <> :tag"); 1.915 + stm.params.tag = PlacesUtils.tagsFolderId; 1.916 + if (stm.executeStep()) 1.917 + bookmarks = stm.row.bookmarks; 1.918 + // Support %S for historical reasons (see bug 600141) 1.919 + document.getElementById("bookmarkCount").value = 1.920 + PluralForm.get(bookmarks, 1.921 + this._stringBundle.GetStringFromName("bookmarksCount.label")) 1.922 + .replace("%S", bookmarks) 1.923 + .replace("#1", bookmarks); 1.924 + } else { 1.925 + document.getElementById("bookmarkCount").hidden = true; 1.926 + } 1.927 + 1.928 + if (Weave.Service.engineManager.get("passwords").enabled) { 1.929 + let logins = Services.logins.getAllLogins({}); 1.930 + // Support %S for historical reasons (see bug 600141) 1.931 + document.getElementById("passwordCount").value = 1.932 + PluralForm.get(logins.length, 1.933 + this._stringBundle.GetStringFromName("passwordsCount.label")) 1.934 + .replace("%S", logins.length) 1.935 + .replace("#1", logins.length); 1.936 + } else { 1.937 + document.getElementById("passwordCount").hidden = true; 1.938 + } 1.939 + 1.940 + if (!Weave.Service.engineManager.get("prefs").enabled) { 1.941 + document.getElementById("prefsWipe").hidden = true; 1.942 + } 1.943 + 1.944 + let addonsEngine = Weave.Service.engineManager.get("addons"); 1.945 + if (addonsEngine.enabled) { 1.946 + let ids = addonsEngine._store.getAllIDs(); 1.947 + let blessedcount = 0; 1.948 + for each (let i in ids) { 1.949 + if (i) { 1.950 + blessedcount++; 1.951 + } 1.952 + } 1.953 + // bug 600141 does not apply, as this does not have to support existing strings 1.954 + document.getElementById("addonCount").value = 1.955 + PluralForm.get(blessedcount, 1.956 + this._stringBundle.GetStringFromName("addonsCount.label")) 1.957 + .replace("#1", blessedcount); 1.958 + } else { 1.959 + document.getElementById("addonCount").hidden = true; 1.960 + } 1.961 + 1.962 + this._case1Setup = true; 1.963 + break; 1.964 + case 2: 1.965 + if (this._case2Setup) 1.966 + break; 1.967 + let count = 0; 1.968 + function appendNode(label) { 1.969 + let box = document.getElementById("clientList"); 1.970 + let node = document.createElement("label"); 1.971 + node.setAttribute("value", label); 1.972 + node.setAttribute("class", "data indent"); 1.973 + box.appendChild(node); 1.974 + } 1.975 + 1.976 + for each (let name in Weave.Service.clientsEngine.stats.names) { 1.977 + // Don't list the current client 1.978 + if (name == Weave.Service.clientsEngine.localName) 1.979 + continue; 1.980 + 1.981 + // Only show the first several client names 1.982 + if (++count <= 5) 1.983 + appendNode(name); 1.984 + } 1.985 + if (count > 5) { 1.986 + // Support %S for historical reasons (see bug 600141) 1.987 + let label = 1.988 + PluralForm.get(count - 5, 1.989 + this._stringBundle.GetStringFromName("additionalClientCount.label")) 1.990 + .replace("%S", count - 5) 1.991 + .replace("#1", count - 5); 1.992 + appendNode(label); 1.993 + } 1.994 + this._case2Setup = true; 1.995 + break; 1.996 + } 1.997 + 1.998 + return true; 1.999 + }, 1.1000 + 1.1001 + // sets class and string on a feedback element 1.1002 + // if no property string is passed in, we clear label/style 1.1003 + _setFeedback: function (element, success, string) { 1.1004 + element.hidden = success || !string; 1.1005 + let classname = success ? "success" : "error"; 1.1006 + let image = element.getElementsByAttribute("class", "statusIcon")[0]; 1.1007 + image.setAttribute("status", classname); 1.1008 + let label = element.getElementsByAttribute("class", "status")[0]; 1.1009 + label.value = string; 1.1010 + }, 1.1011 + 1.1012 + // shim 1.1013 + _setFeedbackMessage: function (element, success, string) { 1.1014 + let str = ""; 1.1015 + if (string) { 1.1016 + try { 1.1017 + str = this._stringBundle.GetStringFromName(string); 1.1018 + } catch(e) {} 1.1019 + 1.1020 + if (!str) 1.1021 + str = Weave.Utils.getErrorString(string); 1.1022 + } 1.1023 + this._setFeedback(element, success, str); 1.1024 + }, 1.1025 + 1.1026 + loadCaptcha: function loadCaptcha() { 1.1027 + let captchaURI = Weave.Service.miscAPI + "captcha_html"; 1.1028 + // First check for NoScript and whitelist the right sites. 1.1029 + this._handleNoScript(true); 1.1030 + if (this.captchaBrowser.currentURI.spec != captchaURI) { 1.1031 + this.captchaBrowser.loadURI(captchaURI); 1.1032 + } 1.1033 + }, 1.1034 + 1.1035 + onStateChange: function(webProgress, request, stateFlags, status) { 1.1036 + // We're only looking for the end of the frame load 1.1037 + if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0) 1.1038 + return; 1.1039 + if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) == 0) 1.1040 + return; 1.1041 + if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) == 0) 1.1042 + return; 1.1043 + 1.1044 + // If we didn't find a captcha, assume it's not needed and don't show it. 1.1045 + let responseStatus = request.QueryInterface(Ci.nsIHttpChannel).responseStatus; 1.1046 + setVisibility(this.captchaBrowser, responseStatus != 404); 1.1047 + //XXX TODO we should really log any responseStatus other than 200 1.1048 + }, 1.1049 + onProgressChange: function() {}, 1.1050 + onStatusChange: function() {}, 1.1051 + onSecurityChange: function() {}, 1.1052 + onLocationChange: function () {} 1.1053 +}; 1.1054 + 1.1055 +// Define lazy getters for various XUL elements. 1.1056 +// 1.1057 +// onWizardAdvance() and onPageShow() are run before init(), so we'll even 1.1058 +// define things that will almost certainly be used (like 'wizard') as a lazy 1.1059 +// getter here. 1.1060 +["wizard", 1.1061 + "pin1", 1.1062 + "pin2", 1.1063 + "pin3", 1.1064 + "pairDeviceErrorRow", 1.1065 + "pairDeviceThrobber"].forEach(function (id) { 1.1066 + XPCOMUtils.defineLazyGetter(gSyncSetup, id, function() { 1.1067 + return document.getElementById(id); 1.1068 + }); 1.1069 +}); 1.1070 +XPCOMUtils.defineLazyGetter(gSyncSetup, "nextFocusEl", function () { 1.1071 + return {pin1: this.pin2, 1.1072 + pin2: this.pin3, 1.1073 + pin3: this.wizard.getButton("next")}; 1.1074 +}); 1.1075 +XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() { 1.1076 + return Services.strings.createBundle("chrome://browser/locale/syncSetup.properties"); 1.1077 +});