Bug 602682 - Sync UI: Implement easy setup. r=mconnor a=blocking-beta8
authorPhilipp von Weitershausen <philipp@weitershausen.de>
Thu, 09 Dec 2010 18:28:41 -0800
changeset 59049 405ab10d1d861e8c1ebd4c30b5c8de6123d40b14
parent 59048 b2b836328e9e77537e5108350b38a3157af725b6
child 59050 04a4bf3504aaa2214261e0b88f58c07479f360f6
push id1
push usershaver@mozilla.com
push dateTue, 04 Jan 2011 17:58:04 +0000
reviewersmconnor, blocking-beta8
bugs602682
milestone2.0b8pre
Bug 602682 - Sync UI: Implement easy setup. r=mconnor a=blocking-beta8 This adds the Add a Device wizard.
browser/base/content/syncAddDevice.js
browser/base/content/syncAddDevice.xul
browser/base/jar.mn
browser/components/preferences/sync.js
browser/components/preferences/sync.xul
browser/locales/en-US/chrome/browser/syncSetup.dtd
new file mode 100644
--- /dev/null
+++ b/browser/base/content/syncAddDevice.js
@@ -0,0 +1,174 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Firefox Sync.
+ *
+ * The Initial Developer of the Original Code is
+ * Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Philipp von Weitershausen <philipp@weitershausen.de>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const PIN_PART_LENGTH = 4;
+
+const ADD_DEVICE_PAGE       = 0;
+const DEVICE_CONNECTED_PAGE = 1;
+const SYNC_KEY_PAGE         = 2;
+
+let gSyncAddDevice = {
+
+  init: function init() {
+    this.pin1.setAttribute("maxlength", PIN_PART_LENGTH);
+    this.pin2.setAttribute("maxlength", PIN_PART_LENGTH);
+    this.pin3.setAttribute("maxlength", PIN_PART_LENGTH);
+
+    this.nextFocusEl = {pin1: this.pin2,
+                        pin2: this.pin3,
+                        pin3: this.wizard.getButton("next")};
+
+    this.throbber = document.getElementById("add-device-throbber");
+    this.errorRow = document.getElementById("errorRow");
+  },
+
+  onPageShow: function onPageShow() {
+    this.wizard.getButton("back").hidden = true;
+
+    switch (this.wizard.pageIndex) {
+      case ADD_DEVICE_PAGE:
+        this.wizard.canAdvance = false;
+        this.wizard.canRewind = false;
+        this.pin1.focus();
+        break;
+      case DEVICE_CONNECTED_PAGE:
+        this.wizard.canAdvance = true;
+        this.wizard.canRewind = false;
+        this.wizard.getButton("next").hidden = true;
+        this.wizard.getButton("cancel").hidden = true;
+        this.wizard.getButton("finish").hidden = false;
+        break;
+      case SYNC_KEY_PAGE:
+        this.wizard.canAdvance = true;
+        this.wizard.canRewind = true;
+        this.wizard.getButton("back").hidden = false;
+        document.getElementById("weavePassphrase").value =
+          Weave.Utils.hyphenatePassphrase(Weave.Service.passphrase);
+        break;
+    }
+  },
+
+  onWizardAdvance: function onWizardAdvance() {
+    switch (this.wizard.pageIndex) {
+      case ADD_DEVICE_PAGE:
+        this.startTransfer();
+        return false;
+      case DEVICE_CONNECTED_PAGE:
+        window.close();
+        return false;
+    }
+    return true;
+  },
+
+  startTransfer: function startTransfer() {
+    this.errorRow.hidden = true;
+    let self = this;
+    this._jpakeclient = new Weave.JPAKEClient({
+      onComplete: function onComplete() {
+        delete self._jpakeclient;
+        self.wizard.pageIndex = DEVICE_CONNECTED_PAGE;
+      },
+      onAbort: function onAbort(error) {
+        delete self._jpakeclient;
+
+        // Aborted by user, ignore.
+        if (!error)
+          return;
+
+        self.errorRow.hidden = false;
+        self.throbber.hidden = true;
+        self.pin1.value = self.pin2.value = self.pin3.value = "";
+        self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+        self.pin1.focus();
+      }
+    });
+    this.throbber.hidden = false;
+    this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+    this.wizard.canAdvance = false;
+
+    let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+    let credentials = {account: Weave.Service.account,
+                       password: Weave.Service.password,
+                       synckey: Weave.Service.passphrase,
+                       serverURL: Weave.Service.serverURL};
+    this._jpakeclient.sendWithPIN(pin, credentials);
+  },
+
+  onWizardBack: function onWizardBack() {
+    if (this.wizard.pageIndex != SYNC_KEY_PAGE)
+      return true;
+
+    this.wizard.pageIndex = ADD_DEVICE_PAGE;
+    return false;
+  },
+
+  onWizardCancel: function onWizardCancel() {
+    if (this._jpakeclient) {
+      this._jpakeclient.abort();
+      delete this._jpakeclient;
+    }
+    return true;
+  },
+
+  onTextBoxInput: function onTextBoxInput(textbox) {
+    if (textbox.value.length == PIN_PART_LENGTH)
+      this.nextFocusEl[textbox.id].focus();
+
+    this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH
+                              && this.pin2.value.length == PIN_PART_LENGTH
+                              && this.pin3.value.length == PIN_PART_LENGTH);
+  },
+
+  goToSyncKeyPage: function goToSyncKeyPage() {
+    this.wizard.pageIndex = SYNC_KEY_PAGE;
+  }
+
+};
+// onWizardAdvance() and onPageShow() are run before init() so we'll set
+// these up as lazy getters.
+["wizard", "pin1", "pin2", "pin3"].forEach(function (id) {
+  XPCOMUtils.defineLazyGetter(gSyncAddDevice, id, function() {
+    return document.getElementById(id);
+  });
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/syncAddDevice.xul
@@ -0,0 +1,161 @@
+<?xml version="1.0"?>
+
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is Firefox Sync.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2009
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Philipp von Weitershausen <philipp@weitershausen.de>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# and other provisions required by the GPL or the LGPL. If you do not delete
+# the provisions above, a recipient may use your version of this file under
+# the terms of any one of the MPL, the GPL or the LGPL.
+#
+# ***** END LICENSE BLOCK *****
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://browser/locale/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        id="wizard"
+        title="&addDevice.title.label;"
+        windowtype="Sync:AddDevice"
+        onwizardnext="return gSyncAddDevice.onWizardAdvance();"
+        onwizardback="return gSyncAddDevice.onWizardBack();"
+        onwizardcancel="gSyncAddDevice.onWizardCancel();"
+        onload="gSyncAddDevice.init();">
+
+  <script type="application/javascript"
+          src="chrome://browser/content/syncAddDevice.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/syncUtils.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/utilityOverlay.js"/>
+  <script type="application/javascript"
+          src="chrome://global/content/printUtils.js"/>
+
+  <wizardpage id="addDevicePage"
+              label="&addDevice.title.label;"
+              onpageshow="gSyncAddDevice.onPageShow();">
+    <description>
+      &addDevice.dialog.description.label;
+      <label class="text-link"
+             value="&addDevice.showMeHow.label;"
+             href="https://services.mozilla.com/sync/help/add-device"/>
+    </description>
+    <spacer flex="1"/>
+    <description>
+      &addDevice.dialog.enterCode.label;
+    </description>
+    <spacer flex="1"/>
+    <vbox align="center">
+      <textbox id="pin1"
+               class="pin"
+               oninput="gSyncAddDevice.onTextBoxInput(this);"
+               onfocus="this.select();"
+               />
+      <textbox id="pin2"
+               class="pin"
+               oninput="gSyncAddDevice.onTextBoxInput(this);"
+               onfocus="this.select();"
+               />
+      <textbox id="pin3"
+               class="pin"
+               oninput="gSyncAddDevice.onTextBoxInput(this);"
+               onfocus="this.select();" 
+              />
+    </vbox>
+    <spacer flex="1"/>
+    <vbox id="add-device-throbber" align="center" hidden="true">
+      <image/>
+    </vbox>
+    <hbox id="errorRow" pack="center" hidden="true">
+      <image class="statusIcon" status="error"/>
+      <label class="status"
+             value="&addDevice.dialog.tryAgain.label;"/>
+    </hbox>
+    <spacer flex="3"/>
+    <label class="text-link"
+           value="&addDevice.dontHaveDevice.label;"
+           onclick="gSyncAddDevice.goToSyncKeyPage();"/>
+  </wizardpage>
+
+  <wizardpage id="deviceConnectedPage"
+              label="&addDevice.dialog.connected.label;"
+              onpageshow="gSyncAddDevice.onPageShow();">
+    <vbox align="center">
+      <image id="successPageIcon"/>
+    </vbox>
+    <separator/>
+    <description class="normal">
+      &addDevice.dialog.successful.label;
+    </description>
+  </wizardpage>
+
+  <!-- Need a non-empty label here, otherwise we get a default label on Mac -->
+  <wizardpage id="syncKeyPage"
+              label=" "
+              onpageshow="gSyncAddDevice.onPageShow();">
+    <description>
+      &addDevice.dialog.syncKey.label;
+    </description>
+    <spacer/>
+
+    <groupbox>
+      <label value="&syncKeyEntry.label;"
+             accesskey="&syncKeyEntry.accesskey;"
+             control="weavePassphrase"/>
+      <textbox id="weavePassphrase"
+               disabled="true"/>
+    </groupbox>
+
+    <groupbox align="center">
+      <description>&syncKeyBackup.description;</description>
+      <hbox>
+        <button id="printSyncKeyButton"
+                label="&button.syncKeyBackup.print.label;"
+                accesskey="&button.syncKeyBackup.print.accesskey;"
+                oncommand="gSyncUtils.passphrasePrint('weavePassphrase');"/>
+        <button id="saveSyncKeyButton"
+                label="&button.syncKeyBackup.save.label;"
+                accesskey="&button.syncKeyBackup.save.accesskey;"
+                oncommand="gSyncUtils.passphraseSave('weavePassphrase');"/>
+      </hbox>
+    </groupbox>
+  </wizardpage>
+
+</wizard>
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -56,16 +56,18 @@ browser.jar:
 *       content/browser/baseMenuOverlay.xul           (content/baseMenuOverlay.xul)
 *       content/browser/nsContextMenu.js              (content/nsContextMenu.js)
 #ifdef MOZ_SERVICES_SYNC
 *       content/browser/aboutSyncTabs.xul             (content/aboutSyncTabs.xul)
         content/browser/aboutSyncTabs.js              (content/aboutSyncTabs.js)
         content/browser/aboutSyncTabs.css             (content/aboutSyncTabs.css)
 *       content/browser/aboutSyncTabs-bindings.xml    (content/aboutSyncTabs-bindings.xml)
 *       content/browser/syncSetup.xul                 (content/syncSetup.xul)
+        content/browser/syncAddDevice.js              (content/syncAddDevice.js)
+*       content/browser/syncAddDevice.xul             (content/syncAddDevice.xul)
         content/browser/syncSetup.js                  (content/syncSetup.js)
 *       content/browser/syncGenericChange.xul         (content/syncGenericChange.xul)
         content/browser/syncGenericChange.js          (content/syncGenericChange.js)
 *       content/browser/syncKey.xhtml                 (content/syncKey.xhtml)
 *       content/browser/syncNotification.xml          (content/syncNotification.xml)
 *       content/browser/syncQuota.xul                 (content/syncQuota.xul)
         content/browser/syncQuota.js                  (content/syncQuota.js)
         content/browser/syncUtils.js                  (content/syncUtils.js)
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -210,13 +210,22 @@ let gSyncPane = {
     let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
     if (win)
       win.focus();
     else 
       window.openDialog("chrome://browser/content/syncQuota.xul", "",
                         "centerscreen,chrome,dialog,modal");
   },
 
+  openAddDevice: function () {
+    let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+    if (win)
+      win.focus();
+    else 
+      window.openDialog("chrome://browser/content/syncAddDevice.xul",
+                        "syncAddDevice", "centerscreen,chrome,resizable=no");
+  },
+
   resetSync: function () {
     this.openSetup(true);
   }
 }
 
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -142,16 +142,20 @@
                           label="&manageAccount.label;"
                           accesskey="&manageAccount.accesskey;"
                           align="left"
                           oncommand="gSyncPane.handleExpanderClick()"/>
                   <spacer/>
                 </row>
               </rows>
             </grid>
+            <!-- TODO l10n -->
+            <label class="text-link"
+                   onclick="gSyncPane.openAddDevice(); return false;"
+                   value="Add a Device"/>
           </groupbox>
           <groupbox>
             <caption label="&syncPrefsCaption.label;"/>
             <grid>
               <columns>
                 <column/>
                 <column flex="1"/>
               </columns>
--- a/browser/locales/en-US/chrome/browser/syncSetup.dtd
+++ b/browser/locales/en-US/chrome/browser/syncSetup.dtd
@@ -54,16 +54,22 @@
 <!-- New Account Page 3: Captcha -->
 <!ENTITY setup.captchaPage2.title.label     "Please Confirm You're Not a Robot">
 <!-- Existing Account Page 1: Add Device (incl. Add a Device dialog strings) -->
 <!ENTITY addDevice.title.label              "Add a Device">
 <!ENTITY addDevice.showMeHow.label          "Show me how.">
 <!ENTITY addDevice.dontHaveDevice.label     "I don't have the device with me">
 <!ENTITY addDevice.setup.description.label  "To activate, go to &syncBrand.shortName.label; Options on your other device and select &#x0022;Add a Device&#x0022;.">
 <!ENTITY addDevice.setup.enterCode.label    "Then, enter this code:">
+<!ENTITY addDevice.dialog.description.label "To activate your new device, go to &syncBrand.shortName.label; Options on the device and select &#x0022;Connect.&#x0022;">
+<!ENTITY addDevice.dialog.enterCode.label   "Enter the code that the device provides:">
+<!ENTITY addDevice.dialog.tryAgain.label    "Please try again.">
+<!ENTITY addDevice.dialog.successful.label  "The device has been successfully added. The initial synchronization can several minutes and will finish in the background.">
+<!ENTITY addDevice.dialog.syncKey.label     "To activate your device you will need to enter your Sync Key. Please print or save this key and take it with you.">
+<!ENTITY addDevice.dialog.connected.label   "Device Connected">
 
 <!-- Existing Account Page 2: Manual Login -->
 <!ENTITY setup.signInPage.title.label "Sign In">
 <!ENTITY existingSyncKey.description "You can get a copy of your Sync Key by going to &syncBrand.shortName.label; Options on your other device, and selecting  &#x0022;My Sync Key&#x0022; under &#x0022;Manage Account&#x0022;.">
 <!ENTITY verifying.label              "Verifying…">
 <!ENTITY resetPassword.label          "Reset Password">
 <!ENTITY lostSyncKey.label            "I have lost my Sync Key">