Wed, 31 Dec 2014 13:27:57 +0100
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);
1019 }
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);
1029 }
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 });