[X2Go-Commits] [pale-moon] 199/294: [BASILISK] Port PM Sync Client - Part 1: Initial transfer

git-admin at x2go.org git-admin at x2go.org
Sat Apr 27 08:58:12 CEST 2019


This is an automated email from the git hooks/post-receive script.

x2go pushed a commit to branch upstream/28.5.0
in repository pale-moon.

commit f98cdab7a516a3896cb03faf3dbec6bb0ee060b7
Author: Matt A. Tobin <email at mattatobin.com>
Date:   Sun Mar 31 18:48:11 2019 -0400

    [BASILISK] Port PM Sync Client - Part 1: Initial transfer
---
 .../basilisk/base/content/browser-syncui.js        |  470 +++++++++
 .../components/sync/aboutSyncTabs-bindings.xml     |   46 +
 .../basilisk/components/sync/aboutSyncTabs.css     |   11 +
 .../basilisk/components/sync/aboutSyncTabs.js      |  313 ++++++
 .../basilisk/components/sync/aboutSyncTabs.xul     |   68 ++
 application/basilisk/components/sync/addDevice.js  |  157 +++
 application/basilisk/components/sync/addDevice.xul |  129 +++
 .../basilisk/components/sync/genericChange.js      |  234 +++++
 .../basilisk/components/sync/genericChange.xul     |  123 +++
 application/basilisk/components/sync/jar.mn        |   22 +
 application/basilisk/components/sync/key.xhtml     |   54 +
 application/basilisk/components/sync/moz.build     |    8 +
 .../basilisk/components/sync/notification.xml      |  129 +++
 application/basilisk/components/sync/progress.js   |   71 ++
 .../basilisk/components/sync/progress.xhtml        |   55 +
 application/basilisk/components/sync/quota.js      |  247 +++++
 application/basilisk/components/sync/quota.xul     |   65 ++
 application/basilisk/components/sync/setup.js      | 1071 ++++++++++++++++++++
 application/basilisk/components/sync/setup.xul     |  491 +++++++++
 application/basilisk/components/sync/utils.js      |  218 ++++
 .../locales/en-US/chrome/browser/syncProgress.dtd  |   15 +
 21 files changed, 3997 insertions(+)

