Bug 1507294 - Display all compatible FxA devices in send tab menu. r=markh
authorEdouard Oger <eoger@fastmail.com>
Fri, 16 Nov 2018 03:03:13 +0000
changeset 446787 68841d4d23dafb234273bbe4e6dbf06056c4ffe9
parent 446786 f3cb436aa13e76996b3b0349bd604f5183f7ee1c
child 446788 599db31d59659e1e1bbf37eea6d12194811aa384
push id35052
push userapavel@mozilla.com
push dateSat, 17 Nov 2018 11:25:40 +0000
treeherdermozilla-central@efc1da42132b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmarkh
bugs1507294
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1507294 - Display all compatible FxA devices in send tab menu. r=markh Differential Revision: https://phabricator.services.mozilla.com/D11955
browser/base/content/browser-sync.js
browser/base/content/test/sync/browser_contextmenu_sendpage.js
browser/base/content/test/sync/browser_contextmenu_sendtab.js
browser/base/content/test/sync/head.js
browser/base/content/test/urlbar/browser_page_action_menu.js
services/fxaccounts/FxAccountsCommands.js
services/fxaccounts/tests/xpcshell/test_commands.js
services/sync/modules/engines/clients.js
services/sync/tests/unit/test_clients_engine.js
--- a/browser/base/content/browser-sync.js
+++ b/browser/base/content/browser-sync.js
@@ -55,19 +55,20 @@ var gSync = {
     return UIState.get().status == UIState.STATUS_SIGNED_IN &&
            (!this.syncReady || Weave.Service.clientsEngine.isFirstSync);
   },
 
   get isSignedIn() {
     return UIState.get().status == UIState.STATUS_SIGNED_IN;
   },
 
