summaryrefslogtreecommitdiffstats
path: root/comm/suite/components/sync/content
diff options
context:
space:
mode:
Diffstat (limited to 'comm/suite/components/sync/content')
-rw-r--r--comm/suite/components/sync/content/aboutSyncTabs-bindings.xml46
-rw-r--r--comm/suite/components/sync/content/aboutSyncTabs.css11
-rw-r--r--comm/suite/components/sync/content/aboutSyncTabs.js293
-rw-r--r--comm/suite/components/sync/content/aboutSyncTabs.xul69
-rw-r--r--comm/suite/components/sync/content/syncAddDevice.js142
-rw-r--r--comm/suite/components/sync/content/syncAddDevice.xul128
-rw-r--r--comm/suite/components/sync/content/syncGenericChange.js232
-rw-r--r--comm/suite/components/sync/content/syncGenericChange.xul116
-rw-r--r--comm/suite/components/sync/content/syncKey.xhtml49
-rw-r--r--comm/suite/components/sync/content/syncNotification.xml93
-rw-r--r--comm/suite/components/sync/content/syncQuota.js252
-rw-r--r--comm/suite/components/sync/content/syncQuota.xul62
-rw-r--r--comm/suite/components/sync/content/syncSetup.js961
-rw-r--r--comm/suite/components/sync/content/syncSetup.xul482
-rw-r--r--comm/suite/components/sync/content/syncUI.js454
-rw-r--r--comm/suite/components/sync/content/syncUtils.js224
16 files changed, 3614 insertions, 0 deletions
diff --git a/comm/suite/components/sync/content/aboutSyncTabs-bindings.xml b/comm/suite/components/sync/content/aboutSyncTabs-bindings.xml
new file mode 100644
index 0000000000..6365bf55fc
--- /dev/null
+++ b/comm/suite/components/sync/content/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>
+ <xul:image class="tabIcon"
+ xbl:inherits="src=icon"/>
+ </xul:vbox>
+ <xul:vbox 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 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/comm/suite/components/sync/content/aboutSyncTabs.css b/comm/suite/components/sync/content/aboutSyncTabs.css
new file mode 100644
index 0000000000..83782b4d5f
--- /dev/null
+++ b/comm/suite/components/sync/content/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://communicator/content/aboutSyncTabs-bindings.xml#tab-listing);
+}
+
+richlistitem[type="client"] {
+ -moz-binding: url(chrome://communicator/content/aboutSyncTabs-bindings.xml#client-listing);
+}
diff --git a/comm/suite/components/sync/content/aboutSyncTabs.js b/comm/suite/components/sync/content/aboutSyncTabs.js
new file mode 100644
index 0000000000..e13c2be685
--- /dev/null
+++ b/comm/suite/components/sync/content/aboutSyncTabs.js
@@ -0,0 +1,293 @@
+/* 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 {Weave} = ChromeUtils.import("resource://services-sync/main.js");
+const {PlacesUIUtils} = ChromeUtils.import("resource:///modules/PlacesUIUtils.jsm");
+const {PlacesUtils} = ChromeUtils.import("resource://gre/modules/PlacesUtils.jsm");
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var RemoteTabViewer = {
+ _tabsList: null,
+
+ init: function () {
+ Services.obs.addObserver(this, "weave:service:login:finish");
+ Services.obs.addObserver(this, "weave:engine:sync:finish");
+
+ 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");
+ },
+
+ buildList: function(force) {
+ if (!Weave.Service.isLoggedIn || !this._refetchTabs(force))
+ return;
+ //XXXzpao We should say something about not being logged in & not having data
+ // or tell the appropriate condition. (bug 583344)
+
+ this._generateTabList();
+ },
+
+ 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.url;
+
+ return item;
+ },
+
+ filterTabs: function(event) {
+ let val = event.target.value.toLowerCase();
+ let numTabs = this._tabsList.getRowCount();
+ let client = null;
+ for (let i = 0; i < numTabs; i++) {
+ let item = this._tabsList.getItemAtIndex(i);
+ let hide = false; // By default, make sure the item is visible.
+ switch (item.getAttribute("type")) {
+ case "tab":
+ if (item.getAttribute("url").toLowerCase().indexOf(val) == -1 &&
+ item.getAttribute("title").toLowerCase().indexOf(val) == -1)
+ hide = true;
+ else
+ client = null; // This client should not be hidden.
+ break;
+ case "client":
+ if (client)
+ client.hidden = true; // Hide the last client, it had no visible tabs.
+ client = item;
+ break;
+ }
+ item.hidden = hide;
+ }
+ if (client)
+ client.hidden = true; // Hide the last client, it had no visible tabs.
+ },
+
+ 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().getBrowser().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.showMinimalAddBookmarkUI(uri, title);
+ },
+
+ bookmarkSelectedTabs: function() {
+ let items = this._tabsList.selectedItems;
+ let URIs = [];
+ let titles = [];
+ 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);
+ titles.push(items[i].getAttribute("title"));
+ }
+ }
+ if (URIs.length)
+ PlacesUIUtils.showMinimalAddMultiBookmarkUI(URIs, titles);
+ },
+
+ 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;
+ },
+
+ _generateTabList: function() {
+ let engine = Weave.Service.engineManager.get("tabs");
+ 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);
+ }
+
+ let seenURLs = new Set();
+ let localURLs = engine.getOpenURLs();
+
+ for (let [guid, client] of Object.entries(engine.getAllClients())) {
+ 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";
+ else // menuseparator
+ el.hidden = mode == "all";
+ el = el.nextSibling;
+ }
+ },
+
+ _refetchTabs: function(force) {
+ if (!force) {
+ // Don't bother refetching tabs if we already did so recently
+ let lastFetch = Services.prefs.getIntPref("services.sync.lastTabFetch", 0);
+ let now = Math.floor(Date.now() / 1000);
+ if (now - lastFetch < 30)
+ return false;
+ }
+
+ // if Clients hasn't synced yet this session, 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._generateTabList();
+ break;
+ }
+ },
+
+ handleClick: function(event) {
+ if (event.target.getAttribute("type") == "tab" && event.button == 1) {
+ let url = event.target.getAttribute("url");
+ openUILink(url, event);
+ let index = this._tabsList.getIndexOfItem(event.target);
+ this._tabsList.removeItemAt(index);
+ }
+ }
+};
+
+var EventDirector = {
+ handleEvent: function(event) {
+ switch (event.type) {
+ case "click":
+ RemoteTabViewer.handleClick(event);
+ break;
+ case "contextmenu":
+ RemoteTabViewer.adjustContextMenu(event);
+ break;
+ case "command":
+ switch (event.target.id) {
+ case "openSingleTab":
+ case "openSelectedTabs":
+ RemoteTabViewer.openSelected();
+ break;
+ case "bookmarkSingleTab":
+ RemoteTabViewer.bookmarkSingleTab();
+ break;
+ case "bookmarkSelectedTabs":
+ RemoteTabViewer.bookmarkSelectedTabs();
+ break;
+ case "buildList":
+ RemoteTabViewer.buildList();
+ break;
+ case "filterTabs":
+ RemoteTabViewer.filterTabs(event);
+ break;
+ }
+ break;
+ }
+ }
+};
+
+window.onload = function() {
+ RemoteTabViewer.init();
+
+ let tabsList = document.getElementById("tabsList");
+ tabsList.addEventListener("click", EventDirector);
+ tabsList.addEventListener("contextmenu", EventDirector);
+
+ document.getElementById("tabListContext")
+ .addEventListener("command", EventDirector);
+ document.getElementById("filterTabs")
+ .addEventListener("command", EventDirector);
+}
+
+window.onunload = function() {
+ RemoteTabViewer.uninit();
+}
diff --git a/comm/suite/components/sync/content/aboutSyncTabs.xul b/comm/suite/components/sync/content/aboutSyncTabs.xul
new file mode 100644
index 0000000000..b0355557de
--- /dev/null
+++ b/comm/suite/components/sync/content/aboutSyncTabs.xul
@@ -0,0 +1,69 @@
+<?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://communicator/skin/" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/aboutSyncTabs.css" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/content/aboutSyncTabs.css" type="text/css"?>
+
+<!DOCTYPE window [
+ <!ENTITY % aboutSyncTabsDTD SYSTEM "chrome://communicator/locale/aboutSyncTabs.dtd">
+ %aboutSyncTabsDTD;
+]>
+
+<window id="tabs-display"
+ xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ title="&tabs.otherComputers.label;">
+
+ <script src="chrome://communicator/content/aboutSyncTabs.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+
+ <html:head>
+ <html:link rel="icon" href="chrome://communicator/skin/sync/sync-16.png"/>
+ </html:head>
+
+ <popupset id="contextmenus">
+ <menupopup id="tabListContext">
+ <menuitem id="openSingleTab"
+ label="&tabs.context.openTab.label;"
+ accesskey="&tabs.context.openTab.accesskey;"
+ showFor="single"/>
+ <menuitem id="bookmarkSingleTab"
+ label="&tabs.context.bookmarkSingleTab.label;"
+ accesskey="&tabs.context.bookmarkSingleTab.accesskey;"
+ showFor="single"/>
+ <menuitem id="openSelectedTabs"
+ label="&tabs.context.openMultipleTabs.label;"
+ accesskey="&tabs.context.openMultipleTabs.accesskey;"
+ showFor="multiple"/>
+ <menuitem id="bookmarkSelectedTabs"
+ label="&tabs.context.bookmarkMultipleTabs.label;"
+ accesskey="&tabs.context.bookmarkMultipleTabs.accesskey;"
+ showFor="multiple"/>
+ <menuseparator/>
+ <menuitem id="buildList"
+ label="&tabs.context.refreshList.label;"
+ accesskey="&tabs.context.refreshList.accesskey;"
+ showFor="all"/>
+ </menupopup>
+ </popupset>
+ <richlistbox id="tabsList"
+ context="tabListContext"
+ seltype="multiple"
+ class="plain"
+ align="center"
+ flex="1">
+ <hbox id="headers" align="center">
+ <label id="tabsListHeading"
+ value="&tabs.otherComputers.label;"/>
+ <spacer flex="1"/>
+ <textbox id="filterTabs"
+ type="search"
+ aria-controls="tabsList"
+ emptytext="&tabs.searchText.label;"/>
+ </hbox>
+ </richlistbox>
+</window>
diff --git a/comm/suite/components/sync/content/syncAddDevice.js b/comm/suite/components/sync/content/syncAddDevice.js
new file mode 100644
index 0000000000..d986b54f00
--- /dev/null
+++ b/comm/suite/components/sync/content/syncAddDevice.js
@@ -0,0 +1,142 @@
+/* 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 {Weave} = ChromeUtils.import("resource://services-sync/main.js");
+var {XPCOMUtils} = ChromeUtils.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.nextFocusEl = { pin1: this.pin2,
+ pin2: this.pin3,
+ pin3: this.wizard.getButton("next") };
+
+ this.throbber = document.getElementById("add-device-throbber");
+ this.errorRow = document.getElementById("errorRow");
+ },
+
+ onPageShow: function onPageShow() {
+ this.wizard.getButton("back").hidden = true;
+
+ switch (this.wizard.pageIndex) {
+ case ADD_DEVICE_PAGE:
+ this.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) {
+ this.wizard.canAdvance = (this.pin1.value.length == PIN_PART_LENGTH &&
+ this.pin2.value.length == PIN_PART_LENGTH &&
+ this.pin3.value.length == PIN_PART_LENGTH);
+ if (textbox && textbox.value.length == PIN_PART_LENGTH)
+ this.nextFocusEl[textbox.id].focus();
+ },
+
+ 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/comm/suite/components/sync/content/syncAddDevice.xul b/comm/suite/components/sync/content/syncAddDevice.xul
new file mode 100644
index 0000000000..e7a30d2aa3
--- /dev/null
+++ b/comm/suite/components/sync/content/syncAddDevice.xul
@@ -0,0 +1,128 @@
+<?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://communicator/skin/sync/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/sync/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://communicator/locale/sync/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://communicator/locale/sync/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
+ xmlns:html="http://www.w3.org/1999/xhtml"
+ id="wizard"
+ title="&addDevice.title.label;"
+ windowtype="Sync:AddDevice"
+ persist="screenX screenY"
+ onwizardnext="return gSyncAddDevice.onWizardAdvance();"
+ onwizardback="return gSyncAddDevice.onWizardBack();"
+ onwizardcancel="gSyncAddDevice.onWizardCancel();"
+ onload="gSyncAddDevice.init();">
+
+ <script src="chrome://communicator/content/sync/syncAddDevice.js"/>
+ <script src="chrome://communicator/content/sync/syncUtils.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="addDevicePage"
+ label="&addDevice.title.label;"
+ onpageshow="gSyncAddDevice.onPageShow();">
+ <description>
+ &addDevice.dialog.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/add-device"/>
+ </description>
+ <spacer flex="1"/>
+ <description>
+ &addDevice.dialog.enterCode.label;
+ </description>
+ <spacer flex="1"/>
+ <vbox align="center">
+ <textbox id="pin1"
+ class="pin"
+ size="4"
+ maxlength="4"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"/>
+ <textbox id="pin2"
+ class="pin"
+ size="4"
+ maxlength="4"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"/>
+ <textbox id="pin3"
+ class="pin"
+ size="4"
+ maxlength="4"
+ oninput="gSyncAddDevice.onTextBoxInput(this);"
+ onfocus="this.select();"/>
+ </vbox>
+ <spacer flex="1"/>
+ <vbox id="add-device-throbber" align="center" hidden="true">
+ <image/>
+ </vbox>
+ <hbox id="errorRow" pack="center" hidden="true">
+ <image class="statusIcon" status="error"/>
+ <label class="status"
+ value="&addDevice.dialog.tryAgain.label;"/>
+ </hbox>
+ <spacer flex="3"/>
+ <label class="text-link"
+ value="&addDevice.dontHaveDevice.label;"
+ onclick="gSyncAddDevice.goToSyncKeyPage();"/>
+ </wizardpage>
+
+ <!-- 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/comm/suite/components/sync/content/syncGenericChange.js b/comm/suite/components/sync/content/syncGenericChange.js
new file mode 100644
index 0000000000..13cab89811
--- /dev/null
+++ b/comm/suite/components/sync/content/syncGenericChange.js
@@ -0,0 +1,232 @@
+/* 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 {Weave} = ChromeUtils.import("resource://services-sync/main.js");
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+var Change = {
+ _dialog: null,
+ _dialogType: null,
+ _status: null,
+ _statusIcon: null,
+ _firstBox: null,
+ _secondBox: null,
+ _passphraseBox: null,
+
+ 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 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._passphraseBox = document.getElementById("passphraseBox");
+
+ this._dialog.getButton("finish").disabled = true;
+ this._dialog.getButton("back").hidden = true;
+
+ this._stringBundle =
+ Services.strings.createBundle("chrome://communicator/locale/sync/syncGenericChange.properties");
+
+ switch (this._dialogType) {
+ case "UpdatePassphrase":
+ case "ResetPassphrase":
+ document.getElementById("textBox1Row").hidden = true;
+ document.getElementById("textBox2Row").hidden = true;
+ document.getElementById("passphraseLabel").value
+ = this._str("new.recoverykey.label");
+ document.getElementById("passphraseSpacer").hidden = false;
+
+ if (this._updatingPassphrase) {
+ document.getElementById("passphraseHelpBox").hidden = false;
+ document.title = this._str("new.recoverykey.title");
+ introText.textContent = this._str("new.recoverykey.introText");
+ this._dialog.getButton("finish").label
+ = this._str("new.recoverykey.acceptButton");
+ }
+ else {
+ document.getElementById("generatePassphraseButton").hidden = false;
+ document.getElementById("passphraseBackupButtons").hidden = false;
+ this._passphraseBox.readOnly = true;
+ let pp = Weave.Service.identity.syncKey;
+ if (Weave.Utils.isPassphrase(pp))
+ pp = Weave.Utils.hyphenatePassphrase(pp);
+ this._passphraseBox.value = pp;
+ this._passphraseBox.focus();
+ document.title = this._str("change.recoverykey.title");
+ introText.textContent = this._str("change.recoverykey.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.textContent = "";
+ 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.textContent = 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._clearStatus();
+ 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();
+ }
+ 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 () {
+ let valid = false;
+ let errorString = "";
+
+ if (this._dialogType == "ChangePassword") {
+ if (this._currentPasswordInvalid)
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox);
+ else
+ [valid, errorString] = gSyncUtils.validatePassword(this._firstBox, this._secondBox);
+ }
+ else {
+ if (!this._updatingPassphrase)
+ return;
+
+ valid = this._passphraseBox.value != "";
+ }
+
+ if (errorString == "")
+ this._clearStatus();
+ else
+ this._updateStatusWithString(errorString, "error");
+
+ this._statusRow.hidden = valid;
+ this._dialog.getButton("finish").disabled = !valid;
+ },
+
+ _str: function Change__string(str) {
+ try {
+ return this._stringBundle.GetStringFromName(str);
+ } catch (e) {
+ Cu.reportError("Missing string: " + str);
+ throw e;
+ }
+ }
+};
diff --git a/comm/suite/components/sync/content/syncGenericChange.xul b/comm/suite/components/sync/content/syncGenericChange.xul
new file mode 100644
index 0000000000..aac4a004fa
--- /dev/null
+++ b/comm/suite/components/sync/content/syncGenericChange.xul
@@ -0,0 +1,116 @@
+<?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://communicator/skin/sync/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/sync/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://communicator/locale/sync/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://communicator/locale/sync/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 src="chrome://communicator/content/sync/syncGenericChange.js"/>
+ <script src="chrome://communicator/content/sync/syncUtils.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="change-page"
+ label="">
+
+ <description id="introText"/>
+
+ <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="&recoveryGenerateNewKey.label;"
+ class="text-link inline-link"
+ onclick="event.stopPropagation();
+ Change.doGeneratePassphrase();"/>
+ </hbox>
+ <textbox id="passphraseBox"
+ flex="1"
+ onfocus="this.select();"
+ oninput="Change.validate();"/>
+ </vbox>
+
+ <vbox id="feedback" pack="center">
+ <hbox id="statusRow" align="center">
+ <image id="statusIcon" class="statusIcon"/>
+ <label id="status" class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+
+ <separator class="thin"/>
+
+ <hbox id="passphraseBackupButtons"
+ hidden="true"
+ pack="center">
+ <button id="printSyncKeyButton"
+ label="&button.syncKeyBackup.print.label;"
+ accesskey="&button.syncKeyBackup.print.accesskey;"
+ oncommand="gSyncUtils.passphrasePrint('passphraseBox');"/>
+ <button id="saveSyncKeyButton"
+ label="&button.syncKeyBackup.save.label;"
+ accesskey="&button.syncKeyBackup.save.accesskey;"
+ oncommand="gSyncUtils.passphraseSave('passphraseBox');"/>
+ </hbox>
+
+ <vbox id="passphraseHelpBox"
+ hidden="true">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="https://services.mozilla.com/sync/help/manual-setup">
+ &addDevice.showMeHow.label;
+ </label>
+ </description>
+ </vbox>
+
+ <spacer id="passphraseSpacer" flex="1" hidden="true"/>
+
+ <description id="warningText" class="data"/>
+
+ <spacer flex="1"/>
+ </wizardpage>
+</wizard>
diff --git a/comm/suite/components/sync/content/syncKey.xhtml b/comm/suite/components/sync/content/syncKey.xhtml
new file mode 100644
index 0000000000..6ce35fa1cc
--- /dev/null
+++ b/comm/suite/components/sync/content/syncKey.xhtml
@@ -0,0 +1,49 @@
+<?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://communicator/locale/sync/syncBrand.dtd">
+ %syncBrandDTD;
+ <!ENTITY % syncKeyDTD SYSTEM "chrome://communicator/locale/sync/syncKey.dtd">
+ %syncKeyDTD;
+]>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+ <title>&syncKey.page.title;</title>
+ <meta name="robots" content="noindex"/>
+ <style type="text/css">
+ #synckey { font-size: 1.5em; }
+ footer { font-size: 0.67em; }
+ </style>
+</head>
+
+<body>
+<h1>&syncKey.page.title;</h1>
+
+<p id="synckey">SYNCKEY</p>
+
+<p>&syncKey.page.description;</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.keepItSafe4.description;</p>
+</div>
+
+<p>&syncKey.findOutMore1.label;<a href="https://services.mozilla.com">https://services.mozilla.com</a>&syncKey.findOutMore2.label;</p>
+
+<footer>
+ &syncKey.footer1.label;<a id="tosLink" href="termsURL">termsURL</a>&syncKey.footer2.label;<a id="ppLink" href="privacyURL">privacyURL</a>&syncKey.footer3.label;
+</footer>
+
+</body>
+</html>
diff --git a/comm/suite/components/sync/content/syncNotification.xml b/comm/suite/components/sync/content/syncNotification.xml
new file mode 100644
index 0000000000..a33a06c030
--- /dev/null
+++ b/comm/suite/components/sync/content/syncNotification.xml
@@ -0,0 +1,93 @@
+<?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">
+
+ <binding id="notificationbox" extends="chrome://global/content/bindings/notification.xml#notificationbox">
+ <implementation>
+ <constructor><![CDATA[
+ let localScope = {};
+ ChromeUtils.import("resource://services-common/observers.js", localScope);
+ ChromeUtils.import("resource://services-sync/notifications.js", localScope);
+
+ localScope.Observers.add("weave:notification:added", this.onNotificationAdded, this);
+ localScope.Observers.add("weave:notification:removed", this.onNotificationRemoved, this);
+ localScope.Notifications.notifications.forEach(this._appendNotification, this);
+ ]]></constructor>
+
+ <destructor><![CDATA[
+ let localScope = {};
+ ChromeUtils.import("resource://services-common/observers.js", localScope);
+ localScope.Observers.remove("weave:notification:added", this.onNotificationAdded, this);
+ localScope.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 (let notification of 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">
+ <implementation>
+ <method name="close">
+ <body><![CDATA[
+ let localScope = {};
+ ChromeUtils.import("resource://services-sync/notifications.js", localScope);
+ localScope.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/comm/suite/components/sync/content/syncQuota.js b/comm/suite/components/sync/content/syncQuota.js
new file mode 100644
index 0000000000..c4d2d03676
--- /dev/null
+++ b/comm/suite/components/sync/content/syncQuota.js
@@ -0,0 +1,252 @@
+/* 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 {Weave} = ChromeUtils.import("resource://services-sync/main.js");
+const {DownloadUtils} = ChromeUtils.import("resource://gre/modules/DownloadUtils.jsm");
+
+var gSyncQuota = {
+
+ init: function init() {
+ this.bundle = document.getElementById("quotaStrings");
+ let caption = document.getElementById("treeCaption");
+ caption.textContent = 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));
+ });
+ },
+
+ uninit: function uninit() {
+ if (this._usage_req)
+ this._usage_req.abort();
+ if (this._quota_req)
+ this._quota_req.abort();
+ },
+
+ onAccept: function onAccept() {
+ let engines = gUsageTreeView.getEnginesToDisable();
+ for (let engine of 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 true;
+ },
+
+ 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 (let engine of 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 (let coll of this._collections) {
+ coll.size = 0;
+ // If we couldn't retrieve any data, just blank out the label.
+ if (!data) {
+ coll.sizeLabel = "";
+ continue;
+ }
+
+ for (let engineName of 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);
+
+ // Display which ones will be removed
+ let freeup = 0;
+ let toremove = [];
+ for (let collection of this._collections) {
+ if (collection.enabled)
+ continue;
+ toremove.push(collection.name);
+ freeup += collection.size;
+ }
+
+ let caption = document.getElementById("treeCaption");
+ if (!toremove.length) {
+ caption.className = "";
+ caption.textContent = gSyncQuota.bundle.getString("quota.treeCaption.label");
+ return;
+ }
+
+ toremove = toremove.map(coll => this._byname[coll].title);
+ toremove = toremove.join(gSyncQuota.bundle.getString("quota.list.separator"));
+ caption.textContent = gSyncQuota.bundle.getFormattedString(
+ "quota.removal.label", [toremove]);
+ if (freeup)
+ caption.textContent += gSyncQuota.bundle.getFormattedString(
+ "quota.freeup.label", gSyncQuota.convertKB(freeup));
+ caption.className = "captionWarning";
+ },
+
+ /*
+ * Return a list of engines (or rather their pref names) that should be
+ * disabled.
+ */
+ getEnginesToDisable: function getEnginesToDisable() {
+ return this._collections.filter(coll => !coll.enabled).map(coll => coll.name);
+ },
+
+ // 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) {},
+};
diff --git a/comm/suite/components/sync/content/syncQuota.xul b/comm/suite/components/sync/content/syncQuota.xul
new file mode 100644
index 0000000000..f7b1fd7aa4
--- /dev/null
+++ b/comm/suite/components/sync/content/syncQuota.xul
@@ -0,0 +1,62 @@
+<?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://communicator/skin/sync/syncQuota.css" type="text/css"?>
+
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://communicator/locale/sync/syncBrand.dtd">
+<!ENTITY % syncQuotaDTD SYSTEM "chrome://communicator/locale/sync/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();"
+ onunload="gSyncQuota.uninit();"
+ buttons="accept,cancel"
+ title="&quota.dialogTitle.label;"
+ ondialogaccept="return gSyncQuota.onAccept();">
+
+ <script src="chrome://communicator/content/sync/syncQuota.js"/>
+
+ <stringbundleset id="stringbundleset">
+ <stringbundle id="quotaStrings"
+ src="chrome://communicator/locale/sync/syncQuota.properties"/>
+ </stringbundleset>
+
+ <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/>
+ </tree>
+ <separator/>
+ <description id="treeCaption"/>
+
+</dialog>
diff --git a/comm/suite/components/sync/content/syncSetup.js b/comm/suite/components/sync/content/syncSetup.js
new file mode 100644
index 0000000000..c648948568
--- /dev/null
+++ b/comm/suite/components/sync/content/syncSetup.js
@@ -0,0 +1,961 @@
+/* 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/. */
+
+// page consts
+
+const INTRO_PAGE = 0;
+const NEW_ACCOUNT_START_PAGE = 1;
+const NEW_ACCOUNT_PP_PAGE = 2;
+const NEW_ACCOUNT_CAPTCHA_PAGE = 3;
+const EXISTING_ACCOUNT_CONNECT_PAGE = 4;
+const EXISTING_ACCOUNT_LOGIN_PAGE = 5;
+const OPTIONS_PAGE = 6;
+const OPTIONS_CONFIRM_PAGE = 7;
+const SETUP_SUCCESS_PAGE = 8;
+
+// 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 {Weave} = ChromeUtils.import("resource://services-sync/main.js");
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {PlacesUtils} = ChromeUtils.import("resource://gre/modules/PlacesUtils.jsm");
+const {PluralForm} = ChromeUtils.import("resource://gre/modules/PluralForm.jsm");
+
+var gSyncSetup = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIWebProgressListener,
+ Ci.nsISupportsWeakReference]),
+
+ captchaBrowser: null,
+ wizard: null,
+ _disabledSites: [],
+
+ status: {
+ password: false,
+ email: false,
+ server: true
+ },
+
+ get _remoteSites() {
+ return [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:changepassphrase", "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", () => addRem(false));
+
+ setTimeout(function () {
+ // Force Service to be loaded so that engines are registered.
+ // See Bug 670082.
+ Weave.Service;
+ }, 0);
+
+ this.captchaBrowser = document.getElementById("captcha");
+ this.wizard = document.getElementById("accountSetup");
+
+ if (window.arguments && window.arguments[0] == true) {
+ // we're resetting sync
+ this._resettingSync = true;
+ this.wizard.pageIndex = OPTIONS_PAGE;
+ }
+ else {
+ this.wizard.canAdvance = false;
+ this.captchaBrowser.addProgressListener(this);
+ Weave.Svc.Prefs.set("firstSync", "notReady");
+ }
+
+ 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;
+ 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].includes(Weave.Status.login))
+ 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);
+ },
+
+ 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").value;
+ 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"), false))
+ return true;
+ }
+ return false;
+ }
+ // Default, e.g. wizard's special page -1 etc.
+ return true;
+ },
+
+ 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 valid, str;
+ if (password.value == document.getElementById("weavePassphrase").value) {
+ // xxxmpc - hack, sigh
+ valid = false;
+ str = Weave.Utils.getErrorString("change.password.pwSameAsRecoveryKey");
+ }
+ else {
+ let pwconfirm = document.getElementById("weavePasswordConfirm");
+ [valid, str] = gSyncUtils.validatePassword(password, pwconfirm);
+ }
+
+ let feedback = document.getElementById("passwordFeedbackRow");
+ this._setFeedback(feedback, valid, str);
+
+ this.status.password = valid;
+ this.checkFields();
+ },
+
+ onPassphraseGenerate: function () {
+ let passphrase = Weave.Utils.generatePassphrase();
+ Weave.Service.identity.syncKey = passphrase;
+ let el = document.getElementById("weavePassphrase");
+ el.value = Weave.Utils.hyphenatePassphrase(passphrase);
+ },
+
+ onPageShow: function() {
+ switch (this.wizard.pageIndex) {
+ case INTRO_PAGE:
+ this.wizard.getButton("next").hidden = true;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("extra1").hidden = true;
+ break;
+ case NEW_ACCOUNT_PP_PAGE:
+ document.getElementById("saveSyncKeyButton").focus();
+ let el = document.getElementById("weavePassphrase");
+ if (!el.value)
+ this.onPassphraseGenerate();
+ 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.wizard.canRewind = true;
+ this.checkFields();
+ break;
+ case EXISTING_ACCOUNT_CONNECT_PAGE:
+ 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.canRewind = true;
+ this.checkFields();
+ break;
+ case SETUP_SUCCESS_PAGE:
+ this.wizard.canRewind = false;
+ this.wizard.canAdvance = true;
+ this.wizard.getButton("back").hidden = true;
+ this.wizard.getButton("next").hidden = true;
+ this.wizard.getButton("cancel").hidden = true;
+ this.wizard.getButton("finish").hidden = false;
+ this._handleSuccess();
+ 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;
+
+ if (!this.wizard.pageIndex)
+ return true;
+
+ switch (this.wizard.pageIndex) {
+ 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();
+ return this.wizard.canAdvance;
+ case NEW_ACCOUNT_CAPTCHA_PAGE:
+ 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");
+ feedback.hidden = false;
+
+ 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;
+ this._handleNoScript(false);
+ this.wizard.pageIndex = SETUP_SUCCESS_PAGE;
+ return false;
+ }
+
+ image.setAttribute("status", "error");
+ label.value = Weave.Utils.getErrorString(error);
+ return false;
+ case NEW_ACCOUNT_PP_PAGE:
+ // Time to load the captcha.
+ // First check for NoScript and whitelist the right sites.
+ this._handleNoScript(true);
+ this.captchaBrowser.loadURI(Weave.Service.miscAPI + "captcha_html");
+ break;
+ 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.wizard.pageIndex = SETUP_SUCCESS_PAGE;
+ 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.onWizardFinish();
+ window.close();
+ 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 OPTIONS_CONFIRM_PAGE:
+ // Backing up from the confirmation page = resetting first sync to merge.
+ document.getElementById("mergeChoiceRadio").selectedIndex = 0;
+ return this.returnFromOptions();
+ }
+ return true;
+ },
+
+ onWizardFinish: function () {
+ this.setupInitialSync();
+
+ if (!this._resettingSync) {
+ function isChecked(element) {
+ return document.getElementById(element).hasAttribute("checked");
+ }
+
+ let prefs = ["engine.bookmarks", "engine.passwords", "engine.history",
+ "engine.tabs", "engine.prefs", "engine.addons"];
+ for (let i = 0; i < prefs.length; i++) {
+ Weave.Svc.Prefs.set(prefs[i], isChecked(prefs[i]));
+ }
+ this._handleNoScript(false);
+ if (Weave.Svc.Prefs.get("firstSync", "") == "notReady")
+ Weave.Svc.Prefs.reset("firstSync");
+
+ Weave.Service.persistLogin();
+ Weave.Svc.Obs.notify("weave:service:setup-complete");
+ if (this._settingUpNew)
+ gSyncUtils.openFirstClientFirstrun();
+ else
+ gSyncUtils.openAddedClientFirstrun();
+ }
+
+ if (!Weave.Service.isLoggedIn)
+ Weave.Service.login();
+
+ Weave.Utils.nextTick(Weave.Service.sync, Weave.Service);
+ },
+
+ onWizardCancel: function () {
+ if (this._resettingSync)
+ return;
+
+ if (this.wizard.pageIndex == SETUP_SUCCESS_PAGE) {
+ this.onWizardFinish();
+ 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;
+ },
+
+ 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() {},
+
+ onPaired: function onPaired() {},
+
+ 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;
+ self.wizard.pageIndex = SETUP_SUCCESS_PAGE;
+ },
+
+ 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.
+ if (!("@maone.net/noscript-service;1" in Cc))
+ return;
+
+ let ns = Cc["@maone.net/noscript-service;1"].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 = [];
+ }
+ },
+
+ _updateControl: function(controlId) {
+ let control = document.getElementById(controlId);
+ if (control.selectedIndex == 0) {
+ control.editable = false;
+ Weave.Svc.Prefs.reset("serverURL");
+ } else {
+ // Prevent double selection upon using down key.
+ control.activeChild = null;
+ control.editable = true;
+ control.value = "";
+ }
+ // Force a style flush to ensure that the binding is attached.
+ control.clientTop;
+ control.focus();
+ return control;
+ },
+
+ onExistingServerCommand: function () {
+ this._updateControl("existingServer");
+ 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 () {
+ document.getElementById("TOSRow").hidden = !this._usingMainServers;
+ let control = this._updateControl("server");
+ if (control.selectedIndex != 0) {
+ // checkServer() will call checkAccount() and checkFields().
+ this.checkServer();
+ return;
+ }
+ 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, true);
+ 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();
+ },
+
+ // xxxmpc - checkRemote is a hack, we can't verify a minimal server is live
+ // without auth, so we won't validate in the existing-server case.
+ _validateServer: function (element, checkRemote) {
+ 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 && checkRemote) {
+ 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);
+ }
+ 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;
+ },
+
+ _handleSuccess: function() {
+ let self = this;
+ function fill(id, string) {
+ document.getElementById(id).textContent =
+ string ? self._stringBundle.GetStringFromName(string) : "";
+ }
+
+ fill("firstSyncAction", "");
+ fill("firstSyncActionWarning", "");
+ if (this._settingUpNew) {
+ fill("firstSyncAction", "newAccount.action.label");
+ fill("firstSyncActionChange", "newAccount.change.label");
+ return;
+ }
+ fill("firstSyncActionChange", "existingAccount.change.label");
+ let action = document.getElementById("mergeChoiceRadio").value;
+ let id = action == "resetClient" ? "firstSyncAction" : "firstSyncActionWarning";
+ fill(id, action + ".change.label");
+ },
+
+ _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;
+ }
+
+ let addonsEngine = Weave.Service.engineManager.get("addons");
+ if (addonsEngine.enabled) {
+ let ids = addonsEngine._store.getAllIDs();
+ let blessedcount = Object.keys(ids).filter(id => ids[id]).length;
+ // 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;
+ }
+
+ if (!Weave.Service.engineManager.get("prefs").enabled) {
+ document.getElementById("prefsWipe").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 (let name of 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);
+ },
+
+ 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 the captcha, assume it's not needed and move on
+ if (request.QueryInterface(Ci.nsIHttpChannel).responseStatus == 404)
+ this.onWizardAdvance();
+ },
+ onProgressChange: function() {},
+ onStatusChange: function() {},
+ onSecurityChange: function() {},
+ onLocationChange: function () {}
+};
+
+// onWizardAdvance() and onPageShow() are run before init(), so we'll set
+// wizard & _stringBundle up as lazy getters.
+XPCOMUtils.defineLazyGetter(gSyncSetup, "wizard", function() {
+ return document.getElementById("accountSetup");
+});
+XPCOMUtils.defineLazyGetter(gSyncSetup, "_stringBundle", function() {
+ return Services.strings.createBundle("chrome://communicator/locale/sync/syncSetup.properties");
+});
diff --git a/comm/suite/components/sync/content/syncSetup.xul b/comm/suite/components/sync/content/syncSetup.xul
new file mode 100644
index 0000000000..2df682bb82
--- /dev/null
+++ b/comm/suite/components/sync/content/syncSetup.xul
@@ -0,0 +1,482 @@
+<?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://communicator/skin/sync/syncSetup.css" type="text/css"?>
+<?xml-stylesheet href="chrome://communicator/skin/sync/syncCommon.css" type="text/css"?>
+
+<!DOCTYPE window [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+<!ENTITY % syncBrandDTD SYSTEM "chrome://communicator/locale/sync/syncBrand.dtd">
+<!ENTITY % syncSetupDTD SYSTEM "chrome://communicator/locale/sync/syncSetup.dtd">
+%brandDTD;
+%syncBrandDTD;
+%syncSetupDTD;
+]>
+<wizard id="accountSetup" 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();"
+ onwizardfinish="gSyncSetup.onWizardFinish();"
+ onwizardcancel="gSyncSetup.onWizardCancel();"
+ onload="gSyncSetup.init();">
+
+ <script src="chrome://communicator/content/sync/syncSetup.js"/>
+ <script src="chrome://communicator/content/sync/syncUtils.js"/>
+ <script src="chrome://communicator/content/utilityOverlay.js"/>
+ <script src="chrome://global/content/printUtils.js"/>
+
+ <wizardpage id="pickSetupType"
+ label="&syncBrand.fullName.label;"
+ onpageshow="gSyncSetup.onPageShow();">
+ <vbox align="center" flex="1">
+ <description id="pickSetupDesc">
+ &setup.pickSetupType.description;
+ </description>
+ <spacer flex="1"/>
+ <button id="newAccount"
+ class="accountChoiceButton"
+ label="&button.createNewAccount.label;"
+ oncommand="gSyncSetup.startNewAccountSetup();"
+ align="center"/>
+ <spacer flex="3"/>
+ </vbox>
+ <separator class="groove"/>
+ <vbox align="center" flex="1">
+ <spacer flex="3"/>
+ <label value="&setup.haveAccount.label;"/>
+ <spacer flex="1"/>
+ <button id="existingAccount"
+ class="accountChoiceButton"
+ label="&button.connect.label;"
+ oncommand="gSyncSetup.useExistingAccount();"/>
+ <spacer flex="3"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="newAccountStart"
+ label="&setup.newAccountDetailsPage.title.label;"
+ 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="&signIn.password.label;"
+ accesskey="&signIn.password.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"
+ accesskey="&server.accesskey;"
+ value="&server.label;"/>
+ <menulist id="server"
+ oncommand="gSyncSetup.onServerCommand();"
+ oninput="gSyncSetup.onServerInput();">
+ <menupopup>
+ <menuitem label="&serverType.main.label;"/>
+ <menuitem label="&serverType.custom2.label;"/>
+ </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>
+ </wizardpage>
+
+ <wizardpage id="newSyncKey"
+ label="&setup.newRecoveryKeyPage.title.label;"
+ onextra1="gSyncSetup.onSyncOptions();"
+ onpageshow="gSyncSetup.onPageShow();">
+ <description>
+ &setup.newRecoveryKeyPage.description.label;
+ </description>
+ <spacer/>
+
+ <groupbox>
+ <label value="&recoveryKeyEntry.label;"
+ accesskey="&recoveryKeyEntry.accesskey;"
+ control="weavePassphrase"/>
+ <textbox id="weavePassphrase"
+ readonly="true"
+ onfocus="this.select();"/>
+ </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="captchaEntry"
+ label="&setup.captchaPage2.title.label;"
+ onextra1="gSyncSetup.onSyncOptions();">
+ <vbox flex="1" align="center">
+ <browser height="150"
+ width="450"
+ id="captcha"
+ type="content"
+ disablehistory="true"/>
+ <spacer flex="1"/>
+ <hbox id="captchaFeedback" hidden="true">
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ <spacer flex="3"/>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="addDevice"
+ label="&addDevice.title.label;"
+ onextra1="gSyncSetup.onSyncOptions();"
+ onpageshow="gSyncSetup.onPageShow();">
+ <description>
+ &addDevice.setup.description.label;
+ <label class="text-link"
+ value="&addDevice.showMeHow.label;"
+ href="https://services.mozilla.com/sync/help/easy-setup"/>
+ </description>
+ <description>&addDevice.setup.enterCode.label;</description>
+ <spacer flex="1"/>
+ <vbox align="center" flex="1">
+ <textbox id="easySetupPIN1"
+ class="pin"
+ value=""
+ size="4"
+ disabled="true"/>
+ <textbox id="easySetupPIN2"
+ class="pin"
+ value=""
+ size="4"
+ disabled="true"/>
+ <textbox id="easySetupPIN3"
+ class="pin"
+ value=""
+ size="4"
+ disabled="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="existingAccountName"/>
+ <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"
+ accesskey="&server.accesskey;"
+ value="&server.label;"/>
+ <menulist id="existingServer"
+ oncommand="gSyncSetup.onExistingServerCommand();"
+ oninput="gSyncSetup.onExistingServerInput();">
+ <menupopup>
+ <menuitem label="&serverType.main.label;"/>
+ <menuitem label="&serverType.custom2.label;"/>
+ </menupopup>
+ </menulist>
+ </row>
+ <row id="existingServerFeedbackRow" align="center" hidden="true">
+ <spacer/>
+ <hbox>
+ <image class="statusIcon"/>
+ <vbox>
+ <label class="status" value=" "/>
+ </vbox>
+ </hbox>
+ </row>
+ </rows>
+ </grid>
+
+ <groupbox>
+ <label id="existingPassphraseLabel"
+ value="&signIn.recoveryKey.label;"
+ accesskey="&signIn.recoveryKey.accesskey;"
+ control="existingPassphrase"/>
+ <textbox id="existingPassphrase"
+ oninput="gSyncSetup.checkFields();"/>
+ <hbox id="login-throbber" hidden="true">
+ <image/>
+ <label value="&verifying.label;"/>
+ </hbox>
+ <vbox align="left" id="existingPassphraseFeedbackRow" hidden="true">
+ <hbox>
+ <image class="statusIcon"/>
+ <label class="status" value=" "/>
+ </hbox>
+ </vbox>
+ </groupbox>
+ <vbox id="passphraseHelpBox">
+ <description>
+ &existingRecoveryKey.description;
+ <label class="text-link"
+ href="https://services.mozilla.com/sync/help/manual-setup">
+ &addDevice.showMeHow.label;
+ </label>
+ <spacer id="passphraseHelpSpacer"/>
+ <label class="text-link"
+ onclick="gSyncSetup.resetPassphrase(); return false;">
+ &resetSyncKey.label;
+ </label>
+ </description>
+ </vbox>
+ </wizardpage>
+
+ <wizardpage id="syncOptionsPage"
+ label="&setup.optionsPage.title;"
+ onpageshow="gSyncSetup.onPageShow();">
+ <groupbox id="syncOptions">
+ <grid>
+ <columns>
+ <column/>
+ <column class="inputColumn" flex="1"/>
+ </columns>
+ <rows>
+ <row align="center">
+ <label value="&syncComputerName.label;"
+ accesskey="&syncComputerName.accesskey;"
+ control="syncComputerName"/>
+ <textbox id="syncComputerName"
+ flex="1"
+ onchange="gSyncUtils.changeName(this);"/>
+ </row>
+ <row>
+ <label value="&syncMy.label;"/>
+ <vbox>
+ <checkbox label="&engine.addons.label;"
+ accesskey="&engine.addons.accesskey;"
+ id="engine.addons"
+ checked="true"/>
+ <checkbox label="&engine.bookmarks.label;"
+ accesskey="&engine.bookmarks.accesskey;"
+ id="engine.bookmarks"
+ checked="true"/>
+ <checkbox label="&engine.history.label;"
+ accesskey="&engine.history.accesskey;"
+ id="engine.history"
+ 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.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 value="resetClient"
+ label="&choice2.merge.main.label;"
+ class="mergeChoiceButton"/>
+ <label value="&choice2.merge.recommended.label;" class="recommended"/>
+ </row>
+ <row align="center">
+ <radio value="wipeClient"
+ label="&choice2.client.main.label;"
+ class="mergeChoiceButton"/>
+ </row>
+ <row align="center">
+ <radio value="wipeRemote"
+ label="&choice2.server.main.label;"
+ class="mergeChoiceButton"/>
+ </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.merge.label;
+ </description>
+ </vbox>
+ <vbox id="chosenActionWipeClient" class="confirm">
+ <description class="normal">
+ &confirm.client2.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.client.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>
+
+ <wizardpage id="successfulSetup"
+ label="&setup.successPage.title;"
+ onextra1="gSyncSetup.onSyncOptions();"
+ onpageshow="gSyncSetup.onPageShow();">
+ <vbox align="center">
+ <image id="successPageIcon"/>
+ </vbox>
+ <separator/>
+ <description class="normal">
+ <html:span id="firstSyncAction"/>
+ <html:strong id="firstSyncActionWarning"/>
+ <html:span id="firstSyncActionChange"/>
+ </description>
+ <description>
+ &continueUsing.label;
+ </description>
+ </wizardpage>
+</wizard>
diff --git a/comm/suite/components/sync/content/syncUI.js b/comm/suite/components/sync/content/syncUI.js
new file mode 100644
index 0000000000..668447c46d
--- /dev/null
+++ b/comm/suite/components/sync/content/syncUI.js
@@ -0,0 +1,454 @@
+/* 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 gSyncUI = {
+ _obs: ["weave:notification:added",
+ "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() {
+ // Update the Tools menu according to whether Sync is set up or not.
+ let taskPopup = document.getElementById("taskPopup");
+ if (taskPopup)
+ taskPopup.addEventListener("popupshowing", this.updateUI.bind(this));
+
+ // Proceed to set up the UI if Sync has already started up.
+ // Otherwise we'll do it when Sync is firing up.
+ if (Cc["@mozilla.org/weave/service;1"]
+ .getService().wrappedJSObject.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 SUI_unload() {
+ gSyncUI._unloaded = true;
+ window.removeEventListener("unload", SUI_unload);
+ Services.obs.removeObserver(gSyncUI, "weave:service:ready");
+
+ if (Weave.Status.ready) {
+ gSyncUI._obs.forEach(function(topic) {
+ Services.obs.removeObserver(gSyncUI, topic);
+ });
+ }
+ });
+ },
+
+ initUI: function SUI_initUI() {
+ this._obs.forEach(function(topic) {
+ Services.obs.addObserver(this, topic, true);
+ }, this);
+
+ // Find the alltabs-popup
+ let popup = document.getElementById("alltabs-popup");
+ if (popup) {
+ popup.addEventListener(
+ "popupshowing", this.alltabsPopupShowing.bind(this), true);
+
+ if (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";
+
+ let statusbar = document.getElementById("status-bar");
+ statusbar.parentNode.insertBefore(notificationbox, statusbar);
+
+ // 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;
+
+ let syncButton = document.getElementById("sync-button");
+ if (syncButton) {
+ syncButton.removeAttribute("status");
+ this._updateLastSyncTime();
+ if (needsSetup)
+ syncButton.removeAttribute("tooltiptext");
+ }
+ },
+
+ alltabsPopupShowing: function(event) {
+ // Should we show the menu item?
+ //XXXphilikon We should remove the check for isLoggedIn here and have
+ // about:sync-tabs auto-login (bug 583344)
+ if (!Weave.Service.isLoggedIn || !Weave.Service.engineManager.get("tabs").enabled)
+ return;
+
+ let label = this._stringBundle.GetStringFromName("tabs.fromOtherComputers.label");
+
+ let popup = document.getElementById("alltabs-popup");
+ if (!popup)
+ return;
+
+ let menuitem = document.createElement("menuitem");
+ menuitem.setAttribute("id", "sync-tabs-menuitem");
+ menuitem.setAttribute("label", label);
+ menuitem.setAttribute("class", "alltabs-item");
+ menuitem.setAttribute("oncommand", "BrowserOpenSyncTabs();");
+
+ let sep = document.createElement("menuseparator");
+ sep.setAttribute("id", "sync-tabs-sep");
+
+ // Fake the tab object on the menu entries, so that we don't have to worry
+ // about removing them ourselves. They will just get cleaned up by popup
+ // binding. This also makes sure the statusbar updates with the URL.
+ menuitem.tab = { "linkedBrowser": { "currentURI": { "spec": label } } };
+ sep.tab = { "linkedBrowser": { "currentURI": { "spec": " " } } };
+
+ popup.insertBefore(sep, popup.firstChild);
+ popup.insertBefore(menuitem, sep);
+ },
+
+ // Functions called by observers
+ onActivityStart: function SUI_onActivityStart() {
+ let syncButton = document.getElementById("sync-button");
+ if (syncButton)
+ syncButton.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);
+ },
+
+ 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");
+ openUILinkIn(statusURL, "tab");
+ },
+
+ // Commands
+ doSync: function SUI_doSync() {
+ setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
+ },
+
+ handleToolbarButton: function SUI_handleToolbarButton() {
+ 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)
+ openSetup: function SUI_openSetup() {
+ let win = Services.wm.getMostRecentWindow("Weave:AccountSetup");
+ if (win)
+ win.focus();
+ else {
+ window.openDialog("chrome://communicator/content/sync/syncSetup.xul",
+ "weaveSetup", "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://communicator/content/sync/syncQuota.xul", "",
+ "centerscreen,chrome,dialog,modal");
+ },
+
+ openPrefs: function SUI_openPrefs() {
+ goPreferences("sync_pane");
+ },
+
+
+ // Helpers
+ _updateLastSyncTime: function SUI__updateLastSyncTime() {
+ 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] of Object.entries(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("https://services.mozilla.com/update/", "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)
+ throw "SyncUI observer called after unload: " + topic;
+
+ 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.onLoginFinish();
+ 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 Services.strings.createBundle("chrome://weave/locale/services/sync.properties");
+});
diff --git a/comm/suite/components/sync/content/syncUtils.js b/comm/suite/components/sync/content/syncUtils.js
new file mode 100644
index 0000000000..fe63654073
--- /dev/null
+++ b/comm/suite/components/sync/content/syncUtils.js
@@ -0,0 +1,224 @@
+/* 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/. */
+
+// Weave should always exist before before this file gets included.
+var gSyncUtils = {
+ _openLink: function (url) {
+ 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://communicator/content/sync/syncGenericChange.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"));
+ },
+
+ // xxxmpc - fix domain before 1.3 final (bug 583652)
+ // xxxInvisibleSmiley - we should really have our own pages
+ // since these refer to Firefox in the page contents
+ _baseURL: "http://www.mozilla.com/firefox/sync/",
+
+ openFirstClientFirstrun: function () {
+ let url = this._baseURL + "firstrun.html";
+ this._openLink(url);
+ },
+
+ openAddedClientFirstrun: function () {
+ let url = this._baseURL + "secondrun.html";
+ this._openLink(url);
+ },
+
+ /**
+ * 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://communicator/content/sync/syncKey.xhtml");
+ iframe.setAttribute("type", "content");
+ iframe.collapsed = true;
+ document.documentElement.appendChild(iframe);
+ iframe.addEventListener("load", function loadListener() {
+ iframe.removeEventListener("load", loadListener, true);
+
+ // Remove the license block.
+ let node = iframe.contentDocument.firstChild;
+ if (node && node.nodeType == Node.COMMENT_NODE)
+ node.remove();
+
+ // 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);
+ }, true);
+ },
+
+ /**
+ * 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._stringBundle.GetStringFromName("save.recoverykey.title");
+ let defaultSaveName = this._stringBundle.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(filepicker.file, -1, parseInt("0600", 8), 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 && val1 == Weave.Service.identity.syncKey)
+ error = "change.password.pwSameAsRecoveryKey";
+ 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];
+ }
+};
+
+var {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
+XPCOMUtils.defineLazyGetter(gSyncUtils, "_stringBundle", function() {
+ return Services.strings.createBundle("chrome://communicator/locale/sync/syncSetup.properties");
+});