diff --git a/application/basilisk/base/content/browser-syncui.js b/application/basilisk/base/content/browser-syncui.js
new file mode 100644
index 0000000..67056e2
--- /dev/null
+++ b/application/basilisk/base/content/browser-syncui.js
@@ -0,0 +1,470 @@
+# 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/.
+
+// gSyncUI handles updating the tools menu
+var gSyncUI = {
+  _obs: ["weave:service:sync:start",
+         "weave:service:sync:delayed",
+         "weave:service:quota:remaining",
+         "weave:service:setup-complete",
+         "weave:service:login:start",
+         "weave:service:login:finish",
+         "weave:service:logout:finish",
+         "weave:service:start-over",
+         "weave:ui:login:error",
+         "weave:ui:sync:error",
+         "weave:ui:sync:finish",
+         "weave:ui:clear-error",
+  ],
+
+  _unloaded: false,
+
+  init: function SUI_init() {
+    // Proceed to set up the UI if Sync has already started up.
+    // Otherwise we'll do it when Sync is firing up.
+    let xps = Components.classes["@mozilla.org/weave/service;1"]
+                                .getService(Components.interfaces.nsISupports)
+                                .wrappedJSObject;
+    if (xps.ready) {
+      this.initUI();
+      return;
+    }
+
+    Services.obs.addObserver(this, "weave:service:ready", true);
+
+    // Remove the observer if the window is closed before the observer
+    // was triggered.
+    window.addEventListener("unload", function onUnload() {
+      gSyncUI._unloaded = true;
+      window.removeEventListener("unload", onUnload, false);
+      Services.obs.removeObserver(gSyncUI, "weave:service:ready");
+
+      if (Weave.Status.ready) {
+        gSyncUI._obs.forEach(function(topic) {
+          Services.obs.removeObserver(gSyncUI, topic);
+        });
+      }
+    }, false);
+  },
+
+  initUI: function SUI_initUI() {
+    // If this is a browser window?
+    if (gBrowser) {
+      this._obs.push("weave:notification:added");
+    }
+
+    this._obs.forEach(function(topic) {
+      Services.obs.addObserver(this, topic, true);
+    }, this);
+
+    if (gBrowser && Weave.Notifications.notifications.length) {
+      this.initNotifications();
+    }
+    this.updateUI();
+  },
+
+  initNotifications: function SUI_initNotifications() {
+    const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+    let notificationbox = document.createElementNS(XULNS, "notificationbox");
+    notificationbox.id = "sync-notifications";
+    notificationbox.setAttribute("flex", "1");
+
+    let bottombox = document.getElementById("browser-bottombox");
+    bottombox.insertBefore(notificationbox, bottombox.firstChild);
+
+    // Force a style flush to ensure that our binding is attached.
+    notificationbox.clientTop;
+
+    // notificationbox will listen to observers from now on.
+    Services.obs.removeObserver(this, "weave:notification:added");
+  },
+
+  _wasDelayed: false,
+
+  _needsSetup: function SUI__needsSetup() {
+    let firstSync = "";
+    try {
+      firstSync = Services.prefs.getCharPref("services.sync.firstSync");
+    } catch (e) { }
+    return Weave.Status.checkSetup() == Weave.CLIENT_NOT_CONFIGURED ||
+           firstSync == "notReady";
+  },
+
+  updateUI: function SUI_updateUI() {
+    let needsSetup = this._needsSetup();
+    document.getElementById("sync-setup-state").hidden = !needsSetup;
+    document.getElementById("sync-syncnow-state").hidden = needsSetup;
+
+    if (!gBrowser)
+      return;
+
+    let button = document.getElementById("sync-button");
+    if (!button)
+      return;
+
+    button.removeAttribute("status");
+    this._updateLastSyncTime();
+    if (needsSetup)
+      button.removeAttribute("tooltiptext");
+  },
+
+
+  // Functions called by observers
+  onActivityStart: function SUI_onActivityStart() {
+    if (!gBrowser)
+      return;
+
+    let button = document.getElementById("sync-button");
+    if (!button)
+      return;
+
+    button.setAttribute("status", "active");
+  },
+
+  onSyncDelay: function SUI_onSyncDelay() {
+    // basically, we want to just inform users that stuff is going to take a while
+    let title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+    let description = this._stringBundle.GetStringFromName("error.sync.no_node_found");
+    let buttons = [new Weave.NotificationButton(
+      this._stringBundle.GetStringFromName("error.sync.serverStatusButton.label"),
+      this._stringBundle.GetStringFromName("error.sync.serverStatusButton.accesskey"),
+      function() { gSyncUI.openServerStatus(); return true; }
+    )];
+    let notification = new Weave.Notification(
+      title, description, null, Weave.Notifications.PRIORITY_INFO, buttons);
+    Weave.Notifications.replaceTitle(notification);
+    this._wasDelayed = true;
+  },
+
+  onLoginFinish: function SUI_onLoginFinish() {
+    // Clear out any login failure notifications
+    let title = this._stringBundle.GetStringFromName("error.login.title");
+    this.clearError(title);
+  },
+
+  onSetupComplete: function SUI_onSetupComplete() {
+    this.onLoginFinish();
+  },
+
+  onLoginError: function SUI_onLoginError() {
+    // if login fails, any other notifications are essentially moot
+    Weave.Notifications.removeAll();
+
+    // if we haven't set up the client, don't show errors
+    if (this._needsSetup()) {
+      this.updateUI();
+      return;
+    }
+
+    let title = this._stringBundle.GetStringFromName("error.login.title");
+
+    let description;
+    if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
+      // Convert to days
+      let lastSync =
+        Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
+      description =
+        this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
+    } else {
+      let reason = Weave.Utils.getErrorString(Weave.Status.login);
+      description =
+        this._stringBundle.formatStringFromName("error.sync.description", [reason], 1);
+    }
+
+    let buttons = [];
+    buttons.push(new Weave.NotificationButton(
+      this._stringBundle.GetStringFromName("error.login.prefs.label"),
+      this._stringBundle.GetStringFromName("error.login.prefs.accesskey"),
+      function() { gSyncUI.openPrefs(); return true; }
+    ));
+
+    let notification = new Weave.Notification(title, description, null,
+                                              Weave.Notifications.PRIORITY_WARNING, buttons);
+    Weave.Notifications.replaceTitle(notification);
+    this.updateUI();
+  },
+
+  onLogout: function SUI_onLogout() {
+    this.updateUI();
+  },
+
+  onStartOver: function SUI_onStartOver() {
+    this.clearError();
+  },
+
+  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.openQuotaDialog(); return true; }
+    ));
+
+    let notification = new Weave.Notification(
+      title, description, null, Weave.Notifications.PRIORITY_WARNING, buttons);
+    Weave.Notifications.replaceTitle(notification);
+  },
+
+  openServerStatus: function () {
+    let statusURL = Services.prefs.getCharPref("services.sync.statusURL");
+    window.openUILinkIn(statusURL, "tab");
+  },
+
+  // Commands
+  doSync: function SUI_doSync() {
+    setTimeout(function() Weave.Service.errorHandler.syncAndReportErrors(), 0);
+  },
+
+  handleToolbarButton: function SUI_handleStatusbarButton() {
+    if (this._needsSetup())
+      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;
+
+    let lastSync;
+    try {
+      lastSync = Services.prefs.getCharPref("services.sync.lastSync");
+    }
+    catch (e) { };
+    if (!lastSync || this._needsSetup()) {
+      syncButton.removeAttribute("tooltiptext");
+      return;
+    }
+
+    // Show the day-of-week and time (HH:MM) of last sync
+    let lastSyncDate = new Date(lastSync).toLocaleFormat("%a %H:%M");
+    let lastSyncLabel =
+      this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDate], 1);
+
+    syncButton.setAttribute("tooltiptext", lastSyncLabel);
+  },
+
+  clearError: function SUI_clearError(errorString) {
+    Weave.Notifications.removeAll(errorString);
+    this.updateUI();
+  },
+
+  onSyncFinish: function SUI_onSyncFinish() {
+    let title = this._stringBundle.GetStringFromName("error.sync.title");
+
+    // Clear out sync failures on a successful sync
+    this.clearError(title);
+
+    if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
+      title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+      this.clearError(title);
+      this._wasDelayed = false;
+    }
+  },
+
+  onSyncError: function SUI_onSyncError() {
+    let title = this._stringBundle.GetStringFromName("error.sync.title");
+
+    if (Weave.Status.login != Weave.LOGIN_SUCCEEDED) {
+      this.onLoginError();
+      return;
+    }
+
+    let description;
+    if (Weave.Status.sync == Weave.PROLONGED_SYNC_FAILURE) {
+      // Convert to days
+      let lastSync =
+        Services.prefs.getIntPref("services.sync.errorhandler.networkFailureReportTimeout") / 86400;
+      description =
+        this._stringBundle.formatStringFromName("error.sync.prolonged_failure", [lastSync], 1);
+    } else {
+      let error = Weave.Utils.getErrorString(Weave.Status.sync);
+      description =
+        this._stringBundle.formatStringFromName("error.sync.description", [error], 1);
+    }
+    let priority = Weave.Notifications.PRIORITY_WARNING;
+    let buttons = [];
+
+    // Check if the client is outdated in some way
+    let outdated = Weave.Status.sync == Weave.VERSION_OUT_OF_DATE;
+    for (let [engine, reason] in Iterator(Weave.Status.engines))
+      outdated = outdated || reason == Weave.VERSION_OUT_OF_DATE;
+
+    if (outdated) {
+      description = this._stringBundle.GetStringFromName(
+        "error.sync.needUpdate.description");
+      buttons.push(new Weave.NotificationButton(
+        this._stringBundle.GetStringFromName("error.sync.needUpdate.label"),
+        this._stringBundle.GetStringFromName("error.sync.needUpdate.accesskey"),
+        function() {
+          window.openUILinkIn(Services.prefs.getCharPref("services.sync.outdated.url"), "tab");
+          return true;
+        }
+      ));
+    }
+    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.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; }
+      ));
+    }
+    else {
+      priority = Weave.Notifications.PRIORITY_INFO;
+      buttons.push(new Weave.NotificationButton(
+        this._stringBundle.GetStringFromName("error.sync.tryAgainButton.label"),
+        this._stringBundle.GetStringFromName("error.sync.tryAgainButton.accesskey"),
+        function() { gSyncUI.doSync(); return true; }
+      ));
+    }
+
+    let notification =
+      new Weave.Notification(title, description, null, priority, buttons);
+    Weave.Notifications.replaceTitle(notification);
+
+    if (this._wasDelayed && Weave.Status.sync != Weave.NO_SYNC_NODE_FOUND) {
+      title = this._stringBundle.GetStringFromName("error.sync.no_node_found.title");
+      Weave.Notifications.removeAll(title);
+      this._wasDelayed = false;
+    }
+
+    this.updateUI();
+  },
+
+  observe: function SUI_observe(subject, topic, data) {
+    if (this._unloaded) {
+      Cu.reportError("SyncUI observer called after unload: " + topic);
+      return;
+    }
+
+    switch (topic) {
+      case "weave:service:sync:start":
+        this.onActivityStart();
+        break;
+      case "weave:ui:sync:finish":
+        this.onSyncFinish();
+        break;
+      case "weave:ui:sync:error":
+        this.onSyncError();
+        break;
+      case "weave:service:sync:delayed":
+        this.onSyncDelay();
+        break;
+      case "weave:service:quota:remaining":
+        this.onQuotaNotice();
+        break;
+      case "weave:service:setup-complete":
+        this.onSetupComplete();
+        break;
+      case "weave:service:login:start":
+        this.onActivityStart();
+        break;
+      case "weave:service:login:finish":
+        this.onLoginFinish();
+        break;
+      case "weave:ui:login:error":
+        this.onLoginError();
+        break;
+      case "weave:service:logout:finish":
+        this.onLogout();
+        break;
+      case "weave:service:start-over":
+        this.onStartOver();
+        break;
+      case "weave:service:ready":
+        this.initUI();
+        break;
+      case "weave:notification:added":
+        this.initNotifications();
+        break;
+      case "weave:ui:clear-error":
+        this.clearError();
+        break;
+    }
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIObserver,
+    Ci.nsISupportsWeakReference
+  ])
+};
+
+XPCOMUtils.defineLazyGetter(gSyncUI, "_stringBundle", function() {
+  //XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
+  //        but for now just make it work
+  return Cc["@mozilla.org/intl/stringbundle;1"].
+         getService(Ci.nsIStringBundleService).
+         createBundle("chrome://weave/locale/services/sync.properties");
+});
+
diff --git a/application/basilisk/components/sync/aboutSyncTabs-bindings.xml b/application/basilisk/components/sync/aboutSyncTabs-bindings.xml
new file mode 100644
index 0000000..e610820
--- /dev/null
+++ b/application/basilisk/components/sync/aboutSyncTabs-bindings.xml
@@ -0,0 +1,46 @@
+<?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/. -->
+
+<bindings id="tabBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+          xmlns:xbl="http://www.mozilla.org/xbl">
+
+  <binding id="tab-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content>
+      <xul:hbox flex="1">
+        <xul:vbox pack="start">
+          <xul:image class="tabIcon"
+                     xbl:inherits="src=icon"/>
+        </xul:vbox>
+        <xul:vbox pack="start" flex="1">
+            <xul:label xbl:inherits="value=title,selected"
+                       crop="end" flex="1" class="title"/>
+            <xul:label xbl:inherits="value=url,selected"
+                       crop="end" flex="1" class="url"/>
+        </xul:vbox>
+      </xul:hbox>
+    </content>
+    <handlers>
+      <handler event="dblclick" button="0">
+        <![CDATA[
+          RemoteTabViewer.openSelected();
+        ]]>
+      </handler>
+    </handlers>
+  </binding>
+
+  <binding id="client-listing" extends="chrome://global/content/bindings/richlistbox.xml#richlistitem">
+    <content>
+      <xul:hbox pack="start" align="center" onfocus="event.target.blur()" onselect="return false;">
+        <xul:image/>
+        <xul:label xbl:inherits="value=clientName"
+                   class="clientName"
+                   crop="center" flex="1"/>
+      </xul:hbox>
+    </content>
+  </binding>
+</bindings>
diff --git a/application/basilisk/components/sync/aboutSyncTabs.css b/application/basilisk/components/sync/aboutSyncTabs.css
new file mode 100644
index 0000000..5a35317
--- /dev/null
+++ b/application/basilisk/components/sync/aboutSyncTabs.css
@@ -0,0 +1,11 @@
+/* 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/. */
+
+richlistitem[type="tab"] {
+  -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#tab-listing);
+}
+
+richlistitem[type="client"] {
+  -moz-binding: url(chrome://browser/content/sync/aboutSyncTabs-bindings.xml#client-listing);
+}
diff --git a/application/basilisk/components/sync/aboutSyncTabs.js b/application/basilisk/components/sync/aboutSyncTabs.js
new file mode 100644
index 0000000..410494b
--- /dev/null
+++ b/application/basilisk/components/sync/aboutSyncTabs.js
@@ -0,0 +1,313 @@
+/* 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/. */
+
+var Cu = Components.utils;
+
+Cu.import("resource://services-common/utils.js");
+Cu.import("resource://services-sync/main.js");
+Cu.import("resource:///modules/PlacesUIUtils.jsm");
+Cu.import("resource://gre/modules/PlacesUtils.jsm", this);
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+var RemoteTabViewer = {
+  _tabsList: null,
+
+  init: function () {
+    Services.obs.addObserver(this, "weave:service:login:finish", false);
+    Services.obs.addObserver(this, "weave:engine:sync:finish", false);
+
+    this._tabsList = document.getElementById("tabsList");
+
+    this.buildList(true);
+  },
+
+  uninit: function () {
+    Services.obs.removeObserver(this, "weave:service:login:finish");
+    Services.obs.removeObserver(this, "weave:engine:sync:finish");
+  },
+
+  createItem: function(attrs) {
+    let item = document.createElement("richlistitem");
+
+    // Copy the attributes from the argument into the item
+    for (let attr in attrs) {
+      item.setAttribute(attr, attrs[attr]);
+    }
+
+    if (attrs["type"] == "tab") {
+      item.label = attrs.title != "" ? attrs.title : attrs.url;
+    }
+
+    return item;
+  },
+
+  filterTabs: function(event) {
+    let val = event.target.value.toLowerCase();
+    let numTabs = this._tabsList.getRowCount();
+    let clientTabs = 0;
+    let currentClient = null;
+
+    for (let i = 0; i < numTabs; i++) {
+      let item = this._tabsList.getItemAtIndex(i);
+      let hide = false;
+      if (item.getAttribute("type") == "tab") {
+        if (!item.getAttribute("url").toLowerCase().includes(val) && 
+            !item.getAttribute("title").toLowerCase().includes(val)) {
+          hide = true;
+        } else {
+          clientTabs++;
+        }
+      }
+      else if (item.getAttribute("type") == "client") {
+        if (currentClient) {
+          if (clientTabs == 0) {
+            currentClient.hidden = true;
+          }
+        }
+        currentClient = item;
+        clientTabs = 0;
+      }
+      item.hidden = hide;
+    }
+    if (clientTabs == 0) {
+      currentClient.hidden = true;
+    }
+  },
+
+  openSelected: function() {
+    let items = this._tabsList.selectedItems;
+    let urls = [];
+    for (let i = 0;i < items.length;i++) {
+      if (items[i].getAttribute("type") == "tab") {
+        urls.push(items[i].getAttribute("url"));
+        let index = this._tabsList.getIndexOfItem(items[i]);
+        this._tabsList.removeItemAt(index);
+      }
+    }
+    if (urls.length) {
+      getTopWin().gBrowser.loadTabs(urls);
+      this._tabsList.clearSelection();
+    }
+  },
+
+  bookmarkSingleTab: function() {
+    let item = this._tabsList.selectedItems[0];
+    let uri = Weave.Utils.makeURI(item.getAttribute("url"));
+    let title = item.getAttribute("title");
+    PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                     , type: "bookmark"
+                                     , uri: uri
+                                     , title: title
+                                     , hiddenRows: [ "description"
+                                                   , "location"
+                                                   , "loadInSidebar"
+                                                   , "keyword" ]
+                                     }, window.top);
+  },
+
+  bookmarkSelectedTabs: function() {
+    let items = this._tabsList.selectedItems;
+    let URIs = [];
+    for (let i = 0;i < items.length;i++) {
+      if (items[i].getAttribute("type") == "tab") {
+        let uri = Weave.Utils.makeURI(items[i].getAttribute("url"));
+        if (!uri) {
+          continue;
+        }
+
+        URIs.push(uri);
+      }
+    }
+    if (URIs.length) {
+      PlacesUIUtils.showBookmarkDialog({ action: "add"
+                                       , type: "folder"
+                                       , URIList: URIs
+                                       , hiddenRows: [ "description" ]
+                                       }, window.top);
+    }
+  },
+
+  getIcon: function (iconUri, defaultIcon) {
+    try {
+      let iconURI = Weave.Utils.makeURI(iconUri);
+      return PlacesUtils.favicons.getFaviconLinkForIcon(iconURI).spec;
+    } catch (ex) {
+      // Do nothing.
+    }
+
+    // Just give the provided default icon or the system's default.
+    return defaultIcon || PlacesUtils.favicons.defaultFavicon.spec;
+  },
+
+  _waitingForBuildList: false,
+
+  _buildListRequested: false,
+
+  buildList: function (force) {
+    if (this._waitingForBuildList) {
+      this._buildListRequested = true;
+      return;
+    }
+
+    this._waitingForBuildList = true;
+    this._buildListRequested = false;
+
+    this._clearTabList();
+
+    if (Weave.Service.isLoggedIn && this._refetchTabs(force)) {
+      this._generateWeaveTabList();
+    } else {
+      //XXXzpao We should say something about not being logged in & not having data
+      //        or tell the appropriate condition. (bug 583344)
+    }
+
+    function complete() {
+      this._waitingForBuildList = false;
+      if (this._buildListRequested) {
+        CommonUtils.nextTick(this.buildList, this);
+      }
+    }
+
+    complete();
+  },
+
+  _clearTabList: function () {
+    let list = this._tabsList;
+
+    // Clear out existing richlistitems
+    let count = list.getRowCount();
+    if (count > 0) {
+      for (let i = count - 1; i >= 0; i--) {
+        list.removeItemAt(i);
+      }
+    }
+  },
+
+  _generateWeaveTabList: function () {
+    let engine = Weave.Service.engineManager.get("tabs");
+    let list = this._tabsList;
+
+    let seenURLs = new Set();
+    let localURLs = engine.getOpenURLs();
+
+    for (let [guid, client] in Iterator(engine.getAllClients())) {
+      // Create the client node, but don't add it in-case we don't show any tabs
+      let appendClient = true;
+
+      client.tabs.forEach(function({title, urlHistory, icon}) {
+        let url = urlHistory[0];
+        if (!url || localURLs.has(url) || seenURLs.has(url)) {
+          return;
+        }
+        seenURLs.add(url);
+
+        if (appendClient) {
+          let attrs = {
+            type: "client",
+            clientName: client.clientName,
+            class: Weave.Service.clientsEngine.isMobile(client.id) ? "mobile" : "desktop"
+          };
+          let clientEnt = this.createItem(attrs);
+          list.appendChild(clientEnt);
+          appendClient = false;
+          clientEnt.disabled = true;
+        }
+        let attrs = {
+          type:  "tab",
+          title: title || url,
+          url:   url,
+          icon:  this.getIcon(icon),
+        }
+        let tab = this.createItem(attrs);
+        list.appendChild(tab);
+      }, this);
+    }
+  },
+
+  adjustContextMenu: function(event) {
+    let mode = "all";
+    switch (this._tabsList.selectedItems.length) {
+      case 0:
+        break;
+      case 1:
+        mode = "single"
+        break;
+      default:
+        mode = "multiple";
+        break;
+    }
+
+    let menu = document.getElementById("tabListContext");
+    let el = menu.firstChild;
+    while (el) {
+      let showFor = el.getAttribute("showFor");
+      if (showFor) {
+        el.hidden = showFor != mode && showFor != "all";
+      }
+
+      el = el.nextSibling;
+    }
+  },
+
+  _refetchTabs: function(force) {
+    if (!force) {
+      // Don't bother refetching tabs if we already did so recently
+      let lastFetch = 0;
+      try {
+        lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch");
+      }
+      catch (e) {
+        /* Just use the default value of 0 */
+      }
+
+      let now = Math.floor(Date.now() / 1000);
+      if (now - lastFetch < 30) {
+        return false;
+      }
+    }
+
+    // if Clients hasn't synced yet this session, we need to sync it as well.
+    if (Weave.Service.clientsEngine.lastSync == 0) {
+      Weave.Service.clientsEngine.sync();
+    }
+
+    // Force a sync only for the tabs engine
+    let engine = Weave.Service.engineManager.get("tabs");
+    engine.lastModified = null;
+    engine.sync();
+    Services.prefs.setIntPref("services.sync.lastTabFetch",
+                              Math.floor(Date.now() / 1000));
+
+    return true;
+  },
+
+  observe: function(subject, topic, data) {
+    switch (topic) {
+      case "weave:service:login:finish":
+        this.buildList(true);
+        break;
+      case "weave:engine:sync:finish":
+        if (subject == "tabs") {
+          this.buildList(false);
+        }
+        break;
+    }
+  },
+
+  handleClick: function(event) {
+    if (event.target.getAttribute("type") != "tab") {
+      return;
+    }
+
+
+    if (event.button == 1) {
+      let url = event.target.getAttribute("url");
+      openUILink(url, event);
+      let index = this._tabsList.getIndexOfItem(event.target);
+      this._tabsList.removeItemAt(index);
+    }
+  }
+}
+
diff --git a/application/basilisk/components/sync/aboutSyncTabs.xul b/application/basilisk/components/sync/aboutSyncTabs.xul
new file mode 100644
index 0000000..a4aa003
--- /dev/null
+++ b/application/basilisk/components/sync/aboutSyncTabs.xul
@@ -0,0 +1,68 @@
+<?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/. -->
+
+<?xml-stylesheet href="chrome://browser/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/skin/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://browser/content/sync/aboutSyncTabs.css" type="text/css"?>
+
+<!DOCTYPE window [
+  <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://browser/locale/aboutSyncTabs.dtd">
+  %aboutSyncTabsDTD;
+]>
+
+<window id="tabs-display"
+        onload="RemoteTabViewer.init()"
+        onunload="RemoteTabViewer.uninit()"
+        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+        xmlns:html="http://www.w3.org/1999/xhtml"
+        title="&tabs.otherDevices.label;">
+  <script type="application/javascript;version=1.8" src="chrome://browser/content/sync/aboutSyncTabs.js"/>
+  <script type="application/javascript" src="chrome://browser/content/utilityOverlay.js"/>
+  <html:head>
+    <html:link rel="icon" href="chrome://browser/skin/sync-16.png"/>
+  </html:head>
+
+  <popupset id="contextmenus">
+    <menupopup id="tabListContext">
+      <menuitem label="&tabs.context.openTab.label;"
+                accesskey="&tabs.context.openTab.accesskey;"
+                oncommand="RemoteTabViewer.openSelected()"
+                showFor="single"/>
+      <menuitem label="&tabs.context.bookmarkSingleTab.label;"
+                accesskey="&tabs.context.bookmarkSingleTab.accesskey;"
+                oncommand="RemoteTabViewer.bookmarkSingleTab(event)"
+                showFor="single"/>
+      <menuitem label="&tabs.context.openMultipleTabs.label;"
+                accesskey="&tabs.context.openMultipleTabs.accesskey;"
+                oncommand="RemoteTabViewer.openSelected()"
+                showFor="multiple"/>
+      <menuitem label="&tabs.context.bookmarkMultipleTabs.label;"
+                accesskey="&tabs.context.bookmarkMultipleTabs.accesskey;"
+                oncommand="RemoteTabViewer.bookmarkSelectedTabs()"
+                showFor="multiple"/>
+      <menuseparator/>
+      <menuitem label="&tabs.context.refreshList.label;"
+                accesskey="&tabs.context.refreshList.accesskey;"
+                oncommand="RemoteTabViewer.buildList()"
+                showFor="all"/>
+    </menupopup>
+  </popupset>
+  <richlistbox context="tabListContext" id="tabsList" seltype="multiple"
+               align="center" flex="1"
+               onclick="RemoteTabViewer.handleClick(event)"
+               oncontextmenu="RemoteTabViewer.adjustContextMenu(event)">
+    <hbox id="headers" align="center">
+      <label id="tabsListHeading"
+             value="&tabs.otherDevices.label;"/>
+      <spacer flex="1"/>
+      <textbox type="search"
+               emptytext="&tabs.searchText.label;"
+               oncommand="RemoteTabViewer.filterTabs(event)"/>
+    </hbox>
+
+  </richlistbox>
+</window>
+
diff --git a/application/basilisk/components/sync/addDevice.js b/application/basilisk/components/sync/addDevice.js
new file mode 100644
index 0000000..0390d43
--- /dev/null
+++ b/application/basilisk/components/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/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var 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;
+
+var 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);
+  });
+});
diff --git a/application/basilisk/components/sync/addDevice.xul b/application/basilisk/components/sync/addDevice.xul
new file mode 100644
index 0000000..f2371aa
--- /dev/null
+++ b/application/basilisk/components/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="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+    </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>
diff --git a/application/basilisk/components/sync/genericChange.js b/application/basilisk/components/sync/genericChange.js
new file mode 100644
index 0000000..df66391
--- /dev/null
+++ b/application/basilisk/components/sync/genericChange.js
@@ -0,0 +1,234 @@
+/* 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/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+
+Components.utils.import("resource://services-sync/main.js");
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+var 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;
+          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 {
+      //Pale Moon: Enforce minimum length of 8 for allowed custom passphrase
+      //and don't restrict it to "out of sync" situations only. People who
+      //go to this page generally know what they are doing ;)
+      valid = this._passphraseBox.value.length >= 8;
+    }
+
+    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);
+  }
+};
diff --git a/application/basilisk/components/sync/genericChange.xul b/application/basilisk/components/sync/genericChange.xul
new file mode 100644
index 0000000..3c0b2cd6
--- /dev/null
+++ b/application/basilisk/components/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="http://www.palemoon.org/sync/help/recoverykey.shtml">
+          &addDevice.showMeHow.label;
+        </label>
+      </description>
+    </vbox>
+
+    <spacer id="passphraseSpacer"
+            flex="1"
+            hidden="true"/>
+
+    <description id="warningText" class="data">
+    </description>
+
+    <spacer flex="1"/>
+  </wizardpage>
+</wizard>
diff --git a/application/basilisk/components/sync/jar.mn b/application/basilisk/components/sync/jar.mn
new file mode 100644
index 0000000..3782038
--- /dev/null
+++ b/application/basilisk/components/sync/jar.mn
@@ -0,0 +1,22 @@
+# 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/.
+
+browser.jar:
+  content/browser/sync/aboutSyncTabs.xul
+  content/browser/sync/aboutSyncTabs.js
+  content/browser/sync/aboutSyncTabs.css
+  content/browser/sync/aboutSyncTabs-bindings.xml
+  content/browser/sync/setup.xul
+  content/browser/sync/addDevice.js
+  content/browser/sync/addDevice.xul
+  content/browser/sync/setup.js
+  content/browser/sync/genericChange.xul
+  content/browser/sync/genericChange.js
+  content/browser/sync/key.xhtml
+  content/browser/sync/notification.xml
+  content/browser/sync/quota.xul
+  content/browser/sync/quota.js
+  content/browser/sync/utils.js
+  content/browser/sync/progress.js
+  content/browser/sync/progress.xhtml
\ No newline at end of file
diff --git a/application/basilisk/components/sync/key.xhtml b/application/basilisk/components/sync/key.xhtml
new file mode 100644
index 0000000..92abf0e
--- /dev/null
+++ b/application/basilisk/components/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="http://www.palemoon.org/sync/">http://www.palemoon.org/sync/</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>
diff --git a/application/basilisk/components/sync/moz.build b/application/basilisk/components/sync/moz.build
new file mode 100644
index 0000000..2d64d50
--- /dev/null
+++ b/application/basilisk/components/sync/moz.build
@@ -0,0 +1,8 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+JAR_MANIFESTS += ['jar.mn']
+
diff --git a/application/basilisk/components/sync/notification.xml b/application/basilisk/components/sync/notification.xml
new file mode 100644
index 0000000..8ac881e
--- /dev/null
+++ b/application/basilisk/components/sync/notification.xml
@@ -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/. -->
+
+<!DOCTYPE bindings [
+<!ENTITY % notificationDTD SYSTEM "chrome://global/locale/notification.dtd">
+%notificationDTD;
+]>
+
+<bindings id="notificationBindings"
+          xmlns="http://www.mozilla.org/xbl"
+          xmlns:xbl="http://www.mozilla.org/xbl"
+          xmlns:xul="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+
+  <binding id="notificationbox" extends="chrome://global/content/bindings/notification.xml#notificationbox">
+    <content>
+      <xul:vbox xbl:inherits="hidden=notificationshidden">
+        <xul:spacer/>
+        <children includes="notification"/>
+      </xul:vbox>
+      <children/>
+    </content>
+
+    <implementation>
+      <constructor><![CDATA[
+        let temp = {};
+        Cu.import("resource://services-common/observers.js", temp);
+        temp.Observers.add("weave:notification:added", this.onNotificationAdded, this);
+        temp.Observers.add("weave:notification:removed", this.onNotificationRemoved, this);
+
+        for each (var notification in Weave.Notifications.notifications)
+          this._appendNotification(notification);
+      ]]></constructor>
+
+      <destructor><![CDATA[
+        let temp = {};
+        Cu.import("resource://services-common/observers.js", temp);
+        temp.Observers.remove("weave:notification:added", this.onNotificationAdded, this);
+        temp.Observers.remove("weave:notification:removed", this.onNotificationRemoved, this);
+      ]]></destructor>
+
+      <method name="onNotificationAdded">
+        <parameter name="subject"/>
+        <parameter name="data"/>
+        <body><![CDATA[
+          this._appendNotification(subject);
+        ]]></body>
+      </method>
+
+      <method name="onNotificationRemoved">
+        <parameter name="subject"/>
+        <parameter name="data"/>
+        <body><![CDATA[
+          // If the view of the notification hasn't been removed yet, remove it.
+          var notifications = this.allNotifications;
+          for each (var notification in notifications) {
+            if (notification.notification == subject) {
+              notification.close();
+              break;
+            }
+          }
+        ]]></body>
+      </method>
+
+      <method name="_appendNotification">
+        <parameter name="notification"/>
+        <body><![CDATA[
+          var node = this.appendNotification(notification.description,
+                                             notification.title,
+                                             notification.iconURL,
+                                             notification.priority,
+                                             notification.buttons);
+          node.notification = notification;
+        ]]></body>
+      </method>
+
+    </implementation>
+  </binding>
+
+  <binding id="notification" extends="chrome://global/content/bindings/notification.xml#notification">
+    <content>
+      <xul:hbox class="notification-inner outset" flex="1" xbl:inherits="type">
+        <xul:toolbarbutton ondblclick="event.stopPropagation();"
+                           class="messageCloseButton close-icon tabbable"
+                           xbl:inherits="hidden=hideclose"
+                           tooltiptext="&closeNotification.tooltip;"
+                           oncommand="document.getBindingParent(this).close()"/>
+        <xul:hbox anonid="details" align="center" flex="1">
+          <xul:image anonid="messageImage" class="messageImage" xbl:inherits="src=image,type"/>
+          <xul:description anonid="messageText" class="messageText" xbl:inherits="xbl:text=label"/>
+
+          <!-- The children are the buttons defined by the notification. -->
+          <xul:hbox oncommand="document.getBindingParent(this)._doButtonCommand(event);">
+            <children/>
+          </xul:hbox>
+        </xul:hbox>
+      </xul:hbox>
+    </content>
+    <implementation>
+      <!-- Note: this used to be a field, but for some reason it kept getting
+         - reset to its default value for TabNotification elements.
+         - As a property, that doesn't happen, even though the property stores
+         - its value in a JS property |_notification| that is not defined
+         - in XBL as a field or property.  Maybe this is wrong, but it works.
+         -->
+      <property name="notification"
+                onget="return this._notification"
+                onset="this._notification = val; return val;"/>
+      <method name="close">
+        <body><![CDATA[
+          Weave.Notifications.remove(this.notification);
+
+          // We should be able to call the base class's close method here
+          // to remove the notification element from the notification box,
+          // but we can't because of bug 373652, so instead we copied its code
+          // and execute it below.
+          var control = this.control;
+          if (control)
+            control.removeNotification(this);
+          else
+            this.hidden = true;
+        ]]></body>
+      </method>
+    </implementation>
+  </binding>
+
+</bindings>
diff --git a/application/basilisk/components/sync/progress.js b/application/basilisk/components/sync/progress.js
new file mode 100644
index 0000000..101160f
--- /dev/null
+++ b/application/basilisk/components/sync/progress.js
@@ -0,0 +1,71 @@
+/* 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 {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://services-sync/main.js");
+
+var gProgressBar;
+var gCounter = 0;
+
+function onLoad(event) {
+  Services.obs.addObserver(onEngineSync, "weave:engine:sync:finish", false);
+  Services.obs.addObserver(onEngineSync, "weave:engine:sync:error", false);
+  Services.obs.addObserver(onServiceSync, "weave:service:sync:finish", false);
+  Services.obs.addObserver(onServiceSync, "weave:service:sync:error", false);
+
+  gProgressBar = document.getElementById('uploadProgressBar');
+
+  if (Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
+    gProgressBar.hidden = false;
+  }
+  else {
+    gProgressBar.hidden = true;
+  }
+}
+
+function onUnload(event) {
+  cleanUpObservers();
+}
+
+function cleanUpObservers() {
+  try {
+    Services.obs.removeObserver(onEngineSync, "weave:engine:sync:finish");
+    Services.obs.removeObserver(onEngineSync, "weave:engine:sync:error");
+    Services.obs.removeObserver(onServiceSync, "weave:service:sync:finish");
+    Services.obs.removeObserver(onServiceSync, "weave:service:sync:error");
+  }
+  catch (e) {
+    // may be double called by unload & exit. Ignore.
+  }
+}
+
+function onEngineSync(subject, topic, data) {
+  // The Clients engine syncs first. At this point we don't necessarily know
+  // yet how many engines will be enabled, so we'll ignore the Clients engine
+  // and evaluate how many engines are enabled when the first "real" engine
+  // syncs.
+  if (data == "clients") {
+    return;
+  }
+
+  if (!gCounter &&
+      Services.prefs.getPrefType("services.sync.firstSync") != Ci.nsIPrefBranch.PREF_INVALID) {
+    gProgressBar.max = Weave.Service.engineManager.getEnabled().length;
+  }
+
+  gCounter += 1;
+  gProgressBar.setAttribute("value", gCounter);
+}
+
+function onServiceSync(subject, topic, data) {
+  // To address the case where 0 engines are synced, we will fill the
+  // progress bar so the user knows that the sync has finished.
+  gProgressBar.setAttribute("value", gProgressBar.max);
+  cleanUpObservers();
+}
+
+function closeTab() {
+  window.close();
+}
diff --git a/application/basilisk/components/sync/progress.xhtml b/application/basilisk/components/sync/progress.xhtml
new file mode 100644
index 0000000..d403cb2
--- /dev/null
+++ b/application/basilisk/components/sync/progress.xhtml
@@ -0,0 +1,55 @@
+<?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 % 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>
+
+    <link rel="stylesheet" type="text/css" media="all"
+          href="chrome://browser/skin/syncProgress.css"/>
+
+    <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>&setup.successPage.title;</title>
+    <div id="floatingBox" class="main-content">
+      <div id="title">
+        <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">
+        <progress id="uploadProgressBar" value="0"/>
+      </div>
+      <div id="bottomRow">
+        <button id="closeButton" onclick="closeTab()">&syncProgress.closeButton; </button>
+      </div>
+    </div>
+  </body>
+</html>
diff --git a/application/basilisk/components/sync/quota.js b/application/basilisk/components/sync/quota.js
new file mode 100644
index 0000000..b416a44
--- /dev/null
+++ b/application/basilisk/components/sync/quota.js
@@ -0,0 +1,247 @@
+/* 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");
+
+var 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);
+  }
+
+};
+
+var 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 cell = this.treeBox.getCellAt(event.clientX, event.clientY);
+    if (cell.col && cell.col.id == "enabled")
+      this.toggle(cell.row);
+  },
+
+  /*
+   * 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);
+  },
+
+  /*
+   * Return a list of engines (or rather their pref names) that should be
+   * disabled.
+   */
+  getEnginesToDisable: function getEnginesToDisable() {
+    // Tycho: return [coll.name for each (coll in this._collections) if (!coll.enabled)];
+    let engines = [];
+    for each (let coll in this._collections) {
+      if (!coll.enabled) {
+        engines.push(coll.name);
+      }
+    }
+    return engines;
+  },
+
+  // 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) {}
+
+};
diff --git a/application/basilisk/components/sync/quota.xul b/application/basilisk/components/sync/quota.xul
new file mode 100644
index 0000000..99e6ed7
--- /dev/null
+++ b/application/basilisk/components/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>
diff --git a/application/basilisk/components/sync/setup.js b/application/basilisk/components/sync/setup.js
new file mode 100644
index 0000000..e8d67a5
--- /dev/null
+++ b/application/basilisk/components/sync/setup.js
@@ -0,0 +1,1071 @@
+/* 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/. */
+
+var Ci = Components.interfaces;
+var Cc = Components.classes;
+var Cr = Components.results;
+var 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");
+          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]));
+      }
+      
+      // XXX: Addons syncing is currently not operational; 
+      // Make doubly-sure to always disable addons syncing pref
+      Weave.Svc.Prefs.set("engine.addons", false);
+      
+      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");
+});
diff --git a/application/basilisk/components/sync/setup.xul b/application/basilisk/components/sync/setup.xul
new file mode 100644
index 0000000..cf2cc77
--- /dev/null
+++ b/application/basilisk/components/sync/setup.xul
@@ -0,0 +1,491 @@
+<?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="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+    </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>
+        <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="http://www.palemoon.org/sync/help/easy-setup.shtml"/>
+    </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="http://www.palemoon.org/sync/help/recoverykey.shtml">
+          &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="false"
+                      hidden="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>
+
diff --git a/application/basilisk/components/sync/utils.js b/application/basilisk/components/sync/utils.js
new file mode 100644
index 0000000..d41ecf1
--- /dev/null
+++ b/application/basilisk/components/sync/utils.js
@@ -0,0 +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 0o600 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.
+var 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 == "accountSetup" && window.opener &&
+        openerDocEl.id == "BrowserPreferences" && !openerDocEl.instantApply)
+      openUILinkIn(url, "window");
+    else if (thisDocEl.id == "BrowserPreferences" && !thisDocEl.instantApply)
+      openUILinkIn(url, "window");
+    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"));
+  },
+
+  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];
+  }
+};
diff --git a/application/basilisk/locales/en-US/chrome/browser/syncProgress.dtd b/application/basilisk/locales/en-US/chrome/browser/syncProgress.dtd
new file mode 100644
index 0000000..db45cb9
--- /dev/null
+++ b/application/basilisk/locales/en-US/chrome/browser/syncProgress.dtd
@@ -0,0 +1,15 @@
+<!-- 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 % 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.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;.">
+

--
Alioth's /home/x2go-admin/maintenancescripts/git/hooks/post-receive-email on /srv/git/code.x2go.org/pale-moon.git


More information about the x2go-commits mailing list