Bug 1353571 part 5 - Merge browser-fxaccounts and browser-syncui in browser-sync. draft
authorEdouard Oger <eoger@fastmail.com>
Wed, 19 Apr 2017 12:18:02 -0400
changeset 565276 cf86567f0d0fe1151ef83d6bfc0c561b87b610bb
parent 565275 1a451ad6753e6e3e0909ddf8b32e6f33d1fa6b18
child 624955 bfb1b9e4c58bc76d3f08edc2e6ecfb39a6ecc872
push id54828
push userbmo:eoger@fastmail.com
push dateWed, 19 Apr 2017 17:55:31 +0000
bugs1353571
milestone55.0a1
Bug 1353571 part 5 - Merge browser-fxaccounts and browser-syncui in browser-sync. MozReview-Commit-ID: dselqIepkh
browser/base/content/browser-context.inc
browser/base/content/browser-fxaccounts.js
browser/base/content/browser-menubar.inc
browser/base/content/browser-sync.js
browser/base/content/browser-syncui.js
browser/base/content/browser.js
browser/base/content/browser.xul
browser/base/content/global-scripts.inc
browser/base/content/nsContextMenu.js
browser/base/content/test/general/browser_contextmenu.js
browser/base/content/test/general/browser_fxaccounts.js
browser/base/content/test/general/browser_visibleTabs_contextMenu.js
browser/base/content/test/general/head.js
browser/base/content/web-panels.xul
browser/base/content/webext-panels.xul
browser/base/jar.mn
browser/components/customizableui/CustomizableWidgets.jsm
browser/components/customizableui/content/panelUI.inc.xul
browser/components/syncedtabs/SyncedTabsDeckComponent.js
browser/components/syncedtabs/TabListView.js
browser/components/uitour/test/browser_fxa.js
services/sync/modules/UIState.jsm
--- a/browser/base/content/browser-context.inc
+++ b/browser/base/content/browser-context.inc
@@ -280,17 +280,17 @@
                 accesskey="&savePageCmd.accesskey2;"
                 oncommand="gContextMenu.savePageAs();"/>
       <menuseparator id="context-sep-sendpagetodevice" hidden="true"/>
       <menu id="context-sendpagetodevice"
                 label="&sendPageToDevice.label;"
                 accesskey="&sendPageToDevice.accesskey;"
                 hidden="true">
         <menupopup id="context-sendpagetodevice-popup"
-                   onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gFxAccounts.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
+                   onpopupshowing="(() => { let browser = gBrowser || getPanelBrowser(); gSync.populateSendTabToDevicesMenu(event.target, browser.currentURI.spec, browser.contentTitle); })()"/>
       </menu>
       <menuseparator id="context-sep-viewbgimage"/>
       <menuitem id="context-viewbgimage"
                 label="&viewBGImageCmd.label;"
                 accesskey="&viewBGImageCmd.accesskey;"
                 oncommand="gContextMenu.viewBGImage(event);"
                 onclick="checkForMiddleClick(this, event);"/>
       <menuitem id="context-undo"
@@ -327,17 +327,17 @@
       <menuitem id="context-searchselect"
                 oncommand="BrowserSearch.loadSearchFromContext(this.searchTerms);"/>
       <menuseparator id="context-sep-sendlinktodevice" hidden="true"/>
       <menu id="context-sendlinktodevice"
                 label="&sendLinkToDevice.label;"
                 accesskey="&sendLinkToDevice.accesskey;"
                 hidden="true">
         <menupopup id="context-sendlinktodevice-popup"
-                   onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
+                   onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, gContextMenu.linkURL, gContextMenu.linkTextStr);"/>
       </menu>
       <menuitem id="context-shareselect"
                 label="&shareSelect.label;"
                 accesskey="&shareSelect.accesskey;"
                 oncommand="gContextMenu.shareSelect();"/>
       <menuseparator id="frame-sep"/>
       <menu id="frame" label="&thisFrameMenu.label;" accesskey="&thisFrameMenu.accesskey;">
         <menupopup>
deleted file mode 100644
--- a/browser/base/content/browser-fxaccounts.js
+++ /dev/null
@@ -1,340 +0,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/. */
-
-Cu.import("resource://services-sync/UIState.jsm");
-
-var gFxAccounts = {
-  _initialized: false,
-
-  get panelUIFooter() {
-    delete this.panelUIFooter;
-    return this.panelUIFooter = document.getElementById("PanelUI-footer-fxa");
-  },
-
-  get panelUIStatus() {
-    delete this.panelUIStatus;
-    return this.panelUIStatus = document.getElementById("PanelUI-fxa-status");
-  },
-
-  get panelUIAvatar() {
-    delete this.panelUIAvatar;
-    return this.panelUIAvatar = document.getElementById("PanelUI-fxa-avatar");
-  },
-
-  get panelUILabel() {
-    delete this.panelUILabel;
-    return this.panelUILabel = document.getElementById("PanelUI-fxa-label");
-  },
-
-  get panelUIIcon() {
-    delete this.panelUIIcon;
-    return this.panelUIIcon = document.getElementById("PanelUI-fxa-icon");
-  },
-
-  get strings() {
-    delete this.strings;
-    return this.strings = Services.strings.createBundle(
-      "chrome://browser/locale/accounts.properties"
-    );
-  },
-
-  get sendTabToDeviceEnabled() {
-    return Services.prefs.getBoolPref("services.sync.sendTabToDevice.enabled");
-  },
-
-  isSendableURI(aURISpec) {
-    if (!aURISpec) {
-      return false;
-    }
-    // Disallow sending tabs with more than 65535 characters.
-    if (aURISpec.length > 65535) {
-      return false;
-    }
-    try {
-      // Filter out un-sendable URIs -- things like local files, object urls, etc.
-      const unsendableRegexp = new RegExp(
-        Services.prefs.getCharPref("services.sync.engine.tabs.filteredUrls"), "i");
-      return !unsendableRegexp.test(aURISpec);
-    } catch (e) {
-      // The preference has been removed, or is an invalid regexp, so we log an
-      // error and treat it as a valid URI -- and the more problematic case is
-      // the length, which we've already addressed.
-      Cu.reportError(`Failed to build url filter regexp for send tab: ${e}`);
-      return true;
-    }
-  },
-
-  get remoteClients() {
-    return Weave.Service.clientsEngine.remoteClients
-           .sort((a, b) => a.name.localeCompare(b.name));
-  },
-
-  init() {
-    // Bail out if we're already initialized and for pop-up windows.
-    if (this._initialized || !window.toolbar.visible) {
-      return;
-    }
-
-    Services.obs.addObserver(this, UIState.ON_UPDATE);
-    setTimeout(() => {
-      if (UIState.ensureReady()) {
-        const state = UIState.get();
-        // The UI is already in the right state, optimize this.
-        if (state.status != UIState.STATUS_NOT_CONFIGURED) {
-          this.updateAllUI(state);
-        }
-      }
-    }, 1000);
-
-    EnsureFxAccountsWebChannel();
-    this._initialized = true;
-  },
-
-  uninit() {
-    if (!this._initialized) {
-      return;
-    }
-
-    Services.obs.removeObserver(this, UIState.ON_UPDATE);
-
-    this._initialized = false;
-  },
-
-  observe(subject, topic, data) {
-    if (topic != UIState.ON_UPDATE) {
-      return;
-    }
-    const state = UIState.get();
-    this.updateAllUI(state);
-  },
-
-  updateAllUI(state) {
-    this.updatePanelBadge(state);
-    this.updatePanelPopup(state);
-    gSyncUI.updateStateBroadcasters(state);
-    gSyncUI.updateSyncButtonsTooltip(state);
-    gSyncUI.updateSyncStatus(state);
-  },
-
-  updatePanelPopup(state) {
-    this.panelUIFooter.hidden = false;
-
-    let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
-    let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
-    let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
-    // The localization string is for the signed in text, but it's the default text as well
-    let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
-
-    const status = state.status;
-    // Reset the button to its original state.
-    this.panelUILabel.setAttribute("label", defaultLabel);
-    this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
-    this.panelUIFooter.removeAttribute("fxastatus");
-    this.panelUIFooter.removeAttribute("fxaprofileimage");
-    this.panelUIAvatar.style.removeProperty("list-style-image");
-    if (status == UIState.STATUS_NOT_CONFIGURED) {
-      // If we're not configured, we won't have a profile, bail early.
-      return;
-    }
-
-    // At this point we consider the user as logged-in (but still can be in an error state)
-    if (status == UIState.STATUS_LOGIN_FAILED) {
-      let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [state.email], 1);
-      this.panelUIFooter.setAttribute("fxastatus", "error");
-      this.panelUILabel.setAttribute("label", errorLabel);
-      this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
-    } else if (status == UIState.STATUS_NOT_VERIFIED) {
-      let tooltipDescription = this.strings.formatStringFromName("verifyDescription", [state.email], 1);
-      this.panelUIFooter.setAttribute("fxastatus", "error");
-      this.panelUIFooter.setAttribute("unverified", "true");
-      this.panelUILabel.setAttribute("label", unverifiedLabel);
-      this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
-    } else {
-      this.panelUIFooter.setAttribute("fxastatus", "signedin");
-      this.panelUILabel.setAttribute("label", state.email);
-    }
-    this.panelUIFooter.setAttribute("fxaprofileimage", "enabled");
-
-    if (state.displayName) {
-      this.panelUILabel.setAttribute("label", state.displayName);
-    }
-    if (state.avatarURL) {
-      this.panelUIFooter.setAttribute("fxaprofileimage", "set");
-      let bgImage = "url(\"" + state.avatarURL + "\")";
-      this.panelUIAvatar.style.listStyleImage = bgImage;
-
-      let img = new Image();
-      img.onerror = () => {
-        // Clear the image if it has trouble loading. Since this callback is asynchronous
-        // we check to make sure the image is still the same before we clear it.
-        if (this.panelUIAvatar.style.listStyleImage === bgImage) {
-          this.panelUIFooter.removeAttribute("fxaprofileimage");
-          this.panelUIAvatar.style.removeProperty("list-style-image");
-        }
-      };
-      img.src = state.avatarURL;
-    }
-  },
-
-  updatePanelBadge(state) {
-    if (state.status == UIState.STATUS_LOGIN_FAILED ||
-        state.status == UIState.STATUS_NOT_VERIFIED) {
-      PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
-    } else {
-      PanelUI.removeNotification("fxa-needs-authentication");
-    }
-  },
-
-  onMenuPanelCommand() {
-    switch (this.panelUIFooter.getAttribute("fxastatus")) {
-    case "signedin":
-      this.openPreferences();
-      break;
-    case "error":
-      if (this.panelUIFooter.getAttribute("unverified")) {
-        this.openPreferences();
-      } else {
-        this.openSignInAgainPage("menupanel");
-      }
-      break;
-    default:
-      this.openPreferences();
-      break;
-    }
-
-    PanelUI.hide();
-  },
-
-  openPreferences() {
-    openPreferences("paneSync", { urlParams: { entrypoint: "menupanel" } });
-  },
-
-  openAccountsPage(action, urlParams = {}) {
-    let params = new URLSearchParams();
-    if (action) {
-      params.set("action", action);
-    }
-    for (let name in urlParams) {
-      if (urlParams[name] !== undefined) {
-        params.set(name, urlParams[name]);
-      }
-    }
-    let url = "about:accounts?" + params;
-    switchToTabHavingURI(url, true, {
-      replaceQueryString: true
-    });
-  },
-
-  openSignInAgainPage(entryPoint) {
-    this.openAccountsPage("reauth", { entrypoint: entryPoint });
-  },
-
-  async openDevicesManagementPage(entryPoint) {
-    let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
-    switchToTabHavingURI(url, true, {
-      replaceQueryString: true
-    });
-  },
-
-  sendTabToDevice(url, clientId, title) {
-    Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
-  },
-
-  populateSendTabToDevicesMenu(devicesPopup, url, title) {
-    // remove existing menu items
-    while (devicesPopup.hasChildNodes()) {
-      devicesPopup.firstChild.remove();
-    }
-
-    const fragment = document.createDocumentFragment();
-
-    const onTargetDeviceCommand = (event) => {
-      let clients = event.target.getAttribute("clientId") ?
-        [event.target.getAttribute("clientId")] :
-        this.remoteClients.map(client => client.id);
-
-      clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
-    }
-
-    function addTargetDevice(clientId, name) {
-      const targetDevice = document.createElement("menuitem");
-      targetDevice.addEventListener("command", onTargetDeviceCommand, true);
-      targetDevice.setAttribute("class", "sendtab-target");
-      targetDevice.setAttribute("clientId", clientId);
-      targetDevice.setAttribute("label", name);
-      fragment.appendChild(targetDevice);
-    }
-
-    const clients = this.remoteClients;
-    for (let client of clients) {
-      addTargetDevice(client.id, client.name);
-    }
-
-    // "All devices" menu item
-    if (clients.length > 1) {
-      const separator = document.createElement("menuseparator");
-      fragment.appendChild(separator);
-      const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
-      addTargetDevice("", allDevicesLabel);
-    }
-
-    devicesPopup.appendChild(fragment);
-  },
-
-  updateTabContextMenu(aPopupMenu, aTargetTab) {
-    if (!this.sendTabToDeviceEnabled ||
-        !this.weaveService.ready) {
-      return;
-    }
-
-    const targetURI = aTargetTab.linkedBrowser.currentURI.spec;
-    const showSendTab = this.remoteClients.length > 0 && this.isSendableURI(targetURI);
-
-    ["context_sendTabToDevice", "context_sendTabToDevice_separator"]
-    .forEach(id => { document.getElementById(id).hidden = !showSendTab });
-  },
-
-  initPageContextMenu(contextMenu) {
-    if (!this.sendTabToDeviceEnabled ||
-        !this.weaveService.ready) {
-      return;
-    }
-
-    const remoteClientPresent = this.remoteClients.length > 0;
-    // showSendLink and showSendPage are mutually exclusive
-    let showSendLink = remoteClientPresent
-                       && (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
-    const showSendPage = !showSendLink && remoteClientPresent
-                         && !(contextMenu.isContentSelected ||
-                              contextMenu.onImage || contextMenu.onCanvas ||
-                              contextMenu.onVideo || contextMenu.onAudio ||
-                              contextMenu.onLink || contextMenu.onTextInput)
-                         && this.isSendableURI(contextMenu.browser.currentURI.spec);
-
-    if (showSendLink) {
-      // This isn't part of the condition above since we don't want to try and
-      // send the page if a link is clicked on or selected but is not sendable.
-      showSendLink = this.isSendableURI(contextMenu.linkURL);
-    }
-
-    ["context-sendpagetodevice", "context-sep-sendpagetodevice"]
-    .forEach(id => contextMenu.showItem(id, showSendPage));
-    ["context-sendlinktodevice", "context-sep-sendlinktodevice"]
-    .forEach(id => contextMenu.showItem(id, showSendLink));
-  }
-};
-
-XPCOMUtils.defineLazyGetter(gFxAccounts, "FxAccountsCommon", function() {
-  return Cu.import("resource://gre/modules/FxAccountsCommon.js", {});
-});
-
-XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
-  "resource://gre/modules/FxAccountsWebChannel.jsm");
-
-
-XPCOMUtils.defineLazyGetter(gFxAccounts, "weaveService", function() {
-  return Components.classes["@mozilla.org/weave/service;1"]
-                   .getService(Components.interfaces.nsISupports)
-                   .wrappedJSObject;
-});
--- a/browser/base/content/browser-menubar.inc
+++ b/browser/base/content/browser-menubar.inc
@@ -487,27 +487,27 @@
                         key="key_openAddons"
                         command="Tools:Addons"/>
 
               <!-- only one of sync-setup, sync-syncnowitem or sync-reauthitem will be showing at once -->
               <menuitem id="sync-setup"
                         label="&syncSignIn.label;"
                         accesskey="&syncSignIn.accesskey;"
                         observes="sync-setup-state"