-  get remoteClients() {
-    return Weave.Service.clientsEngine.remoteClients
-           .sort((a, b) => a.name.localeCompare(b.name));
+  get sendTabTargets() {
+    return Weave.Service.clientsEngine.fxaDevices
+      .sort((a, b) => a.name.localeCompare(b.name))
+      .filter(d => !d.isCurrentDevice && (fxAccounts.commands.sendTab.isDeviceCompatible(d) || d.clientRecord));
   },
 
   get offline() {
     return Weave.Service.scheduler.offline;
   },
 
   _generateNodeGetters() {
     for (let k of ["Status", "Avatar", "Label", "Container"]) {
@@ -311,64 +312,54 @@ var gSync = {
   },
 
   openSendToDevicePromo() {
     let url = this.PRODUCT_INFO_BASE_URL;
     url += "send-tabs/?utm_source=" + Services.appinfo.name.toLowerCase();
     switchToTabHavingURI(url, true, { replaceQueryString: true });
   },
 
-  async sendTabToDevice(url, clients, title) {
-    let devices;
-    try {
-      devices = await fxAccounts.getDeviceList();
-    } catch (e) {
-      console.error("Could not get the FxA device list", e);
-      devices = []; // We can still run in degraded mode.
-    }
+  async sendTabToDevice(url, targets, title) {
     const fxaCommandsDevices = [];
     const oldSendTabClients = [];
-    for (const client of clients) {
-      const device = devices.find(d => d.id == client.fxaDeviceId);
-      if (!device) {
-        console.error(`Could not find associated FxA device for ${client.name}`);
-        continue;
-      } else if ((await fxAccounts.commands.sendTab.isDeviceCompatible(device))) {
-        fxaCommandsDevices.push(device);
+    for (const target of targets) {
+      if (fxAccounts.commands.sendTab.isDeviceCompatible(target)) {
+        fxaCommandsDevices.push(target);
+      } else if (target.clientRecord) {
+        oldSendTabClients.push(target.clientRecord);
       } else {
-        oldSendTabClients.push(client);
+        console.error(`Target ${target.id} unsuitable for send tab.`);
       }
     }
     if (fxaCommandsDevices.length) {
       console.log(`Sending a tab to ${fxaCommandsDevices.map(d => d.name).join(", ")} using FxA commands.`);
       const report = await fxAccounts.commands.sendTab.send(fxaCommandsDevices, {url, title});
       for (let {device, error} of report.failed) {
         console.error(`Failed to send a tab with FxA commands for ${device.name}.
                        Falling back on the Sync back-end`, error);
-        const client = clients.find(c => c.fxaDeviceId == device.id);
-        if (!client) {
+        if (!device.clientRecord) {
           console.error(`Could not find associated Sync device for ${device.name}`);
           continue;
         }
-        oldSendTabClients.push(client);
+        oldSendTabClients.push(device.clientRecord);
       }
     }
     for (let client of oldSendTabClients) {
       try {
         console.log(`Sending a tab to ${client.name} using Sync.`);
         await Weave.Service.clientsEngine.sendURIToClientForDisplay(url, client.id, title);
       } catch (e) {
         console.error("Could not send tab to device.", e);
       }
     }
   },
 
   populateSendTabToDevicesMenu(devicesPopup, url, title, multiselected, createDeviceNodeFn) {
     if (!createDeviceNodeFn) {
-      createDeviceNodeFn = (clientId, name, clientType, lastModified) => {
+      createDeviceNodeFn = (targetId, name, targetType, lastModified) => {
         let eltName = name ? "menuitem" : "menuseparator";
         return document.createXULElement(eltName);
       };
     }
 
     // remove existing menu items
     for (let i = devicesPopup.children.length - 1; i >= 0; --i) {
       let child = devicesPopup.children[i];
@@ -380,17 +371,17 @@ var gSync = {
     if (gSync.syncConfiguredAndLoading) {
       // We can only be in this case in the page action menu.
       return;
     }
 
     const fragment = document.createDocumentFragment();
 
     const state = UIState.get();
-    if (state.status == UIState.STATUS_SIGNED_IN && this.remoteClients.length > 0) {
+    if (state.status == UIState.STATUS_SIGNED_IN && this.sendTabTargets.length > 0) {
       this._appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title, multiselected);
     } else if (state.status == UIState.STATUS_SIGNED_IN) {
       this._appendSendTabSingleDevice(fragment, createDeviceNodeFn);
     } else if (state.status == UIState.STATUS_NOT_VERIFIED ||
                state.status == UIState.STATUS_LOGIN_FAILED) {
       this._appendSendTabVerify(fragment, createDeviceNodeFn);
     } else /* status is STATUS_NOT_CONFIGURED */ {
       this._appendSendTabUnconfigured(fragment, createDeviceNodeFn);
@@ -398,56 +389,64 @@ var gSync = {
 
     devicesPopup.appendChild(fragment);
   },
 
   // TODO: once our transition from the old-send tab world is complete,
   // this list should be built using the FxA device list instead of the client
   // collection.
   _appendSendTabDeviceList(fragment, createDeviceNodeFn, url, title, multiselected) {
+    const targets = this.sendTabTargets;
+
     let tabsToSend = multiselected ?
       gBrowser.selectedTabs.map(t => {
         return {
           url: t.linkedBrowser.currentURI.spec,
           title: t.linkedBrowser.contentTitle,
         };
       }) : [{url, title}];
 
     const onSendAllCommand = (event) => {
       for (let t of tabsToSend) {
-        this.sendTabToDevice(t.url, this.remoteClients, t.title);
+        this.sendTabToDevice(t.url, targets, t.title);
       }
     };
     const onTargetDeviceCommand = (event) => {
-      const clientId = event.target.getAttribute("clientId");
-      const client = this.remoteClients.find(c => c.id == clientId);
+      const targetId = event.target.getAttribute("clientId");
+      const target = targets.find(t => t.id == targetId);
       for (let t of tabsToSend) {
-        this.sendTabToDevice(t.url, [client], t.title);
+        this.sendTabToDevice(t.url, [target], t.title);
       }
     };
 
-    function addTargetDevice(clientId, name, clientType, lastModified) {
-      const targetDevice = createDeviceNodeFn(clientId, name, clientType, lastModified);
-      targetDevice.addEventListener("command", clientId ? onTargetDeviceCommand :
+    function addTargetDevice(targetId, name, targetType, lastModified) {
+      const targetDevice = createDeviceNodeFn(targetId, name, targetType, lastModified);
+      targetDevice.addEventListener("command", targetId ? onTargetDeviceCommand :
                                                           onSendAllCommand, true);
       targetDevice.classList.add("sync-menuitem", "sendtab-target");
-      targetDevice.setAttribute("clientId", clientId);
-      targetDevice.setAttribute("clientType", clientType);
+      targetDevice.setAttribute("clientId", targetId);
+      targetDevice.setAttribute("clientType", targetType);
       targetDevice.setAttribute("label", name);
       fragment.appendChild(targetDevice);
     }
 
-    const clients = this.remoteClients;
-    for (let client of clients) {
-      const type = Weave.Service.clientsEngine.getClientType(client.id);
-      addTargetDevice(client.id, client.name, type, new Date(client.serverLastModified * 1000));
+    for (let target of targets) {
+      let type, lastModified;
+      if (target.clientRecord) {
+        type = Weave.Service.clientsEngine.getClientType(target.clientRecord.id);
+        lastModified = new Date(target.clientRecord.serverLastModified * 1000);
+      } else {
+        type = target.type === "desktop" ? "desktop" : "phone"; // Normalizing the FxA types just in case.
+        lastModified = null;
+      }
+      addTargetDevice(target.id, target.name, type, lastModified);
     }
 
     // "Send to All Devices" menu item
-    if (clients.length > 1) {
+    if (targets.length > 1) {
       const separator = createDeviceNodeFn();
       separator.classList.add("sync-menuitem");
       fragment.appendChild(separator);
       const allDevicesLabel = this.fxaStrings.GetStringFromName("sendToAllDevices.menuitem");
       addTargetDevice("", allDevicesLabel, "");
     }
   },
 
--- a/browser/base/content/test/sync/browser_contextmenu_sendpage.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendpage.js
@@ -1,25 +1,25 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+const targetsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
 
 add_task(async function setup() {
   await promiseSyncReady();
   // gSync.init() is called in a requestIdleCallback. Force its initialization.
   gSync.init();
   sinon.stub(Weave.Service.clientsEngine, "getClientType").returns("desktop");
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
 });
 
 add_task(async function test_page_contextmenu() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Foo" },
     { label: "Bar" },
@@ -27,17 +27,17 @@ add_task(async function test_page_contex
     { label: "Send to All Devices" },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_link_contextmenu() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
   let expectation = sandbox.mock(gSync)
                            .expects("sendTabToDevice")
                            .once()
                            .withExactArgs("https://www.example.org/", [{id: 1, name: "Foo"}], "Click on me!!");
 
   // Add a link to the page
   await ContentTask.spawn(gBrowser.selectedBrowser, null, () => {
@@ -54,17 +54,17 @@ add_task(async function test_link_contex
   document.getElementById("context-sendlinktodevice-popup").querySelector("menuitem").click();
   await hideContentContextMenu();
 
   expectation.verify();
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_no_remote_clients() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: [],
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: [],
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "No Devices Connected", disabled: true },
     "----",
@@ -72,88 +72,88 @@ add_task(async function test_page_contex
     { label: "Learn About Sending Tabs..." },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_one_remote_client() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: [{ id: 1, name: "Foo"}],
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: [{ id: 1, name: "Foo"}],
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Foo" },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_not_sendable() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: false });
 
   await openContentContextMenu("#moztext");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
   checkPopup();
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_not_synced_yet() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, remoteClients: [],
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, targets: [],
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
 
   await openContentContextMenu("#moztext");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
   checkPopup();
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_sync_not_ready_configured() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
 
   await openContentContextMenu("#moztext");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, true, "Send tab to device is disabled");
   checkPopup();
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_sync_not_ready_other_state() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
                                       state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Account Not Verified", disabled: true },
     "----",
     { label: "Verify Your Account..." },
   ]);
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_unconfigured() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: null,
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: null,
                                       state: UIState.STATUS_NOT_CONFIGURED, isSendableURI: true });
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Not Connected to Sync", disabled: true },
     "----",
@@ -162,17 +162,17 @@ add_task(async function test_page_contex
   ]);
 
   await hideContentContextMenu();
 
   sandbox.restore();
 });
 
 add_task(async function test_page_contextmenu_not_verified() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: null,
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: null,
                                       state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
 
   await openContentContextMenu("#moztext", "context-sendpagetodevice");
   is(document.getElementById("context-sendpagetodevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context-sendpagetodevice").disabled, false, "Send tab to device is enabled");
   checkPopup([
     { label: "Account Not Verified", disabled: true },
     "----",
--- a/browser/base/content/test/sync/browser_contextmenu_sendtab.js
+++ b/browser/base/content/test/sync/browser_contextmenu_sendtab.js
@@ -2,17 +2,17 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 const chrome_base = "chrome://mochitests/content/browser/browser/base/content/test/general/";
 Services.scriptloader.loadSubScript(chrome_base + "head.js", this);
 /* import-globals-from ../general/head.js */
 
-const remoteClientsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
+const targetsFixture = [ { id: 1, name: "Foo"}, { id: 2, name: "Bar"} ];
 
 let [testTab] = gBrowser.visibleTabs;
 
 function updateTabContextMenu(tab) {
   let menu = document.getElementById("tabContextMenu");
   if (!tab)
     tab = gBrowser.selectedTab;
   var evt = new Event("");
@@ -30,17 +30,17 @@ add_task(async function setup() {
   await BrowserTestUtils.openNewForegroundTab(gBrowser, "about:mozilla");
   registerCleanupFunction(() => {
     gBrowser.removeCurrentTab();
   });
   is(gBrowser.visibleTabs.length, 2, "there are two visible tabs");
 });
 
 add_task(async function test_tab_contextmenu() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
   let expectation = sandbox.mock(gSync)
                            .expects("sendTabToDevice")
                            .once()
                            .withExactArgs("about:mozilla", [{id: 1, name: "Foo"}], "The Book of Mozilla, 11:14");
 
   updateTabContextMenu(testTab);
   await openTabContextMenu("context_sendTabToDevice");
@@ -50,61 +50,61 @@ add_task(async function test_tab_context
   document.getElementById("context_sendTabToDevicePopupMenu").querySelector("menuitem").click();
 
   await hideTabContextMenu();
   expectation.verify();
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_unconfigured() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: remoteClientsFixture,
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: targetsFixture,
                                       state: UIState.STATUS_NOT_CONFIGURED, isSendableURI: true });
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_not_sendable() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, remoteClients: [{ id: 1, name: "Foo"}],
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: true, targets: [{ id: 1, name: "Foo"}],
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: false });
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_not_synced_yet() {
-  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, remoteClients: [],
+  const sandbox = setupSendTabMocks({ syncReady: true, clientsSynced: false, targets: [],
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_sync_not_ready_configured() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
                                       state: UIState.STATUS_SIGNED_IN, isSendableURI: true });
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, true, "Send tab to device is disabled");
 
   sandbox.restore();
 });
 
 add_task(async function test_tab_contextmenu_sync_not_ready_other_state() {
-  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, remoteClients: null,
+  const sandbox = setupSendTabMocks({ syncReady: false, clientsSynced: false, targets: null,
                                       state: UIState.STATUS_NOT_VERIFIED, isSendableURI: true });
 
   updateTabContextMenu(testTab);
   is(document.getElementById("context_sendTabToDevice").hidden, false, "Send tab to device is shown");
   is(document.getElementById("context_sendTabToDevice").disabled, false, "Send tab to device is enabled");
 
   sandbox.restore();
 });
--- a/browser/base/content/test/sync/head.js
+++ b/browser/base/content/test/sync/head.js
@@ -9,17 +9,17 @@ registerCleanupFunction(function() {
 
 function promiseSyncReady() {
   let service = Cc["@mozilla.org/weave/service;1"]
                   .getService(Ci.nsISupports)
                   .wrappedJSObject;
   return service.whenLoaded();
 }
 
-function setupSendTabMocks({ syncReady, clientsSynced, remoteClients, state, isSendableURI }) {
+function setupSendTabMocks({ syncReady, clientsSynced, targets, state, isSendableURI }) {
   const sandbox = sinon.sandbox.create();
   sandbox.stub(gSync, "syncReady").get(() => syncReady);
   sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => !clientsSynced);
-  sandbox.stub(gSync, "remoteClients").get(() => remoteClients);
+  sandbox.stub(gSync, "sendTabTargets").get(() => targets);
   sandbox.stub(UIState, "get").returns({ status: state });
   sandbox.stub(gSync, "isSendableURI").returns(isSendableURI);
   return sandbox;
 }
--- a/browser/base/content/test/urlbar/browser_page_action_menu.js
+++ b/browser/base/content/test/urlbar/browser_page_action_menu.js
@@ -3,20 +3,21 @@
 /* global sinon, UIState */
 Services.scriptloader.loadSubScript("resource://testing-common/sinon-2.3.2.js");
 
 registerCleanupFunction(function() {
   delete window.sinon;
 });
 
 const lastModifiedFixture = 1507655615.87; // Approx Oct 10th 2017
-const mockRemoteClients = [
-  { id: "0", name: "foo", type: "mobile", serverLastModified: lastModifiedFixture },
-  { id: "1", name: "bar", type: "desktop", serverLastModified: lastModifiedFixture },
-  { id: "2", name: "baz", type: "mobile", serverLastModified: lastModifiedFixture },
+const mockTargets = [
+  { id: "0", name: "foo", type: "phone", clientRecord: {id: "cli0", serverLastModified: lastModifiedFixture, type: "phone"} },
+  { id: "1", name: "bar", type: "desktop", clientRecord: {id: "cli1", serverLastModified: lastModifiedFixture, type: "desktop"} },
+  { id: "2", name: "baz", type: "phone", clientRecord: {id: "cli2", serverLastModified: lastModifiedFixture, type: "phone"} },
+  { id: "3", name: "no client record device", type: "phone" },
 ];
 
 add_task(async function bookmark() {
   // Open a unique page.
   let url = "http://example.com/browser_page_action_menu";
   await BrowserTestUtils.withNewTab(url, async () => {
     // Open the panel.
     await promisePageActionPanelOpen();
@@ -263,18 +264,18 @@ add_task(async function sendToDevice_syn
     const syncReady = sandbox.stub(gSync, "syncReady").get(() => false);
     const lastSync = sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => true);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
 
     sandbox.stub(Weave.Service, "sync").callsFake(() => {
       syncReady.get(() => true);
       lastSync.get(() => Date.now());
-      sandbox.stub(gSync, "remoteClients").get(() => mockRemoteClients);
-      sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockRemoteClients.find(c => c.id == id).type);
+      sandbox.stub(gSync, "sendTabTargets").get(() => mockTargets);
+      sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
     });
 
     let onShowingSubview = BrowserPageActions.sendToDevice.onShowingSubview;
     sandbox.stub(BrowserPageActions.sendToDevice, "onShowingSubview").callsFake((...args) => {
       this.numCall++ || (this.numCall = 1);
       onShowingSubview.call(BrowserPageActions.sendToDevice, ...args);
       testSendTabToDeviceMenu(this.numCall);
     });
@@ -309,23 +310,23 @@ add_task(async function sendToDevice_syn
         // The devices should be shown in the subview.
         let expectedItems = [
           {
             className: "pageAction-sendToDevice-notReady",
             display: "none",
             disabled: true,
           },
         ];
-        for (let client of mockRemoteClients) {
+        for (let target of mockTargets) {
           expectedItems.push({
             attrs: {
-              clientId: client.id,
-              label: client.name,
-              clientType: client.type,
-              tooltiptext: gSync.formatLastSyncDate(new Date(lastModifiedFixture * 1000)),
+              clientId: target.id,
+              label: target.name,
+              clientType: target.type,
+              tooltiptext: target.clientRecord ? gSync.formatLastSyncDate(new Date(lastModifiedFixture * 1000)) : "",
             },
           });
         }
         expectedItems.push(
           null,
           {
             attrs: {
               label: "Send to All Devices",
@@ -400,18 +401,18 @@ add_task(async function sendToDevice_noD
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => true);
     sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
-    sandbox.stub(gSync, "remoteClients").get(() => []);
-    sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockRemoteClients.find(c => c.id == id).type);
+    sandbox.stub(gSync, "sendTabTargets").get(() => []);
+    sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
     let cleanUp = () => {
       sandbox.restore();
     };
     registerCleanupFunction(cleanUp);
 
     // Open the panel.
     await promisePageActionPanelOpen();
@@ -466,18 +467,18 @@ add_task(async function sendToDevice_dev
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => true);
     sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
-    sandbox.stub(gSync, "remoteClients").get(() => mockRemoteClients);
-    sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockRemoteClients.find(c => c.id == id).type);
+    sandbox.stub(gSync, "sendTabTargets").get(() => mockTargets);
+    sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
     let cleanUp = () => {
       sandbox.restore();
     };
     registerCleanupFunction(cleanUp);
 
     // Open the panel.
     await promisePageActionPanelOpen();
@@ -494,22 +495,22 @@ add_task(async function sendToDevice_dev
     // The devices should be shown in the subview.
     let expectedItems = [
       {
         className: "pageAction-sendToDevice-notReady",
         display: "none",
         disabled: true,
       },
     ];
-    for (let client of mockRemoteClients) {
+    for (let target of mockTargets) {
       expectedItems.push({
         attrs: {
-          clientId: client.id,
-          label: client.name,
-          clientType: client.type,
+          clientId: target.id,
+          label: target.name,
+          clientType: target.type,
         },
       });
     }
     expectedItems.push(
       null,
       {
         attrs: {
           label: "Send to All Devices",
@@ -532,18 +533,18 @@ add_task(async function sendToDevice_tit
   await BrowserTestUtils.withNewTab("http://example.com/a", async otherBrowser => {
     await BrowserTestUtils.withNewTab("http://example.com/b", async () => {
       await promiseSyncReady();
       const sandbox = sinon.sandbox.create();
       sandbox.stub(gSync, "syncReady").get(() => true);
       sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
       sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
       sandbox.stub(gSync, "isSendableURI").returns(true);
-      sandbox.stub(gSync, "remoteClients").get(() => []);
-      sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockRemoteClients.find(c => c.id == id).type);
+      sandbox.stub(gSync, "sendTabTargets").get(() => []);
+      sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
       let cleanUp = () => {
         sandbox.restore();
       };
       registerCleanupFunction(cleanUp);
 
       // Open the panel.  Only one tab is selected, so the action's title should
       // be "Send Tab to Device".
@@ -589,18 +590,18 @@ add_task(async function sendToDevice_inU
   // Open a tab that's sendable.
   await BrowserTestUtils.withNewTab("http://example.com/", async () => {
     await promiseSyncReady();
     const sandbox = sinon.sandbox.create();
     sandbox.stub(gSync, "syncReady").get(() => true);
     sandbox.stub(Weave.Service.clientsEngine, "isFirstSync").get(() => false);
     sandbox.stub(UIState, "get").returns({ status: UIState.STATUS_SIGNED_IN });
     sandbox.stub(gSync, "isSendableURI").returns(true);
-    sandbox.stub(gSync, "remoteClients").get(() => mockRemoteClients);
-    sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockRemoteClients.find(c => c.id == id).type);
+    sandbox.stub(gSync, "sendTabTargets").get(() => mockTargets);
+    sandbox.stub(Weave.Service.clientsEngine, "getClientType").callsFake(id => mockTargets.find(c => c.clientRecord && c.clientRecord.id == id).clientRecord.type);
 
     let cleanUp = () => {
       sandbox.restore();
     };
     registerCleanupFunction(cleanUp);
 
     // Add Send to Device to the urlbar.
     let action = PageActions.actionForID("sendToDevice");
@@ -623,22 +624,22 @@ add_task(async function sendToDevice_inU
     // The devices should be shown in the subview.
     let expectedItems = [
       {
         className: "pageAction-sendToDevice-notReady",
         display: "none",
         disabled: true,
       },
     ];
-    for (let client of mockRemoteClients) {
+    for (let target of mockTargets) {
       expectedItems.push({
         attrs: {
-          clientId: client.id,
-          label: client.name,
-          clientType: client.type,
+          clientId: target.id,
+          label: target.name,
+          clientType: target.type,
         },
       });
     }
     expectedItems.push(
       null,
       {
         attrs: {
           label: "Send to All Devices",
--- a/services/fxaccounts/FxAccountsCommands.js
+++ b/services/fxaccounts/FxAccountsCommands.js
@@ -174,24 +174,19 @@ class SendTab {
         log.error("Error while invoking a send tab command.", error);
         report.failed.push({device, error});
       }
     }
     return report;
   }
 
   // Returns true if the target device is compatible with FxA Commands Send tab.
-  async isDeviceCompatible(device) {
-    if (!Services.prefs.getBoolPref("identity.fxaccounts.commands.enabled", true) ||
-        !device.availableCommands || !device.availableCommands[COMMAND_SENDTAB]) {
-      return false;
-    }
-    const {kid: theirKid} = JSON.parse(device.availableCommands[COMMAND_SENDTAB]);
-    const ourKid = await this._getKid();
-    return theirKid == ourKid;
+  isDeviceCompatible(device) {
+    return Services.prefs.getBoolPref("identity.fxaccounts.commands.enabled", true) &&
+           device.availableCommands && device.availableCommands[COMMAND_SENDTAB];
   }
 
   // Handle incoming send tab payload, called by FxAccountsCommands.
   async handle(sender, {encrypted}) {
     if (!sender) {
       log.warn("Incoming tab is from an unknown device (maybe disconnected?)");
     }
     const bytes = await this._decrypt(encrypted);
@@ -203,30 +198,29 @@ class SendTab {
       id: sender ? sender.id : "",
       name: sender ? sender.name : "",
     };
     const {title, url: uri} = data.entries[current];
     log.info(`Tab received with FxA commands: ${title} from ${tabSender.name}.`);
     Observers.notify("fxaccounts:commands:open-uri", [{uri, title, sender: tabSender}]);
   }
 
-  async _getKid() {
-    let {kXCS} = await this._fxAccounts.getKeys();
-    return kXCS;
-  }
-
   async _encrypt(bytes, device) {
     let bundle = device.availableCommands[COMMAND_SENDTAB];
     if (!bundle) {
       throw new Error(`Device ${device.id} does not have send tab keys.`);
     }
+    const {kSync, kXCS: ourKid} = await this._fxAccounts.getKeys();
+    const {kid: theirKid} = JSON.parse(device.availableCommands[COMMAND_SENDTAB]);
+    if (theirKid != ourKid) {
+      throw new Error("Target Send Tab key ID is different from ours");
+    }
     const json = JSON.parse(bundle);
     const wrapper = new CryptoWrapper();
     wrapper.deserialize({payload: json});
-    const {kSync} = await this._fxAccounts.getKeys();
     const syncKeyBundle = BulkKeyBundle.fromHexKey(kSync);
     let {publicKey, authSecret} = await wrapper.decrypt(syncKeyBundle);
     authSecret = urlsafeBase64Decode(authSecret);
     publicKey = urlsafeBase64Decode(publicKey);
 
     const {ciphertext: encrypted} = await PushCrypto.encrypt(bytes, publicKey, authSecret);
     return urlsafeBase64Encode(encrypted);
   }
@@ -273,27 +267,26 @@ class SendTab {
     if (!sendTabKeys) {
       sendTabKeys = await this._generateAndPersistKeys();
     }
     // Strip the private key from the bundle to encrypt.
     const keyToEncrypt = {
       publicKey: sendTabKeys.publicKey,
       authSecret: sendTabKeys.authSecret,
     };
-    const {kSync} = await this._fxAccounts.getSignedInUser();
-    if (!kSync) {
+    const {kSync, kXCS} = await this._fxAccounts.getKeys();
+    if (!kSync || !kXCS) {
       return null;
     }
     const wrapper = new CryptoWrapper();
     wrapper.cleartext = keyToEncrypt;
     const keyBundle = BulkKeyBundle.fromHexKey(kSync);
     await wrapper.encrypt(keyBundle);
-    const kid = await this._getKid();
     return JSON.stringify({
-      kid,
+      kid: kXCS,
       IV: wrapper.IV,
       hmac: wrapper.hmac,
       ciphertext: wrapper.ciphertext,
     });
   }
 }
 
 function urlsafeBase64Encode(buffer) {
--- a/services/fxaccounts/tests/xpcshell/test_commands.js
+++ b/services/fxaccounts/tests/xpcshell/test_commands.js
@@ -2,40 +2,25 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
 ChromeUtils.import("resource://testing-common/Assert.jsm");
 ChromeUtils.import("resource://gre/modules/FxAccountsCommands.js");
 
 add_task(async function test_sendtab_isDeviceCompatible() {
-  const fxAccounts = {
-    getKeys() {
-      return {
-        kXCS: "abcd",
-      };
-    },
-  };
-  const sendTab = new SendTab(null, fxAccounts);
+  const sendTab = new SendTab(null, null);
   let device = {name: "My device"};
-  Assert.ok(!(await sendTab.isDeviceCompatible(device)));
+  Assert.ok(!sendTab.isDeviceCompatible(device));
   device = {name: "My device", availableCommands: {}};
-  Assert.ok(!(await sendTab.isDeviceCompatible(device)));
+  Assert.ok(!sendTab.isDeviceCompatible(device));
   device = {name: "My device", availableCommands: {
-    "https://identity.mozilla.com/cmd/open-uri": JSON.stringify({
-      kid: "dcba",
-    }),
+    "https://identity.mozilla.com/cmd/open-uri": "payload",
   }};
-  Assert.ok(!(await sendTab.isDeviceCompatible(device)));
-  device = {name: "My device", availableCommands: {
-    "https://identity.mozilla.com/cmd/open-uri": JSON.stringify({
-      kid: "abcd",
-    }),
-  }};
-  Assert.ok((await sendTab.isDeviceCompatible(device)));
+  Assert.ok(sendTab.isDeviceCompatible(device));
 });
 
 add_task(async function test_sendtab_send() {
   const commands = {
     invoke: sinon.spy((cmd, device, payload) => {
       if (device.name == "Device 1") {
         throw new Error("Invoke error!");
       }
--- a/services/sync/modules/engines/clients.js
+++ b/services/sync/modules/engines/clients.js
@@ -45,16 +45,20 @@ ChromeUtils.defineModuleGetter(this, "ge
 
 const CLIENTS_TTL = 1814400; // 21 days
 const CLIENTS_TTL_REFRESH = 604800; // 7 days
 const STALE_CLIENT_REMOTE_AGE = 604800; // 7 days
 
 // TTL of the message sent to another device when sending a tab
 const NOTIFY_TAB_SENT_TTL_SECS = 1 * 3600; // 1 hour
 
+// This is to avoid multiple sequential syncs ending up calling
+// this expensive endpoint multiple times in a row.
+const TIME_BETWEEN_FXA_DEVICES_FETCH_MS = 10 * 1000;
+
 // Reasons behind sending collection_changed push notifications.
 const COLLECTION_MODIFIED_REASON_SENDTAB = "sendtab";
 const COLLECTION_MODIFIED_REASON_FIRSTSYNC = "firstsync";
 
 const SUPPORTED_PROTOCOL_VERSIONS = [SYNC_API_VERSION];
 const LAST_MODIFIED_ON_PROCESS_COMMAND_PREF = "services.sync.clients.lastModifiedOnProcessCommands";
 
 function hasDupeCommand(commands, action) {
@@ -125,16 +129,20 @@ ClientEngine.prototype = {
 
   get lastRecordUpload() {
     return Svc.Prefs.get(this.name + ".lastRecordUpload", 0);
   },
   set lastRecordUpload(value) {
     Svc.Prefs.set(this.name + ".lastRecordUpload", Math.floor(value));
   },
 
+  get fxaDevices() {
+    return this._fxaDevices;
+  },
+
   get remoteClients() {
     // return all non-stale clients for external consumption.
     return Object.values(this._store._remoteClients).filter(v => !v.stale);
   },
 
   remoteClient(id) {
     let client = this._store._remoteClients[id];
     return client && !client.stale ? client : null;
@@ -322,17 +330,18 @@ ClientEngine.prototype = {
   async _removeClientCommands(clientId) {
     const allCommands = await this._readCommands();
     delete allCommands[clientId];
     await this._saveCommands(allCommands);
   },
 
   async updateKnownStaleClients() {
     this._log.debug("Updating the known stale clients");
-    await this._refreshKnownStaleClients();
+    // _fetchFxADevices side effect updates this._knownStaleFxADeviceIds.
+    await this._fetchFxADevices();
     let localFxADeviceId = await fxAccounts.getDeviceId();
     // Process newer records first, so that if we hit a record with a device ID
     // we've seen before, we can mark it stale immediately.
     let clientList = Object.values(this._store._remoteClients).sort((a, b) =>
       b.serverLastModified - a.serverLastModified);
     let seenDeviceIds = new Set([localFxADeviceId]);
     for (let client of clientList) {
       // Clients might not have an `fxaDeviceId` if they fail the FxA
@@ -348,32 +357,40 @@ ClientEngine.prototype = {
                        ` - duplicate device id ${client.fxaDeviceId}`);
         client.stale = true;
       } else {
         seenDeviceIds.add(client.fxaDeviceId);
       }
     }
   },
 
-  // We assume that clients not present in the FxA Device Manager list have been
-  // disconnected and so are stale
-  async _refreshKnownStaleClients() {
+  async _fetchFxADevices() {
+    const now = new Date().getTime();
+    if ((this._lastFxADevicesFetch || 0) + TIME_BETWEEN_FXA_DEVICES_FETCH_MS >= now) {
+      return;
+    }
+    const remoteClients = Object.values(this.remoteClients);
+    try {
+      this._fxaDevices = await this.fxAccounts.getDeviceList();
+      for (const device of this._fxaDevices) {
+        device.clientRecord = remoteClients.find(c => c.fxaDeviceId == device.id);
+      }
+    } catch (e) {
+      this._log.error("Could not retrieve the FxA device list", e);
+      this._fxaDevices = [];
+    }
+    this._lastFxADevicesFetch = now;
+
+    // We assume that clients not present in the FxA Device Manager list have been
+    // disconnected and so are stale
     this._log.debug("Refreshing the known stale clients list");
     let localClients = Object.values(this._store._remoteClients)
                              .filter(client => client.fxaDeviceId) // iOS client records don't have fxaDeviceId
                              .map(client => client.fxaDeviceId);
-    let fxaClients;
-    try {
-      let deviceList = await this.fxAccounts.getDeviceList();
-      fxaClients = deviceList.map(device => device.id);
-    } catch (ex) {
-      this._log.error("Could not retrieve the FxA device list", ex);
-      this._knownStaleFxADeviceIds = [];
-      return;
-    }
+    const fxaClients = this._fxaDevices.map(device => device.id);
     this._knownStaleFxADeviceIds = Utils.arraySub(localClients, fxaClients);
   },
 
   async _syncStartup() {
     // Reupload new client record periodically.
     if (Date.now() / 1000 - this.lastRecordUpload > CLIENTS_TTL_REFRESH) {
       await this._tracker.addChangedID(this.localID);
     }
@@ -381,21 +398,18 @@ ClientEngine.prototype = {
   },
 
   async _processIncoming() {
     // Fetch all records from the server.
     await this.resetLastSync();
     this._incomingClients = {};
     try {
       await SyncEngine.prototype._processIncoming.call(this);
-      // Refresh the known stale clients list at startup and when we receive
-      // "device connected/disconnected" push notifications.
-      if (!this._knownStaleFxADeviceIds) {
-        await this._refreshKnownStaleClients();
-      }
+      // Update FxA Device list.
+      await this._fetchFxADevices();
       // Since clients are synced unconditionally, any records in the local store
       // that don't exist on the server must be for disconnected clients. Remove
       // them, so that we don't upload records with commands for clients that will
       // never see them. We also do this to filter out stale clients from the
       // tabs collection, since showing their list of tabs is confusing.
       for (let id in this._store._remoteClients) {
         if (!this._incomingClients[id]) {
           this._log.info(`Removing local state for deleted client ${id}`);
--- a/services/sync/tests/unit/test_clients_engine.js
+++ b/services/sync/tests/unit/test_clients_engine.js
@@ -43,16 +43,17 @@ function compareCommands(actual, expecte
   tweakedActual.map(elt => delete elt.flowID);
   deepEqual(tweakedActual, expected, description);
   // each item must have a unique flowID.
   let allIDs = new Set(actual.map(elt => elt.flowID).filter(fid => !!fid));
   equal(allIDs.size, actual.length, "all items have unique IDs");
 }
 
 async function syncClientsEngine(server) {
+  engine._lastFxADevicesFetch = 0;
   engine.lastModified = server.getCollection("foo", "clients").timestamp;
   await engine._sync();
 }
 
 add_task(async function setup() {
   engine = Service.clientsEngine;
 });
 
@@ -1818,64 +1819,28 @@ add_task(async function device_disconnec
 });
 
 add_task(async function update_known_stale_clients() {
   const makeFakeClient = (id) => ({ id, fxaDeviceId: `fxa-${id}` });
   const clients = [makeFakeClient("one"), makeFakeClient("two"), makeFakeClient("three")];
   const stubRemoteClients = sinon.stub(engine._store, "_remoteClients").get(() => {
     return clients;
   });
-  const stubRefresh = sinon.stub(engine, "_refreshKnownStaleClients", () => {
+  const stubFetchFxADevices = sinon.stub(engine, "_fetchFxADevices", () => {
     engine._knownStaleFxADeviceIds = ["fxa-one", "fxa-two"];
   });
 
   engine._knownStaleFxADeviceIds = null;
   await engine.updateKnownStaleClients();
   ok(clients[0].stale);
   ok(clients[1].stale);
   ok(!clients[2].stale);
 
   stubRemoteClients.restore();
-  stubRefresh.restore();
-});
-
-add_task(async function process_incoming_refreshes_known_stale_clients() {
-  const stubProcessIncoming = sinon.stub(SyncEngine.prototype, "_processIncoming");
-  const stubRefresh = sinon.stub(engine, "_refreshKnownStaleClients", () => {
-    engine._knownStaleFxADeviceIds = ["one", "two"];
-  });
-
-  engine._knownStaleFxADeviceIds = null;
-  await engine._processIncoming();
-  ok(stubRefresh.calledOnce, "Should refresh the known stale clients");
-  stubRefresh.reset();
-
-  await engine._processIncoming();
-  ok(stubRefresh.notCalled, "Should not refresh the known stale clients since it's already populated");
-
-  stubProcessIncoming.restore();
-  stubRefresh.restore();
-});
-
-add_task(async function process_incoming_refreshes_known_stale_clients() {
-  Services.prefs.clearUserPref("services.sync.clients.lastModifiedOnProcessCommands");
-  engine._localClientLastModified = Math.round(Date.now() / 1000);
-
-  const stubRemoveLocalCommand = sinon.stub(engine, "removeLocalCommand");
-  const tabProcessedSpy = sinon.spy(engine, "_handleDisplayURIs");
-  engine.localCommands = [{ command: "displayURI", args: ["https://foo.bar", "fxaid1", "foo"] }];
-
-  await engine.processIncomingCommands();
-  ok(tabProcessedSpy.calledOnce);
-  // Let's say we failed to upload and we end up calling processIncomingCommands again
-  await engine.processIncomingCommands();
-  ok(tabProcessedSpy.calledOnce);
-
-  tabProcessedSpy.restore();
-  stubRemoveLocalCommand.restore();
+  stubFetchFxADevices.restore();
 });
 
 add_task(async function test_create_record_command_limit() {
   await engine._store.wipe();
   await generateNewKeys(Service.collectionKeys);
 
   let server = await serverForFoo(engine);
   await SyncTestingInfrastructure(server);