Bug 951857 - Bring back UI entry points and the setup dialog for old sync r=markh
authorTim Taubert <ttaubert@mozilla.com>
Wed, 18 Dec 2013 23:10:11 +0100
changeset 161507 1e5821e13a77c56d138cf04d3243be25295b079f
parent 161506 882f69c9cadc20b1633ee843ddedc21c24213018
child 161508 04c636ebe2dc0cc578d8d7b38c0fa3bf8ca1c19f
push id25884
push userttaubert@mozilla.com
push dateSat, 21 Dec 2013 00:37:32 +0000
treeherdermozilla-central@b3d4af4ec2df [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs951857
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 951857 - Bring back UI entry points and the setup dialog for old sync r=markh From 046c19dae155a175cf6fa5e480f1fa1882f7d51f Mon Sep 17 00:00:00 2001
browser/base/content/browser-menubar.inc
browser/base/content/browser-syncui.js
browser/base/content/sync/addDevice.js
browser/base/content/sync/addDevice.xul
browser/base/content/sync/genericChange.js
browser/base/content/sync/genericChange.xul
browser/base/content/sync/key.xhtml
browser/base/content/sync/progress.xhtml
browser/base/content/sync/quota.js
browser/base/content/sync/quota.xul
browser/base/content/sync/setup.js
browser/base/content/sync/setup.xul
browser/base/content/sync/utils.js
browser/base/jar.mn
browser/components/preferences/in-content/sync.js
browser/components/preferences/in-content/sync.xul
browser/components/preferences/sync.js
browser/components/preferences/sync.xul
browser/locales/en-US/chrome/browser/preferences/sync.dtd
browser/locales/en-US/chrome/browser/syncGenericChange.properties
browser/locales/en-US/chrome/browser/syncKey.dtd
browser/locales/en-US/chrome/browser/syncProgress.dtd
browser/locales/en-US/chrome/browser/syncQuota.dtd
browser/locales/en-US/chrome/browser/syncQuota.properties
browser/locales/en-US/chrome/browser/syncSetup.dtd
browser/locales/en-US/chrome/browser/syncSetup.properties
browser/locales/jar.mn
browser/themes/linux/jar.mn
browser/themes/linux/syncCommon.css
browser/themes/linux/syncQuota.css
browser/themes/linux/syncSetup.css
browser/themes/osx/jar.mn
browser/themes/osx/syncCommon.css
browser/themes/osx/syncQuota.css
browser/themes/osx/syncSetup.css
browser/themes/windows/jar.mn
browser/themes/windows/syncCommon.css
browser/themes/windows/syncQuota.css
browser/themes/windows/syncSetup.css
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -447,17 +447,17 @@
                         key="key_openAddons"
                         command="Tools:Addons"/>
 #ifdef MOZ_SERVICES_SYNC
               <!-- only one of sync-setup or sync-menu will be showing at once -->
               <menuitem id="sync-setup"
                         label="&syncSetup.label;"
                         accesskey="&syncSetup.accesskey;"
                         observes="sync-setup-state"
-                        oncommand="gSyncUI.openAccountsPage()"/>
+                        oncommand="gSyncUI.openSetup()"/>
               <menuitem id="sync-syncnowitem"
                         label="&syncSyncNowItem.label;"
                         accesskey="&syncSyncNowItem.accesskey;"
                         observes="sync-syncnow-state"
                         oncommand="gSyncUI.doSync(event);"/>
 #endif
               <menuseparator id="devToolsSeparator"/>
               <menu id="webDeveloperMenu"
--- a/browser/base/content/browser-syncui.js
+++ b/browser/base/content/browser-syncui.js
@@ -200,17 +200,17 @@ let gSyncUI = {
 
   onQuotaNotice: function onQuotaNotice(subject, data) {
     let title = this._stringBundle.GetStringFromName("warning.sync.quota.label");
     let description = this._stringBundle.GetStringFromName("warning.sync.quota.description");
     let buttons = [];
     buttons.push(new Weave.NotificationButton(
       this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.label"),
       this._stringBundle.GetStringFromName("error.sync.viewQuotaButton.accesskey"),
-      function() { gSyncUI.openAccountsPage(); return true; }
+      function() { gSyncUI.openQuotaDialog(); return true; }
     ));
 
     let notification = new Weave.Notification(
       title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
     Weave.Notifications.replaceTitle(notification);
   },
 
   _getAppName: function () {
@@ -248,36 +248,79 @@ let gSyncUI = {
     Weave.Notifications.replaceTitle(notification);
   },
 
   openServerStatus: function () {
     let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
     window.openUILinkIn(statusURL, "tab");
   },
 
-  openAccountsPage: function () {
-    switchToTabHavingURI("about:accounts", true);
-  },
-
   // Commands
   doSync: function SUI_doSync() {
     setTimeout(function() Weave.Service.errorHandler.syncAndReportErrors(), 0);
   },
 
   handleToolbarButton: function SUI_handleStatusbarButton() {
     if (this._needsSetup())
-      this.openAccountsPage();
+      this.openSetup();
     else
       this.doSync();
   },
 
+  //XXXzpao should be part of syncCommon.js - which we might want to make a module...
+  //        To be fixed in a followup (bug 583366)
+
+  /**
+   * Invoke the Sync setup wizard.
+   *
+   * @param wizardType
+   *        Indicates type of wizard to launch:
+   *          null    -- regular set up wizard
+   *          "pair"  -- pair a device first
+   *          "reset" -- reset sync
+   */
+
+  openSetup: function SUI_openSetup(wizardType) {
+    let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+    if (win)
+      win.focus();
+    else {
+      window.openDialog("chrome://browser/content/sync/setup.xul",
+                        "weaveSetup", "centerscreen,chrome,resizable=no",
+                        wizardType);
+    }
+  },
+
+  openAddDevice: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return;
+
+    let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+    if (win)
+      win.focus();
+    else
+      window.openDialog("chrome://browser/content/sync/addDevice.xul",
+                        "syncAddDevice", "centerscreen,chrome,resizable=no");
+  },
+
+  openQuotaDialog: function SUI_openQuotaDialog() {
+    let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
+    if (win)
+      win.focus();
+    else
+      Services.ww.activeWindow.openDialog(
+        "chrome://browser/content/sync/quota.xul", "",
+        "centerscreen,chrome,dialog,modal");
+  },
+
   openPrefs: function SUI_openPrefs() {
     openPreferences("paneSync");
   },
 
+
   // Helpers
   _updateLastSyncTime: function SUI__updateLastSyncTime() {
     if (!gBrowser)
       return;
 
     let syncButton = document.getElementById("sync-button");
     if (!syncButton)
       return;
@@ -358,17 +401,17 @@ let gSyncUI = {
     else if (Weave.Status.sync == Weave.OVER_QUOTA) {
       description = this._stringBundle.GetStringFromName(
         "error.sync.quota.description");
       buttons.push(new Weave.NotificationButton(
         this._stringBundle.GetStringFromName(
           "error.sync.viewQuotaButton.label"),
         this._stringBundle.GetStringFromName(
           "error.sync.viewQuotaButton.accesskey"),
-        function() { gSyncUI.openAccountsPage(); return true; } )
+        function() { gSyncUI.openQuotaDialog(); return true; } )
       );
     }
     else if (Weave.Status.enforceBackoff) {
       priority = Weave.Notifications.PRIORITY_INFO;
       buttons.push(new Weave.NotificationButton(
         this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
         this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
         function() { gSyncUI.openServerStatus(); return true; }
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/addDevice.js
@@ -0,0 +1,157 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+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 SYNC_KEY_PAGE         = 1;
+const DEVICE_CONNECTED_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("pairDeviceThrobber");
+    this.errorRow = document.getElementById("errorRow");
+
+    // Kick off a sync. That way the server will have the most recent data from
+    // this computer and it will show up immediately on the new device.
+    Weave.Service.scheduler.scheduleNextSync(0);
+  },
+
+  onPageShow: function onPageShow() {
+    this.wizard.getButton("back").hidden = true;
+
+    switch (this.wizard.pageIndex) {
+      case ADD_DEVICE_PAGE:
+        this.onTextBoxInput();
+        this.wizard.canRewind = false;
+        this.wizard.getButton("next").hidden = false;
+        this.pin1.focus();
+        break;
+      case SYNC_KEY_PAGE:
+        this.wizard.canAdvance = false;
+        this.wizard.canRewind = true;
+        this.wizard.getButton("back").hidden = false;
+        this.wizard.getButton("next").hidden = true;
+        document.getElementById("weavePassphrase").value =
+          Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+        break;
+      case DEVICE_CONNECTED_PAGE:
+        this.wizard.canAdvance = true;
+        this.wizard.canRewind = false;
+        this.wizard.getButton("cancel").hidden = true;
+        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;
+    // When onAbort is called, Weave may already be gone.
+    const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+    let self = this;
+    let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+      onPaired: function onPaired() {
+        let credentials = {account:   Weave.Service.identity.account,
+                           password:  Weave.Service.identity.basicPassword,
+                           synckey:   Weave.Service.identity.syncKey,
+                           serverURL: Weave.Service.serverURL};
+        jpakeclient.sendAndComplete(credentials);
+      },
+      onComplete: function onComplete() {
+        delete self._jpakeclient;
+        self.wizard.pageIndex = DEVICE_CONNECTED_PAGE;
+
+        // Schedule a Sync for soonish to fetch the data uploaded by the
+        // device with which we just paired.
+        Weave.Service.scheduler.scheduleNextSync(Weave.Service.scheduler.activeInterval);
+      },
+      onAbort: function onAbort(error) {
+        delete self._jpakeclient;
+
+        // Aborted by user, ignore.
+        if (error == JPAKE_ERROR_USERABORT) {
+          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 expectDelay = false;
+    jpakeclient.pairWithPIN(pin, expectDelay);
+  },
+
+  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 && 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/sync/addDevice.xul
@@ -0,0 +1,129 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?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="&pairDevice.title.label;"
+        windowtype="Sync:AddDevice"
+        persist="screenX screenY"
+        onwizardnext="return gSyncAddDevice.onWizardAdvance();"
+        onwizardback="return gSyncAddDevice.onWizardBack();"
+        onwizardcancel="gSyncAddDevice.onWizardCancel();"
+        onload="gSyncAddDevice.init();">
+
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/addDevice.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/utils.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="&pairDevice.title.label;"
+              onpageshow="gSyncAddDevice.onPageShow();">
+    <description>
+      &pairDevice.dialog.description.label;
+      <label class="text-link"
+             value="&addDevice.showMeHow.label;"
+             href="https://services.mozilla.com/sync/help/add-device"/>
+    </description>
+    <separator class="groove-thin"/>
+    <description>
+      &addDevice.dialog.enterCode.label;
+    </description>
+    <separator class="groove-thin"/>
+    <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>
+    <separator class="groove-thin"/>
+    <vbox id="pairDeviceThrobber" 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>
+
+  <!-- Need a non-empty label here, otherwise we get a default label on Mac -->
+  <wizardpage id="syncKeyPage"
+              label=" "
+              onpageshow="gSyncAddDevice.onPageShow();">
+    <description>
+      &addDevice.dialog.recoveryKey.label;
+    </description>
+    <spacer/>
+
+    <groupbox>
+      <label value="&recoveryKeyEntry.label;"
+             accesskey="&recoveryKeyEntry.accesskey;"
+             control="weavePassphrase"/>
+      <textbox id="weavePassphrase"
+               readonly="true"/>
+    </groupbox>
+
+    <groupbox align="center">
+      <description>&recoveryKeyBackup.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>
+
+  <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>
+
+</wizard>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/genericChange.js
@@ -0,0 +1,235 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+let Change = {
+  _dialog: null,
+  _dialogType: null,
+  _status: null,
+  _statusIcon: null,
+  _firstBox: null,
+  _secondBox: null,
+
+  get _passphraseBox() {
+    delete this._passphraseBox;
+    return this._passphraseBox = document.getElementById("passphraseBox");
+  },
+
+  get _currentPasswordInvalid() {
+    return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+  },
+
+  get _updatingPassphrase() {
+    return this._dialogType == "UpdatePassphrase";
+  },
+
+  onLoad: function Change_onLoad() {
+    /* Load labels */
+    let introText = document.getElementById("introText");
+    let introText2 = document.getElementById("introText2");
+    let warningText = document.getElementById("warningText");
+
+    // load some other elements & info from the window
+    this._dialog = document.getElementById("change-dialog");
+    this._dialogType = window.arguments[0];
+    this._duringSetup = window.arguments[1];
+    this._status = document.getElementById("status");
+    this._statusIcon = document.getElementById("statusIcon");
+    this._statusRow = document.getElementById("statusRow");
+    this._firstBox = document.getElementById("textBox1");
+    this._secondBox = document.getElementById("textBox2");
+
+    this._dialog.getButton("finish").disabled = true;
+    this._dialog.getButton("back").hidden = true;
+
+    this._stringBundle =
+      Services.strings.createBundle("chrome://browser/locale/syncGenericChange.properties");
+
+    switch (this._dialogType) {
+      case "UpdatePassphrase":
+      case "ResetPassphrase":
+        document.getElementById("textBox1Row").hidden = true;
+        document.getElementById("textBox2Row").hidden = true;
+        document.getElementById("passphraseLabel").value
+          = this._str("new.recoverykey.label");
+        document.getElementById("passphraseSpacer").hidden = false;
+
+        if (this._updatingPassphrase) {
+          document.getElementById("passphraseHelpBox").hidden = false;
+          document.title = this._str("new.recoverykey.title");
+          introText.textContent = this._str("new.recoverykey.introText");
+          this._dialog.getButton("finish").label
+            = this._str("new.recoverykey.acceptButton");
+        }
+        else {
+          document.getElementById("generatePassphraseButton").hidden = false;
+          document.getElementById("passphraseBackupButtons").hidden = false;
+          this._passphraseBox.setAttribute("readonly", "true");
+          let pp = Weave.Service.identity.syncKey;
+          if (Weave.Utils.isPassphrase(pp))
+             pp = Weave.Utils.hyphenatePassphrase(pp);
+          this._passphraseBox.value = pp;
+          this._passphraseBox.focus();
+          document.title = this._str("change.recoverykey.title");
+          introText.textContent = this._str("change.synckey.introText2");
+          warningText.textContent = this._str("change.recoverykey.warningText");
+          this._dialog.getButton("finish").label
+            = this._str("change.recoverykey.acceptButton");
+          if (this._duringSetup) {
+            this._dialog.getButton("finish").disabled = false;
+          }
+        }
+        break;
+      case "ChangePassword":
+        document.getElementById("passphraseRow").hidden = true;
+        let box1label = document.getElementById("textBox1Label");
+        let box2label = document.getElementById("textBox2Label");
+        box1label.value = this._str("new.password.label");
+
+        if (this._currentPasswordInvalid) {
+          document.title = this._str("new.password.title");
+          introText.textContent = this._str("new.password.introText");
+          this._dialog.getButton("finish").label
+            = this._str("new.password.acceptButton");
+          document.getElementById("textBox2Row").hidden = true;
+        }
+        else {
+          document.title = this._str("change.password.title");
+          box2label.value = this._str("new.password.confirm");
+          introText.textContent = this._str("change.password3.introText");
+          warningText.textContent = this._str("change.password.warningText");
+          this._dialog.getButton("finish").label
+            = this._str("change.password.acceptButton");
+        }
+        break;
+    }
+    document.getElementById("change-page")
+            .setAttribute("label", document.title);
+  },
+
+  _clearStatus: function _clearStatus() {
+    this._status.value = "";
+    this._statusIcon.removeAttribute("status");
+  },
+
+  _updateStatus: function Change__updateStatus(str, state) {
+     this._updateStatusWithString(this._str(str), state);
+  },
+  
+  _updateStatusWithString: function Change__updateStatusWithString(string, state) {
+    this._statusRow.hidden = false;
+    this._status.value = string;
+    this._statusIcon.setAttribute("status", state);
+
+    let error = state == "error";
+    this._dialog.getButton("cancel").disabled = !error;
+    this._dialog.getButton("finish").disabled = !error;
+    document.getElementById("printSyncKeyButton").disabled = !error;
+    document.getElementById("saveSyncKeyButton").disabled = !error;
+
+    if (state == "success")
+      window.setTimeout(window.close, 1500);
+  },
+
+  onDialogAccept: function() {
+    switch (this._dialogType) {
+      case "UpdatePassphrase":
+      case "ResetPassphrase":
+        return this.doChangePassphrase();
+        break;
+      case "ChangePassword":
+        return this.doChangePassword();
+        break;
+    }
+  },
+
+  doGeneratePassphrase: function () {
+    let passphrase = Weave.Utils.generatePassphrase();
+    this._passphraseBox.value = Weave.Utils.hyphenatePassphrase(passphrase);
+    this._dialog.getButton("finish").disabled = false;
+  },
+
+  doChangePassphrase: function Change_doChangePassphrase() {
+    let pp = Weave.Utils.normalizePassphrase(this._passphraseBox.value);
+    if (this._updatingPassphrase) {
+      Weave.Service.identity.syncKey = pp;
+      if (Weave.Service.login()) {
+        this._updateStatus("change.recoverykey.success", "success");
+        Weave.Service.persistLogin();
+        Weave.Service.scheduler.delayedAutoConnect(0);
+      }
+      else {
+        this._updateStatus("new.passphrase.status.incorrect", "error");
+      }
+    }
+    else {
+      this._updateStatus("change.recoverykey.label", "active");
+
+      if (Weave.Service.changePassphrase(pp))
+        this._updateStatus("change.recoverykey.success", "success");
+      else
+        this._updateStatus("change.recoverykey.error", "error");
+    }
+
+    return false;
+  },
+
+  doChangePassword: function Change_doChangePassword() {
+    if (this._currentPasswordInvalid) {
+      Weave.Service.identity.basicPassword = this._firstBox.value;
+      if (Weave.Service.login()) {
+        this._updateStatus("change.password.status.success", "success");
+        Weave.Service.persistLogin();
+      }
+      else {
+        this._updateStatus("new.password.status.incorrect", "error");
+      }
+    }
+    else {
+      this._updateStatus("change.password.status.active", "active");
+
+      if (Weave.Service.changePassword(this._firstBox.value))
+        this._updateStatus("change.password.status.success", "success");
+      else
+        this._updateStatus("change.password.status.error", "error");
+    }
+
+    return false;
+  },
+
+  validate: function (event) {
+    let valid = false;
+    let errorString = "";
+
+    if (this._dialogType == "ChangePassword") {
+      if (this._currentPasswordInvalid)
+        [valid, errorString] = gSyncUtils.validatePassword(this._firstBox);
+      else
+        [valid, errorString] = gSyncUtils.validatePassword(this._firstBox, this._secondBox);
+    }
+    else {
+      if (!this._updatingPassphrase)
+        return;
+
+      valid = this._passphraseBox.value != "";
+    }
+
+    if (errorString == "")
+      this._clearStatus();
+    else
+      this._updateStatusWithString(errorString, "error");
+
+    this._statusRow.hidden = valid;
+    this._dialog.getButton("finish").disabled = !valid;
+  },
+
+  _str: function Change__string(str) {
+    return this._stringBundle.GetStringFromName(str);
+  }
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/genericChange.xul
@@ -0,0 +1,123 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?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="change-dialog"
+        windowtype="Weave:ChangeSomething"
+        persist="screenX screenY"
+        onwizardnext="Change.onLoad()"
+        onwizardfinish="return Change.onDialogAccept();">
+
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/genericChange.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/utils.js"/>
+  <script type="application/javascript"
+          src="chrome://global/content/printUtils.js"/>
+
+  <wizardpage id="change-page"
+              label="">
+
+    <description id="introText">
+    </description>
+
+    <separator class="thin"/>
+
+    <groupbox>
+      <grid>
+        <columns>
+          <column align="right"/>
+          <column flex="3"/>
+          <column flex="1"/>
+        </columns>
+        <rows>
+          <row id="textBox1Row" align="center">
+            <label id="textBox1Label" control="textBox1"/>
+            <textbox id="textBox1" type="password" oninput="Change.validate()"/>
+            <spacer/>
+          </row>
+          <row id="textBox2Row" align="center">
+            <label id="textBox2Label" control="textBox2"/>
+            <textbox id="textBox2" type="password" oninput="Change.validate()"/>
+            <spacer/>
+          </row>
+        </rows>
+      </grid>
+
+      <vbox id="passphraseRow">
+        <hbox flex="1">
+          <label id="passphraseLabel" control="passphraseBox"/>
+          <spacer flex="1"/>
+          <label id="generatePassphraseButton"
+                 hidden="true"
+                 value="&syncGenerateNewKey.label;"
+                 class="text-link inline-link"
+                 onclick="event.stopPropagation();
+                          Change.doGeneratePassphrase();"/>
+        </hbox>
+        <textbox id="passphraseBox"
+                 flex="1"
+                 onfocus="this.select()"
+                 oninput="Change.validate()"/>
+      </vbox>
+
+      <vbox id="feedback" pack="center">
+        <hbox id="statusRow" align="center">
+          <image id="statusIcon" class="statusIcon"/>
+          <label id="status" class="status" value=" "/>
+        </hbox>
+      </vbox>
+    </groupbox>
+
+    <separator class="thin"/>
+
+    <hbox id="passphraseBackupButtons"
+          hidden="true"
+          pack="center">
+      <button id="printSyncKeyButton"
+              label="&button.syncKeyBackup.print.label;"
+              accesskey="&button.syncKeyBackup.print.accesskey;"
+              oncommand="gSyncUtils.passphrasePrint('passphraseBox');"/>
+      <button id="saveSyncKeyButton"
+              label="&button.syncKeyBackup.save.label;"
+              accesskey="&button.syncKeyBackup.save.accesskey;"
+              oncommand="gSyncUtils.passphraseSave('passphraseBox');"/>
+    </hbox>
+
+    <vbox id="passphraseHelpBox"
+          hidden="true">
+      <description>
+        &existingRecoveryKey.description;
+        <label class="text-link"
+               href="https://services.mozilla.com/sync/help/manual-setup">
+          &addDevice.showMeHow.label;
+        </label>
+      </description>
+    </vbox>
+
+    <spacer id="passphraseSpacer"
+            flex="1"
+            hidden="true"/>
+
+    <description id="warningText" class="data">
+    </description>
+
+    <spacer flex="1"/>
+  </wizardpage>
+</wizard>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/key.xhtml
@@ -0,0 +1,54 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!DOCTYPE html [
+  <!ENTITY % htmlDTD PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "DTD/xhtml1-strict.dtd">
+  %htmlDTD;
+  <!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+  %syncBrandDTD;
+  <!ENTITY % syncKeyDTD SYSTEM "chrome://browser/locale/syncKey.dtd">
+  %syncKeyDTD;
+  <!ENTITY % globalDTD SYSTEM "chrome://global/locale/global.dtd" >
+  %globalDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>&syncKey.page.title;</title>
+  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
+  <meta name="robots" content="noindex"/>
+  <style type="text/css">
+    #synckey { font-size: 150% }
+    footer { font-size: 70% }
+    /* Bug 575675: Need to have an a:visited rule in a chrome document. */
+    a:visited { color: purple; }
+  </style>
+</head>
+
+<body dir="&locale.dir;">
+<h1>&syncKey.page.title;</h1>
+
+<p id="synckey" dir="ltr">SYNCKEY</p>
+
+<p>&syncKey.page.description2;</p>
+
+<div id="column1">
+  <h2>&syncKey.keepItSecret.heading;</h2>
+  <p>&syncKey.keepItSecret.description;</p>
+</div>
+
+<div id="column2">
+  <h2>&syncKey.keepItSafe.heading;</h2>
+  <p><em>&syncKey.keepItSafe1.description;</em>&syncKey.keepItSafe2.description;<em>&syncKey.keepItSafe3.description;</em>&syncKey.keepItSafe4a.description;</p>
+</div>
+
+<p>&syncKey.findOutMore1.label;<a href="https://services.mozilla.com">https://services.mozilla.com</a>&syncKey.findOutMore2.label;</p>
+
+<footer>
+ &syncKey.footer1.label;<a id="tosLink" href="termsURL">termsURL</a>&syncKey.footer2.label;<a id="ppLink" href="privacyURL">privacyURL</a>&syncKey.footer3.label;
+</footer>
+
+</body>
+</html>
--- a/browser/base/content/sync/progress.xhtml
+++ b/browser/base/content/sync/progress.xhtml
@@ -6,16 +6,19 @@
 <!DOCTYPE html [
   <!ENTITY % htmlDTD
     PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
     "DTD/xhtml1-strict.dtd">
   %htmlDTD;
   <!ENTITY % syncProgressDTD
     SYSTEM "chrome://browser/locale/syncProgress.dtd">
     %syncProgressDTD;
+  <!ENTITY % syncSetupDTD
+    SYSTEM "chrome://browser/locale/syncSetup.dtd">
+    %syncSetupDTD;
   <!ENTITY % globalDTD 
     SYSTEM "chrome://global/locale/global.dtd">
     %globalDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <title>&syncProgress.pageTitle;</title>
@@ -25,20 +28,20 @@
 
     <link rel="icon" type="image/png" id="favicon"
           href="chrome://browser/skin/sync-16.png"/>
 
     <script type="text/javascript;version=1.8"
             src="chrome://browser/content/sync/progress.js"/>
   </head>
   <body onload="onLoad(event)" onunload="onUnload(event)" dir="&locale.dir;">
-    <title>&syncProgress.headline;</title>
+    <title>&setup.successPage.title;</title>
     <div id="floatingBox" class="main-content">
       <div id="title">
-        <h1>&syncProgress.headline;</h1>
+        <h1>&setup.successPage.title;</h1>
       </div>
       <div id="successLogo">
         <img id="brandSyncLogo" src="chrome://browser/skin/sync-128.png" alt="&syncProgress.logoAltText;" />
       </div>
       <div id="loadingText">
         <p id="blurb">&syncProgress.textBlurb; </p>
       </div>
       <div id="progressBar">
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/quota.js
@@ -0,0 +1,268 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/DownloadUtils.jsm");
+
+let gSyncQuota = {
+
+  init: function init() {
+    this.bundle = document.getElementById("quotaStrings");
+    let caption = document.getElementById("treeCaption");
+    caption.firstChild.nodeValue = this.bundle.getString("quota.treeCaption.label");
+
+    gUsageTreeView.init();
+    this.tree = document.getElementById("usageTree");
+    this.tree.view = gUsageTreeView;
+
+    this.loadData();
+  },
+
+  loadData: function loadData() {
+    this._usage_req = Weave.Service.getStorageInfo(Weave.INFO_COLLECTION_USAGE,
+                                                   function (error, usage) {
+      delete gSyncQuota._usage_req;
+      // displayUsageData handles null values, so no need to check 'error'.
+      gUsageTreeView.displayUsageData(usage);
+    });
+
+    let usageLabel = document.getElementById("usageLabel");
+    let bundle = this.bundle;
+
+    this._quota_req = Weave.Service.getStorageInfo(Weave.INFO_QUOTA,
+                                                   function (error, quota) {
+      delete gSyncQuota._quota_req;
+
+      if (error) {
+        usageLabel.value = bundle.getString("quota.usageError.label");
+        return;
+      }
+      let used = gSyncQuota.convertKB(quota[0]);
+      if (!quota[1]) {
+        // No quota on the server.
+        usageLabel.value = bundle.getFormattedString(
+          "quota.usageNoQuota.label", used);
+        return;
+      }
+      let percent = Math.round(100 * quota[0] / quota[1]);
+      let total = gSyncQuota.convertKB(quota[1]);
+      usageLabel.value = bundle.getFormattedString(
+        "quota.usagePercentage.label", [percent].concat(used).concat(total));
+    });
+  },
+
+  onCancel: function onCancel() {
+    if (this._usage_req) {
+      this._usage_req.abort();
+    }
+    if (this._quota_req) {
+      this._quota_req.abort();
+    }
+    return true;
+  },
+
+  onAccept: function onAccept() {
+    let engines = gUsageTreeView.getEnginesToDisable();
+    for each (let engine in engines) {
+      Weave.Service.engineManager.get(engine).enabled = false;
+    }
+    if (engines.length) {
+      // The 'Weave' object will disappear once the window closes.
+      let Service = Weave.Service;
+      Weave.Utils.nextTick(function() { Service.sync(); });
+    }
+    return this.onCancel();
+  },
+
+  convertKB: function convertKB(value) {
+    return DownloadUtils.convertByteUnits(value * 1024);
+  }
+
+};
+
+let gUsageTreeView = {
+
+  _ignored: {keys: true,
+             meta: true,
+             clients: true},
+
+  /*
+   * Internal data structures underlaying the tree.
+   */
+  _collections: [],
+  _byname: {},
+
+  init: function init() {
+    let retrievingLabel = gSyncQuota.bundle.getString("quota.retrieving.label");
+    for each (let engine in Weave.Service.engineManager.getEnabled()) {
+      if (this._ignored[engine.name])
+        continue;
+
+      // Some engines use the same pref, which means they can only be turned on
+      // and off together. We need to combine them here as well.
+      let existing = this._byname[engine.prefName];
+      if (existing) {
+        existing.engines.push(engine.name);
+        continue;
+      }
+
+      let obj = {name: engine.prefName,
+                 title: this._collectionTitle(engine),
+                 engines: [engine.name],
+                 enabled: true,
+                 sizeLabel: retrievingLabel};
+      this._collections.push(obj);
+      this._byname[engine.prefName] = obj;
+    }
+  },
+
+  _collectionTitle: function _collectionTitle(engine) {
+    try {
+      return gSyncQuota.bundle.getString(
+        "collection." + engine.prefName + ".label");
+    } catch (ex) {
+      return engine.Name;
+    }
+  },
+
+  /*
+   * Process the quota information as returned by info/collection_usage.
+   */
+  displayUsageData: function displayUsageData(data) {
+    for each (let coll in this._collections) {
+      coll.size = 0;
+      // If we couldn't retrieve any data, just blank out the label.
+      if (!data) {
+        coll.sizeLabel = "";
+        continue;
+      }
+
+      for each (let engineName in coll.engines)
+        coll.size += data[engineName] || 0;
+      let sizeLabel = "";
+      sizeLabel = gSyncQuota.bundle.getFormattedString(
+        "quota.sizeValueUnit.label", gSyncQuota.convertKB(coll.size));
+      coll.sizeLabel = sizeLabel;
+    }
+    let sizeColumn = this.treeBox.columns.getNamedColumn("size");
+    this.treeBox.invalidateColumn(sizeColumn);
+  },
+
+  /*
+   * Handle click events on the tree.
+   */
+  onTreeClick: function onTreeClick(event) {
+    if (event.button == 2)
+      return;
+
+    let row = {}, col = {};
+    this.treeBox.getCellAt(event.clientX, event.clientY, row, col, {});
+    if (col.value && col.value.id == "enabled")
+      this.toggle(row.value);
+  },
+
+  /*
+   * Toggle enabled state of an engine.
+   */
+  toggle: function toggle(row) {
+    // Update the tree
+    let collection = this._collections[row];
+    collection.enabled = !collection.enabled;
+    this.treeBox.invalidateRow(row);
+
+    // Display which ones will be removed 
+    let freeup = 0;
+    let toremove = [];
+    for each (collection in this._collections) {
+      if (collection.enabled)
+        continue;
+      toremove.push(collection.name);
+      freeup += collection.size;
+    }
+
+    let caption = document.getElementById("treeCaption");
+    if (!toremove.length) {
+      caption.className = "";
+      caption.firstChild.nodeValue = gSyncQuota.bundle.getString(
+        "quota.treeCaption.label");
+      return;
+    }
+
+    toremove = [this._byname[coll].title for each (coll in toremove)];
+    toremove = toremove.join(gSyncQuota.bundle.getString("quota.list.separator"));
+    caption.firstChild.nodeValue = gSyncQuota.bundle.getFormattedString(
+      "quota.removal.label", [toremove]);
+    if (freeup)
+      caption.firstChild.nodeValue += gSyncQuota.bundle.getFormattedString(
+        "quota.freeup.label", gSyncQuota.convertKB(freeup));
+    caption.className = "captionWarning";
+  },
+
+  /*
+   * Return a list of engines (or rather their pref names) that should be
+   * disabled.
+   */
+  getEnginesToDisable: function getEnginesToDisable() {
+    return [coll.name for each (coll in this._collections) if (!coll.enabled)];
+  },
+
+  // nsITreeView
+
+  get rowCount() {
+    return this._collections.length;
+  },
+
+  getRowProperties: function(index) { return ""; },
+  getCellProperties: function(row, col) { return ""; },
+  getColumnProperties: function(col) { return ""; },
+  isContainer: function(index) { return false; },
+  isContainerOpen: function(index) { return false; },
+  isContainerEmpty: function(index) { return false; },
+  isSeparator: function(index) { return false; },
+  isSorted: function() { return false; },
+  canDrop: function(index, orientation, dataTransfer) { return false; },
+  drop: function(row, orientation, dataTransfer) {},
+  getParentIndex: function(rowIndex) {},
+  hasNextSibling: function(rowIndex, afterIndex) { return false; },
+  getLevel: function(index) { return 0; },
+  getImageSrc: function(row, col) {},
+
+  getCellValue: function(row, col) {
+    return this._collections[row].enabled;
+  },
+
+  getCellText: function getCellText(row, col) {
+    let collection = this._collections[row];
+    switch (col.id) {
+      case "collection":
+        return collection.title;
+      case "size":
+        return collection.sizeLabel;
+      default:
+        return "";
+    }
+  },
+
+  setTree: function setTree(tree) {
+    this.treeBox = tree;
+  },
+
+  toggleOpenState: function(index) {},
+  cycleHeader: function(col) {},
+  selectionChanged: function() {},
+  cycleCell: function(row, col) {},
+  isEditable: function(row, col) { return false; },
+  isSelectable: function (row, col) { return false; },
+  setCellValue: function(row, col, value) {},
+  setCellText: function(row, col, value) {},
+  performAction: function(action) {},
+  performActionOnRow: function(action, row) {},
+  performActionOnCell: function(action, row, col) {}
+
+};
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/quota.xul
@@ -0,0 +1,65 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/syncQuota.css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://browser/locale/syncBrand.dtd">
+<!ENTITY % syncQuotaDTD SYSTEM "chrome://browser/locale/syncQuota.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncQuotaDTD;
+]>
+<dialog id="quotaDialog"
+        windowtype="Sync:ViewQuota"
+        persist="screenX screenY width height"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        onload="gSyncQuota.init()"
+        buttons="accept,cancel"
+        title="&quota.dialogTitle.label;"
+        ondialogcancel="return gSyncQuota.onCancel();"
+        ondialogaccept="return gSyncQuota.onAccept();">
+
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/quota.js"/>
+
+  <stringbundleset id="stringbundleset">
+    <stringbundle id="quotaStrings"
+                  src="chrome://browser/locale/syncQuota.properties"/>
+  </stringbundleset>
+
+  <vbox flex="1">
+    <label id="usageLabel"
+           value="&quota.retrievingInfo.label;"/>
+    <separator/>
+    <tree id="usageTree"
+          seltype="single"
+          hidecolumnpicker="true"
+          onclick="gUsageTreeView.onTreeClick(event);"
+          flex="1">
+      <treecols>
+        <treecol id="enabled"
+                 type="checkbox"
+                 fixed="true"/>
+        <splitter class="tree-splitter"/>
+        <treecol id="collection"
+                 label="&quota.typeColumn.label;"
+                 flex="1"/>
+        <splitter class="tree-splitter"/>
+        <treecol id="size"
+                 label="&quota.sizeColumn.label;"
+                 flex="1"/>
+      </treecols>
+      <treechildren flex="1"/>
+    </tree>
+    <separator/>
+    <description id="treeCaption"> </description>
+  </vbox>
+
+</dialog>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/setup.js
@@ -0,0 +1,1074 @@
+// -*- Mode: js2; tab-width: 2; indent-tabs-mode: nil; js2-basic-offset: 2; js2-skip-preprocessor-directives: t; -*-
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+const Ci = Components.interfaces;
+const Cc = Components.classes;
+const Cr = Components.results;
+const Cu = Components.utils;
+
+// page consts
+
+const PAIR_PAGE                     = 0;
+const INTRO_PAGE                    = 1;
+const NEW_ACCOUNT_START_PAGE        = 2;
+const EXISTING_ACCOUNT_CONNECT_PAGE = 3;
+const EXISTING_ACCOUNT_LOGIN_PAGE   = 4;
+const OPTIONS_PAGE                  = 5;
+const OPTIONS_CONFIRM_PAGE          = 6;
+
+// Broader than we'd like, but after this changed from api-secure.recaptcha.net
+// we had no choice. At least we only do this for the duration of setup.
+// See discussion in Bugs 508112 and 653307.
+const RECAPTCHA_DOMAIN = "https://www.google.com";
+
+const PIN_PART_LENGTH = 4;
+
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm");
+Cu.import("resource://gre/modules/PluralForm.jsm");
+
+
+function setVisibility(element, visible) {
+  element.style.visibility = visible ? "visible" : "hidden";
+}
+
+var gSyncSetup = {
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsISupports,
+                                         Ci.nsIWebProgressListener,
+                                         Ci.nsISupportsWeakReference]),
+
+  captchaBrowser: null,
+  wizard: null,
+  _disabledSites: [],
+
+  status: {
+    password: false,
+    email: false,
+    server: false
+  },
+
+  get _remoteSites() [Weave.Service.serverURL, RECAPTCHA_DOMAIN],
+
+  get _usingMainServers() {
+    if (this._settingUpNew)
+      return document.getElementById("server").selectedIndex == 0;
+    return document.getElementById("existingServer").selectedIndex == 0;
+  },
+
+  init: function () {
+    let obs = [
+      ["weave:service:change-passphrase", "onResetPassphrase"],
+      ["weave:service:login:start",       "onLoginStart"],
+      ["weave:service:login:error",       "onLoginEnd"],
+      ["weave:service:login:finish",      "onLoginEnd"]];
+
+    // Add the observers now and remove them on unload
+    let self = this;
+    let addRem = function(add) {
+      obs.forEach(function([topic, func]) {
+        //XXXzpao This should use Services.obs.* but Weave's Obs does nice handling
+        //        of `this`. Fix in a followup. (bug 583347)
+        if (add)
+          Weave.Svc.Obs.add(topic, self[func], self);
+        else
+          Weave.Svc.Obs.remove(topic, self[func], self);
+      });
+    };
+    addRem(true);
+    window.addEventListener("unload", function() addRem(false), false);
+
+    window.setTimeout(function () {
+      // Force Service to be loaded so that engines are registered.
+      // See Bug 670082.
+      Weave.Service;
+    }, 0);
+
+    this.captchaBrowser = document.getElementById("captcha");
+
+    this.wizardType = null;
+    if (window.arguments && window.arguments[0]) {
+      this.wizardType = window.arguments[0];
+    }
+    switch (this.wizardType) {
+      case null:
+        this.wizard.pageIndex = INTRO_PAGE;
+        // Fall through!
+      case "pair":
+        this.captchaBrowser.addProgressListener(this);
+        Weave.Svc.Prefs.set("firstSync", "notReady");
+        break;
+      case "reset":
+        this._resettingSync = true;
+        this.wizard.pageIndex = OPTIONS_PAGE;
+        break;
+    }
+
+    this.wizard.getButton("extra1").label =
+      this._stringBundle.GetStringFromName("button.syncOptions.label");
+
+    // Remember these values because the options pages change them temporarily.
+    this._nextButtonLabel = this.wizard.getButton("next").label;
+    this._nextButtonAccesskey = this.wizard.getButton("next")
+                                           .getAttribute("accesskey");
+    this._backButtonLabel = this.wizard.getButton("back").label;
+    this._backButtonAccesskey = this.wizard.getButton("back")
+                                           .getAttribute("accesskey");
+  },
+
+  startNewAccountSetup: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return false;
+    this._settingUpNew = true;
+    this.wizard.pageIndex = NEW_ACCOUNT_START_PAGE;
+  },
+
+  useExistingAccount: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return false;
+    this._settingUpNew = false;
+    if (this.wizardType == "pair") {
+      // We're already pairing, so there's no point in pairing again.
+      // Go straight to the manual login page.
+      this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+    } else {
+      this.wizard.pageIndex = EXISTING_ACCOUNT_CONNECT_PAGE;
+    }
+  },
+
+  resetPassphrase: function resetPassphrase() {
+    // Apply the existing form fields so that
+    // Weave.Service.changePassphrase() has the necessary credentials.
+    Weave.Service.identity.account = document.getElementById("existingAccountName").value;
+    Weave.Service.identity.basicPassword = document.getElementById("existingPassword").value;
+
+    // Generate a new passphrase so that Weave.Service.login() will
+    // actually do something.
+    let passphrase = Weave.Utils.generatePassphrase();
+    Weave.Service.identity.syncKey = passphrase;
+
+    // Only open the dialog if username + password are actually correct.
+    Weave.Service.login();
+    if ([Weave.LOGIN_FAILED_INVALID_PASSPHRASE,
+         Weave.LOGIN_FAILED_NO_PASSPHRASE,
+         Weave.LOGIN_SUCCEEDED].indexOf(Weave.Status.login) == -1) {
+      return;
+    }
+
+    // Hide any errors about the passphrase, we know it's not right.
+    let feedback = document.getElementById("existingPassphraseFeedbackRow");
+    feedback.hidden = true;
+    let el = document.getElementById("existingPassphrase");
+    el.value = Weave.Utils.hyphenatePassphrase(passphrase);
+
+    // changePassphrase() will sync, make sure we set the "firstSync" pref
+    // according to the user's pref.
+    Weave.Svc.Prefs.reset("firstSync");
+    this.setupInitialSync();
+    gSyncUtils.resetPassphrase(true);
+  },
+
+  onResetPassphrase: function () {
+    document.getElementById("existingPassphrase").value =
+      Weave.Utils.hyphenatePassphrase(Weave.Service.identity.syncKey);
+    this.checkFields();
+    this.wizard.advance();
+  },
+
+  onLoginStart: function () {
+    this.toggleLoginFeedback(false);
+  },
+
+  onLoginEnd: function () {
+    this.toggleLoginFeedback(true);
+  },
+
+  sendCredentialsAfterSync: function () {
+    let send = function() {
+      Services.obs.removeObserver("weave:service:sync:finish", send);
+      Services.obs.removeObserver("weave:service:sync:error", send);
+      let credentials = {account:   Weave.Service.identity.account,
+                         password:  Weave.Service.identity.basicPassword,
+                         synckey:   Weave.Service.identity.syncKey,
+                         serverURL: Weave.Service.serverURL};
+      this._jpakeclient.sendAndComplete(credentials);
+    }.bind(this);
+    Services.obs.addObserver("weave:service:sync:finish", send, false);
+    Services.obs.addObserver("weave:service:sync:error", send, false);
+  },
+
+  toggleLoginFeedback: function (stop) {
+    document.getElementById("login-throbber").hidden = stop;
+    let password = document.getElementById("existingPasswordFeedbackRow");
+    let server = document.getElementById("existingServerFeedbackRow");
+    let passphrase = document.getElementById("existingPassphraseFeedbackRow");
+
+    if (!stop || (Weave.Status.login == Weave.LOGIN_SUCCEEDED)) {
+      password.hidden = server.hidden = passphrase.hidden = true;
+      return;
+    }
+
+    let feedback;
+    switch (Weave.Status.login) {
+      case Weave.LOGIN_FAILED_NETWORK_ERROR:
+      case Weave.LOGIN_FAILED_SERVER_ERROR:
+        feedback = server;
+        break;
+      case Weave.LOGIN_FAILED_LOGIN_REJECTED:
+      case Weave.LOGIN_FAILED_NO_USERNAME:
+      case Weave.LOGIN_FAILED_NO_PASSWORD:
+        feedback = password;
+        break;
+      case Weave.LOGIN_FAILED_INVALID_PASSPHRASE:
+        feedback = passphrase;
+        break;
+    }
+    this._setFeedbackMessage(feedback, false, Weave.Status.login);
+  },
+
+  setupInitialSync: function () {
+    let action = document.getElementById("mergeChoiceRadio").selectedItem.id;
+    switch (action) {
+      case "resetClient":
+        // if we're not resetting sync, we don't need to explicitly
+        // call resetClient
+        if (!this._resettingSync)
+          return;
+        // otherwise, fall through
+      case "wipeClient":
+      case "wipeRemote":
+        Weave.Svc.Prefs.set("firstSync", action);
+        break;
+    }
+  },
+
+  // fun with validation!
+  checkFields: function () {
+    this.wizard.canAdvance = this.readyToAdvance();
+  },
+
+  readyToAdvance: function () {
+    switch (this.wizard.pageIndex) {
+      case INTRO_PAGE:
+        return false;
+      case NEW_ACCOUNT_START_PAGE:
+        for (let i in this.status) {
+          if (!this.status[i])
+            return false;
+        }
+        if (this._usingMainServers)
+          return document.getElementById("tos").checked;
+
+        return true;
+      case EXISTING_ACCOUNT_LOGIN_PAGE:
+        let hasUser = document.getElementById("existingAccountName").value != "";
+        let hasPass = document.getElementById("existingPassword").value != "";
+        let hasKey = document.getElementById("existingPassphrase").value != "";
+
+        if (hasUser && hasPass && hasKey) {
+          if (this._usingMainServers)
+            return true;
+
+          if (this._validateServer(document.getElementById("existingServer"))) {
+            return true;
+          }
+        }
+        return false;
+    }
+    // Default, e.g. wizard's special page -1 etc.
+    return true;
+  },
+
+  onPINInput: function onPINInput(textbox) {
+    if (textbox && textbox.value.length == PIN_PART_LENGTH) {
+      this.nextFocusEl[textbox.id].focus();
+    }
+    this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH &&
+                              this.pin2.value.length == PIN_PART_LENGTH &&
+                              this.pin3.value.length == PIN_PART_LENGTH);
+  },
+
+  onEmailInput: function () {
+    // Check account validity when the user stops typing for 1 second.
+    if (this._checkAccountTimer)
+      window.clearTimeout(this._checkAccountTimer);
+    this._checkAccountTimer = window.setTimeout(function () {
+      gSyncSetup.checkAccount();
+    }, 1000);
+  },
+
+  checkAccount: function() {
+    delete this._checkAccountTimer;
+    let value = Weave.Utils.normalizeAccount(
+      document.getElementById("weaveEmail").value);
+    if (!value) {
+      this.status.email = false;
+      this.checkFields();
+      return;
+    }
+
+    let re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
+    let feedback = document.getElementById("emailFeedbackRow");
+    let valid = re.test(value);
+
+    let str = "";
+    if (!valid) {
+      str = "invalidEmail.label";
+    } else {
+      let availCheck = Weave.Service.checkAccount(value);
+      valid = availCheck == "available";
+      if (!valid) {
+        if (availCheck == "notAvailable")
+          str = "usernameNotAvailable.label";
+        else
+          str = availCheck;
+      }
+    }
+
+    this._setFeedbackMessage(feedback, valid, str);
+    this.status.email = valid;
+    if (valid)
+      Weave.Service.identity.account = value;
+    this.checkFields();
+  },
+
+  onPasswordChange: function () {
+    let password = document.getElementById("weavePassword");
+    let pwconfirm = document.getElementById("weavePasswordConfirm");
+    let [valid, errorString] = gSyncUtils.validatePassword(password, pwconfirm);
+
+    let feedback = document.getElementById("passwordFeedbackRow");
+    this._setFeedback(feedback, valid, errorString);
+
+    this.status.password = valid;
+    this.checkFields();
+  },
+
+  onPageShow: function() {
+    switch (this.wizard.pageIndex) {
+      case PAIR_PAGE:
+        this.wizard.getButton("back").hidden = true;
+        this.wizard.getButton("extra1").hidden = true;
+        this.onPINInput();
+        this.pin1.focus();
+        break;
+      case INTRO_PAGE:
+        // We may not need the captcha in the Existing Account branch of the
+        // wizard. However, we want to preload it to avoid any flickering while
+        // the Create Account page is shown.
+        this.loadCaptcha();
+        this.wizard.getButton("next").hidden = true;
+        this.wizard.getButton("back").hidden = true;
+        this.wizard.getButton("extra1").hidden = true;
+        this.checkFields();
+        break;
+      case NEW_ACCOUNT_START_PAGE:
+        this.wizard.getButton("extra1").hidden = false;
+        this.wizard.getButton("next").hidden = false;
+        this.wizard.getButton("back").hidden = false;
+        this.onServerCommand();
+        this.wizard.canRewind = true;
+        this.checkFields();
+        break;
+      case EXISTING_ACCOUNT_CONNECT_PAGE:
+        Weave.Svc.Prefs.set("firstSync", "existingAccount");
+        this.wizard.getButton("next").hidden = false;
+        this.wizard.getButton("back").hidden = false;
+        this.wizard.getButton("extra1").hidden = false;
+        this.wizard.canAdvance = false;
+        this.wizard.canRewind = true;
+        this.startEasySetup();
+        break;
+      case EXISTING_ACCOUNT_LOGIN_PAGE:
+        this.wizard.getButton("next").hidden = false;
+        this.wizard.getButton("back").hidden = false;
+        this.wizard.getButton("extra1").hidden = false;
+        this.wizard.canRewind = true;
+        this.checkFields();
+        break;
+      case OPTIONS_PAGE:
+        this.wizard.canRewind = false;
+        this.wizard.canAdvance = true;
+        if (!this._resettingSync) {
+          this.wizard.getButton("next").label =
+            this._stringBundle.GetStringFromName("button.syncOptionsDone.label");
+          this.wizard.getButton("next").removeAttribute("accesskey");
+        }
+        this.wizard.getButton("next").hidden = false;
+        this.wizard.getButton("back").hidden = true;
+        this.wizard.getButton("cancel").hidden = !this._resettingSync;
+        this.wizard.getButton("extra1").hidden = true;
+        document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
+        document.getElementById("syncOptions").collapsed = this._resettingSync;
+        document.getElementById("mergeOptions").collapsed = this._settingUpNew;
+        break;
+      case OPTIONS_CONFIRM_PAGE:
+        this.wizard.canRewind = true;
+        this.wizard.canAdvance = true;
+        this.wizard.getButton("back").label =
+          this._stringBundle.GetStringFromName("button.syncOptionsCancel.label");
+        this.wizard.getButton("back").removeAttribute("accesskey");
+        this.wizard.getButton("back").hidden = this._resettingSync;
+        this.wizard.getButton("next").hidden = false;
+        this.wizard.getButton("finish").hidden = true;
+        break;
+    }
+  },
+
+  onWizardAdvance: function () {
+    // Check pageIndex so we don't prompt before the Sync setup wizard appears.
+    // This is a fallback in case the Master Password gets locked mid-wizard.
+    if ((this.wizard.pageIndex >= 0) &&
+        !Weave.Utils.ensureMPUnlocked()) {
+      return false;
+    }
+
+    switch (this.wizard.pageIndex) {
+      case PAIR_PAGE:
+        this.startPairing();
+        return false;
+      case NEW_ACCOUNT_START_PAGE:
+        // If the user selects Next (e.g. by hitting enter) when we haven't
+        // executed the delayed checks yet, execute them immediately.
+        if (this._checkAccountTimer) {
+          this.checkAccount();
+        }
+        if (this._checkServerTimer) {
+          this.checkServer();
+        }
+        if (!this.wizard.canAdvance) {
+          return false;
+        }
+
+        let doc = this.captchaBrowser.contentDocument;
+        let getField = function getField(field) {
+          let node = doc.getElementById("recaptcha_" + field + "_field");
+          return node && node.value;
+        };
+
+        // Display throbber
+        let feedback = document.getElementById("captchaFeedback");
+        let image = feedback.firstChild;
+        let label = image.nextSibling;
+        image.setAttribute("status", "active");
+        label.value = this._stringBundle.GetStringFromName("verifying.label");
+        setVisibility(feedback, true);
+
+        let password = document.getElementById("weavePassword").value;
+        let email = Weave.Utils.normalizeAccount(
+          document.getElementById("weaveEmail").value);
+        let challenge = getField("challenge");
+        let response = getField("response");
+
+        let error = Weave.Service.createAccount(email, password,
+                                                challenge, response);
+
+        if (error == null) {
+          Weave.Service.identity.account = email;
+          Weave.Service.identity.basicPassword = password;
+          Weave.Service.identity.syncKey = Weave.Utils.generatePassphrase();
+          this._handleNoScript(false);
+          Weave.Svc.Prefs.set("firstSync", "newAccount");
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+          if (document.getElementById("metroSetupCheckbox").checked) {
+            Services.metro.storeSyncInfo(email, password, Weave.Service.identity.syncKey);
+          }
+#endif
+#endif
+          this.wizardFinish();
+          return false;
+        }
+
+        image.setAttribute("status", "error");
+        label.value = Weave.Utils.getErrorString(error);
+        return false;
+      case EXISTING_ACCOUNT_LOGIN_PAGE:
+        Weave.Service.identity.account = Weave.Utils.normalizeAccount(
+          document.getElementById("existingAccountName").value);
+        Weave.Service.identity.basicPassword =
+          document.getElementById("existingPassword").value;
+        let pp = document.getElementById("existingPassphrase").value;
+        Weave.Service.identity.syncKey = Weave.Utils.normalizePassphrase(pp);
+        if (Weave.Service.login()) {
+          this.wizardFinish();
+        }
+        return false;
+      case OPTIONS_PAGE:
+        let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
+        // No confirmation needed on new account setup or merge option
+        // with existing account.
+        if (this._settingUpNew || (!this._resettingSync && desc == 0))
+          return this.returnFromOptions();
+        return this._handleChoice();
+      case OPTIONS_CONFIRM_PAGE:
+        if (this._resettingSync) {
+          this.wizardFinish();
+          return false;
+        }
+        return this.returnFromOptions();
+    }
+    return true;
+  },
+
+  onWizardBack: function () {
+    switch (this.wizard.pageIndex) {
+      case NEW_ACCOUNT_START_PAGE:
+      case EXISTING_ACCOUNT_LOGIN_PAGE:
+        this.wizard.pageIndex = INTRO_PAGE;
+        return false;
+      case EXISTING_ACCOUNT_CONNECT_PAGE:
+        this.abortEasySetup();
+        this.wizard.pageIndex = INTRO_PAGE;
+        return false;
+      case EXISTING_ACCOUNT_LOGIN_PAGE:
+        // If we were already pairing on entry, we went straight to the manual
+        // login page. If subsequently we go back, return to the page that lets
+        // us choose whether we already have an account.
+        if (this.wizardType == "pair") {
+          this.wizard.pageIndex = INTRO_PAGE;
+          return false;
+        }
+        return true;
+      case OPTIONS_CONFIRM_PAGE:
+        // Backing up from the confirmation page = resetting first sync to merge.
+        document.getElementById("mergeChoiceRadio").selectedIndex = 0;
+        return this.returnFromOptions();
+    }
+    return true;
+  },
+
+  wizardFinish: function () {
+    this.setupInitialSync();
+
+    if (this.wizardType == "pair") {
+      this.completePairing();
+    }
+
+    if (!this._resettingSync) {
+      function isChecked(element) {
+        return document.getElementById(element).hasAttribute("checked");
+      }
+
+      let prefs = ["engine.bookmarks", "engine.passwords", "engine.history",
+                   "engine.tabs", "engine.prefs", "engine.addons"];
+      for (let i = 0;i < prefs.length;i++) {
+        Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i]));
+      }
+      this._handleNoScript(false);
+      if (Weave.Svc.Prefs.get("firstSync", "") == "notReady")
+        Weave.Svc.Prefs.reset("firstSync");
+
+      Weave.Service.persistLogin();
+      Weave.Svc.Obs.notify("weave:service:setup-complete");
+
+      gSyncUtils.openFirstSyncProgressPage();
+    }
+    Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
+    window.close();
+  },
+
+  onWizardCancel: function () {
+    if (this._resettingSync)
+      return;
+
+    this.abortEasySetup();
+    this._handleNoScript(false);
+    Weave.Service.startOver();
+  },
+
+  onSyncOptions: function () {
+    this._beforeOptionsPage = this.wizard.pageIndex;
+    this.wizard.pageIndex = OPTIONS_PAGE;
+  },
+
+  returnFromOptions: function() {
+    this.wizard.getButton("next").label = this._nextButtonLabel;
+    this.wizard.getButton("next").setAttribute("accesskey",
+                                               this._nextButtonAccesskey);
+    this.wizard.getButton("back").label = this._backButtonLabel;
+    this.wizard.getButton("back").setAttribute("accesskey",
+                                               this._backButtonAccesskey);
+    this.wizard.getButton("cancel").hidden = false;
+    this.wizard.getButton("extra1").hidden = false;
+    this.wizard.pageIndex = this._beforeOptionsPage;
+    return false;
+  },
+
+  startPairing: function startPairing() {
+    this.pairDeviceErrorRow.hidden = true;
+    // When onAbort is called, Weave may already be gone.
+    const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+    let self = this;
+    let jpakeclient = this._jpakeclient = new Weave.JPAKEClient({
+      onPaired: function onPaired() {
+        self.wizard.pageIndex = INTRO_PAGE;
+      },
+      onComplete: function onComplete() {
+        // This method will never be called since SendCredentialsController
+        // will take over after the wizard completes.
+      },
+      onAbort: function onAbort(error) {
+        delete self._jpakeclient;
+
+        // Aborted by user, ignore. The window is almost certainly going to close
+        // or is already closed.
+        if (error == JPAKE_ERROR_USERABORT) {
+          return;
+        }
+
+        self.pairDeviceErrorRow.hidden = false;
+        self.pairDeviceThrobber.hidden = true;
+        self.pin1.value = self.pin2.value = self.pin3.value = "";
+        self.pin1.disabled = self.pin2.disabled = self.pin3.disabled = false;
+        if (self.wizard.pageIndex == PAIR_PAGE) {
+          self.pin1.focus();
+        }
+      }
+    });
+    this.pairDeviceThrobber.hidden = false;
+    this.pin1.disabled = this.pin2.disabled = this.pin3.disabled = true;
+    this.wizard.canAdvance = false;
+
+    let pin = this.pin1.value + this.pin2.value + this.pin3.value;
+    let expectDelay = true;
+    jpakeclient.pairWithPIN(pin, expectDelay);
+  },
+
+  completePairing: function completePairing() {
+    if (!this._jpakeclient) {
+      // The channel was aborted while we were setting up the account
+      // locally. XXX TODO should we do anything here, e.g. tell
+      // the user on the last wizard page that it's ok, they just
+      // have to pair again?
+      return;
+    }
+    let controller = new Weave.SendCredentialsController(this._jpakeclient,
+                                                         Weave.Service);
+    this._jpakeclient.controller = controller;
+  },
+
+  startEasySetup: function () {
+    // Don't do anything if we have a client already (e.g. we went to
+    // Sync Options and just came back).
+    if (this._jpakeclient)
+      return;
+
+    // When onAbort is called, Weave may already be gone
+    const JPAKE_ERROR_USERABORT = Weave.JPAKE_ERROR_USERABORT;
+
+    let self = this;
+    this._jpakeclient = new Weave.JPAKEClient({
+      displayPIN: function displayPIN(pin) {
+        document.getElementById("easySetupPIN1").value = pin.slice(0, 4);
+        document.getElementById("easySetupPIN2").value = pin.slice(4, 8);
+        document.getElementById("easySetupPIN3").value = pin.slice(8);
+      },
+
+      onPairingStart: function onPairingStart() {},
+
+      onComplete: function onComplete(credentials) {
+        Weave.Service.identity.account = credentials.account;
+        Weave.Service.identity.basicPassword = credentials.password;
+        Weave.Service.identity.syncKey = credentials.synckey;
+        Weave.Service.serverURL = credentials.serverURL;
+        gSyncSetup.wizardFinish();
+      },
+
+      onAbort: function onAbort(error) {
+        delete self._jpakeclient;
+
+        // Ignore if wizard is aborted.
+        if (error == JPAKE_ERROR_USERABORT)
+          return;
+
+        // Automatically go to manual setup if we couldn't acquire a channel.
+        if (error == Weave.JPAKE_ERROR_CHANNEL) {
+          self.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+          return;
+        }
+
+        // Restart on all other errors.
+        self.startEasySetup();
+      }
+    });
+    this._jpakeclient.receiveNoPIN();
+  },
+
+  abortEasySetup: function () {
+    document.getElementById("easySetupPIN1").value = "";
+    document.getElementById("easySetupPIN2").value = "";
+    document.getElementById("easySetupPIN3").value = "";
+    if (!this._jpakeclient)
+      return;
+
+    this._jpakeclient.abort();
+    delete this._jpakeclient;
+  },
+
+  manualSetup: function () {
+    this.abortEasySetup();
+    this.wizard.pageIndex = EXISTING_ACCOUNT_LOGIN_PAGE;
+  },
+
+  // _handleNoScript is needed because it blocks the captcha. So we temporarily
+  // allow the necessary sites so that we can verify the user is in fact a human.
+  // This was done with the help of Giorgio (NoScript author). See bug 508112.
+  _handleNoScript: function (addExceptions) {
+    // if NoScript isn't installed, or is disabled, bail out.
+    let ns = Cc["@maone.net/noscript-service;1"];
+    if (ns == null)
+      return;
+
+    ns = ns.getService().wrappedJSObject;
+    if (addExceptions) {
+      this._remoteSites.forEach(function(site) {
+        site = ns.getSite(site);
+        if (!ns.isJSEnabled(site)) {
+          this._disabledSites.push(site); // save status
+          ns.setJSEnabled(site, true); // allow site
+        }
+      }, this);
+    }
+    else {
+      this._disabledSites.forEach(function(site) {
+        ns.setJSEnabled(site, false);
+      });
+      this._disabledSites = [];
+    }
+  },
+
+  onExistingServerCommand: function () {
+    let control = document.getElementById("existingServer");
+    if (control.selectedIndex == 0) {
+      control.removeAttribute("editable");
+      Weave.Svc.Prefs.reset("serverURL");
+    } else {
+      control.setAttribute("editable", "true");
+      // Force a style flush to ensure that the binding is attached.
+      control.clientTop;
+      control.value = "";
+      control.inputField.focus();
+    }
+    document.getElementById("existingServerFeedbackRow").hidden = true;
+    this.checkFields();
+  },
+
+  onExistingServerInput: function () {
+    // Check custom server validity when the user stops typing for 1 second.
+    if (this._existingServerTimer)
+      window.clearTimeout(this._existingServerTimer);
+    this._existingServerTimer = window.setTimeout(function () {
+      gSyncSetup.checkFields();
+    }, 1000);
+  },
+
+  onServerCommand: function () {
+    setVisibility(document.getElementById("TOSRow"), this._usingMainServers);
+    let control = document.getElementById("server");
+    if (!this._usingMainServers) {
+      control.setAttribute("editable", "true");
+      // Force a style flush to ensure that the binding is attached.
+      control.clientTop;
+      control.value = "";
+      control.inputField.focus();
+      // checkServer() will call checkAccount() and checkFields().
+      this.checkServer();
+      return;
+    }
+    control.removeAttribute("editable");
+    Weave.Svc.Prefs.reset("serverURL");
+    if (this._settingUpNew) {
+      this.loadCaptcha();
+    }
+    this.checkAccount();
+    this.status.server = true;
+    document.getElementById("serverFeedbackRow").hidden = true;
+    this.checkFields();
+  },
+
+  onServerInput: function () {
+    // Check custom server validity when the user stops typing for 1 second.
+    if (this._checkServerTimer)
+      window.clearTimeout(this._checkServerTimer);
+    this._checkServerTimer = window.setTimeout(function () {
+      gSyncSetup.checkServer();
+    }, 1000);
+  },
+
+  checkServer: function () {
+    delete this._checkServerTimer;
+    let el = document.getElementById("server");
+    let valid = false;
+    let feedback = document.getElementById("serverFeedbackRow");
+    let str = "";
+    if (el.value) {
+      valid = this._validateServer(el);
+      let str = valid ? "" : "serverInvalid.label";
+      this._setFeedbackMessage(feedback, valid, str);
+    }
+    else
+      this._setFeedbackMessage(feedback, true);
+
+    // Recheck account against the new server.
+    if (valid)
+      this.checkAccount();
+
+    this.status.server = valid;
+    this.checkFields();
+  },
+
+  _validateServer: function (element) {
+    let valid = false;
+    let val = element.value;
+    if (!val)
+      return false;
+
+    let uri = Weave.Utils.makeURI(val);
+
+    if (!uri)
+      uri = Weave.Utils.makeURI("https://" + val);
+
+    if (uri && this._settingUpNew) {
+      function isValid(uri) {
+        Weave.Service.serverURL = uri.spec;
+        let check = Weave.Service.checkAccount("a");
+        return (check == "available" || check == "notAvailable");
+      }
+
+      if (uri.schemeIs("http")) {
+        uri.scheme = "https";
+        if (isValid(uri))
+          valid = true;
+        else
+          // setting the scheme back to http
+          uri.scheme = "http";
+      }
+      if (!valid)
+        valid = isValid(uri);
+
+      if (valid) {
+        this.loadCaptcha();
+      }
+    }
+    else if (uri) {
+      valid = true;
+      Weave.Service.serverURL = uri.spec;
+    }
+
+    if (valid)
+      element.value = Weave.Service.serverURL;
+    else
+      Weave.Svc.Prefs.reset("serverURL");
+
+    return valid;
+  },
+
+  _handleChoice: function () {
+    let desc = document.getElementById("mergeChoiceRadio").selectedIndex;
+    document.getElementById("chosenActionDeck").selectedIndex = desc;
+    switch (desc) {
+      case 1:
+        if (this._case1Setup)
+          break;
+
+        let places_db = PlacesUtils.history
+                                   .QueryInterface(Ci.nsPIPlacesDatabase)
+                                   .DBConnection;
+        if (Weave.Service.engineManager.get("history").enabled) {
+          let daysOfHistory = 0;
+          let stm = places_db.createStatement(
+            "SELECT ROUND(( " +
+              "strftime('%s','now','localtime','utc') - " +
+              "( " +
+                "SELECT visit_date FROM moz_historyvisits " +
+                "ORDER BY visit_date ASC LIMIT 1 " +
+                ")/1000000 " +
+              ")/86400) AS daysOfHistory ");
+
+          if (stm.step())
+            daysOfHistory = stm.getInt32(0);
+          // Support %S for historical reasons (see bug 600141)
+          document.getElementById("historyCount").value =
+            PluralForm.get(daysOfHistory,
+                           this._stringBundle.GetStringFromName("historyDaysCount.label"))
+                      .replace("%S", daysOfHistory)
+                      .replace("#1", daysOfHistory);
+        } else {
+          document.getElementById("historyCount").hidden = true;
+        }
+
+        if (Weave.Service.engineManager.get("bookmarks").enabled) {
+          let bookmarks = 0;
+          let stm = places_db.createStatement(
+            "SELECT count(*) AS bookmarks " +
+            "FROM moz_bookmarks b " +
+            "LEFT JOIN moz_bookmarks t ON " +
+            "b.parent = t.id WHERE b.type = 1 AND t.parent <> :tag");
+          stm.params.tag = PlacesUtils.tagsFolderId;
+          if (stm.executeStep())
+            bookmarks = stm.row.bookmarks;
+          // Support %S for historical reasons (see bug 600141)
+          document.getElementById("bookmarkCount").value =
+            PluralForm.get(bookmarks,
+                           this._stringBundle.GetStringFromName("bookmarksCount.label"))
+                      .replace("%S", bookmarks)
+                      .replace("#1", bookmarks);
+        } else {
+          document.getElementById("bookmarkCount").hidden = true;
+        }
+
+        if (Weave.Service.engineManager.get("passwords").enabled) {
+          let logins = Services.logins.getAllLogins({});
+          // Support %S for historical reasons (see bug 600141)
+          document.getElementById("passwordCount").value =
+            PluralForm.get(logins.length,
+                           this._stringBundle.GetStringFromName("passwordsCount.label"))
+                      .replace("%S", logins.length)
+                      .replace("#1", logins.length);
+        } else {
+          document.getElementById("passwordCount").hidden = true;
+        }
+
+        if (!Weave.Service.engineManager.get("prefs").enabled) {
+          document.getElementById("prefsWipe").hidden = true;
+        }
+
+        let addonsEngine = Weave.Service.engineManager.get("addons");
+        if (addonsEngine.enabled) {
+          let ids = addonsEngine._store.getAllIDs();
+          let blessedcount = 0;
+          for each (let i in ids) {
+            if (i) {
+              blessedcount++;
+            }
+          }
+          // bug 600141 does not apply, as this does not have to support existing strings
+          document.getElementById("addonCount").value =
+            PluralForm.get(blessedcount,
+                           this._stringBundle.GetStringFromName("addonsCount.label"))
+                      .replace("#1", blessedcount);
+        } else {
+          document.getElementById("addonCount").hidden = true;
+        }
+
+        this._case1Setup = true;
+        break;
+      case 2:
+        if (this._case2Setup)
+          break;
+        let count = 0;
+        function appendNode(label) {
+          let box = document.getElementById("clientList");
+          let node = document.createElement("label");
+          node.setAttribute("value", label);
+          node.setAttribute("class", "data indent");
+          box.appendChild(node);
+        }
+
+        for each (let name in Weave.Service.clientsEngine.stats.names) {
+          // Don't list the current client
+          if (name == Weave.Service.clientsEngine.localName)
+            continue;
+
+          // Only show the first several client names
+          if (++count <= 5)
+            appendNode(name);
+        }
+        if (count > 5) {
+          // Support %S for historical reasons (see bug 600141)
+          let label =
+            PluralForm.get(count - 5,
+                           this._stringBundle.GetStringFromName("additionalClientCount.label"))
+                      .replace("%S", count - 5)
+                      .replace("#1", count - 5);
+          appendNode(label);
+        }
+        this._case2Setup = true;
+        break;
+    }
+
+    return true;
+  },
+
+  // sets class and string on a feedback element
+  // if no property string is passed in, we clear label/style
+  _setFeedback: function (element, success, string) {
+    element.hidden = success || !string;
+    let classname = success ? "success" : "error";
+    let image = element.getElementsByAttribute("class", "statusIcon")[0];
+    image.setAttribute("status", classname);
+    let label = element.getElementsByAttribute("class", "status")[0];
+    label.value = string;
+  },
+
+  // shim
+  _setFeedbackMessage: function (element, success, string) {
+    let str = "";
+    if (string) {
+      try {
+        str = this._stringBundle.GetStringFromName(string);
+      } catch(e) {}
+
+      if (!str)
+        str = Weave.Utils.getErrorString(string);
+    }
+    this._setFeedback(element, success, str);
+  },
+
+  loadCaptcha: function loadCaptcha() {
+    let captchaURI = Weave.Service.miscAPI + "captcha_html";
+    // First check for NoScript and whitelist the right sites.
+    this._handleNoScript(true);
+    if (this.captchaBrowser.currentURI.spec != captchaURI) {
+      this.captchaBrowser.loadURI(captchaURI);
+    }
+  },
+
+  onStateChange: function(webProgress, request, stateFlags, status) {
+    // We're only looking for the end of the frame load
+    if ((stateFlags & Ci.nsIWebProgressListener.STATE_STOP) == 0)
+      return;
+    if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_NETWORK) == 0)
+      return;
+    if ((stateFlags & Ci.nsIWebProgressListener.STATE_IS_WINDOW) == 0)
+      return;
+
+    // If we didn't find a captcha, assume it's not needed and don't show it.
+    let responseStatus = request.QueryInterface(Ci.nsIHttpChannel).responseStatus;
+    setVisibility(this.captchaBrowser, responseStatus != 404);
+    //XXX TODO we should really log any responseStatus other than 200
+  },
+  onProgressChange: function() {},
+  onStatusChange: function() {},
+  onSecurityChange: function() {},
+  onLocationChange: function () {}
+};
+
+// Define lazy getters for various XUL elements.
+//
+// onWizardAdvance() and onPageShow() are run before init(), so we'll even
+// define things that will almost certainly be used (like 'wizard') as a lazy
+// getter here.
+["wizard",
+ "pin1",
+ "pin2",
+ "pin3",
+ "pairDeviceErrorRow",
+ "pairDeviceThrobber"].forEach(function (id) {
+  XPCOMUtils.defineLazyGetter(gSyncSetup, id, function() {
+    return document.getElementById(id);
+  });
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "nextFocusEl", function () {
+  return {pin1: this.pin2,
+          pin2: this.pin3,
+          pin3: this.wizard.getButton("next")};
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() {
+  return Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+});
new file mode 100644
--- /dev/null
+++ b/browser/base/content/sync/setup.xul
@@ -0,0 +1,504 @@
+<?xml version="1.0"?>
+
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<?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 id="wizard"
+        title="&accountSetupTitle.label;"
+        windowtype="Weave:AccountSetup"
+        persist="screenX screenY"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        onwizardnext="return gSyncSetup.onWizardAdvance()"
+        onwizardback="return gSyncSetup.onWizardBack()"
+        onwizardcancel="gSyncSetup.onWizardCancel()"
+        onload="gSyncSetup.init()">
+
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/setup.js"/>
+  <script type="application/javascript"
+          src="chrome://browser/content/sync/utils.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="&pairDevice.title.label;"
+              onpageshow="gSyncSetup.onPageShow()">
+    <description>
+      &pairDevice.dialog.description.label;
+      <label class="text-link"
+             value="&addDevice.showMeHow.label;"
+             href="https://services.mozilla.com/sync/help/add-device"/>
+    </description>
+    <separator class="groove-thin"/>
+    <description>
+      &addDevice.dialog.enterCode.label;
+    </description>
+    <separator class="groove-thin"/>
+    <vbox align="center">
+      <textbox id="pin1"
+               class="pin"
+               oninput="gSyncSetup.onPINInput(this);"
+               onfocus="this.select();"
+               />
+      <textbox id="pin2"
+               class="pin"
+               oninput="gSyncSetup.onPINInput(this);"
+               onfocus="this.select();"
+               />
+      <textbox id="pin3"
+               class="pin"
+               oninput="gSyncSetup.onPINInput(this);"
+               onfocus="this.select();" 
+               />
+    </vbox>
+    <separator class="groove-thin"/>
+    <vbox id="pairDeviceThrobber" align="center" hidden="true">
+      <image/>
+    </vbox>
+    <hbox id="pairDeviceErrorRow" pack="center" hidden="true">
+      <image class="statusIcon" status="error"/>
+      <label class="status"
+             value="&addDevice.dialog.tryAgain.label;"/>
+    </hbox>
+  </wizardpage>
+
+  <wizardpage id="pickSetupType"
+              label="&syncBrand.fullName.label;"
+              onpageshow="gSyncSetup.onPageShow()">
+    <vbox align="center" flex="1">
+      <description style="padding: 0 7em;">
+        &setup.pickSetupType.description2;
+      </description>
+      <spacer flex="3"/>
+      <button id="newAccount"
+              class="accountChoiceButton"
+              label="&button.createNewAccount.label;"
+              oncommand="gSyncSetup.startNewAccountSetup()"
+              align="center"/>
+      <spacer flex="1"/>
+    </vbox>
+    <separator class="groove"/>
+    <vbox align="center" flex="1">
+      <spacer flex="1"/>
+      <button id="existingAccount"
+              class="accountChoiceButton"
+              label="&button.haveAccount.label;"
+              oncommand="gSyncSetup.useExistingAccount()"/>
+      <spacer flex="3"/>
+    </vbox>
+  </wizardpage>
+
+  <wizardpage label="&setup.newAccountDetailsPage.title.label;"
+              id="newAccountStart"
+              onextra1="gSyncSetup.onSyncOptions()"
+              onpageshow="gSyncSetup.onPageShow();">
+    <grid>
+      <columns>
+        <column/>
+        <column class="inputColumn" flex="1"/>
+      </columns>
+      <rows>
+        <row id="emailRow" align="center">
+          <label value="&setup.emailAddress.label;"
+                 accesskey="&setup.emailAddress.accesskey;"
+                 control="weaveEmail"/>
+          <textbox id="weaveEmail"
+                   oninput="gSyncSetup.onEmailInput()"/>
+        </row>
+        <row id="emailFeedbackRow" align="center" hidden="true">
+          <spacer/>
+          <hbox>
+            <image class="statusIcon"/>
+            <label class="status" value=" "/>
+          </hbox>
+        </row>
+        <row id="passwordRow" align="center">
+          <label value="&setup.choosePassword.label;"
+                 accesskey="&setup.choosePassword.accesskey;"
+                 control="weavePassword"/>
+          <textbox id="weavePassword"
+                   type="password"
+                   onchange="gSyncSetup.onPasswordChange()"/>
+        </row>
+        <row id="confirmRow" align="center">
+          <label value="&setup.confirmPassword.label;"
+                 accesskey="&setup.confirmPassword.accesskey;"
+                 control="weavePasswordConfirm"/>
+          <textbox id="weavePasswordConfirm"
+                   type="password"
+                   onchange="gSyncSetup.onPasswordChange()"/>
+        </row>
+        <row id="passwordFeedbackRow" align="center" hidden="true">
+          <spacer/>
+          <hbox>
+            <image class="statusIcon"/>
+            <label class="status" value=" "/>
+          </hbox>
+        </row>
+        <row align="center">
+          <label control="server"
+                 value="&server.label;"/>
+          <menulist id="server"
+                    oncommand="gSyncSetup.onServerCommand()"
+                    oninput="gSyncSetup.onServerInput()">
+            <menupopup>
+              <menuitem label="&serverType.default.label;"
+                        value="main"/>
+              <menuitem label="&serverType.custom2.label;"
+                        value="custom"/>
+            </menupopup>
+          </menulist>
+        </row>
+        <row id="serverFeedbackRow" align="center" hidden="true">
+          <spacer/>
+          <hbox>
+            <image class="statusIcon"/>
+            <label class="status" value=" "/>
+          </hbox>
+        </row>
+#ifdef XP_WIN
+#ifdef MOZ_METRO
+        <row id="metroRow" align="center">
+          <spacer/>
+          <hbox align="center">
+            <checkbox label="&setup.setupMetro.label;"
+                      accesskey="&setup.setupMetro.accesskey;"
+                      control="weavePasswordConfirm"
+                      id="metroSetupCheckbox"
+                      checked="true"/>
+          </hbox>
+        </row>
+#endif
+#endif
+        <row id="TOSRow" align="center">
+          <spacer/>
+          <hbox align="center">
+            <checkbox id="tos"
+                      accesskey="&setup.tosAgree1.accesskey;"
+                      oncommand="this.focus(); gSyncSetup.checkFields();"/>
+            <description id="tosDesc"
+                         flex="1"
+                         onclick="document.getElementById('tos').focus();
+                                  document.getElementById('tos').click()">
+              &setup.tosAgree1.label;
+              <label class="text-link inline-link"
+                     onclick="event.stopPropagation();gSyncUtils.openToS();">
+                &setup.tosLink.label;
+              </label>
+              &setup.tosAgree2.label;
+              <label class="text-link inline-link"
+                     onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();">
+                &setup.ppLink.label;
+              </label>
+              &setup.tosAgree3.label;
+            </description>
+          </hbox>
+        </row>
+      </rows>
+    </grid>
+    <spacer flex="1"/>
+    <vbox flex="1" align="center">
+      <browser height="150"
+               width="500"
+               id="captcha"
+               type="content"
+               disablehistory="true"/>
+      <spacer flex="1"/>
+      <hbox id="captchaFeedback">
+        <image class="statusIcon"/>
+        <label class="status" value=" "/>
+      </hbox>
+    </vbox>
+  </wizardpage>
+
+  <wizardpage id="addDevice"
+              label="&pairDevice.title.label;"
+              onextra1="gSyncSetup.onSyncOptions()"
+              onpageshow="gSyncSetup.onPageShow()">
+    <description>
+      &pairDevice.setup.description.label;
+      <label class="text-link"
+             value="&addDevice.showMeHow.label;"
+             href="https://services.mozilla.com/sync/help/easy-setup"/>
+    </description>
+    <label value="&addDevice.setup.enterCode.label;"
+           control="easySetupPIN1"/>
+    <spacer flex="1"/>
+    <vbox align="center" flex="1">
+      <textbox id="easySetupPIN1"
+               class="pin"
+               value=""
+               readonly="true"
+               />
+      <textbox id="easySetupPIN2"
+               class="pin"
+               value=""
+               readonly="true"
+               />
+      <textbox id="easySetupPIN3"
+               class="pin"
+               value=""
+               readonly="true"
+               />
+    </vbox>
+    <spacer flex="3"/>
+    <label class="text-link"
+           value="&addDevice.dontHaveDevice.label;"
+           onclick="gSyncSetup.manualSetup();"/>
+  </wizardpage>
+
+  <wizardpage id="existingAccount"
+              label="&setup.signInPage.title.label;"
+              onextra1="gSyncSetup.onSyncOptions()"
+              onpageshow="gSyncSetup.onPageShow()">
+      <grid>
+        <columns>
+          <column/>
+          <column class="inputColumn" flex="1"/>
+        </columns>
+        <rows>
+          <row id="existingAccountRow" align="center">
+            <label id="existingAccountLabel"
+                   value="&signIn.account2.label;"
+                   accesskey="&signIn.account2.accesskey;"
+                   control="existingAccount"/>
+            <textbox id="existingAccountName"
+                     oninput="gSyncSetup.checkFields(event)"
+                     onchange="gSyncSetup.checkFields(event)"/>
+          </row>
+          <row id="existingPasswordRow" align="center">
+            <label id="existingPasswordLabel"
+                   value="&signIn.password.label;"
+                   accesskey="&signIn.password.accesskey;"
+                   control="existingPassword"/>
+            <textbox id="existingPassword"
+                     type="password"
+                     onkeyup="gSyncSetup.checkFields(event)"
+                     onchange="gSyncSetup.checkFields(event)"/>
+          </row>
+          <row id="existingPasswordFeedbackRow" align="center" hidden="true">
+            <spacer/>
+            <hbox>
+              <image class="statusIcon"/>
+              <label class="status" value=" "/>
+            </hbox>
+          </row>
+          <row align="center">
+            <spacer/>
+            <label class="text-link"
+                   value="&resetPassword.label;"
+                   onclick="gSyncUtils.resetPassword(); return false;"/>
+          </row>
+          <row align="center">
+            <label control="existingServer"
+                   value="&server.label;"/>
+            <menulist id="existingServer"
+                      oncommand="gSyncSetup.onExistingServerCommand()"
+                      oninput="gSyncSetup.onExistingServerInput()">
+              <menupopup>
+                <menuitem label="&serverType.default.label;"
+                          value="main"/>
+                <menuitem label="&serverType.custom2.label;"
+                          value="custom"/>
+              </menupopup>
+            </menulist>
+          </row>
+          <row id="existingServerFeedbackRow" align="center" hidden="true">
+            <spacer/>
+            <hbox>
+              <image class="statusIcon"/>
+              <vbox>
+                <label class="status" value=" "/>
+              </vbox>
+            </hbox>
+          </row>
+        </rows>
+      </grid>
+
+    <groupbox>
+      <label id="existingPassphraseLabel"
+             value="&signIn.recoveryKey.label;"
+             accesskey="&signIn.recoveryKey.accesskey;"
+             control="existingPassphrase"/>
+      <textbox id="existingPassphrase"
+               oninput="gSyncSetup.checkFields()"/>
+      <hbox id="login-throbber" hidden="true">
+        <image/>
+        <label value="&verifying.label;"/>
+      </hbox>
+      <vbox align="left" id="existingPassphraseFeedbackRow" hidden="true">
+        <hbox>
+          <image class="statusIcon"/>
+          <label class="status" value=" "/>
+        </hbox>
+      </vbox>
+    </groupbox>
+
+    <vbox id="passphraseHelpBox">
+      <description>
+        &existingRecoveryKey.description;
+        <label class="text-link"
+               href="https://services.mozilla.com/sync/help/manual-setup">
+          &addDevice.showMeHow.label;
+        </label>
+        <spacer id="passphraseHelpSpacer"/>
+        <label class="text-link"
+               onclick="gSyncSetup.resetPassphrase(); return false;">
+          &resetSyncKey.label;
+        </label>
+      </description>
+    </vbox>
+  </wizardpage>
+
+  <wizardpage id="syncOptionsPage"
+              label="&setup.optionsPage.title;"
+              onpageshow="gSyncSetup.onPageShow()">
+    <groupbox id="syncOptions">
+    <grid>
+      <columns>
+        <column/>
+        <column flex="1" style="-moz-margin-end: 2px"/>
+      </columns>
+      <rows>
+        <row align="center">
+          <label value="&syncDeviceName.label;"
+                 accesskey="&syncDeviceName.accesskey;"
+                 control="syncComputerName"/>
+          <textbox id="syncComputerName" flex="1"
+                   onchange="gSyncUtils.changeName(this)"/>
+        </row>
+        <row>
+          <label value="&syncMy.label;" />
+          <vbox>
+            <checkbox label="&engine.addons.label;"
+                      accesskey="&engine.addons.accesskey;"
+                      id="engine.addons"
+                      checked="true"/>
+            <checkbox label="&engine.bookmarks.label;"
+                      accesskey="&engine.bookmarks.accesskey;"
+                      id="engine.bookmarks"
+                      checked="true"/>
+            <checkbox label="&engine.passwords.label;"
+                      accesskey="&engine.passwords.accesskey;"
+                      id="engine.passwords"
+                      checked="true"/>
+            <checkbox label="&engine.prefs.label;"
+                      accesskey="&engine.prefs.accesskey;"
+                      id="engine.prefs"
+                      checked="true"/>
+            <checkbox label="&engine.history.label;"
+                      accesskey="&engine.history.accesskey;"
+                      id="engine.history"
+                      checked="true"/>
+            <checkbox label="&engine.tabs.label;"
+                      accesskey="&engine.tabs.accesskey;"
+                      id="engine.tabs"
+                      checked="true"/>
+          </vbox>
+        </row>
+      </rows>
+    </grid>
+    </groupbox>
+
+    <groupbox id="mergeOptions">
+      <radiogroup id="mergeChoiceRadio" pack="start">
+        <grid>
+          <columns>
+            <column/>
+            <column flex="1"/>
+          </columns>
+          <rows flex="1">
+            <row align="center">
+              <radio id="resetClient"
+                     class="mergeChoiceButton"
+                     aria-labelledby="resetClientLabel"/>
+              <label id="resetClientLabel" control="resetClient">
+                <html:strong>&choice2.merge.recommended.label;</html:strong>
+                &choice2a.merge.main.label;
+              </label>
+            </row>
+            <row align="center">
+              <radio id="wipeClient"
+                     class="mergeChoiceButton"
+                     aria-labelledby="wipeClientLabel"/>
+              <label id="wipeClientLabel"
+                     control="wipeClient">
+                &choice2a.client.main.label;
+              </label>
+            </row>
+            <row align="center">
+              <radio id="wipeRemote"
+                     class="mergeChoiceButton"
+                     aria-labelledby="wipeRemoteLabel"/>
+              <label id="wipeRemoteLabel"
+                     control="wipeRemote">
+                &choice2a.server.main.label;
+              </label>
+            </row>
+          </rows>
+        </grid>
+      </radiogroup>
+    </groupbox>
+  </wizardpage>
+
+  <wizardpage id="syncOptionsConfirm"
+              label="&setup.optionsConfirmPage.title;"
+              onpageshow="gSyncSetup.onPageShow()">
+      <deck id="chosenActionDeck">
+        <vbox id="chosenActionMerge" class="confirm">
+          <description class="normal">
+            &confirm.merge2.label;
+          </description>
+        </vbox>
+        <vbox id="chosenActionWipeClient" class="confirm">
+          <description class="normal">
+            &confirm.client3.label;
+          </description>
+          <separator class="thin"/>
+          <vbox id="dataList">
+            <label class="data indent" id="bookmarkCount"/>
+            <label class="data indent" id="historyCount"/>
+            <label class="data indent" id="passwordCount"/>
+            <label class="data indent" id="addonCount"/>
+            <label class="data indent" id="prefsWipe"
+                   value="&engine.prefs.label;"/>
+          </vbox>
+          <separator class="thin"/>
+          <description class="normal">
+            &confirm.client2.moreinfo.label;
+          </description>
+        </vbox>
+        <vbox id="chosenActionWipeServer" class="confirm">
+          <description class="normal">
+            &confirm.server2.label;
+          </description>
+          <separator class="thin"/>
+          <vbox id="clientList">
+          </vbox>
+        </vbox>
+      </deck>
+  </wizardpage>
+  <!-- In terms of the wizard flow shown to the user, the 'syncOptionsConfirm'
+       page above is not the last wizard page. To prevent the wizard binding from
+       assuming that it is, we're inserting this dummy page here. This also means
+      that the wizard needs to always be closed manually via wizardFinish(). -->
+  <wizardpage>
+  </wizardpage>
+</wizard>
+
--- a/browser/base/content/sync/utils.js
+++ b/browser/base/content/sync/utils.js
@@ -1,36 +1,218 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// Equivalent to 0600 permissions; used for saved Sync Recovery Key.
+// This constant can be replaced when the equivalent values are available to
+// chrome JS; see Bug 433295 and Bug 757351.
+const PERMISSIONS_RWUSR = 0x180;
+
 // Weave should always exist before before this file gets included.
 let gSyncUtils = {
+  get bundle() {
+    delete this.bundle;
+    return this.bundle = Services.strings.createBundle("chrome://browser/locale/syncSetup.properties");
+  },
+
   // opens in a new window if we're in a modal prefwindow world, in a new tab otherwise
   _openLink: function (url) {
     let thisDocEl = document.documentElement,
         openerDocEl = window.opener && window.opener.document.documentElement;
-    if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply) {
+    if (thisDocEl.id == "accountSetup" && window.opener &&
+        openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
+      openUILinkIn(url, "window");
+    else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
       openUILinkIn(url, "window");
-    } else {
+    else if (document.documentElement.id == "change-dialog")
+      Services.wm.getMostRecentWindow("navigator:browser")
+              .openUILinkIn(url, "tab");
+    else
       openUILinkIn(url, "tab");
-    }
   },
 
   changeName: function changeName(input) {
     // Make sure to update to a modified name, e.g., empty-string -> default
     Weave.Service.clientsEngine.localName = input.value;
     input.value = Weave.Service.clientsEngine.localName;
   },
 
+  openChange: function openChange(type, duringSetup) {
+    // Just re-show the dialog if it's already open
+    let openedDialog = Services.wm.getMostRecentWindow("Sync:" + type);
+    if (openedDialog != null) {
+      openedDialog.focus();
+      return;
+    }
+
+    // Open up the change dialog
+    let changeXUL = "chrome://browser/content/sync/genericChange.xul";
+    let changeOpt = "centerscreen,chrome,resizable=no";
+    Services.ww.activeWindow.openDialog(changeXUL, "", changeOpt,
+                                        type, duringSetup);
+  },
+
+  changePassword: function () {
+    if (Weave.Utils.ensureMPUnlocked())
+      this.openChange("ChangePassword");
+  },
+
+  resetPassphrase: function (duringSetup) {
+    if (Weave.Utils.ensureMPUnlocked())
+      this.openChange("ResetPassphrase", duringSetup);
+  },
+
+  updatePassphrase: function () {
+    if (Weave.Utils.ensureMPUnlocked())
+      this.openChange("UpdatePassphrase");
+  },
+
+  resetPassword: function () {
+    this._openLink(Weave.Service.pwResetURL);
+  },
+
   openToS: function () {
     this._openLink(Weave.Svc.Prefs.get("termsURL"));
   },
 
   openPrivacyPolicy: function () {
     this._openLink(Weave.Svc.Prefs.get("privacyURL"));
   },
 
-  openAccountsPage: function () {
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
-    win.switchToTabHavingURI("about:accounts", true);
+  openFirstSyncProgressPage: function () {
+    this._openLink("about:sync-progress");
+  },
+
+  /**
+   * Prepare an invisible iframe with the passphrase backup document.
+   * Used by both the print and saving methods.
+   *
+   * @param elid : ID of the form element containing the passphrase.
+   * @param callback : Function called once the iframe has loaded.
+   */
+  _preparePPiframe: function(elid, callback) {
+    let pp = document.getElementById(elid).value;
+
+    // Create an invisible iframe whose contents we can print.
+    let iframe = document.createElement("iframe");
+    iframe.setAttribute("src", "chrome://browser/content/sync/key.xhtml");
+    iframe.collapsed = true;
+    document.documentElement.appendChild(iframe);
+    iframe.contentWindow.addEventListener("load", function() {
+      iframe.contentWindow.removeEventListener("load", arguments.callee, false);
+
+      // Insert the Sync Key into the page.
+      let el = iframe.contentDocument.getElementById("synckey");
+      el.firstChild.nodeValue = pp;
+
+      // Insert the TOS and Privacy Policy URLs into the page.
+      let termsURL = Weave.Svc.Prefs.get("termsURL");
+      el = iframe.contentDocument.getElementById("tosLink");
+      el.setAttribute("href", termsURL);
+      el.firstChild.nodeValue = termsURL;
+
+      let privacyURL = Weave.Svc.Prefs.get("privacyURL");
+      el = iframe.contentDocument.getElementById("ppLink");
+      el.setAttribute("href", privacyURL);
+      el.firstChild.nodeValue = privacyURL;
+
+      callback(iframe);
+    }, false);
+  },
+
+  /**
+   * Print passphrase backup document.
+   * 
+   * @param elid : ID of the form element containing the passphrase.
+   */
+  passphrasePrint: function(elid) {
+    this._preparePPiframe(elid, function(iframe) {
+      let webBrowserPrint = iframe.contentWindow
+                                  .QueryInterface(Ci.nsIInterfaceRequestor)
+                                  .getInterface(Ci.nsIWebBrowserPrint);
+      let printSettings = PrintUtils.getPrintSettings();
+
+      // Display no header/footer decoration except for the date.
+      printSettings.headerStrLeft
+        = printSettings.headerStrCenter
+        = printSettings.headerStrRight
+        = printSettings.footerStrLeft
+        = printSettings.footerStrCenter = "";
+      printSettings.footerStrRight = "&D";
+
+      try {
+        webBrowserPrint.print(printSettings, null);
+      } catch (ex) {
+        // print()'s return codes are expressed as exceptions. Ignore.
+      }
+    });
+  },
+
+  /**
+   * Save passphrase backup document to disk as HTML file.
+   * 
+   * @param elid : ID of the form element containing the passphrase.
+   */
+  passphraseSave: function(elid) {
+    let dialogTitle = this.bundle.GetStringFromName("save.recoverykey.title");
+    let defaultSaveName = this.bundle.GetStringFromName("save.recoverykey.defaultfilename");
+    this._preparePPiframe(elid, function(iframe) {
+      let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
+      let fpCallback = function fpCallback_done(aResult) {
+        if (aResult == Ci.nsIFilePicker.returnOK ||
+            aResult == Ci.nsIFilePicker.returnReplace) {
+          let stream = Cc["@mozilla.org/network/file-output-stream;1"].
+                       createInstance(Ci.nsIFileOutputStream);
+          stream.init(fp.file, -1, PERMISSIONS_RWUSR, 0);
+
+          let serializer = new XMLSerializer();
+          let output = serializer.serializeToString(iframe.contentDocument);
+          output = output.replace(/<!DOCTYPE (.|\n)*?]>/,
+            '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" ' +
+            '"DTD/xhtml1-strict.dtd">');
+          output = Weave.Utils.encodeUTF8(output);
+          stream.write(output, output.length);
+        }
+      };
+
+      fp.init(window, dialogTitle, Ci.nsIFilePicker.modeSave);
+      fp.appendFilters(Ci.nsIFilePicker.filterHTML);
+      fp.defaultString = defaultSaveName;
+      fp.open(fpCallback);
+      return false;
+    });
+  },
+
+  /**
+   * validatePassword
+   *
+   * @param el1 : the first textbox element in the form
+   * @param el2 : the second textbox element, if omitted it's an update form
+   * 
+   * returns [valid, errorString]
+   */
+  validatePassword: function (el1, el2) {
+    let valid = false;
+    let val1 = el1.value;
+    let val2 = el2 ? el2.value : "";
+    let error = "";
+
+    if (!el2)
+      valid = val1.length >= Weave.MIN_PASS_LENGTH;
+    else if (val1 && val1 == Weave.Service.identity.username)
+      error = "change.password.pwSameAsUsername";
+    else if (val1 && val1 == Weave.Service.identity.account)
+      error = "change.password.pwSameAsEmail";
+    else if (val1 && val1 == Weave.Service.identity.basicPassword)
+      error = "change.password.pwSameAsPassword";
+    else if (val1 && val2) {
+      if (val1 == val2 && val1.length >= Weave.MIN_PASS_LENGTH)
+        valid = true;
+      else if (val1.length < Weave.MIN_PASS_LENGTH)
+        error = "change.password.tooShort";
+      else if (val1 != val2)
+        error = "change.password.mismatch";
+    }
+    let errorString = error ? Weave.Utils.getErrorString(error) : "";
+    return [valid, errorString];
   }
 };
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -75,17 +75,26 @@ browser.jar:
         content/browser/pageinfo/feeds.xml            (content/pageinfo/feeds.xml)
         content/browser/pageinfo/permissions.js       (content/pageinfo/permissions.js)
         content/browser/pageinfo/security.js          (content/pageinfo/security.js)
 #ifdef MOZ_SERVICES_SYNC
         content/browser/sync/aboutSyncTabs.xul        (content/sync/aboutSyncTabs.xul)
         content/browser/sync/aboutSyncTabs.js         (content/sync/aboutSyncTabs.js)
         content/browser/sync/aboutSyncTabs.css        (content/sync/aboutSyncTabs.css)
         content/browser/sync/aboutSyncTabs-bindings.xml  (content/sync/aboutSyncTabs-bindings.xml)
+*       content/browser/sync/setup.xul                (content/sync/setup.xul)
+        content/browser/sync/addDevice.js             (content/sync/addDevice.js)
+        content/browser/sync/addDevice.xul            (content/sync/addDevice.xul)
+*       content/browser/sync/setup.js                 (content/sync/setup.js)
+        content/browser/sync/genericChange.xul        (content/sync/genericChange.xul)
+        content/browser/sync/genericChange.js         (content/sync/genericChange.js)
+        content/browser/sync/key.xhtml                (content/sync/key.xhtml)
         content/browser/sync/notification.xml         (content/sync/notification.xml)
+        content/browser/sync/quota.xul                (content/sync/quota.xul)
+        content/browser/sync/quota.js                 (content/sync/quota.js)
         content/browser/sync/utils.js                 (content/sync/utils.js)
         content/browser/sync/progress.js              (content/sync/progress.js)
         content/browser/sync/progress.xhtml           (content/sync/progress.xhtml)
 #endif
         content/browser/openLocation.js               (content/openLocation.js)
         content/browser/openLocation.xul              (content/openLocation.xul)
         content/browser/safeMode.css                  (content/safeMode.css)
         content/browser/safeMode.js                   (content/safeMode.js)
--- a/browser/components/preferences/in-content/sync.js
+++ b/browser/components/preferences/in-content/sync.js
@@ -1,16 +1,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://services-sync/main.js");
 
 const PAGE_NO_ACCOUNT = 0;
 const PAGE_HAS_ACCOUNT = 1;
+const PAGE_NEEDS_UPDATE = 2;
 
 let gSyncPane = {
   _stringBundle: null,
   prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
               "engine.tabs", "engine.history"],
 
   get page() {
     return document.getElementById("weavePrefsDeck").selectedIndex;
@@ -19,16 +20,23 @@ let gSyncPane = {
   set page(val) {
     document.getElementById("weavePrefsDeck").selectedIndex = val;
   },
 
   get _usingCustomServer() {
     return Weave.Svc.Prefs.isSet("serverURL");
   },
 
+  needsUpdate: function () {
+    this.page = PAGE_NEEDS_UPDATE;
+    let label = document.getElementById("loginError");
+    label.value = Weave.Utils.getErrorString(Weave.Status.login);
+    label.className = "error";
+  },
+
   init: function () {
     // If the Service hasn't finished initializing, wait for it.
     let xps = Components.classes["@mozilla.org/weave/service;1"]
                                 .getService(Components.interfaces.nsISupports)
                                 .wrappedJSObject;
 
     if (xps.ready) {
       this._init();
@@ -76,25 +84,103 @@ let gSyncPane = {
 
     this._stringBundle =
       Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
     this.updateWeavePrefs();
   },
 
   updateWeavePrefs: function () {
     if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
-        Weave.Svc.Prefs.get("firstSync", "") == "notReady" ||
-        Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
-        Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+        Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
+    } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
+               Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+      this.needsUpdate();
     } else {
       this.page = PAGE_HAS_ACCOUNT;
       document.getElementById("accountName").value = Weave.Service.identity.account;
       document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
       document.getElementById("tosPP").hidden = this._usingCustomServer;
     }
   },
 
-  openAccountsPage: function () {
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
-    win.switchToTabHavingURI("about:accounts", true);
-  }
+  startOver: function (showDialog) {
+    if (showDialog) {
+      let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+                  Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL + 
+                  Services.prompt.BUTTON_POS_1_DEFAULT;
+      let buttonChoice =
+        Services.prompt.confirmEx(window,
+                                  this._stringBundle.GetStringFromName("syncUnlink.title"),
+                                  this._stringBundle.GetStringFromName("syncUnlink.label"),
+                                  flags,
+                                  this._stringBundle.GetStringFromName("syncUnlinkConfirm.label"),
+                                  null, null, null, {});
+
+      // If the user selects cancel, just bail
+      if (buttonChoice == 1)
+        return;
+    }
+
+    Weave.Service.startOver();
+    this.updateWeavePrefs();
+  },
+
+  updatePass: function () {
+    if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
+      gSyncUtils.changePassword();
+    else
+      gSyncUtils.updatePassphrase();
+  },
+
+  resetPass: function () {
+    if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED)
+      gSyncUtils.resetPassword();
+    else
+      gSyncUtils.resetPassphrase();
+  },
+
+  /**
+   * Invoke the Sync setup wizard.
+   * 
+   * @param wizardType
+   *        Indicates type of wizard to launch:
+   *          null    -- regular set up wizard
+   *          "pair"  -- pair a device first
+   *          "reset" -- reset sync
+   */
+  openSetup: function (wizardType) {
+    let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+    if (win)
+      win.focus();
+    else {
+      window.openDialog("chrome://browser/content/sync/setup.xul",
+                        "weaveSetup", "centerscreen,chrome,resizable=no",
+                        wizardType);
+    }
+  },
+
+  openQuotaDialog: function () {
+    let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
+    if (win)
+      win.focus();
+    else 
+      window.openDialog("chrome://browser/content/sync/quota.xul", "",
+                        "centerscreen,chrome,dialog,modal");
+  },
+
+  openAddDevice: function () {
+    if (!Weave.Utils.ensureMPUnlocked())
+      return;
+    
+    let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+    if (win)
+      win.focus();
+    else 
+      window.openDialog("chrome://browser/content/sync/addDevice.xul",
+                        "syncAddDevice", "centerscreen,chrome,resizable=no");
+  },
+
+  resetSync: function () {
+    this.openSetup("reset");
+  },
 };
+
--- a/browser/components/preferences/in-content/sync.xul
+++ b/browser/components/preferences/in-content/sync.xul
@@ -31,32 +31,58 @@
 <deck id="weavePrefsDeck" data-category="paneSync" hidden="true">
   <vbox id="noAccount" align="center">
     <spacer flex="1"/>
     <description id="syncDesc">
       &weaveDesc.label;
     </description>
     <separator/>
     <label class="text-link"
-           onclick="event.stopPropagation(); gSyncPane.openAccountsPage();"
+           onclick="event.stopPropagation(); gSyncPane.openSetup(null);"
            value="&setupButton.label;"/>
+    <separator/>
+    <label class="text-link"
+           onclick="event.stopPropagation(); gSyncPane.openSetup('pair');"
+           value="&pairDevice.label;"/>
     <spacer flex="3"/>
   </vbox>
 
   <vbox id="hasAccount">
     <groupbox class="syncGroupBox">
       <!-- label is set to account name -->
-      <caption id="accountCaption" align="center" flex="1">
+      <caption id="accountCaption" align="center">
         <image id="accountCaptionImage"/>
-        <label id="accountName" value="" flex="1"/>
+        <label id="accountName" value=""/>
+      </caption>
 
-        <label class="text-link"
-               onclick="event.stopPropagation();gSyncUtils.openAccountsPage();"
-               value="&accountsLink.label;"/>
-      </caption>
+      <hbox>
+        <button type="menu"
+                label="&manageAccount.label;"
+                accesskey="&manageAccount.accesskey;">
+          <menupopup>
+            <menuitem label="&viewQuota.label;"
+                      oncommand="gSyncPane.openQuotaDialog();"/>
+            <menuseparator/>
+            <menuitem label="&changePassword2.label;"
+                      oncommand="gSyncUtils.changePassword();"/>
+            <menuitem label="&myRecoveryKey.label;"
+                      oncommand="gSyncUtils.resetPassphrase();"/>
+            <menuseparator/>
+            <menuitem label="&resetSync2.label;"
+                      oncommand="gSyncPane.resetSync();"/>
+          </menupopup>
+        </button>
+      </hbox>
+
+      <hbox>
+        <label id="syncAddDeviceLabel"
+               class="text-link"
+               onclick="gSyncPane.openAddDevice(); return false;"
+               value="&pairDevice.label;"/>
+      </hbox>
 
       <vbox>
         <label value="&syncMy.label;" />
         <richlistbox id="syncEnginesList"
                      orient="vertical"
                      onselect="if (this.selectedCount) this.clearSelection();">
           <richlistitem>
             <checkbox label="&engine.addons.label;"
@@ -103,19 +129,40 @@
             <label value="&syncDeviceName.label;"
                    accesskey="&syncDeviceName.accesskey;"
                    control="syncComputerName"/>
             <textbox id="syncComputerName"
                      onchange="gSyncUtils.changeName(this)"/>
           </row>
         </rows>
       </grid>
+      <hbox>
+        <label class="text-link"
+               onclick="gSyncPane.startOver(true); return false;"
+               value="&unlinkDevice.label;"/>
+      </hbox>
     </groupbox>
     <hbox id="tosPP" pack="center">
       <label class="text-link"
              onclick="event.stopPropagation();gSyncUtils.openToS();"
              value="&prefs.tosLink.label;"/>
       <label class="text-link"
              onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
              value="&prefs.ppLink.label;"/>
     </hbox>
   </vbox>
+
+  <vbox id="needsUpdate" align="center" pack="center">
+    <hbox>
+      <label id="loginError" value=""/>
+      <label class="text-link"
+             onclick="gSyncPane.updatePass(); return false;"
+             value="&updatePass.label;"/>
+      <label class="text-link"
+             onclick="gSyncPane.resetPass(); return false;"
+             value="&resetPass.label;"/>
+    </hbox>
+    <label class="text-link"
+           onclick="gSyncPane.startOver(true); return false;"
+           value="&unlinkDevice.label;"/>
+  </vbox>
+
 </deck>
--- a/browser/components/preferences/sync.js
+++ b/browser/components/preferences/sync.js
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 Components.utils.import("resource://services-sync/main.js");
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 const PAGE_NO_ACCOUNT = 0;
 const PAGE_HAS_ACCOUNT = 1;
+const PAGE_NEEDS_UPDATE = 2;
 
 let gSyncPane = {
   _stringBundle: null,
   prefArray: ["engine.bookmarks", "engine.passwords", "engine.prefs",
               "engine.tabs", "engine.history"],
 
   get page() {
     return document.getElementById("weavePrefsDeck").selectedIndex;
@@ -20,16 +21,23 @@ let gSyncPane = {
   set page(val) {
     document.getElementById("weavePrefsDeck").selectedIndex = val;
   },
 
   get _usingCustomServer() {
     return Weave.Svc.Prefs.isSet("serverURL");
   },
 
+  needsUpdate: function () {
+    this.page = PAGE_NEEDS_UPDATE;
+    let label = document.getElementById("loginError");
+    label.value = Weave.Utils.getErrorString(Weave.Status.login);
+    label.className = "error";
+  },
+
   init: function () {
     // If the Service hasn't finished initializing, wait for it.
     let xps = Components.classes["@mozilla.org/weave/service;1"]
                                 .getService(Components.interfaces.nsISupports)
                                 .wrappedJSObject;
 
     if (xps.ready) {
       this._init();
@@ -76,25 +84,109 @@ let gSyncPane = {
 
     this._stringBundle =
       Services.strings.createBundle("chrome://browser/locale/preferences/preferences.properties");
     this.updateWeavePrefs();
   },
 
   updateWeavePrefs: function () {
     if (Weave.Status.service == Weave.CLIENT_NOT_CONFIGURED ||
-        Weave.Svc.Prefs.get("firstSync", "") == "notReady" ||
-        Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
-        Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+        Weave.Svc.Prefs.get("firstSync", "") == "notReady") {
       this.page = PAGE_NO_ACCOUNT;
+    } else if (Weave.Status.login == Weave.LOGIN_FAILED_INVALID_PASSPHRASE ||
+               Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+      this.needsUpdate();
     } else {
       this.page = PAGE_HAS_ACCOUNT;
       document.getElementById("accountName").value = Weave.Service.identity.account;
       document.getElementById("syncComputerName").value = Weave.Service.clientsEngine.localName;
       document.getElementById("tosPP").hidden = this._usingCustomServer;
     }
   },
 
-  openAccountsPage: function () {
-    let win = Services.wm.getMostRecentWindow("navigator:browser");
-    win.switchToTabHavingURI("about:accounts", true);
-  }
+  startOver: function (showDialog) {
+    if (showDialog) {
+      let flags = Services.prompt.BUTTON_POS_0 * Services.prompt.BUTTON_TITLE_IS_STRING +
+                  Services.prompt.BUTTON_POS_1 * Services.prompt.BUTTON_TITLE_CANCEL + 
+                  Services.prompt.BUTTON_POS_1_DEFAULT;
+      let buttonChoice =
+        Services.prompt.confirmEx(window,
+                                  this._stringBundle.GetStringFromName("syncUnlink.title"),
+                                  this._stringBundle.GetStringFromName("syncUnlink.label"),
+                                  flags,
+                                  this._stringBundle.GetStringFromName("syncUnlinkConfirm.label"),
+                                  null, null, null, {});
+
+      // If the user selects cancel, just bail
+      if (buttonChoice == 1) {
+        return;
+      }
+    }
+
+    Weave.Service.startOver();
+    this.updateWeavePrefs();
+  },
+
+  updatePass: function () {
+    if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+      gSyncUtils.changePassword();
+    } else {
+      gSyncUtils.updatePassphrase();
+    }
+  },
+
+  resetPass: function () {
+    if (Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED) {
+      gSyncUtils.resetPassword();
+    } else {
+      gSyncUtils.resetPassphrase();
+    }
+  },
+
+  /**
+   * Invoke the Sync setup wizard.
+   *
+   * @param wizardType
+   *        Indicates type of wizard to launch:
+   *          null    -- regular set up wizard
+   *          "pair"  -- pair a device first
+   *          "reset" -- reset sync
+   */
+  openSetup: function (wizardType) {
+    let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+    if (win) {
+      win.focus();
+    } else {
+      window.openDialog("chrome://browser/content/sync/setup.xul",
+                        "weaveSetup", "centerscreen,chrome,resizable=no",
+                        wizardType);
+    }
+  },
+
+  openQuotaDialog: function () {
+    let win = Services.wm.getMostRecentWindow("Sync:ViewQuota");
+    if (win) {
+      win.focus();
+    } else {
+      window.openDialog("chrome://browser/content/sync/quota.xul", "",
+                        "centerscreen,chrome,dialog,modal");
+    }
+  },
+
+  openAddDevice: function () {
+    if (!Weave.Utils.ensureMPUnlocked()) {
+      return;
+    }
+
+    let win = Services.wm.getMostRecentWindow("Sync:AddDevice");
+    if (win) {
+      win.focus();
+    } else {
+      window.openDialog("chrome://browser/content/sync/addDevice.xul",
+                        "syncAddDevice", "centerscreen,chrome,resizable=no");
+    }
+  },
+
+  resetSync: function () {
+    this.openSetup("reset");
+  },
 };
+
--- a/browser/components/preferences/sync.xul
+++ b/browser/components/preferences/sync.xul
@@ -40,32 +40,58 @@
       <deck id="weavePrefsDeck">
         <vbox id="noAccount" align="center">
           <spacer flex="1"/>
           <description id="syncDesc">
             &weaveDesc.label;
           </description>
           <separator/>
           <label class="text-link"
-                 onclick="event.stopPropagation(); gSyncPane.openAccountsPage();"
+                 onclick="event.stopPropagation(); gSyncPane.openSetup(null);"
                  value="&setupButton.label;"/>
+          <separator/>
+          <label class="text-link"
+                 onclick="event.stopPropagation(); gSyncPane.openSetup('pair');"
+                 value="&pairDevice.label;"/>
           <spacer flex="3"/>
         </vbox>
 
         <vbox id="hasAccount">
           <groupbox class="syncGroupBox">
             <!-- label is set to account name -->
-            <caption id="accountCaption" align="center" flex="1">
+            <caption id="accountCaption" align="center">
               <image id="accountCaptionImage"/>
-              <label id="accountName" value="" flex="1"/>
+              <label id="accountName" value=""/>
+            </caption>
 
-              <label class="text-link"
-                     onclick="event.stopPropagation();gSyncUtils.openAccountsPage();"
-                     value="&accountsLink.label;"/>
-            </caption>
+            <hbox>
+              <button type="menu"
+                      label="&manageAccount.label;"
+                      accesskey="&manageAccount.accesskey;">
+                <menupopup>
+                  <menuitem label="&viewQuota.label;"
+                            oncommand="gSyncPane.openQuotaDialog();"/>
+                  <menuseparator/>
+                  <menuitem label="&changePassword2.label;"
+                            oncommand="gSyncUtils.changePassword();"/>
+                  <menuitem label="&myRecoveryKey.label;"
+                            oncommand="gSyncUtils.resetPassphrase();"/>
+                  <menuseparator/>
+                  <menuitem label="&resetSync2.label;"
+                            oncommand="gSyncPane.resetSync();"/>
+                </menupopup>
+              </button>
+            </hbox>
+
+            <hbox>
+              <label id="syncAddDeviceLabel"
+                     class="text-link"
+                     onclick="gSyncPane.openAddDevice(); return false;"
+                     value="&pairDevice.label;"/>
+            </hbox>
 
             <vbox>
               <label value="&syncMy.label;" />
               <richlistbox id="syncEnginesList"
                            orient="vertical"
                            onselect="if (this.selectedCount) this.clearSelection();">
                 <richlistitem>
                   <checkbox label="&engine.addons.label;"
@@ -112,21 +138,41 @@
                   <label value="&syncDeviceName.label;"
                          accesskey="&syncDeviceName.accesskey;"
                          control="syncComputerName"/>
                   <textbox id="syncComputerName"
                            onchange="gSyncUtils.changeName(this)"/>
                 </row>
               </rows>
             </grid>
+            <hbox>
+              <label class="text-link"
+                     onclick="gSyncPane.startOver(true); return false;"
+                     value="&unlinkDevice.label;"/>
+            </hbox>
           </groupbox>
           <hbox id="tosPP" pack="center">
             <label class="text-link"
                    onclick="event.stopPropagation();gSyncUtils.openToS();"
                    value="&prefs.tosLink.label;"/>
             <label class="text-link"
                    onclick="event.stopPropagation();gSyncUtils.openPrivacyPolicy();"
                    value="&prefs.ppLink.label;"/>
           </hbox>
         </vbox>
+
+        <vbox id="needsUpdate" align="center" pack="center">
+          <hbox>
+            <label id="loginError" value=""/>
+            <label class="text-link"
+                   onclick="gSyncPane.updatePass(); return false;"
+                   value="&updatePass.label;"/>
+            <label class="text-link"
+                   onclick="gSyncPane.resetPass(); return false;"
+                   value="&resetPass.label;"/>
+          </hbox>
+          <label class="text-link"
+                 onclick="gSyncPane.startOver(true); return false;"
+                 value="&unlinkDevice.label;"/>
+        </vbox>
       </deck>
   </prefpane>
 </overlay>
--- a/browser/locales/en-US/chrome/browser/preferences/sync.dtd
+++ b/browser/locales/en-US/chrome/browser/preferences/sync.dtd
@@ -3,17 +3,30 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!-- The page shown when not logged in... -->
 <!ENTITY setupButton.label          "Set Up &syncBrand.fullName.label;">
 <!ENTITY setupButton.accesskey      "S">
 <!ENTITY weaveDesc.label            "&syncBrand.fullName.label; lets you access your history, bookmarks, passwords and open tabs across all your devices.">
 
 <!-- The page shown when logged in... -->
-<!ENTITY accountsLink.label         "Manage Account">
+
+<!-- Login error feedback -->
+<!ENTITY updatePass.label             "Update">
+<!ENTITY resetPass.label              "Reset">
+
+<!-- Manage Account -->
+<!ENTITY manageAccount.label          "Manage Account">
+<!ENTITY manageAccount.accesskey      "n">
+<!ENTITY viewQuota.label              "View Quota">
+<!ENTITY changePassword2.label        "Change Password…">
+<!ENTITY myRecoveryKey.label          "My Recovery Key">
+<!ENTITY resetSync2.label             "Reset Sync…">
+
+<!ENTITY pairDevice.label             "Pair a Device">
 
 <!ENTITY syncMy.label               "Sync My">
 <!ENTITY engine.bookmarks.label     "Bookmarks">
 <!ENTITY engine.bookmarks.accesskey "m">
 <!ENTITY engine.tabs.label          "Tabs">
 <!ENTITY engine.tabs.accesskey      "T">
 <!ENTITY engine.history.label       "History">
 <!ENTITY engine.history.accesskey   "r">
@@ -22,12 +35,13 @@
 <!ENTITY engine.prefs.label         "Preferences">
 <!ENTITY engine.prefs.accesskey     "S">
 <!ENTITY engine.addons.label        "Add-ons">
 <!ENTITY engine.addons.accesskey    "A">
 
 <!-- Device Settings -->
 <!ENTITY syncDeviceName.label       "Device Name:">
 <!ENTITY syncDeviceName.accesskey   "c">
+<!ENTITY unlinkDevice.label           "Unlink This Device">
 
 <!-- Footer stuff -->
 <!ENTITY prefs.tosLink.label        "Terms of Service">
 <!ENTITY prefs.ppLink.label         "Privacy Policy">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncGenericChange.properties
@@ -0,0 +1,37 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+#LOCALIZATION NOTE (change.password.title): This (and associated change.password/passphrase) are used when the user elects to change their password.
+change.password.title = Change your Password
+change.password.acceptButton = Change Password
+change.password.status.active = Changing your password…
+change.password.status.success = Your password has been changed.
+change.password.status.error = There was an error changing your password.
+
+change.password3.introText = Your password must be at least 8 characters long.  It cannot be the same as either your user name or your Recovery Key.
+change.password.warningText = Note: All of your other devices will be unable to connect to your account once you change this password.
+
+change.recoverykey.title = My Recovery Key
+change.recoverykey.acceptButton = Change Recovery Key
+change.recoverykey.label = Changing Recovery Key and uploading local data, please wait…
+change.recoverykey.error = There was an error while changing your Recovery Key!
+change.recoverykey.success = Your Recovery Key was successfully changed!
+
+change.synckey.introText2 = To ensure your total privacy, all of your data is encrypted prior to being uploaded. The key to decrypt your data is not uploaded.
+# LOCALIZATION NOTE (change.recoverykey.warningText) "Sync" should match &syncBrand.shortName.label; from syncBrand.dtd
+change.recoverykey.warningText = Note: Changing this will erase all data stored on the Sync server and upload new data secured by this Recovery Key. Your other devices will not sync until the new Recovery Key is entered for that device.
+
+new.recoverykey.label = Your Recovery Key
+
+# LOCALIZATION NOTE (new.password.title): This (and associated new.password/passphrase) are used on a second computer when it detects that your password or passphrase has been changed on a different device.
+new.password.title            = Update Password
+new.password.introText        = Your password was rejected by the server, please update your password.
+new.password.label            = Enter your new password
+new.password.confirm          = Confirm your new password
+new.password.acceptButton     = Update Password
+new.password.status.incorrect = Password incorrect, please try again.
+
+new.recoverykey.title          = Update Recovery Key
+new.recoverykey.introText      = Your Recovery Key was changed using another device, please enter your updated Recovery Key.
+new.recoverykey.acceptButton     = Update Recovery Key
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncKey.dtd
@@ -0,0 +1,18 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY syncKey.page.title               "Your &syncBrand.fullName.label; Key">
+<!ENTITY syncKey.page.description2        "This key is used to decode the data in your &syncBrand.fullName.label; account. You will need to enter the key each time you configure &syncBrand.fullName.label; on a new device.">
+<!ENTITY syncKey.keepItSecret.heading     "Keep it secret">
+<!ENTITY syncKey.keepItSecret.description "Your &syncBrand.fullName.label; account is encrypted to protect your privacy. Without this key, it would take years for anyone to decode your personal information. You are the only person who holds this key. This means you're the only one who can access your &syncBrand.fullName.label; data.">
+<!ENTITY syncKey.keepItSafe.heading       "Keep it safe">
+<!ENTITY syncKey.keepItSafe1.description  "Do not lose this key.">
+<!ENTITY syncKey.keepItSafe2.description  " We don't keep a copy of your key (that wouldn't be keeping it secret!) so ">
+<!ENTITY syncKey.keepItSafe3.description  "we can't help you recover it">
+<!ENTITY syncKey.keepItSafe4a.description " if it's lost. You'll need to use this key any time you connect a new device to &syncBrand.fullName.label;.">
+<!ENTITY syncKey.findOutMore1.label       "Find out more about &syncBrand.fullName.label; and your privacy at ">
+<!ENTITY syncKey.findOutMore2.label       ".">
+<!ENTITY syncKey.footer1.label            "&syncBrand.fullName.label; Terms of Service are available at ">
+<!ENTITY syncKey.footer2.label            ". The Privacy Policy is available at ">
+<!ENTITY syncKey.footer3.label            ".">
--- a/browser/locales/en-US/chrome/browser/syncProgress.dtd
+++ b/browser/locales/en-US/chrome/browser/syncProgress.dtd
@@ -3,13 +3,13 @@
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!ENTITY % brandDTD
     SYSTEM "chrome://branding/locale/brand.dtd">
     %brandDTD;
 
 <!-- These strings are used in the sync progress upload page -->
 <!ENTITY syncProgress.pageTitle    "Your First Sync">
-<!ENTITY syncProgress.headline     "Setup Complete">
 <!ENTITY syncProgress.textBlurb    "Your data is now being encrypted and uploaded in the background. You can close this tab and continue using &brandShortName;.">
 <!ENTITY syncProgress.closeButton  "Close">
 <!ENTITY syncProgress.logoAltText  "&brandShortName; logo">
 <!ENTITY syncProgress.diffText     "&brandShortName; will now automatically sync in the background.  You can close this tab and continue using &brandShortName;.">
+
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncQuota.dtd
@@ -0,0 +1,8 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY quota.dialogTitle.label    "Server Quota">
+<!ENTITY quota.retrievingInfo.label "Retrieving quota information…">
+<!ENTITY quota.typeColumn.label     "Type">
+<!ENTITY quota.sizeColumn.label     "Size">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncQuota.properties
@@ -0,0 +1,42 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+collection.addons.label    = Add-ons
+collection.bookmarks.label = Bookmarks
+collection.history.label   = History
+collection.passwords.label = Passwords
+collection.prefs.label     = Preferences
+collection.tabs.label      = Tabs
+
+# LOCALIZATION NOTE (quota.usageNoQuota.label): %1$S and %2$S are numeric value
+# and unit (as defined in the download manager) of the amount of space occupied
+# on the server
+quota.usageNoQuota.label    = You are currently using %1$S %2$S.
+# LOCALIZATION NOTE (quota.usagePercentage.label):
+# %1$S is the percentage of space used,
+# %2$S and %3$S numeric value and unit (as defined in the download manager)
+# of the amount of space used,
+# %3$S and %4$S numeric value and unit (as defined in the download manager)
+# of the total space available.
+quota.usagePercentage.label = You are using %1$S%% (%2$S %3$S) of your allowed %4$S %5$S.
+quota.usageError.label      = Could not retrieve quota information.
+quota.retrieving.label      = Retrieving…
+# LOCALIZATION NOTE (quota.sizeValueUnit.label): %1$S is the amount of space
+# occupied by the engine, %2$K the corresponding unit (e.g. kB) as defined in
+# the download manager.
+quota.sizeValueUnit.label   = %1$S %2$S
+quota.remove.label          = Remove
+quota.treeCaption.label     = Uncheck items to stop syncing them and free up space on the server.
+# LOCALIZATION NOTE (quota.removal.label): %S is a list of engines that will be
+# disabled and whose data will be removed once the user confirms.
+quota.removal.label         = Firefox Sync will remove the following data: %S.
+# LOCALIZATION NOTE (quota.list.separator): This is the separator string used
+# for the list of engines (incl. spaces where appropriate)
+quota.list.separator        = ,\u0020
+# LOCALIZATION NOTE (quota.freeup.label): %1$S and %2$S are numeric value
+# and unit (as defined in the download manager) of the amount of space freed
+# up by disabling the unchecked engines.  If displayed this string is
+# concatenated directly to quota.removal.label and may need to start off with
+# whitespace.
+quota.freeup.label          = \u0020This will free up %1$S %2$S.
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncSetup.dtd
@@ -0,0 +1,116 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<!ENTITY accountSetupTitle.label    "&syncBrand.fullName.label; Setup">
+
+<!-- First page of the wizard -->
+
+<!ENTITY setup.pickSetupType.description2 "Welcome! If you've never used &syncBrand.fullName.label; before, you will need to create a new account.">
+<!ENTITY button.createNewAccount.label "Create a New Account">
+<!ENTITY button.haveAccount.label      "I Have an Account">
+
+<!ENTITY setup.choicePage.title.label     "Have you used &syncBrand.fullName.label; before?">
+<!ENTITY setup.choicePage.new.label       "I've never used &syncBrand.shortName.label; before">
+<!ENTITY setup.choicePage.existing2.label "I'm already using &syncBrand.shortName.label; on another device">
+
+<!-- New Account AND Existing Account -->
+<!ENTITY server.label               "Server">
+<!ENTITY serverType.default.label      "Default: Mozilla &syncBrand.fullName.label; server">
+<!ENTITY serverType.custom2.label   "Use a custom server…">
+<!ENTITY signIn.account2.label      "Account">
+<!ENTITY signIn.account2.accesskey  "A">
+<!ENTITY signIn.password.label      "Password">
+<!ENTITY signIn.password.accesskey  "P">
+<!ENTITY signIn.recoveryKey.label       "Recovery Key">
+<!ENTITY signIn.recoveryKey.accesskey   "K">
+
+<!-- New Account Page 1: Basic Account Info -->
+<!ENTITY setup.newAccountDetailsPage.title.label "Account Details">
+<!ENTITY setup.emailAddress.label     "Email Address">
+<!ENTITY setup.emailAddress.accesskey "E">
+<!ENTITY setup.choosePassword.label      "Choose a Password">
+<!ENTITY setup.choosePassword.accesskey  "P">
+<!ENTITY setup.confirmPassword.label     "Confirm Password">
+<!ENTITY setup.confirmPassword.accesskey "m">
+<!ENTITY setup.setupMetro.label     "Sync with Windows 8 style &brandShortName;">
+<!ENTITY setup.setupMetro.accesskey "S">
+
+<!-- LOCALIZATION NOTE: tosAgree1, tosLink, tosAgree2, ppLink, tosAgree3 are
+     joined with implicit white space, so spaces in the strings aren't necessary -->
+<!ENTITY setup.tosAgree1.label      "I agree to the">
+<!ENTITY setup.tosAgree1.accesskey  "a">
+<!ENTITY setup.tosLink.label        "Terms of Service">
+<!ENTITY setup.tosAgree2.label      "and the">
+<!ENTITY setup.ppLink.label         "Privacy Policy">
+<!ENTITY setup.tosAgree3.label      "">
+<!ENTITY setup.tosAgree2.accesskey  "">
+
+<!-- My Recovery Key dialog -->
+<!ENTITY setup.newRecoveryKeyPage.title.label "&brandShortName; Cares About Your Privacy">
+<!ENTITY setup.newRecoveryKeyPage.description.label "To ensure your total privacy, all of your data is encrypted prior to being uploaded. The Recovery Key which is necessary to decrypt your data is not uploaded.">
+<!ENTITY recoveryKeyEntry.label        "Your Recovery Key">
+<!ENTITY recoveryKeyEntry.accesskey    "K">
+<!ENTITY syncGenerateNewKey.label  "Generate a new key">
+<!ENTITY recoveryKeyBackup.description "Your Recovery Key is required to access &syncBrand.fullName.label; on other machines. Please create a backup copy. We cannot help you recover your Recovery Key.">
+
+<!ENTITY button.syncKeyBackup.print.label     "Print…">
+<!ENTITY button.syncKeyBackup.print.accesskey "P">
+<!ENTITY button.syncKeyBackup.save.label      "Save…">
+<!ENTITY button.syncKeyBackup.save.accesskey  "S">
+
+<!-- Existing Account Page 1: Pair a Device (incl. Pair a Device dialog strings) -->
+<!ENTITY pairDevice.title.label             "Pair a Device">
+<!ENTITY addDevice.showMeHow.label          "Show me how.">
+<!ENTITY addDevice.dontHaveDevice.label     "I don't have the device with me">
+<!ENTITY pairDevice.setup.description.label  "To activate, select &#x0022;Pair a Device&#x0022; on your other device.">
+<!ENTITY addDevice.setup.enterCode.label    "Then, enter this code:">
+<!ENTITY pairDevice.dialog.description.label "To activate your new device, select &#x0022;Set Up Sync&#x0022; on the device.">
+<!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 take several minutes and will finish in the background.">
+<!ENTITY addDevice.dialog.recoveryKey.label     "To activate your device you will need to enter your Recovery 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 existingRecoveryKey.description "You can get a copy of your Recovery Key by going to &syncBrand.shortName.label; Options on your other device, and selecting  &#x0022;My Recovery Key&#x0022; under &#x0022;Manage Account&#x0022;.">
+<!ENTITY verifying.label              "Verifying…">
+<!ENTITY resetPassword.label          "Reset Password">
+<!ENTITY resetSyncKey.label           "I have lost my other device.">
+
+<!-- Sync Options -->
+<!ENTITY setup.optionsPage.title      "Sync Options">
+<!ENTITY syncDeviceName.label       "Device Name:">
+<!ENTITY syncDeviceName.accesskey   "c">
+
+<!ENTITY syncMy.label               "Sync My">
+<!ENTITY engine.bookmarks.label     "Bookmarks">
+<!ENTITY engine.bookmarks.accesskey "m">
+<!ENTITY engine.tabs.label          "Tabs">
+<!ENTITY engine.tabs.accesskey      "T">
+<!ENTITY engine.history.label       "History">
+<!ENTITY engine.history.accesskey   "r">
+<!ENTITY engine.passwords.label     "Passwords">
+<!ENTITY engine.passwords.accesskey "P">
+<!ENTITY engine.prefs.label         "Preferences">
+<!ENTITY engine.prefs.accesskey     "S">
+<!ENTITY engine.addons.label        "Add-ons">
+<!ENTITY engine.addons.accesskey    "A">
+
+<!ENTITY choice2a.merge.main.label       "Merge this device's data with my &syncBrand.shortName.label; data">
+<!ENTITY choice2.merge.recommended.label "Recommended:">
+<!ENTITY choice2a.client.main.label      "Replace all data on this device with my &syncBrand.shortName.label; data">
+<!ENTITY choice2a.server.main.label      "Replace all other devices with this device's data">
+
+<!-- Confirm Merge Options -->
+<!ENTITY setup.optionsConfirmPage.title "Confirm">
+<!ENTITY confirm.merge2.label    "&syncBrand.fullName.label; will now merge all this device's browser data into your Sync account.">
+<!ENTITY confirm.client3.label         "Warning: The following &brandShortName; data on this device  will be deleted:">
+<!ENTITY confirm.client2.moreinfo.label "&brandShortName; will then copy your &syncBrand.fullName.label; data to this device.">
+<!ENTITY confirm.server2.label         "Warning: The following devices will be overwritten with your local data:">
+
+<!-- New & Existing Account: Setup Complete -->
+<!ENTITY setup.successPage.title "Setup Complete">
+<!ENTITY changeOptions.label "You can change this preference by selecting Sync Options below.">
+<!ENTITY continueUsing.label "You may now continue using &brandShortName;.">
new file mode 100644
--- /dev/null
+++ b/browser/locales/en-US/chrome/browser/syncSetup.properties
@@ -0,0 +1,51 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+button.syncOptions.label       = Sync Options
+button.syncOptionsDone.label   = Done
+button.syncOptionsCancel.label = Cancel
+
+invalidEmail.label          = Invalid email address
+serverInvalid.label         = Please enter a valid server URL
+usernameNotAvailable.label  = Already in use
+
+verifying.label = Verifying…
+
+# LOCALIZATION NOTE (additionalClientCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of additional clients (was %S for a short while, use #1 instead, even if both work)
+additionalClientCount.label = and #1 additional device;and #1 additional devices
+# LOCALIZATION NOTE (bookmarksCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of bookmarks (was %S for a short while, use #1 instead, even if both work)
+bookmarksCount.label        = #1 bookmark;#1 bookmarks
+# LOCALIZATION NOTE (historyDaysCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of days (was %S for a short while, use #1 instead, even if both work)
+historyDaysCount.label      = #1 day of history;#1 days of history
+# LOCALIZATION NOTE (passwordsCount.label):
+# Semicolon-separated list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of passwords (was %S for a short while, use #1 instead, even if both work)
+passwordsCount.label        = #1 password;#1 passwords
+# LOCALIZATION NOTE (addonsCount.label): Semicolon-separated list of plural forms.
+# See: http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 is the number of add-ons, see the link above for forms
+addonsCount.label        = #1 addon;#1 addons
+
+save.recoverykey.title = Save Recovery Key
+save.recoverykey.defaultfilename = Firefox Recovery Key.html
+
+newAccount.action.label = Firefox Sync is now set up to automatically sync all of your browser data.
+newAccount.change.label = You can choose exactly what to sync by selecting Sync Options below.
+resetClient.change2.label = Firefox Sync will now merge all this device's browser data into your Sync account.
+wipeClient.change2.label = Firefox Sync will now replace all of the browser data on this device with the data in your Sync account.
+wipeRemote.change2.label = Firefox Sync will now replace all of the browser data in your Sync account with the data on this device.
+existingAccount.change.label = You can change this preference by selecting Sync Options below.
+
+# Several other strings are used (via Weave.Status.login), but they come from
+#  /services/sync
--- a/browser/locales/jar.mn
+++ b/browser/locales/jar.mn
@@ -114,16 +114,22 @@
     locale/browser/preferences/privacy.dtd            (%chrome/browser/preferences/privacy.dtd)
     locale/browser/preferences/security.dtd           (%chrome/browser/preferences/security.dtd)
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/preferences/sync.dtd               (%chrome/browser/preferences/sync.dtd)
 #endif
     locale/browser/preferences/tabs.dtd               (%chrome/browser/preferences/tabs.dtd)
 #ifdef MOZ_SERVICES_SYNC
     locale/browser/syncBrand.dtd                (%chrome/browser/syncBrand.dtd)
+    locale/browser/syncSetup.dtd                (%chrome/browser/syncSetup.dtd)
+    locale/browser/syncSetup.properties         (%chrome/browser/syncSetup.properties)
+    locale/browser/syncGenericChange.properties         (%chrome/browser/syncGenericChange.properties)
+    locale/browser/syncKey.dtd                  (%chrome/browser/syncKey.dtd)
+    locale/browser/syncQuota.dtd                (%chrome/browser/syncQuota.dtd)
+    locale/browser/syncQuota.properties         (%chrome/browser/syncQuota.properties)
 #endif
 % locale browser-region @AB_CD@ %locale/browser-region/
     locale/browser-region/region.properties        (%chrome/browser-region/region.properties)
 # the following files are browser-specific overrides
     locale/browser/netError.dtd                (%chrome/overrides/netError.dtd)
     locale/browser/appstrings.properties       (%chrome/overrides/appstrings.properties)
     locale/browser/downloads/settingsChange.dtd  (%chrome/overrides/settingsChange.dtd)
 % override chrome://global/locale/netError.dtd chrome://browser/locale/netError.dtd
--- a/browser/themes/linux/jar.mn
+++ b/browser/themes/linux/jar.mn
@@ -270,16 +270,19 @@ browser.jar:
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-24-throbber.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-notification-24.png
+  skin/classic/browser/syncSetup.css
+  skin/classic/browser/syncCommon.css
+  skin/classic/browser/syncQuota.css
   skin/classic/browser/syncProgress.css
 #endif
   skin/classic/browser/notification-pluginNormal.png  (../shared/plugins/notification-pluginNormal.png)
   skin/classic/browser/notification-pluginAlert.png   (../shared/plugins/notification-pluginAlert.png)
   skin/classic/browser/notification-pluginBlocked.png (../shared/plugins/notification-pluginBlocked.png)
   skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png   (../shared/devtools/tooltip/arrow-horizontal-dark.png)
   skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png   (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
   skin/classic/browser/devtools/tooltip/arrow-vertical-dark.png   (../shared/devtools/tooltip/arrow-vertical-dark.png)
new file mode 100644
--- /dev/null
+++ b/browser/themes/linux/syncCommon.css
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+  color: -moz-dialogtext;
+}
+
+.statusIcon {
+  -moz-margin-start: 4px;
+  max-height: 16px;
+  max-width: 16px;
+}
+
+.statusIcon[status="active"] {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.statusIcon[status="error"] {
+  list-style-image: url("moz-icon://stock/gtk-dialog-error?size=menu");
+}
+
+.statusIcon[status="success"] {
+  list-style-image: url("moz-icon://stock/gtk-dialog-info?size=menu");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+   a separate stylesheet for it. */
+.data {
+  font-size: 90%;
+  font-weight: bold;
+}
+
+dialog#change-dialog {
+  width: 40em;
+}
+
+image#syncIcon {
+  list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+  margin-top: 2px;
+}
+
+#feedback {
+  height: 2em;
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/linux/syncQuota.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#quotaDialog {
+  width: 33em;
+  height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+  list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+  list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+  list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+  height: 4em;
+}
+
+.captionWarning {
+  font-weight: bold;
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/linux/syncSetup.css
@@ -0,0 +1,127 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+wizard {
+  -moz-appearance: none;
+  width: 55em;
+  height: 45em;
+  padding: 0;
+  background-color: Window;
+}
+
+.wizard-page-box {
+  -moz-appearance: none;
+  padding-left: 0;
+  padding-right: 0;
+  margin: 0;
+}
+
+wizardpage {
+  -moz-box-pack: center;
+  -moz-box-align: center;
+  margin: 0;
+  padding: 0 6em;
+  background-color: Window;
+}
+
+.wizard-header {
+  -moz-appearance: none;
+  border: none;
+  padding: 2em 0 1em 0;
+  text-align: center;
+}
+.wizard-header-label {
+  font-size: 24pt;
+  font-weight: normal;
+}
+
+.wizard-buttons {
+  background-color: rgba(0,0,0,0.1);
+  padding: 1em;
+}
+
+.wizard-buttons-separator {
+  visibility: collapse;
+}
+
+.wizard-header-icon {
+  visibility: collapse;
+}
+
+.accountChoiceButton {
+  font: menu;
+}
+
+.confirm {
+  border: 1px solid black;
+  padding: 1em;
+  border-radius: 5px;
+}
+
+/* Override the text-link style from global.css */
+description > .text-link,
+description > .text-link:focus {
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+}
+
+
+.success,
+.error {
+  padding: 2px;
+  border-radius: 2px;
+}
+
+.error {
+  background-color: #FF0000 !important;
+  color: #FFFFFF !important;
+}
+
+.success {
+  background-color: #00FF00 !important;
+}
+
+.warning {
+  font-weight: bold;
+  font-size: 100%;
+  color: red;
+}
+
+.mainDesc {
+  font-weight: bold;
+  font-size: 100%;
+}
+
+.normal {
+  font-size: 100%;
+}
+
+.inputColumn {
+  -moz-margin-end: 2px
+}
+
+.pin {
+  font-size: 18pt;
+  width: 4em;
+  text-align: center; 
+}
+
+#passphraseHelpSpacer {
+  width: 0.5em;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#captchaFeedback {
+  visibility: hidden;
+}
+
+#successPageIcon {
+  /* TODO replace this with a 128px version (bug 591122) */
+  list-style-image: url("chrome://browser/skin/sync-32.png");
+}
--- a/browser/themes/osx/jar.mn
+++ b/browser/themes/osx/jar.mn
@@ -371,16 +371,19 @@ browser.jar:
   skin/classic/browser/sync-throbber.png
   skin/classic/browser/sync-16.png
   skin/classic/browser/sync-32.png
   skin/classic/browser/sync-bg.png
   skin/classic/browser/sync-128.png
   skin/classic/browser/sync-desktopIcon.png
   skin/classic/browser/sync-mobileIcon.png
   skin/classic/browser/sync-notification-24.png
+  skin/classic/browser/syncSetup.css
+  skin/classic/browser/syncCommon.css
+  skin/classic/browser/syncQuota.css
   skin/classic/browser/syncProgress.css
 #endif
   skin/classic/browser/lion/keyhole-circle.png              (keyhole-circle-lion.png)
   skin/classic/browser/keyhole-circle@2x.png                (keyhole-circle-lion@2x.png)
   skin/classic/browser/Toolbar-background-noise.png         (Toolbar-background-noise.png)
   skin/classic/browser/lion/toolbarbutton-dropmarker.png    (toolbarbutton-dropmarker-lion.png)
   skin/classic/browser/toolbarbutton-dropmarker@2x.png      (toolbarbutton-dropmarker-lion@2x.png)
   skin/classic/browser/lion/tabbrowser/alltabs-box-bkgnd-icon.png      (tabbrowser/alltabs-box-bkgnd-icon-lion.png)
new file mode 100644
--- /dev/null
+++ b/browser/themes/osx/syncCommon.css
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+  color: -moz-dialogtext;
+}
+
+.statusIcon {
+  -moz-margin-start: 4px;
+  max-height: 16px;
+  max-width: 16px;
+}
+
+.statusIcon[status="active"] {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.statusIcon[status="error"] {
+  list-style-image: url("chrome://global/skin/icons/error-16.png");
+}
+
+.statusIcon[status="success"] {
+  list-style-image: url("chrome://global/skin/icons/information-16.png");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+   a separate stylesheet for it. */
+.data {
+  font-size: 90%;
+  font-weight: bold;
+}
+
+dialog#change-dialog {
+  width: 40em;
+}
+
+image#syncIcon {
+  list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+  margin-top: 2px;
+}
+
+#feedback {
+  height: 2em;
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/osx/syncQuota.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#quotaDialog {
+  width: 33em;
+  height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+  list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+  list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+  list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+  height: 4em;
+}
+
+.captionWarning {
+  font-weight: bold;
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/osx/syncSetup.css
@@ -0,0 +1,126 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+wizard {
+  -moz-appearance: none;
+  width: 55em;
+  height: 45em;
+  padding: 0;
+  background-color: Window;
+}
+
+.wizard-page-box {
+  -moz-appearance: none;
+  padding-left: 0;
+  padding-right: 0;
+  margin: 0;
+}
+
+wizardpage {
+  -moz-box-pack: center;
+  -moz-box-align: center;
+  margin: 0;
+  padding: 0 6em;
+  background-color: Window;
+}
+
+.wizard-header {
+  -moz-appearance: none;
+  border: none;
+  padding: 2em 0 1em 0;
+  text-align: center;
+}
+.wizard-header-label {
+  font-size: 24pt;
+  font-weight: normal;
+}
+
+.wizard-buttons {
+  background-color: rgba(0,0,0,0.1);
+  padding: 1em;
+}
+
+.wizard-buttons-separator {
+  visibility: collapse;
+}
+
+.wizard-header-icon {
+  visibility: collapse;
+}
+
+.accountChoiceButton {
+  font: menu;
+}
+
+.confirm {
+  border: 1px solid black;
+  padding: 1em;
+  border-radius: 5px;
+}
+
+/* Override the text-link style from global.css */
+description > .text-link,
+description > .text-link:focus {
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+}
+
+.success,
+.error {
+  padding: 2px;
+  border-radius: 2px;
+}
+
+.error {
+  background-color: #FF0000 !important;
+  color: #FFFFFF !important;
+}
+
+.success {
+  background-color: #00FF00 !important;
+}
+
+.warning {
+  font-weight: bold;
+  font-size: 100%;
+  color: red;
+}
+
+.mainDesc {
+  font-weight: bold;
+  font-size: 100%;
+}
+
+.normal {
+  font-size: 100%;
+}
+
+.inputColumn {
+  -moz-margin-end: 2px
+}
+
+.pin {
+  font-size: 18pt;
+  width: 4em;
+  text-align: center; 
+}
+
+#passphraseHelpSpacer {
+  width: 0.5em;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#captchaFeedback {
+  visibility: hidden;
+}
+
+#successPageIcon {
+  /* TODO replace this with a 128px version (bug 591122) */
+  list-style-image: url("chrome://browser/skin/sync-32.png");
+}
--- a/browser/themes/windows/jar.mn
+++ b/browser/themes/windows/jar.mn
@@ -296,16 +296,19 @@ browser.jar:
         skin/classic/browser/sync-throbber.png
         skin/classic/browser/sync-16.png
         skin/classic/browser/sync-32.png
         skin/classic/browser/sync-128.png
         skin/classic/browser/sync-bg.png
         skin/classic/browser/sync-desktopIcon.png
         skin/classic/browser/sync-mobileIcon.png
         skin/classic/browser/sync-notification-24.png
+        skin/classic/browser/syncSetup.css
+        skin/classic/browser/syncCommon.css
+        skin/classic/browser/syncQuota.css
         skin/classic/browser/syncProgress.css
 #endif
         skin/classic/browser/devtools/tooltip/arrow-horizontal-dark.png   (../shared/devtools/tooltip/arrow-horizontal-dark.png)
         skin/classic/browser/devtools/tooltip/arrow-horizontal-dark@2x.png   (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
         skin/classic/browser/devtools/tooltip/arrow-vertical-dark.png   (../shared/devtools/tooltip/arrow-vertical-dark.png)
         skin/classic/browser/devtools/tooltip/arrow-vertical-dark@2x.png   (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
         skin/classic/browser/devtools/tooltip/arrow-horizontal-light.png   (../shared/devtools/tooltip/arrow-horizontal-light.png)
         skin/classic/browser/devtools/tooltip/arrow-horizontal-light@2x.png   (../shared/devtools/tooltip/arrow-horizontal-light@2x.png)
@@ -601,16 +604,19 @@ browser.jar:
         skin/classic/aero/browser/sync-throbber.png
         skin/classic/aero/browser/sync-16.png
         skin/classic/aero/browser/sync-32.png
         skin/classic/aero/browser/sync-128.png
         skin/classic/aero/browser/sync-bg.png
         skin/classic/aero/browser/sync-desktopIcon.png
         skin/classic/aero/browser/sync-mobileIcon.png
         skin/classic/aero/browser/sync-notification-24.png
+        skin/classic/aero/browser/syncSetup.css
+        skin/classic/aero/browser/syncCommon.css
+        skin/classic/aero/browser/syncQuota.css
         skin/classic/aero/browser/syncProgress.css
 #endif
 #endif
         skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-dark.png   (../shared/devtools/tooltip/arrow-horizontal-dark.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-dark@2x.png   (../shared/devtools/tooltip/arrow-horizontal-dark@2x.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-vertical-dark.png   (../shared/devtools/tooltip/arrow-vertical-dark.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-vertical-dark@2x.png   (../shared/devtools/tooltip/arrow-vertical-dark@2x.png)
         skin/classic/aero/browser/devtools/tooltip/arrow-horizontal-light.png   (../shared/devtools/tooltip/arrow-horizontal-light.png)
new file mode 100644
--- /dev/null
+++ b/browser/themes/windows/syncCommon.css
@@ -0,0 +1,49 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* The following are used by both sync/setup.xul and sync/genericChange.xul */
+.status {
+  color: -moz-dialogtext;
+}
+
+.statusIcon {
+  -moz-margin-start: 4px;
+  max-height: 16px;
+  max-width: 16px;
+}
+
+.statusIcon[status="active"] {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+.statusIcon[status="error"] {
+  list-style-image: url("chrome://global/skin/icons/error-16.png");
+}
+
+.statusIcon[status="success"] {
+  list-style-image: url("chrome://global/skin/icons/information-16.png");
+}
+
+/* .data is only used by sync/genericChange.xul, but it seems unnecessary to have
+   a separate stylesheet for it. */
+.data {
+  font-size: 90%;
+  font-weight: bold;
+}
+
+dialog#change-dialog {
+  width: 40em;
+}
+
+image#syncIcon {
+  list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#introText {
+  margin-top: 2px;
+}
+
+#feedback {
+  height: 2em;
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/windows/syncQuota.css
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#quotaDialog {
+  width: 33em;
+  height: 25em;
+}
+
+treechildren::-moz-tree-checkbox {
+  list-style-image: none;
+}
+treechildren::-moz-tree-checkbox(checked) {
+  list-style-image: url("chrome://global/skin/checkbox/cbox-check.gif");
+}
+treechildren::-moz-tree-checkbox(disabled) {
+  list-style-image: url("chrome://global/skin/checkbox/cbox-check-dis.gif");
+}
+
+#treeCaption {
+  height: 4em;
+}
+
+.captionWarning {
+  font-weight: bold;
+}
new file mode 100644
--- /dev/null
+++ b/browser/themes/windows/syncSetup.css
@@ -0,0 +1,132 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+wizard {
+  -moz-appearance: none;
+  width: 55em;
+  height: 45em;
+  padding: 0;
+  background-color: Window;
+}
+
+.wizard-page-box {
+  -moz-appearance: none;
+  padding-left: 0;
+  padding-right: 0;
+  margin: 0;
+}
+
+wizardpage {
+  -moz-box-pack: center;
+  -moz-box-align: center;
+  margin: 0;
+  padding: 0 6em;
+  background-color: Window;
+}
+
+.wizard-header {
+  -moz-appearance: none;
+  border: none;
+  padding: 2em 0 1em 0;
+  text-align: center;
+}
+.wizard-header-label {
+  font-size: 24pt;
+  font-weight: normal;
+}
+
+.wizard-buttons {
+  background-color: rgba(0,0,0,0.1);
+  padding: 1em;
+}
+
+.wizard-buttons-separator {
+  visibility: collapse;
+}
+
+.wizard-header-icon {
+  visibility: collapse;
+}
+
+.accountChoiceButton {
+  font: menu;
+}
+
+.confirm {
+  border: 1px solid black;
+  padding: 1em;
+  border-radius: 5px;
+}
+
+/* Override the text-link style from global.css */
+description > .text-link,
+description > .text-link:focus {
+  margin: 0px;
+  padding: 0px;
+  border: 0px;
+}
+
+
+.success,
+.error {
+  padding: 2px;
+  border-radius: 2px;
+}
+
+.error {
+  background-color: #FF0000 !important;
+  color: #FFFFFF !important;
+}
+
+.success {
+  background-color: #00FF00 !important;
+}
+
+.warning {
+  font-weight: bold;
+  font-size: 100%;
+  color: red;
+}
+
+.mainDesc {
+  font-weight: bold;
+  font-size: 100%;
+}
+
+.normal {
+  font-size: 100%;
+}
+
+.inputColumn {
+  -moz-margin-end: 2px
+}
+
+.pin {
+  font-size: 18pt;
+  width: 4em;
+  text-align: center; 
+}
+
+#passphraseHelpSpacer {
+  width: 0.5em;
+}
+
+#pairDeviceThrobber > image,
+#login-throbber > image {
+  list-style-image: url("chrome://global/skin/icons/loading_16.png");
+}
+
+#captchaFeedback {
+  visibility: hidden;
+}
+
+#successPageIcon {
+  /* TODO replace this with a 128px version (bug 591122) */
+  list-style-image: url("chrome://browser/skin/sync-32.png");
+}
+
+#tosDesc {
+  margin-left: -7px;
+  margin-bottom: 3px;
+}
\ No newline at end of file