-                        oncommand="gSyncUI.openPrefs('menubar')"/>
+                        oncommand="gSync.openPrefs('menubar')"/>
               <menuitem id="sync-syncnowitem"
                         label="&syncSyncNowItem.label;"
                         accesskey="&syncSyncNowItem.accesskey;"
                         observes="sync-syncnow-state"
-                        oncommand="gSyncUI.doSync(event);"/>
+                        oncommand="gSync.doSync(event);"/>
               <menuitem id="sync-reauthitem"
                         label="&syncReAuthItem.label;"
                         accesskey="&syncReAuthItem.accesskey;"
                         observes="sync-reauth-state"
-                        oncommand="gSyncUI.openSignInAgainPage('menubar');"/>
+                        oncommand="gSync.openSignInAgainPage('menubar');"/>
               <menuseparator id="devToolsSeparator"/>
               <menu id="webDeveloperMenu"
                     label="&webDeveloperMenu.label;"
                     accesskey="&webDeveloperMenu.accesskey;">
                 <menupopup id="menuWebDeveloperPopup">
                   <menuitem id="menu_pageSource"
                             observes="devtoolsMenuBroadcaster_PageSource"
                             accesskey="&pageSourceCmd.accesskey;"/>
new file mode 100644
--- /dev/null
+++ b/browser/base/content/browser-sync.js
@@ -0,0 +1,644 @@
+/* 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/. */
+
+Cu.import("resource://services-sync/UIState.jsm");
+
+const MIN_STATUS_ANIMATION_DURATION = 1600;
+
+var gSync = {
+  _initialized: false,
+  // The last sync start time. Used to calculate the leftover animation time
+  // once syncing completes (bug 1239042).
+  _syncStartTime: 0,
+  _syncAnimationTimer: 0,
+  _withinLastWeekFormat: null,
+  _oneWeekOrOlderFormat: null,
+
+  _obs: [
+    "weave:engine:sync:finish",
+    "quit-application",
+    UIState.ON_UPDATE
+  ],
+
+  get panelUIFooter() {
+    delete this.panelUIFooter;
+    return this.panelUIFooter = document.getElementById("PanelUI-footer-fxa");
+  },
+
+  get panelUIStatus() {
+    delete this.panelUIStatus;
+    return this.panelUIStatus = document.getElementById("PanelUI-fxa-status");
+  },
+
+  get panelUIAvatar() {
+    delete this.panelUIAvatar;
+    return this.panelUIAvatar = document.getElementById("PanelUI-fxa-avatar");
+  },
+
+  get panelUILabel() {
+    delete this.panelUILabel;
+    return this.panelUILabel = document.getElementById("PanelUI-fxa-label");
+  },
+
+  get panelUIIcon() {
+    delete this.panelUIIcon;
+    return this.panelUIIcon = document.getElementById("PanelUI-fxa-icon");
+  },
+
+  get strings() {
+    delete this.strings;
+    return this.strings = Services.strings.createBundle(
+      "chrome://browser/locale/accounts.properties"
+    );
+  },
+
+  get syncStrings() {
+    delete this.syncStrings;
+    // XXXzpao these strings should probably be moved from /services to /browser... (bug 583381)
+    //        but for now just make it work
+    return this.syncStrings = Services.strings.createBundle(
+      "chrome://weave/locale/sync.properties"
+    );
+  },
+
+  get sendTabToDeviceEnabled() {
+    return Services.prefs.getBoolPref("services.sync.sendTabToDevice.enabled");
+  },
+
+  get remoteClients() {
+    return Weave.Service.clientsEngine.remoteClients
+           .sort((a, b) => a.name.localeCompare(b.name));
+  },
+
+  init() {
+    // Bail out if we're already initialized and for pop-up windows.
+    if (this._initialized || !window.toolbar.visible) {
+      return;
+    }
+
+    for (let topic of this._obs) {
+      Services.obs.addObserver(this, topic, true);
+    }
+
+    // initial label for the sync buttons.
+    let broadcaster = document.getElementById("sync-status");
+    broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
+
+    this.maybeMoveSyncedTabsButton();
+
+    EnsureFxAccountsWebChannel();
+
+    setTimeout(() => {
+      if (UIState.ensureReady()) {
+        const state = UIState.get();
+        // The UI is already in the right state, optimize this.
+        if (state.status != UIState.STATUS_NOT_CONFIGURED) {
+          this.updateAllUI(state);
+        }
+      }
+    }, 1000);
+
+    this._initialized = true;
+  },
+
+  uninit() {
+    if (!this._initialized) {
+      return;
+    }
+
+    for (let topic of this._obs) {
+      Services.obs.removeObserver(this, topic);
+    }
+
+    this._initialized = false;
+  },
+
+  observe(subject, topic, data) {
+    if (!this._initialized) {
+      Cu.reportError("browser-sync observer called after unload: " + topic);
+      return;
+    }
+    switch (topic) {
+      case UIState.ON_UPDATE:
+        const state = UIState.get();
+        this.updateAllUI(state);
+        break;
+      case "quit-application":
+        // Stop the animation timer on shutdown, since we can't update the UI
+        // after this.
+        clearTimeout(this._syncAnimationTimer);
+        break;
+      case "weave:engine:sync:finish":
+        if (data != "clients") {
+          return;
+        }
+        this.onClientsSynced();
+        break;
+    }
+  },
+
+  updateAllUI(state) {
+    this.updatePanelBadge(state);
+    this.updatePanelPopup(state);
+    this.updateStateBroadcasters(state);
+    this.updateSyncButtonsTooltip(state);
+    this.updateSyncStatus(state);
+  },
+
+  updatePanelPopup(state) {
+    this.panelUIFooter.hidden = false;
+
+    let defaultLabel = this.panelUIStatus.getAttribute("defaultlabel");
+    let errorLabel = this.panelUIStatus.getAttribute("errorlabel");
+    let unverifiedLabel = this.panelUIStatus.getAttribute("unverifiedlabel");
+    // The localization string is for the signed in text, but it's the default text as well
+    let defaultTooltiptext = this.panelUIStatus.getAttribute("signedinTooltiptext");
+
+    const status = state.status;
+    // Reset the button to its original state.
+    this.panelUILabel.setAttribute("label", defaultLabel);
+    this.panelUIStatus.setAttribute("tooltiptext", defaultTooltiptext);
+    this.panelUIFooter.removeAttribute("fxastatus");
+    this.panelUIFooter.removeAttribute("fxaprofileimage");
+    this.panelUIAvatar.style.removeProperty("list-style-image");
+    if (status == UIState.STATUS_NOT_CONFIGURED) {
+      // If we're not configured, we won't have a profile, bail early.
+      return;
+    }
+
+    // At this point we consider the user as logged-in (but still can be in an error state)
+    if (status == UIState.STATUS_LOGIN_FAILED) {
+      let tooltipDescription = this.strings.formatStringFromName("reconnectDescription", [state.email], 1);
+      this.panelUIFooter.setAttribute("fxastatus", "error");
+      this.panelUILabel.setAttribute("label", errorLabel);
+      this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
+    } else if (status == UIState.STATUS_NOT_VERIFIED) {
+      let tooltipDescription = this.strings.formatStringFromName("verifyDescription", [state.email], 1);
+      this.panelUIFooter.setAttribute("fxastatus", "error");
+      this.panelUIFooter.setAttribute("unverified", "true");
+      this.panelUILabel.setAttribute("label", unverifiedLabel);
+      this.panelUIStatus.setAttribute("tooltiptext", tooltipDescription);
+    } else {
+      this.panelUIFooter.setAttribute("fxastatus", "signedin");
+      this.panelUILabel.setAttribute("label", state.email);
+    }
+    this.panelUIFooter.setAttribute("fxaprofileimage", "enabled");
+
+    if (state.displayName) {
+      this.panelUILabel.setAttribute("label", state.displayName);
+    }
+    if (state.avatarURL) {
+      this.panelUIFooter.setAttribute("fxaprofileimage", "set");
+      let bgImage = "url(\"" + state.avatarURL + "\")";
+      this.panelUIAvatar.style.listStyleImage = bgImage;
+
+      let img = new Image();
+      img.onerror = () => {
+        // Clear the image if it has trouble loading. Since this callback is asynchronous
+        // we check to make sure the image is still the same before we clear it.
+        if (this.panelUIAvatar.style.listStyleImage === bgImage) {
+          this.panelUIFooter.removeAttribute("fxaprofileimage");
+          this.panelUIAvatar.style.removeProperty("list-style-image");
+        }
+      };
+      img.src = state.avatarURL;
+    }
+  },
+
+  updatePanelBadge(state) {
+    if (state.status == UIState.STATUS_LOGIN_FAILED ||
+        state.status == UIState.STATUS_NOT_VERIFIED) {
+      PanelUI.showBadgeOnlyNotification("fxa-needs-authentication");
+    } else {
+      PanelUI.removeNotification("fxa-needs-authentication");
+    }
+  },
+
+  updateStateBroadcasters(state) {
+    const status = state.status;
+
+    // Start off with a clean slate
+    document.getElementById("sync-reauth-state").hidden = true;
+    document.getElementById("sync-setup-state").hidden = true;
+    document.getElementById("sync-syncnow-state").hidden = true;
+
+    if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
+      document.getElementById("sync-syncnow-state").hidden = false;
+    } else if (status == UIState.STATUS_LOGIN_FAILED) {
+      // unhiding this element makes the menubar show the login failure state.
+      document.getElementById("sync-reauth-state").hidden = false;
+    } else if (status == UIState.STATUS_NOT_CONFIGURED ||
+               status == UIState.STATUS_NOT_VERIFIED) {
+      document.getElementById("sync-setup-state").hidden = false;
+    } else {
+      document.getElementById("sync-syncnow-state").hidden = false;
+    }
+  },
+
+  updateSyncStatus(state) {
+    const broadcaster = document.getElementById("sync-status");
+    const syncingUI = broadcaster.getAttribute("syncstatus") == "active";
+    if (state.syncing != syncingUI) { // Do we need to update the UI?
+      state.syncing ? this.onActivityStart() : this.onActivityStop();
+    }
+  },
+
+  onMenuPanelCommand() {
+    switch (this.panelUIFooter.getAttribute("fxastatus")) {
+    case "signedin":
+      this.openPreferences();
+      break;
+    case "error":
+      if (this.panelUIFooter.getAttribute("unverified")) {
+        this.openPreferences();
+      } else {
+        this.openSignInAgainPage("menupanel");
+      }
+      break;
+    default:
+      this.openPreferences();
+      break;
+    }
+
+    PanelUI.hide();
+  },
+
+  openPreferences() {
+    openPreferences("paneSync", { urlParams: { entrypoint: "menupanel" } });
+  },
+
+  openAccountsPage(action, urlParams = {}) {
+    let params = new URLSearchParams();
+    if (action) {
+      params.set("action", action);
+    }
+    for (let name in urlParams) {
+      if (urlParams[name] !== undefined) {
+        params.set(name, urlParams[name]);
+      }
+    }
+    let url = "about:accounts?" + params;
+    switchToTabHavingURI(url, true, {
+      replaceQueryString: true
+    });
+  },
+
+  openSignInAgainPage(entryPoint) {
+    this.openAccountsPage("reauth", { entrypoint: entryPoint });
+  },
+
+  async openDevicesManagementPage(entryPoint) {
+    let url = await fxAccounts.promiseAccountsManageDevicesURI(entryPoint);
+    switchToTabHavingURI(url, true, {
+      replaceQueryString: true
+    });
+  },
+
+  sendTabToDevice(url, clientId, title) {
+    Weave.Service.clientsEngine.sendURIToClientForDisplay(url, clientId, title);
+  },
+
+  populateSendTabToDevicesMenu(devicesPopup, url, title) {
+    // remove existing menu items
+    while (devicesPopup.hasChildNodes()) {
+      devicesPopup.firstChild.remove();
+    }
+
+    const fragment = document.createDocumentFragment();
+
+    const onTargetDeviceCommand = (event) => {
+      let clients = event.target.getAttribute("clientId") ?
+        [event.target.getAttribute("clientId")] :
+        this.remoteClients.map(client => client.id);
+
+      clients.forEach(clientId => this.sendTabToDevice(url, clientId, title));
+    }
+
+    function addTargetDevice(clientId, name) {
+      const targetDevice = document.createElement("menuitem");
+      targetDevice.addEventListener("command", onTargetDeviceCommand, true);
+      targetDevice.setAttribute("class", "sendtab-target");
+      targetDevice.setAttribute("clientId", clientId);
+      targetDevice.setAttribute("label", name);
+      fragment.appendChild(targetDevice);
+    }
+
+    const clients = this.remoteClients;
+    for (let client of clients) {
+      addTargetDevice(client.id, client.name);
+    }
+
+    // "All devices" menu item
+    if (clients.length > 1) {
+      const separator = document.createElement("menuseparator");
+      fragment.appendChild(separator);
+      const allDevicesLabel = this.strings.GetStringFromName("sendTabToAllDevices.menuitem");
+      addTargetDevice("", allDevicesLabel);
+    }
+
+    devicesPopup.appendChild(fragment);
+  },
+
+  isSendableURI(aURISpec) {
+    if (!aURISpec) {
+      return false;
+    }
+    // Disallow sending tabs with more than 65535 characters.
+    if (aURISpec.length > 65535) {
+      return false;
+    }
+    try {
+      // Filter out un-sendable URIs -- things like local files, object urls, etc.
+      const unsendableRegexp = new RegExp(
+        Services.prefs.getCharPref("services.sync.engine.tabs.filteredUrls"), "i");
+      return !unsendableRegexp.test(aURISpec);
+    } catch (e) {
+      // The preference has been removed, or is an invalid regexp, so we log an
+      // error and treat it as a valid URI -- and the more problematic case is
+      // the length, which we've already addressed.
+      Cu.reportError(`Failed to build url filter regexp for send tab: ${e}`);
+      return true;
+    }
+  },
+
+  updateTabContextMenu(aPopupMenu, aTargetTab) {
+    if (!this.sendTabToDeviceEnabled ||
+        !this.weaveService.ready) {
+      return;
+    }
+
+    const targetURI = aTargetTab.linkedBrowser.currentURI.spec;
+    const showSendTab = this.remoteClients.length > 0 && this.isSendableURI(targetURI);
+
+    ["context_sendTabToDevice", "context_sendTabToDevice_separator"]
+    .forEach(id => { document.getElementById(id).hidden = !showSendTab });
+  },
+
+  initPageContextMenu(contextMenu) {
+    if (!this.sendTabToDeviceEnabled ||
+        !this.weaveService.ready) {
+      return;
+    }
+
+    const remoteClientPresent = this.remoteClients.length > 0;
+    // showSendLink and showSendPage are mutually exclusive
+    let showSendLink = remoteClientPresent
+                       && (contextMenu.onSaveableLink || contextMenu.onPlainTextLink);
+    const showSendPage = !showSendLink && remoteClientPresent
+                         && !(contextMenu.isContentSelected ||
+                              contextMenu.onImage || contextMenu.onCanvas ||
+                              contextMenu.onVideo || contextMenu.onAudio ||
+                              contextMenu.onLink || contextMenu.onTextInput)
+                         && this.isSendableURI(contextMenu.browser.currentURI.spec);
+
+    if (showSendLink) {
+      // This isn't part of the condition above since we don't want to try and
+      // send the page if a link is clicked on or selected but is not sendable.
+      showSendLink = this.isSendableURI(contextMenu.linkURL);
+    }
+
+    ["context-sendpagetodevice", "context-sep-sendpagetodevice"]
+    .forEach(id => contextMenu.showItem(id, showSendPage));
+    ["context-sendlinktodevice", "context-sep-sendlinktodevice"]
+    .forEach(id => contextMenu.showItem(id, showSendLink));
+  },
+
+  // Functions called by observers
+  onActivityStart() {
+    this.log.debug("onActivityStart");
+
+    clearTimeout(this._syncAnimationTimer);
+    this._syncStartTime = Date.now();
+
+    let broadcaster = document.getElementById("sync-status");
+    broadcaster.setAttribute("syncstatus", "active");
+    broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncing2.label"));
+    broadcaster.setAttribute("disabled", "true");
+  },
+
+  _updateSyncStatus() {
+    if (!gBrowser)
+      return;
+    let broadcaster = document.getElementById("sync-status");
+    broadcaster.removeAttribute("syncstatus");
+    broadcaster.removeAttribute("disabled");
+    broadcaster.setAttribute("label", this.syncStrings.GetStringFromName("syncnow.label"));
+  },
+
+  onActivityStop() {
+    this.log.debug("onActivityStop");
+
+    let now = Date.now();
+    let syncDuration = now - this._syncStartTime;
+
+    if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
+      let animationTime = MIN_STATUS_ANIMATION_DURATION - syncDuration;
+      clearTimeout(this._syncAnimationTimer);
+      this._syncAnimationTimer = setTimeout(() => this._updateSyncStatus(), animationTime);
+    } else {
+      this._updateSyncStatus();
+    }
+  },
+
+  // Commands
+  // doSync forces a sync - it *does not* return a promise as it is called
+  // via the various UI components.
+  doSync() {
+    const state = UIState.get();
+    if (state.status == UIState.STATUS_SIGNED_IN) {
+      setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
+    }
+    Services.obs.notifyObservers(null, "cloudsync:user-sync");
+  },
+
+  // Handle clicking the toolbar button - which either opens the Sync setup
+  // pages or forces a sync now. Does *not* return a promise as it is called
+  // via the UI.
+  handleToolbarButton() {
+    const state = UIState.get();
+    if (state.status == UIState.STATUS_SIGNED_IN) {
+      this.doSync();
+    } else {
+      this.openPrefs();
+    }
+  },
+
+  /**
+   * Open the Sync preferences.
+   *
+   * @param entryPoint
+   *        Indicates the entrypoint from where this method was called.
+   */
+  openPrefs(entryPoint = "syncbutton") {
+    openPreferences("paneSync", { urlParams: { entrypoint: entryPoint } });
+  },
+
+  openSignInAgainPage(entryPoint = "syncbutton") {
+    gSync.openSignInAgainPage(entryPoint);
+  },
+
+  openSyncedTabsPanel() {
+    let placement = CustomizableUI.getPlacementOfWidget("sync-button");
+    let area = placement ? placement.area : CustomizableUI.AREA_NAVBAR;
+    let anchor = document.getElementById("sync-button") ||
+                 document.getElementById("PanelUI-menu-button");
+    if (area == CustomizableUI.AREA_PANEL) {
+      // The button is in the panel, so we need to show the panel UI, then our
+      // subview.
+      PanelUI.show().then(() => {
+        PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
+      }).catch(Cu.reportError);
+    } else {
+      // It is placed somewhere else - just try and show it.
+      PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
+    }
+  },
+
+  /* After Sync is initialized we perform a once-only check for the sync
+     button being in "customize purgatory" and if so, move it to the panel.
+     This is done primarily for profiles created before SyncedTabs landed,
+     where the button defaulted to being in that purgatory.
+     We use a preference to ensure we only do it once, so people can still
+     customize it away and have it stick.
+  */
+  maybeMoveSyncedTabsButton() {
+    const prefName = "browser.migrated-sync-button";
+    let migrated = Services.prefs.getBoolPref(prefName, false);
+    if (migrated) {
+      return;
+    }
+    if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
+      CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
+    }
+    Services.prefs.setBoolPref(prefName, true);
+  },
+
+  /* Update the tooltip for the sync-status broadcaster (which will update the
+     Sync Toolbar button and the Sync spinner in the FxA hamburger area.)
+     If Sync is configured, the tooltip is when the last sync occurred,
+     otherwise the tooltip reflects the fact that Sync needs to be
+     (re-)configured.
+  */
+  updateSyncButtonsTooltip(state) {
+    const status = state.status;
+
+    // This is a little messy as the Sync buttons are 1/2 Sync related and
+    // 1/2 FxA related - so for some strings we use Sync strings, but for
+    // others we reach into gSync for strings.
+    let tooltiptext;
+    if (status == UIState.STATUS_NOT_VERIFIED) {
+      // "needs verification"
+      tooltiptext = gSync.strings.formatStringFromName("verifyDescription", [state.email], 1);
+    } else if (status == UIState.STATUS_NOT_CONFIGURED) {
+      // "needs setup".
+      tooltiptext = this.syncStrings.GetStringFromName("signInToSync.description");
+    } else if (status == UIState.STATUS_LOGIN_FAILED) {
+      // "need to reconnect/re-enter your password"
+      tooltiptext = gSync.strings.formatStringFromName("reconnectDescription", [state.email], 1);
+    } else {
+      // Sync appears configured - format the "last synced at" time.
+      try {
+        let lastSync = new Date(state.lastSync);
+        tooltiptext = this.formatLastSyncDate(lastSync);
+      } catch (e) {
+        // pref doesn't exist (which will be the case until we've seen the
+        // first successful sync) or is invalid (which should be impossible!)
+        // Just leave tooltiptext as the empty string in these cases, which
+        // will cause the tooltip to be removed below.
+      }
+    }
+
+    let broadcaster = document.getElementById("sync-status");
+    if (broadcaster) {
+      if (tooltiptext) {
+        broadcaster.setAttribute("tooltiptext", tooltiptext);
+      } else {
+        broadcaster.removeAttribute("tooltiptext");
+      }
+    }
+  },
+
+  getWithinLastWeekFormat() {
+    return this._withinLastWeekFormat ||
+           (this._withinLastWeekFormat =
+             new Intl.DateTimeFormat(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}));
+  },
+
+  getOneWeekOrOlderFormat() {
+    return this._oneWeekOrOlderFormat ||
+           (this._oneWeekOrOlderFormat =
+             new Intl.DateTimeFormat(undefined, {month: "long", day: "numeric"}));
+  },
+
+  formatLastSyncDate(date) {
+    let sixDaysAgo = (() => {
+      let tempDate = new Date();
+      tempDate.setDate(tempDate.getDate() - 6);
+      tempDate.setHours(0, 0, 0, 0);
+      return tempDate;
+    })();
+
+    // It may be confusing for the user to see "Last Sync: Monday" when the last
+    // sync was indeed a Monday, but 3 weeks ago.
+    let dateFormat = date < sixDaysAgo ? this.getOneWeekOrOlderFormat() : this.getWithinLastWeekFormat();
+
+    let lastSyncDateString = dateFormat.format(date);
+    return this.syncStrings.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
+  },
+
+  onClientsSynced() {
+    let broadcaster = document.getElementById("sync-syncnow-state");
+    if (broadcaster) {
+      if (Weave.Service.clientsEngine.stats.numClients > 1) {
+        broadcaster.setAttribute("devices-status", "multi");
+      } else {
+        broadcaster.setAttribute("devices-status", "single");
+      }
+    }
+  },
+
+  // Note that we don't show login errors in a notification bar here, but do
+  // still need to track a login-failed state so the "Tools" menu updates
+  // with the correct state.
+  loginFailed() {
+    // If Sync isn't already ready, we don't want to force it to initialize
+    // by referencing Weave.Status - and it isn't going to be accurate before
+    // Sync is ready anyway.
+    if (!this.weaveService.ready) {
+      this.log.debug("loginFailed has sync not ready, so returning false");
+      return false;
+    }
+    this.log.debug("loginFailed has sync state=${sync}",
+                   { sync: Weave.Status.login});
+    return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
+  },
+
+  QueryInterface: XPCOMUtils.generateQI([
+    Ci.nsIObserver,
+    Ci.nsISupportsWeakReference
+  ])
+};
+
+XPCOMUtils.defineLazyGetter(gSync, "FxAccountsCommon", function() {
+  return Cu.import("resource://gre/modules/FxAccountsCommon.js", {});
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "EnsureFxAccountsWebChannel",
+  "resource://gre/modules/FxAccountsWebChannel.jsm");
+
+
+XPCOMUtils.defineLazyGetter(gSync, "weaveService", function() {
+  return Components.classes["@mozilla.org/weave/service;1"]
+                   .getService(Components.interfaces.nsISupports)
+                   .wrappedJSObject;
+});
+
+if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
+  XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
+                                    "resource://gre/modules/CloudSync.jsm");
+}
+
+XPCOMUtils.defineLazyGetter(gSync, "log", function() {
+  return Log.repository.getLogger("browserwindow.sync");
+});
deleted file mode 100644
--- a/browser/base/content/browser-syncui.js
+++ /dev/null
@@ -1,395 +0,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/. */
-
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-Cu.import("resource://gre/modules/Services.jsm");
-Cu.import("resource://services-sync/UIState.jsm");
-
-if (AppConstants.MOZ_SERVICES_CLOUDSYNC) {
-  XPCOMUtils.defineLazyModuleGetter(this, "CloudSync",
-                                    "resource://gre/modules/CloudSync.jsm");
-}
-
-XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
-                                  "resource://gre/modules/FxAccounts.jsm");
-
-const MIN_STATUS_ANIMATION_DURATION = 1600;
-
-// gSyncUI handles updating the tools menu and displaying notifications.
-var gSyncUI = {
-  _obs: [
-    "weave:engine:sync:finish"
-  ],
-
-  _unloaded: false,
-  // The last sync start time. Used to calculate the leftover animation time
-  // once syncing completes (bug 1239042).
-  _syncStartTime: 0,
-  _syncAnimationTimer: 0,
-  _withinLastWeekFormat: null,
-  _oneWeekOrOlderFormat: null,
-
-  init() {
-    // Proceed to set up the UI if Sync has already started up.
-    // Otherwise we'll do it when Sync is firing up.
-    if (this.weaveService.ready) {
-      this.initUI();
-      return;
-    }
-
-    Services.obs.addObserver(this, "weave:service:ready", true);
-    Services.obs.addObserver(this, "quit-application", true);
-
-    // Remove the observer if the window is closed before the observer
-    // was triggered.
-    window.addEventListener("unload", () => {
-      this._unloaded = true;
-      Services.obs.removeObserver(this, "weave:service:ready");
-      Services.obs.removeObserver(this, "quit-application");
-
-      if (this.weaveService.ready) {
-        this._obs.forEach(topic => {
-          Services.obs.removeObserver(this, topic);
-        });
-      }
-    }, { once: true });
-  },
-
-  initUI: function SUI_initUI() {
-    // If this is a browser window?
-    if (gBrowser) {
-      this._obs.push("weave:notification:added");
-    }
-
-    this._obs.forEach(function(topic) {
-      Services.obs.addObserver(this, topic, true);
-    }, this);
-
-    // initial label for the sync buttons.
-    let broadcaster = document.getElementById("sync-status");
-    broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
-
-    this.maybeMoveSyncedTabsButton();
-  },
-
-
-  // Returns a promise that resolves with true if Sync needs to be configured,
-  // false otherwise.
-  _needsSetup() {
-    return fxAccounts.getSignedInUser().then(user => {
-      // We want to treat "account needs verification" as "needs setup".
-      return !(user && user.verified);
-    });
-  },
-
-  // Returns a promise that resolves with true if the user currently signed in
-  // to Sync needs to be verified, false otherwise.
-  _needsVerification() {
-    return fxAccounts.getSignedInUser().then(user => {
-      // If there is no user, they can't be in a "needs verification" state.
-      if (!user) {
-        return false;
-      }
-      return !user.verified;
-    });
-  },
-
-  // Note that we don't show login errors in a notification bar here, but do
-  // still need to track a login-failed state so the "Tools" menu updates
-  // with the correct state.
-  loginFailed() {
-    // If Sync isn't already ready, we don't want to force it to initialize
-    // by referencing Weave.Status - and it isn't going to be accurate before
-    // Sync is ready anyway.
-    if (!this.weaveService.ready) {
-      this.log.debug("loginFailed has sync not ready, so returning false");
-      return false;
-    }
-    this.log.debug("loginFailed has sync state=${sync}",
-                   { sync: Weave.Status.login});
-    return Weave.Status.login == Weave.LOGIN_FAILED_LOGIN_REJECTED;
-  },
-
-  updateStateBroadcasters(state) {
-    const status = state.status;
-
-    // Start off with a clean slate
-    document.getElementById("sync-reauth-state").hidden = true;
-    document.getElementById("sync-setup-state").hidden = true;
-    document.getElementById("sync-syncnow-state").hidden = true;
-
-    if (CloudSync && CloudSync.ready && CloudSync().adapters.count) {
-      document.getElementById("sync-syncnow-state").hidden = false;
-    } else if (status == UIState.STATUS_LOGIN_FAILED) {
-      // unhiding this element makes the menubar show the login failure state.
-      document.getElementById("sync-reauth-state").hidden = false;
-    } else if (status == UIState.STATUS_NOT_CONFIGURED ||
-               status == UIState.STATUS_NOT_VERIFIED) {
-      document.getElementById("sync-setup-state").hidden = false;
-    } else {
-      document.getElementById("sync-syncnow-state").hidden = false;
-    }
-  },
-
-  updateSyncStatus(state) {
-    const broadcaster = document.getElementById("sync-status");
-    const syncingUI = broadcaster.getAttribute("syncstatus") == "active";
-    if (state.syncing != syncingUI) { // Do we need to update the UI?
-      state.syncing ? this.onActivityStart() : this.onActivityStop();
-    }
-  },
-
-  // Functions called by observers
-  onActivityStart() {
-    this.log.debug("onActivityStart");
-
-    clearTimeout(this._syncAnimationTimer);
-    this._syncStartTime = Date.now();
-
-    let broadcaster = document.getElementById("sync-status");
-    broadcaster.setAttribute("syncstatus", "active");
-    broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncing2.label"));
-    broadcaster.setAttribute("disabled", "true");
-  },
-
-  _updateSyncStatus() {
-    if (!gBrowser)
-      return;
-    let broadcaster = document.getElementById("sync-status");
-    broadcaster.removeAttribute("syncstatus");
-    broadcaster.removeAttribute("disabled");
-    broadcaster.setAttribute("label", this._stringBundle.GetStringFromName("syncnow.label"));
-  },
-
-  onActivityStop() {
-    this.log.debug("onActivityStop");
-
-    let now = Date.now();
-    let syncDuration = now - this._syncStartTime;
-
-    if (syncDuration < MIN_STATUS_ANIMATION_DURATION) {
-      let animationTime = MIN_STATUS_ANIMATION_DURATION - syncDuration;
-      clearTimeout(this._syncAnimationTimer);
-      this._syncAnimationTimer = setTimeout(() => this._updateSyncStatus(), animationTime);
-    } else {
-      this._updateSyncStatus();
-    }
-  },
-
-  // Commands
-  // doSync forces a sync - it *does not* return a promise as it is called
-  // via the various UI components.
-  doSync() {
-    this._needsSetup().then(needsSetup => {
-      if (!needsSetup) {
-        setTimeout(() => Weave.Service.errorHandler.syncAndReportErrors(), 0);
-      }
-      Services.obs.notifyObservers(null, "cloudsync:user-sync");
-    }).catch(err => {
-      this.log.error("Failed to force a sync", err);
-    });
-  },
-
-  // Handle clicking the toolbar button - which either opens the Sync setup
-  // pages or forces a sync now. Does *not* return a promise as it is called
-  // via the UI.
-  handleToolbarButton() {
-    this._needsSetup().then(needsSetup => {
-      if (needsSetup || this.loginFailed()) {
-        this.openPrefs();
-      } else {
-        this.doSync();
-      }
-    }).catch(err => {
-      this.log.error("Failed to handle toolbar button command", err);
-    });
-  },
-
-  /**
-   * Open the Sync preferences.
-   *
-   * @param entryPoint
-   *        Indicates the entrypoint from where this method was called.
-   */
-  openPrefs(entryPoint = "syncbutton") {
-    openPreferences("paneSync", { urlParams: { entrypoint: entryPoint } });
-  },
-
-  openSignInAgainPage(entryPoint = "syncbutton") {
-    gFxAccounts.openSignInAgainPage(entryPoint);
-  },
-
-  openSyncedTabsPanel() {
-    let placement = CustomizableUI.getPlacementOfWidget("sync-button");
-    let area = placement ? placement.area : CustomizableUI.AREA_NAVBAR;
-    let anchor = document.getElementById("sync-button") ||
-                 document.getElementById("PanelUI-menu-button");
-    if (area == CustomizableUI.AREA_PANEL) {
-      // The button is in the panel, so we need to show the panel UI, then our
-      // subview.
-      PanelUI.show().then(() => {
-        PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
-      }).catch(Cu.reportError);
-    } else {
-      // It is placed somewhere else - just try and show it.
-      PanelUI.showSubView("PanelUI-remotetabs", anchor, area);
-    }
-  },
-
-  /* After Sync is initialized we perform a once-only check for the sync
-     button being in "customize purgatory" and if so, move it to the panel.
-     This is done primarily for profiles created before SyncedTabs landed,
-     where the button defaulted to being in that purgatory.
-     We use a preference to ensure we only do it once, so people can still
-     customize it away and have it stick.
-  */
-  maybeMoveSyncedTabsButton() {
-    const prefName = "browser.migrated-sync-button";
-    let migrated = Services.prefs.getBoolPref(prefName, false);
-    if (migrated) {
-      return;
-    }
-    if (!CustomizableUI.getPlacementOfWidget("sync-button")) {
-      CustomizableUI.addWidgetToArea("sync-button", CustomizableUI.AREA_PANEL);
-    }
-    Services.prefs.setBoolPref(prefName, true);
-  },
-
-  /* Update the tooltip for the sync-status broadcaster (which will update the
-     Sync Toolbar button and the Sync spinner in the FxA hamburger area.)
-     If Sync is configured, the tooltip is when the last sync occurred,
-     otherwise the tooltip reflects the fact that Sync needs to be
-     (re-)configured.
-  */
-  updateSyncButtonsTooltip(state) {
-    const status = state.status;
-
-    // This is a little messy as the Sync buttons are 1/2 Sync related and
-    // 1/2 FxA related - so for some strings we use Sync strings, but for
-    // others we reach into gFxAccounts for strings.
-    let tooltiptext;
-    if (status == UIState.STATUS_NOT_VERIFIED) {
-      // "needs verification"
-      tooltiptext = gFxAccounts.strings.formatStringFromName("verifyDescription", [state.email], 1);
-    } else if (status == UIState.STATUS_NOT_CONFIGURED) {
-      // "needs setup".
-      tooltiptext = this._stringBundle.GetStringFromName("signInToSync.description");
-    } else if (status == UIState.STATUS_LOGIN_FAILED) {
-      // "need to reconnect/re-enter your password"
-      tooltiptext = gFxAccounts.strings.formatStringFromName("reconnectDescription", [state.email], 1);
-    } else {
-      // Sync appears configured - format the "last synced at" time.
-      try {
-        let lastSync = new Date(state.lastSync);
-        tooltiptext = this.formatLastSyncDate(lastSync);
-      } catch (e) {
-        // pref doesn't exist (which will be the case until we've seen the
-        // first successful sync) or is invalid (which should be impossible!)
-        // Just leave tooltiptext as the empty string in these cases, which
-        // will cause the tooltip to be removed below.
-      }
-    }
-
-    let broadcaster = document.getElementById("sync-status");
-    if (broadcaster) {
-      if (tooltiptext) {
-        broadcaster.setAttribute("tooltiptext", tooltiptext);
-      } else {
-        broadcaster.removeAttribute("tooltiptext");
-      }
-    }
-  },
-
-  getWithinLastWeekFormat() {
-    return this._withinLastWeekFormat ||
-           (this._withinLastWeekFormat =
-             new Intl.DateTimeFormat(undefined, {weekday: "long", hour: "numeric", minute: "numeric"}));
-  },
-
-  getOneWeekOrOlderFormat() {
-    return this._oneWeekOrOlderFormat ||
-           (this._oneWeekOrOlderFormat =
-             new Intl.DateTimeFormat(undefined, {month: "long", day: "numeric"}));
-  },
-
-  formatLastSyncDate(date) {
-    let sixDaysAgo = (() => {
-      let tempDate = new Date();
-      tempDate.setDate(tempDate.getDate() - 6);
-      tempDate.setHours(0, 0, 0, 0);
-      return tempDate;
-    })();
-
-    // It may be confusing for the user to see "Last Sync: Monday" when the last
-    // sync was indeed a Monday, but 3 weeks ago.
-    let dateFormat = date < sixDaysAgo ? this.getOneWeekOrOlderFormat() : this.getWithinLastWeekFormat();
-
-    let lastSyncDateString = dateFormat.format(date);
-    return this._stringBundle.formatStringFromName("lastSync2.label", [lastSyncDateString], 1);
-  },
-
-  onClientsSynced() {
-    let broadcaster = document.getElementById("sync-syncnow-state");
-    if (broadcaster) {
-      if (Weave.Service.clientsEngine.stats.numClients > 1) {
-        broadcaster.setAttribute("devices-status", "multi");
-      } else {
-        broadcaster.setAttribute("devices-status", "single");
-      }
-    }
-  },
-
-  observe: function SUI_observe(subject, topic, data) {
-    this.log.debug("observed", topic);
-    if (this._unloaded) {
-      Cu.reportError("SyncUI observer called after unload: " + topic);
-      return;
-    }
-
-    // Unwrap, just like Svc.Obs, but without pulling in that dependency.
-    if (subject && typeof subject == "object" &&
-        ("wrappedJSObject" in subject) &&
-        ("observersModuleSubjectWrapper" in subject.wrappedJSObject)) {
-      subject = subject.wrappedJSObject.object;
-    }
-
-    // Now non-activity state (eg, enabled, errors, etc)
-    // Note that sync uses the ":ui:" notifications for errors because sync.
-    switch (topic) {
-      case "weave:engine:sync:finish":
-        if (data != "clients") {
-          return;
-        }
-        this.onClientsSynced();
-        break;
-      case "quit-application":
-        // Stop the animation timer on shutdown, since we can't update the UI
-        // after this.
-        clearTimeout(this._syncAnimationTimer);
-        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/sync.properties");
-});
-
-XPCOMUtils.defineLazyGetter(gSyncUI, "log", function() {
-  return Log.repository.getLogger("browserwindow.syncui");
-});
-
-XPCOMUtils.defineLazyGetter(gSyncUI, "weaveService", function() {
-  return Components.classes["@mozilla.org/weave/service;1"]
-                   .getService(Components.interfaces.nsISupports)
-                   .wrappedJSObject;
-});
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1502,18 +1502,17 @@ var gBrowserInit = {
 
     if (Win7Features)
       Win7Features.onOpenWindow();
 
     FullScreen.init();
     PointerLock.init();
 
     // initialize the sync UI
-    gSyncUI.init();
-    gFxAccounts.init();
+    gSync.init();
 
     if (AppConstants.MOZ_DATA_REPORTING)
       gDataNotificationInfoBar.init();
 
     gBrowserThumbnails.init();
 
     gMenuButtonUpdateBadge.init();
 
@@ -1648,17 +1647,17 @@ var gBrowserInit = {
     CombinedStopReload.uninit();
 
     gGestureSupport.init(false);
 
     gHistorySwipeAnimation.uninit();
 
     FullScreen.uninit();
 
-    gFxAccounts.uninit();
+    gSync.uninit();
 
     gExtensionsNotifications.uninit();
 
     Services.obs.removeObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
     try {
       gBrowser.removeProgressListener(window.XULBrowserWindow);
       gBrowser.removeTabsProgressListener(window.TabsProgressListener);
@@ -1810,19 +1809,16 @@ if (AppConstants.platform == "macosx") {
     this._delayedStartupTimeoutId = null;
 
     // initialise the offline listener
     BrowserOffline.init();
 
     // initialize the private browsing UI
     gPrivateBrowsingUI.init();
 
-    // initialize the sync UI
-    gSyncUI.init();
-
     if (AppConstants.E10S_TESTING_ONLY) {
       gRemoteTabsUI.init();
     }
   };
 
   gBrowserInit.nonBrowserWindowShutdown = function() {
     let dockSupport = Cc["@mozilla.org/widget/macdocksupport;1"]
                       .getService(Ci.nsIMacDockSupport);
@@ -6741,17 +6737,17 @@ function checkEmptyPageOrigin(browser = 
   }
   // ... so for those that don't have them, enforce that the page has the
   // system principal (this matches e.g. on about:newtab).
   let ssm = Services.scriptSecurityManager;
   return ssm.isSystemPrincipal(contentPrincipal);
 }
 
 function BrowserOpenSyncTabs() {
-  gSyncUI.openSyncedTabsPanel();
+  gSync.openSyncedTabsPanel();
 }
 
 function ReportFalseDeceptiveSite() {
   let docURI = gBrowser.selectedBrowser.documentURI;
   let isPhishingPage =
     docURI && docURI.spec.startsWith("about:blocked?e=deceptiveBlocked");
 
   if (isPhishingPage) {
@@ -8083,17 +8079,17 @@ var TabContextMenu = {
     }
 
     this.contextTab.toggleMuteMenuItem = toggleMute;
     this._updateToggleMuteMenuItem(this.contextTab);
 
     this.contextTab.addEventListener("TabAttrModified", this);
     aPopupMenu.addEventListener("popuphiding", this);
 
-    gFxAccounts.updateTabContextMenu(aPopupMenu, this.contextTab);
+    gSync.updateTabContextMenu(aPopupMenu, this.contextTab);
   },
   handleEvent(aEvent) {
     switch (aEvent.type) {
       case "popuphiding":
         gBrowser.removeEventListener("TabAttrModified", this);
         aEvent.target.removeEventListener("popuphiding", this);
         break;
       case "TabAttrModified":
--- a/browser/base/content/browser.xul
+++ b/browser/base/content/browser.xul
@@ -102,17 +102,17 @@
                 tbattr="tabbrowser-remote"
                 hidden="true"
                 oncommand="gBrowser.openNonRemoteWindow(TabContextMenu.contextTab);"/>
 #endif
       <menuseparator id="context_sendTabToDevice_separator" hidden="true"/>
       <menu id="context_sendTabToDevice" label="&sendTabToDevice.label;"
             accesskey="&sendTabToDevice.accesskey;" hidden="true">
         <menupopup id="context_sendTabToDevicePopupMenu"
-                   onpopupshowing="gFxAccounts.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
+                   onpopupshowing="gSync.populateSendTabToDevicesMenu(event.target, TabContextMenu.contextTab.linkedBrowser.currentURI.spec, TabContextMenu.contextTab.linkedBrowser.contentTitle);"/>
       </menu>
       <menuseparator/>
       <menuitem id="context_reloadAllTabs" label="&reloadAllTabs.label;" accesskey="&reloadAllTabs.accesskey;"
                 tbattr="tabbrowser-multiple-visible"
                 oncommand="gBrowser.reloadAllTabs();"/>
       <menuitem id="context_bookmarkAllTabs"
                 label="&bookmarkAllTabs.label;"
                 accesskey="&bookmarkAllTabs.accesskey;"
@@ -477,17 +477,17 @@
                 id="syncedTabsCopySelected"/>
       <menuseparator/>
       <menuitem label="&syncedTabs.context.openAllInTabs.label;"
                 accesskey="&syncedTabs.context.openAllInTabs.accesskey;"
                 id="syncedTabsOpenAllInTabs"/>
       <menuitem label="&syncedTabs.context.managedevices.label;"
                 accesskey="&syncedTabs.context.managedevices.accesskey;"
                 id="syncedTabsManageDevices"
-                oncommand="gFxAccounts.openDevicesManagementPage('syncedtabs-sidebar');"/>
+                oncommand="gSync.openDevicesManagementPage('syncedtabs-sidebar');"/>
       <menuitem label="&syncSyncNowItem.label;"
                 accesskey="&syncSyncNowItem.accesskey;"
                 id="syncedTabsRefresh"/>
     </menupopup>
     <menupopup id="SyncedTabsSidebarTabsFilterContext"
                class="textbox-contextmenu">
       <menuitem label="&undoCmd.label;"
                 accesskey="&undoCmd.accesskey;"
--- a/browser/base/content/global-scripts.inc
+++ b/browser/base/content/global-scripts.inc
@@ -25,18 +25,17 @@
 <script type="application/javascript" src="chrome://browser/content/browser-gestureSupport.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-media.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-plugins.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-refreshblocker.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-safebrowsing.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-sidebar.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
-<script type="application/javascript" src="chrome://browser/content/browser-syncui.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-tabsintitlebar.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-thumbnails.js"/>
 <script type="application/javascript" src="chrome://browser/content/browser-trackingprotection.js"/>
 
 #ifdef MOZ_DATA_REPORTING
 <script type="application/javascript" src="chrome://browser/content/browser-data-submission-info-bar.js"/>
 #endif
 
-<script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
+<script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
--- a/browser/base/content/nsContextMenu.js
+++ b/browser/base/content/nsContextMenu.js
@@ -596,17 +596,17 @@ nsContextMenu.prototype = {
       return;
     }
     let popup = document.getElementById("fill-login-popup");
     let insertBeforeElement = document.getElementById("fill-login-no-logins");
     popup.insertBefore(fragment, insertBeforeElement);
   },
 
   initSyncItems() {
-    gFxAccounts.initPageContextMenu(this);
+    gSync.initPageContextMenu(this);
   },
 
   openPasswordManager() {
     LoginHelper.openPasswordManager(window, gContextMenuContentData.documentURIObject.host);
   },
 
   inspectNode() {
     let gBrowser = this.browser.ownerGlobal.gBrowser;
--- a/browser/base/content/test/general/browser_contextmenu.js
+++ b/browser/base/content/test/general/browser_contextmenu.js
@@ -888,17 +888,17 @@ add_task(function* test_input_spell_fals
     ]
   );
   */
 });
 
 const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
 
 add_task(function* test_plaintext_sendpagetodevice() {
-  if (!gFxAccounts.sendTabToDeviceEnabled) {
+  if (!gSync.sendTabToDeviceEnabled) {
     return;
   }
   yield ensureSyncReady();
   const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
 
   let plainTextItemsWithSendPage =
                     ["context-navigation",   null,
                       ["context-back",         false,
@@ -926,17 +926,17 @@ add_task(function* test_plaintext_sendpa
         yield openMenuItemSubmenu("context-sendpagetodevice");
       }
     });
 
   restoreRemoteClients(oldGetter);
 });
 
 add_task(function* test_link_sendlinktodevice() {
-  if (!gFxAccounts.sendTabToDeviceEnabled) {
+  if (!gSync.sendTabToDeviceEnabled) {
     return;
   }
   yield ensureSyncReady();
   const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
 
   yield test_contextmenu("#test-link",
     ["context-openlinkintab", true,
      ...(hasContainers ? ["context-openlinkinusercontext-menu", true] : []),
--- a/browser/base/content/test/general/browser_fxaccounts.js
+++ b/browser/base/content/test/general/browser_fxaccounts.js
@@ -4,45 +4,45 @@
 var {Log} = Cu.import("resource://gre/modules/Log.jsm", {});
 var {Task} = Cu.import("resource://gre/modules/Task.jsm", {});
 var {fxAccounts} = Cu.import("resource://gre/modules/FxAccounts.jsm", {});
 var FxAccountsCommon = {};
 Cu.import("resource://gre/modules/FxAccountsCommon.js", FxAccountsCommon);
 
 const TEST_ROOT = "http://example.com/browser/browser/base/content/test/general/";
 
-// instrument gFxAccounts to send observer notifications when it's done
+// instrument gSync to send observer notifications when it's done
 // what it does.
 (function() {
   let unstubs = {}; // The original functions we stub out.
 
   // The stub functions.
   let stubs = {
     updateUI() {
-      return unstubs["updateUI"].call(gFxAccounts).then(() => {
+      return unstubs["updateUI"].call(gSync).then(() => {
         Services.obs.notifyObservers(null, "test:browser_fxaccounts:updateUI");
       });
     },
     // Opening preferences is trickier than it should be as leaks are reported
     // due to the promises it fires off at load time  and there's no clear way to
     // know when they are done.
     // So just ensure openPreferences is called rather than whether it opens.
     openPreferences() {
       Services.obs.notifyObservers(null, "test:browser_fxaccounts:openPreferences");
     }
   };
 
   for (let name in stubs) {
-    unstubs[name] = gFxAccounts[name];
-    gFxAccounts[name] = stubs[name];
+    unstubs[name] = gSync[name];
+    gSync[name] = stubs[name];
   }
   // and undo our damage at the end.
   registerCleanupFunction(() => {
     for (let name in unstubs) {
-      gFxAccounts[name] = unstubs[name];
+      gSync[name] = unstubs[name];
     }
     stubs = unstubs = null;
   });
 })();
 
 // Other setup/cleanup
 var newTab;
 
@@ -111,17 +111,17 @@ add_task(function* test_unverifiedUser()
 });
 */
 
 add_task(function* test_verifiedUserEmptyProfile() {
   // We see 2 updateUI() calls - one for the signedInUser and one after
   // we first fetch the profile. We want them both to fire or we aren't testing
   // the state we think we are testing.
   let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 2);
-  gFxAccounts._cachedProfile = null;
+  gSync._cachedProfile = null;
   configureProfileURL({}); // successful but empty profile.
   yield setSignedInUser(true); // this will fire the observer that does the update.
   yield promiseUpdateDone;
 
   // Check the world.
   Assert.ok(isFooterVisible())
   Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
   Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
@@ -131,41 +131,41 @@ add_task(function* test_verifiedUserEmpt
   let promisePreferencesOpened = promiseObserver("test:browser_fxaccounts:openPreferences");
   panelUIStatus.click();
   yield promisePreferencesOpened;
   yield signOut();
 });
 
 add_task(function* test_verifiedUserDisplayName() {
   let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 2);
-  gFxAccounts._cachedProfile = null;
+  gSync._cachedProfile = null;
   configureProfileURL({ displayName: "Test User Display Name" });
   yield setSignedInUser(true); // this will fire the observer that does the update.
   yield promiseUpdateDone;
 
   Assert.ok(isFooterVisible())
   Assert.equal(panelUILabel.getAttribute("label"), "Test User Display Name");
   Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
                panelUIStatus.getAttribute("signedinTooltiptext"));
   Assert.equal(panelUIFooter.getAttribute("fxastatus"), "signedin");
   yield signOut();
 });
 
 add_task(function* test_profileNotificationsClearsCache() {
   let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 1);
-  gFxAccounts._cachedProfile = { foo: "bar" };
+  gSync._cachedProfile = { foo: "bar" };
   Services.obs.notifyObservers(null, this.FxAccountsCommon.ON_PROFILE_CHANGE_NOTIFICATION);
-  Assert.ok(!gFxAccounts._cachedProfile);
+  Assert.ok(!gSync._cachedProfile);
   yield promiseUpdateDone;
 });
 
 add_task(function* test_verifiedUserProfileFailure() {
   // profile failure means only one observer fires.
   let promiseUpdateDone = promiseObserver("test:browser_fxaccounts:updateUI", 1);
-  gFxAccounts._cachedProfile = null;
+  gSync._cachedProfile = null;
   configureProfileURL(null, 500);
   yield setSignedInUser(true); // this will fire the observer that does the update.
   yield promiseUpdateDone;
 
   Assert.ok(isFooterVisible());
   Assert.equal(panelUILabel.getAttribute("label"), "foo@example.com");
   Assert.equal(panelUIStatus.getAttribute("tooltiptext"),
                panelUIStatus.getAttribute("signedinTooltiptext"));
--- a/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
+++ b/browser/base/content/test/general/browser_visibleTabs_contextMenu.js
@@ -12,35 +12,35 @@ add_task(function* test() {
   is(gBrowser.visibleTabs.length, 2, "there are now two visible tabs");
 
   // Check the context menu with two tabs
   updateTabContextMenu(origTab);
   is(document.getElementById("context_closeTab").disabled, false, "Close Tab is enabled");
   is(document.getElementById("context_reloadAllTabs").disabled, false, "Reload All Tabs is enabled");
 
 
-  if (gFxAccounts.sendTabToDeviceEnabled) {
-    const origIsSendableURI = gFxAccounts.isSendableURI;
-    gFxAccounts.isSendableURI = () => true;
+  if (gSync.sendTabToDeviceEnabled) {
+    const origIsSendableURI = gSync.isSendableURI;
+    gSync.isSendableURI = () => true;
     // Check the send tab to device menu item
     yield ensureSyncReady();
     const oldGetter = setupRemoteClientsFixture(remoteClientsFixture);
     yield updateTabContextMenu(origTab, function* () {
       yield openMenuItemSubmenu("context_sendTabToDevice");
     });
     is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
     let targets = document.getElementById("context_sendTabToDevicePopupMenu").childNodes;
     is(targets[0].getAttribute("label"), "Foo", "Foo target is present");
     is(targets[1].getAttribute("label"), "Bar", "Bar target is present");
     is(targets[3].getAttribute("label"), "All Devices", "All Devices target is present");
-    gFxAccounts.isSendableURI = () => false;
+    gSync.isSendableURI = () => false;
     updateTabContextMenu(origTab);
     is(document.getElementById("context_sendTabToDevice").hidden, true, "Send tab to device is hidden");
     restoreRemoteClients(oldGetter);
-    gFxAccounts.isSendableURI = origIsSendableURI;
+    gSync.isSendableURI = origIsSendableURI;
   }
 
   // Hide the original tab.
   gBrowser.selectedTab = testTab;
   gBrowser.showOnlyTheseTabs([testTab]);
   is(gBrowser.visibleTabs.length, 1, "now there is only one visible tab");
 
   // Check the context menu with one tab.
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -831,26 +831,26 @@ function getCertExceptionDialog(aLocatio
       }
     }
   }
   return undefined;
 }
 
 function setupRemoteClientsFixture(fixture) {
   let oldRemoteClientsGetter =
-    Object.getOwnPropertyDescriptor(gFxAccounts, "remoteClients").get;
+    Object.getOwnPropertyDescriptor(gSync, "remoteClients").get;
 
-  Object.defineProperty(gFxAccounts, "remoteClients", {
+  Object.defineProperty(gSync, "remoteClients", {
     get() { return fixture; }
   });
   return oldRemoteClientsGetter;
 }
 
 function restoreRemoteClients(getter) {
-  Object.defineProperty(gFxAccounts, "remoteClients", {
+  Object.defineProperty(gSync, "remoteClients", {
     get: getter
   });
 }
 
 function* openMenuItemSubmenu(id) {
   let menuPopup = document.getElementById(id).menupopup;
   let menuPopupPromise = BrowserTestUtils.waitForEvent(menuPopup, "popupshown");
   menuPopup.showPopup();
--- a/browser/base/content/web-panels.xul
+++ b/browser/base/content/web-panels.xul
@@ -19,17 +19,17 @@
 <page id="webpanels-window"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="load()" onunload="unload()">
   <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
-  <script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
+  <script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
   <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
   <script type="application/javascript" src="chrome://browser/content/web-panels.js"/>
 
   <stringbundleset id="stringbundleset"> 
     <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
   </stringbundleset>
 
   <broadcasterset id="mainBroadcasterSet">
--- a/browser/base/content/webext-panels.xul
+++ b/browser/base/content/webext-panels.xul
@@ -19,17 +19,17 @@
 <page id="webextpanels-window"
         xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         onload="load()">
   <script type="application/javascript" src="chrome://global/content/contentAreaUtils.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-places.js"/>
   <script type="application/javascript" src="chrome://browser/content/browser-social.js"/>
-  <script type="application/javascript" src="chrome://browser/content/browser-fxaccounts.js"/>
+  <script type="application/javascript" src="chrome://browser/content/browser-sync.js"/>
   <script type="application/javascript" src="chrome://browser/content/nsContextMenu.js"/>
   <script type="application/javascript" src="chrome://browser/content/webext-panels.js"/>
 
   <stringbundleset id="stringbundleset">
     <stringbundle id="bundle_browser" src="chrome://browser/locale/browser.properties"/>
   </stringbundleset>
 
   <broadcasterset id="mainBroadcasterSet">
--- a/browser/base/jar.mn
+++ b/browser/base/jar.mn
@@ -68,26 +68,25 @@ browser.jar:
         content/browser/browser-captivePortal.js      (content/browser-captivePortal.js)
         content/browser/browser-ctrlTab.js            (content/browser-ctrlTab.js)
         content/browser/browser-customization.js      (content/browser-customization.js)
         content/browser/browser-data-submission-info-bar.js (content/browser-data-submission-info-bar.js)
         content/browser/browser-compacttheme.js       (content/browser-compacttheme.js)
         content/browser/browser-feeds.js              (content/browser-feeds.js)
         content/browser/browser-fullScreenAndPointerLock.js  (content/browser-fullScreenAndPointerLock.js)
         content/browser/browser-fullZoom.js           (content/browser-fullZoom.js)
-        content/browser/browser-fxaccounts.js         (content/browser-fxaccounts.js)
         content/browser/browser-gestureSupport.js     (content/browser-gestureSupport.js)
         content/browser/browser-media.js              (content/browser-media.js)
         content/browser/browser-places.js             (content/browser-places.js)
         content/browser/browser-plugins.js            (content/browser-plugins.js)
         content/browser/browser-refreshblocker.js     (content/browser-refreshblocker.js)
         content/browser/browser-safebrowsing.js       (content/browser-safebrowsing.js)
         content/browser/browser-sidebar.js            (content/browser-sidebar.js)
         content/browser/browser-social.js             (content/browser-social.js)
-        content/browser/browser-syncui.js             (content/browser-syncui.js)
+        content/browser/browser-sync.js               (content/browser-sync.js)
 *       content/browser/browser-tabPreviews.xml       (content/browser-tabPreviews.xml)
 #ifdef CAN_DRAW_IN_TITLEBAR
         content/browser/browser-tabsintitlebar.js       (content/browser-tabsintitlebar.js)
 #else
         content/browser/browser-tabsintitlebar.js       (content/browser-tabsintitlebar-stub.js)
 #endif
         content/browser/browser-thumbnails.js         (content/browser-thumbnails.js)
         content/browser/browser-trackingprotection.js (content/browser-trackingprotection.js)
--- a/browser/components/customizableui/CustomizableWidgets.jsm
+++ b/browser/components/customizableui/CustomizableWidgets.jsm
@@ -467,17 +467,17 @@ const CustomizableWidgets = [
     },
     _appendClient(client, attachFragment, maxTabs = this.TABS_PER_PAGE) {
       let doc = attachFragment.ownerDocument;
       // Create the element for the remote client.
       let clientItem = doc.createElementNS(kNSXUL, "label");
       clientItem.setAttribute("itemtype", "client");
       let window = doc.defaultView;
       clientItem.setAttribute("tooltiptext",
-        window.gSyncUI.formatLastSyncDate(new Date(client.lastModified)));
+        window.gSync.formatLastSyncDate(new Date(client.lastModified)));
       clientItem.textContent = client.name;
 
       attachFragment.appendChild(clientItem);
 
       if (client.tabs.length == 0) {
         let label = this._appendMessageLabel("notabsforclientlabel", attachFragment);
         label.setAttribute("class", "PanelUI-remotetabs-notabsforclient-label");
       } else {
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -32,25 +32,25 @@
         <hbox id="PanelUI-footer-fxa">
           <hbox id="PanelUI-fxa-status"
                 label="&fxaSignedIn.tooltip;"
                 defaultlabel="&fxaSignIn.label;"
                 signedinTooltiptext="&fxaSignedIn.tooltip;"
                 tooltiptext="&fxaSignedIn.tooltip;"
                 errorlabel="&fxaSignInError.label;"
                 unverifiedlabel="&fxaUnverified.label;"
-                onclick="if (event.which == 1) gFxAccounts.onMenuPanelCommand();">
+                onclick="if (event.which == 1) gSync.onMenuPanelCommand();">
             <image id="PanelUI-fxa-avatar"/>
             <toolbarbutton id="PanelUI-fxa-label"
                            label="&fxaSignIn.label;"
                            fxabrandname="&syncBrand.fxAccount.label;"/>
           </hbox>
           <toolbarseparator/>
           <toolbarbutton id="PanelUI-fxa-icon"
-                         oncommand="gSyncUI.doSync();"
+                         oncommand="gSync.doSync();"
                          closemenu="none">
             <observes element="sync-status" attribute="syncstatus"/>
             <observes element="sync-status" attribute="tooltiptext"/>
           </toolbarbutton>
         </hbox>
 
         <hbox id="PanelUI-footer-inner">
           <toolbarbutton id="PanelUI-customize" label="&appMenuCustomize.label;"
@@ -122,21 +122,21 @@
           <vbox id="PanelUI-remotetabs-buttons">
             <toolbarbutton id="PanelUI-remotetabs-view-sidebar"
                            class="subviewbutton"
                            observes="viewTabsSidebar"
                            label="&appMenuRemoteTabs.sidebar.label;"/>
             <toolbarbutton id="PanelUI-remotetabs-view-managedevices"
                            class="subviewbutton"
                            label="&appMenuRemoteTabs.managedevices.label;"
-                           oncommand="gFxAccounts.openDevicesManagementPage('syncedtabs-menupanel');"/>
+                           oncommand="gSync.openDevicesManagementPage('syncedtabs-menupanel');"/>
             <toolbarbutton id="PanelUI-remotetabs-syncnow"
                            observes="sync-status"
                            class="subviewbutton"
-                           oncommand="gSyncUI.doSync();"
+                           oncommand="gSync.doSync();"
                            closemenu="none"/>
             <menuseparator id="PanelUI-remotetabs-separator"/>
           </vbox>
           <deck id="PanelUI-remotetabs-deck">
             <!-- Sync is ready to Sync and the "tabs" engine is enabled -->
             <vbox id="PanelUI-remotetabs-tabspane">
               <vbox id="PanelUI-remotetabs-tabslist"
                     showAllLabel="&appMenuRemoteTabs.showAll.label;"
@@ -151,17 +151,17 @@
               <vbox class="PanelUI-remotetabs-instruction-box">
                 <hbox pack="center">
                   <html:img class="fxaSyncIllustration" src="chrome://browser/skin/fxa/sync-illustration.svg"/>
                 </hbox>
                 <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.tabsnotsyncing.label;</label>
                 <hbox pack="center">
                   <toolbarbutton class="PanelUI-remotetabs-prefs-button"
                                  label="&appMenuRemoteTabs.openprefs.label;"
-                                 oncommand="gSyncUI.openPrefs('synced-tabs');"/>
+                                 oncommand="gSync.openPrefs('synced-tabs');"/>
                 </hbox>
               </vbox>
             </hbox>
             <!-- Sync is ready to Sync but we are still fetching the tabs to show -->
             <vbox id="PanelUI-remotetabs-fetching">
               <!-- Show intentionally blank panel, see bug 1239845 -->
             </vbox>
             <!-- Sync has only 1 (ie, this) device connected -->
@@ -185,31 +185,31 @@
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
                 observes="sync-setup-state">
             <html:img class="fxaSyncIllustration" src="chrome://browser/skin/fxa/sync-illustration.svg"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-prefs-button"
                            label="&appMenuRemoteTabs.signin.label;"
-                           oncommand="gSyncUI.openPrefs('synced-tabs');"/>
+                           oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
           <!-- When Sync needs re-authentication. This uses the exact same messaging
                as "Sync is not configured" but remains a separate box so we get
                the goodness of observing broadcasters to manage the hidden states -->
           <vbox id="PanelUI-remotetabs-reauthsync"
                 flex="1"
                 align="center"
                 class="PanelUI-remotetabs-instruction-box"
                 observes="sync-reauth-state">
             <html:img class="fxaSyncIllustration" src="chrome://browser/skin/fxa/sync-illustration.svg"/>
             <label class="PanelUI-remotetabs-instruction-label">&appMenuRemoteTabs.notsignedin.label;</label>
             <toolbarbutton class="PanelUI-remotetabs-prefs-button"
                            label="&appMenuRemoteTabs.signin.label;"
-                           oncommand="gSyncUI.openPrefs('synced-tabs');"/>
+                           oncommand="gSync.openPrefs('synced-tabs');"/>
           </vbox>
         </hbox>
       </vbox>
     </panelview>
 
     <panelview id="PanelUI-bookmarks" flex="1" class="PanelUI-subView">
       <label value="&bookmarksMenu.label;" class="panel-subview-header"/>
       <vbox class="panel-subview-body">
--- a/browser/components/syncedtabs/SyncedTabsDeckComponent.js
+++ b/browser/components/syncedtabs/SyncedTabsDeckComponent.js
@@ -117,17 +117,17 @@ SyncedTabsDeckComponent.prototype = {
   // There's no good way to mock fxAccounts in browser tests where it's already
   // been instantiated, so we have this method for stubbing.
   _accountStatus() {
     return this._fxAccounts.accountStatus();
   },
 
   getPanelStatus() {
     return this._accountStatus().then(exists => {
-      if (!exists || this._getChromeWindow(this._window).gSyncUI.loginFailed()) {
+      if (!exists || this._getChromeWindow(this._window).gSync.loginFailed()) {
         return this.PANELS.NOT_AUTHED_INFO;
       }
       if (!this._SyncedTabs.isConfiguredToSyncTabs) {
         return this.PANELS.TABS_DISABLED;
       }
       if (!this._SyncedTabs.hasSyncedThisSession) {
         return this.PANELS.TABS_FETCHING;
       }
@@ -161,12 +161,12 @@ SyncedTabsDeckComponent.prototype = {
     this._openUrl(href, event);
   },
 
   _openUrl(url, event) {
     this._window.openUILink(url, event);
   },
 
   openSyncPrefs() {
-    this._getChromeWindow(this._window).gSyncUI.openPrefs("tabs-sidebar");
+    this._getChromeWindow(this._window).gSync.openPrefs("tabs-sidebar");
   }
 };
 
--- a/browser/components/syncedtabs/TabListView.js
+++ b/browser/components/syncedtabs/TabListView.js
@@ -208,17 +208,17 @@ TabListView.prototype = {
    * Update the element representing an item, ensuring it's in sync with the
    * underlying data.
    * @param {client} item - Item to use as a source.
    * @param {Element} itemNode - Element to update.
    */
   _updateClient(item, itemNode) {
     itemNode.setAttribute("id", "item-" + item.id);
     let lastSync = new Date(item.lastModified);
-    let lastSyncTitle = getChromeWindow(this._window).gSyncUI.formatLastSyncDate(lastSync);
+    let lastSyncTitle = getChromeWindow(this._window).gSync.formatLastSyncDate(lastSync);
     itemNode.setAttribute("title", lastSyncTitle);
     if (item.closed) {
       itemNode.classList.add("closed");
     } else {
       itemNode.classList.remove("closed");
     }
     if (item.selected) {
       itemNode.classList.add("selected");
--- a/browser/components/uitour/test/browser_fxa.js
+++ b/browser/components/uitour/test/browser_fxa.js
@@ -13,34 +13,34 @@ var gContentAPI;
 var gContentWindow;
 
 function test() {
   UITourTest();
 }
 
 registerCleanupFunction(function*() {
   yield signOut();
-  gFxAccounts.updateUI();
+  gSync.updateUI();
 });
 
 var tests = [
   taskify(function* test_highlight_accountStatus_loggedOut() {
     let userData = yield fxAccounts.getSignedInUser();
     is(userData, null, "Not logged in initially");
     yield showMenuPromise("appMenu");
     yield showHighlightPromise("accountStatus");
     let highlight = document.getElementById("UITourHighlightContainer");
     is(highlight.getAttribute("targetName"), "accountStatus", "Correct highlight target");
   }),
 
   taskify(function* test_highlight_accountStatus_loggedIn() {
     yield setSignedInUser();
     let userData = yield fxAccounts.getSignedInUser();
     isnot(userData, null, "Logged in now");
-    gFxAccounts.updateUI(); // Causes a leak (see bug 1332985)
+    gSync.updateUI(); // Causes a leak (see bug 1332985)
     yield showMenuPromise("appMenu");
     yield showHighlightPromise("accountStatus");
     let highlight = document.getElementById("UITourHighlightContainer");
     is(highlight.popupBoxObject.anchorNode.id, "PanelUI-fxa-avatar", "Anchored on avatar");
     is(highlight.getAttribute("targetName"), "accountStatus", "Correct highlight target");
   }),
 ];
 
--- a/services/sync/modules/UIState.jsm
+++ b/services/sync/modules/UIState.jsm
@@ -201,17 +201,21 @@ const UIStateInternal = {
     } catch (e) {
       // Not fetching the profile is sad but the FxA logs will already have noise.
       return null;
     }
   },
 
   _setLastSyncTime(state) {
     if (state.status == UIState.STATUS_SIGNED_IN) {
-      state.lastSync = Services.prefs.getCharPref("services.sync.lastSync");
+      try {
+        state.lastSync = Services.prefs.getCharPref("services.sync.lastSync");
+      } catch (e) {
+        // This is ok
+      }
     }
   },
 
   _calculateStateChecksum(state) {
     let desc = `${state.status}|${state.email}|${state.displayName}|
                 ${state.avatarURL}|${state.lastSync}|${this._syncing}`;
     let checksum = 0, i = 0, len = desc.length;
     while (i < len) {