/* Any copyright is dedicated to the Public Domain.
 * http://creativecommons.org/publicdomain/zero/1.0/ */

ChromeUtils.defineESModuleGetters(globalThis, {
  SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
});

const twoTabs = [
  {
    type: "tab",
    title: "Phabricator Home",
    url: "https://phabricator.services.mozilla.com/",
    icon: "https://phabricator.services.mozilla.com/favicon.d25d81d39065.ico",
    lastUsed: 1655745700, // Mon, 20 Jun 2022 17:21:40 GMT
  },
  {
    type: "tab",
    title: "Firefox Privacy Notice",
    url: "https://www.mozilla.org/en-US/privacy/firefox/",
    icon: "https://www.mozilla.org/media/img/favicons/mozilla/favicon.d25d81d39065.ico",
    lastUsed: 1655745700, // Mon, 20 Jun 2022 17:21:40 GMT
  },
];
const syncedTabsData2 = structuredClone(syncedTabsData1);
syncedTabsData2[1].tabs = [...syncedTabsData2[1].tabs, ...twoTabs];

const syncedTabsData3 = [
  {
    id: 1,
    type: "client",
    name: "My desktop",
    clientType: "desktop",
    lastModified: 1655730486760,
    tabs: [
      {
        type: "tab",
        title: "Sandboxes - Sinon.JS",
        url: "https://sinonjs.org/releases/latest/sandbox/",
        icon: "https://sinonjs.org/assets/images/favicon.png",
        lastUsed: 1655391592, // Thu Jun 16 2022 14:59:52 GMT+0000
      },
    ],
  },
];

const syncedTabsData4 = structuredClone(syncedTabsData3);
syncedTabsData4[0].tabs = [...syncedTabsData4[0].tabs, ...twoTabs];

const syncedTabsData5 = [
  {
    id: 1,
    type: "client",
    name: "My desktop",
    clientType: "desktop",
    lastModified: Date.now(),
    tabs: [
      {
        type: "tab",
        title: "Example2",
        url: "https://example.com",
        icon: "https://example/favicon.png",
        lastUsed: Math.floor((Date.now() - 1000 * 60) / 1000), // This is one minute from now, which is below the threshold for 'Just now'
      },
    ],
  },
];

const desktopTabs = [
  {
    type: "tab",
    title: "Internet for people, not profits - Mozilla",
    url: "https://www.mozilla.org/",
    icon: "https://www.mozilla.org/media/img/favicons/mozilla/favicon.d25d81d39065.ico",
    lastUsed: 1655730486, // Mon Jan 19 1970 22:55:30 GMT+0000
  },
  {
    type: "tab",
    title: "Firefox Privacy Notice",
    url: "https://www.mozilla.org/en-US/privacy/firefox/",
    icon: "https://www.mozilla.org/media/img/favicons/mozilla/favicon.d25d81d39065.ico",
    lastUsed: 1673991540155, // Tue, 17 Jan 2023 16:39:00 GMT
  },
  {
    type: "tab",
    title: "Bugzilla Main Page",
    url: "https://bugzilla.mozilla.org/",
    icon: "https://bugzilla.mozilla.org/extensions/BMO/web/images/favicon.ico",
    lastUsed: 1673513538000, // Thu, 12 Jan 2023 03:52:18 GMT
  },
];

const mobileTabs = [
  {
    type: "tab",
    title: "Internet for people, not profits - Mozilla",
    url: "https://www.mozilla.org/",
    icon: "https://www.mozilla.org/media/img/favicons/mozilla/favicon.d25d81d39065.ico",
    lastUsed: 1606510800000, // Fri Nov 27 2020 16:00:00 GMT+0000
  },
  {
    type: "tab",
    title: "Firefox Privacy Notice",
    url: "https://www.mozilla.org/en-US/privacy/firefox/",
    icon: "https://www.mozilla.org/media/img/favicons/mozilla/favicon.d25d81d39065.ico",
    lastUsed: 1606510800000, // Fri Nov 27 2020 16:00:00 GMT+0000
  },
];

const syncedTabsData6 = structuredClone(syncedTabsData1);
syncedTabsData6[0].tabs = desktopTabs;
syncedTabsData6[1].tabs = mobileTabs;

const NO_TABS_EVENTS = [
  ["firefoxview", "entered", "firefoxview", undefined],
  ["firefoxview", "synced_tabs", "tabs", undefined, { count: "0" }],
];

const TAB_PICKUP_EVENT = [
  ["firefoxview", "entered", "firefoxview", undefined],
  ["firefoxview", "synced_tabs", "tabs", undefined, { count: "1" }],
  [
    "firefoxview",
    "tab_pickup",
    "tabs",
    undefined,
    { position: "1", deviceType: "desktop" },
  ],
];

const TAB_PICKUP_OPEN_EVENT = [
  ["firefoxview", "tab_pickup_open", "tabs", "false"],
];

registerCleanupFunction(async function () {
  cleanup_tab_pickup();
});

add_setup(async function setup() {
  // set updateTimeMs to 0 to prevent unexpected/unrelated DOM mutations during testing
  await SpecialPowers.pushPrefEnv({
    set: [["browser.tabs.firefox-view.updateTimeMs", 0]],
  });
});

add_task(async function test_tab_list_ordering() {
  const sandbox = setupRecentDeviceListMocks();
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs1 = getMockTabData(syncedTabsData1);
  let mockTabs2 = getMockTabData(syncedTabsData2);
  let getRecentTabsResult = mockTabs1;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    const { document } = browser.contentWindow;

    await setupListState(browser);

    testVisibility(browser, {
      expectedVisible: {
        "ol.synced-tabs-list": true,
      },
    });

    ok(
      document.querySelector("ol.synced-tabs-list").children.length === 3,
      "synced-tabs-list should have three list items"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .firstChild.textContent.includes("Internet for people, not profits"),
      "First list item in synced-tabs-list is in the correct order"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .children[2].textContent.includes("Sandboxes - Sinon.JS"),
      "Last list item in synced-tabs-list is in the correct order"
    );

    getRecentTabsResult = mockTabs2;
    // Initiate a synced tabs update
    Services.obs.notifyObservers(null, "services.sync.tabs.changed");

    const syncedTabsList = document.querySelector("ol.synced-tabs-list");
    // first list item has been updated
    await BrowserTestUtils.waitForMutationCondition(
      syncedTabsList,
      { childList: true, subtree: true },
      () => syncedTabsList.firstChild.textContent.includes("Firefox")
    );

    ok(
      document.querySelector("ol.synced-tabs-list").children.length === 3,
      "Synced-tabs-list should still have three list items"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .children[1].textContent.includes("Phabricator"),
      "Second list item in synced-tabs-list has been updated"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .children[2].textContent.includes("Internet for people, not profits"),
      "Last list item in synced-tabs-list has been updated"
    );

    sandbox.restore();
    cleanup_tab_pickup();
  });
});

add_task(async function test_empty_list_items() {
  const sandbox = setupRecentDeviceListMocks();
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs1 = getMockTabData(syncedTabsData3);
  let mockTabs2 = getMockTabData(syncedTabsData4);
  let getRecentTabsResult = mockTabs1;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    const { document } = browser.contentWindow;

    await setupListState(browser);

    testVisibility(browser, {
      expectedVisible: {
        "ol.synced-tabs-list": true,
      },
    });

    ok(
      document.querySelector("ol.synced-tabs-list").children.length === 3,
      "synced-tabs-list should have three list items"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .firstChild.textContent.includes("Sandboxes - Sinon.JS"),
      "First list item in synced-tabs-list is in the correct order"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .children[1].classList.contains("synced-tab-li-placeholder"),
      "Second list item in synced-tabs-list should be a placeholder"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .lastChild.classList.contains("synced-tab-li-placeholder"),
      "Last list item in synced-tabs-list should be a placeholder"
    );

    getRecentTabsResult = mockTabs2;
    // Initiate a synced tabs update
    Services.obs.notifyObservers(null, "services.sync.tabs.changed");

    const syncedTabsList = document.querySelector("ol.synced-tabs-list");
    // first list item has been updated
    await BrowserTestUtils.waitForMutationCondition(
      syncedTabsList,
      { childList: true, subtree: true },
      () =>
        syncedTabsList.firstChild.textContent.includes("Firefox Privacy Notice")
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .children[1].textContent.includes("Phabricator"),
      "Second list item in synced-tabs-list has been updated"
    );

    ok(
      document
        .querySelector("ol.synced-tabs-list")
        .lastChild.textContent.includes("Sandboxes - Sinon.JS"),
      "Last list item in synced-tabs-list has been updated"
    );

    sandbox.restore();
    cleanup_tab_pickup();
  });
});

add_task(async function test_empty_list() {
  await clearAllParentTelemetryEvents();
  const sandbox = setupRecentDeviceListMocks();
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs1 = getMockTabData([]);
  let mockTabs2 = getMockTabData(syncedTabsData4);
  let getRecentTabsResult = mockTabs1;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    const { document } = browser.contentWindow;

    await setupListState(browser);
    info("setupListState complete, checking placeholder and list visibility");
    testVisibility(browser, {
      expectedVisible: {
        "#synced-tabs-placeholder": true,
        "ol.synced-tabs-list": false,
      },
    });

    ok(
      document
        .querySelector("#synced-tabs-placeholder")
        .classList.contains("empty-container"),
      "collapsible container should have correct styling when the list is empty"
    );

    await TestUtils.waitForCondition(
      () => {
        let events = Services.telemetry.snapshotEvents(
          Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
          false
        ).parent;
        return events && events.length >= 2;
      },
      "Waiting for entered and synced_tabs firefoxview telemetry events.",
      200,
      100
    );

    TelemetryTestUtils.assertEvents(
      NO_TABS_EVENTS,
      { category: "firefoxview" },
      { clear: true, process: "parent" }
    );

    getRecentTabsResult = mockTabs2;
    // Initiate a synced tabs update
    Services.obs.notifyObservers(null, "services.sync.tabs.changed");

    const syncedTabsList = document.querySelector("ol.synced-tabs-list");
    await BrowserTestUtils.waitForMutationCondition(
      syncedTabsList,
      { childList: true, subtree: true },
      () => syncedTabsList.children.length
    );

    testVisibility(browser, {
      expectedVisible: {
        "#synced-tabs-placeholder": false,
        "ol.synced-tabs-list": true,
      },
    });

    sandbox.restore();
    cleanup_tab_pickup();
  });
});

add_task(async function test_time_updates_correctly() {
  await SpecialPowers.pushPrefEnv({
    set: [["browser.tabs.firefox-view.updateTimeMs", 100]],
  });
  await clearAllParentTelemetryEvents();

  const sandbox = setupRecentDeviceListMocks();
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs1 = getMockTabData(syncedTabsData5);
  let getRecentTabsResult = mockTabs1;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    const { document } = browser.contentWindow;

    await setupListState(browser);

    let initialTimeText = document.querySelector(
      "span.synced-tab-li-time"
    ).textContent;
    Assert.stringContains(
      initialTimeText,
      "Just now",
      "synced-tab-li-time text is 'Just now'"
    );

    await SpecialPowers.pushPrefEnv({
      set: [["browser.tabs.firefox-view.updateTimeMs", 100]],
    });

    const timeLabel = document.querySelector("span.synced-tab-li-time");
    await BrowserTestUtils.waitForMutationCondition(
      timeLabel,
      { childList: true, subtree: true },
      () => !timeLabel.textContent.includes("now")
    );

    isnot(
      timeLabel.textContent,
      initialTimeText,
      "synced-tab-li-time text has updated"
    );

    document.querySelector(".synced-tab-a").click();

    await TestUtils.waitForCondition(
      () => {
        let events = Services.telemetry.snapshotEvents(
          Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
          false
        ).parent;
        return events && events.length >= 3;
      },
      "Waiting for entered, synced_tabs, and tab_pickup firefoxview telemetry events.",
      200,
      100
    );

    TelemetryTestUtils.assertEvents(
      TAB_PICKUP_EVENT,
      { category: "firefoxview" },
      { clear: true, process: "parent" }
    );

    let gBrowser = browser.getTabBrowser();
    is(
      gBrowser.visibleTabs.indexOf(gBrowser.selectedTab),
      0,
      "Tab opened at the beginning of the tab strip"
    );
    gBrowser.removeTab(gBrowser.selectedTab);
    // make sure we're back on fx-view
    browser.ownerGlobal.FirefoxViewHandler.openTab();

    info("Waiting for the tab pickup summary to be visible");
    await waitForElementVisible(browser, "#tab-pickup-container > summary");
    // click on the details summary and verify telemetry gets logged for this event
    await clearAllParentTelemetryEvents();
    info("clicking the summary to collapse it");
    document.querySelector("#tab-pickup-container > summary").click();

    await TestUtils.waitForCondition(
      () => {
        let events = Services.telemetry.snapshotEvents(
          Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
          false
        ).parent;
        return events && events.length >= 1;
      },
      "Waiting for tab_pickup_open firefoxview telemetry event.",
      200,
      100
    );
    TelemetryTestUtils.assertEvents(
      TAB_PICKUP_OPEN_EVENT,
      { category: "firefoxview" },
      { clear: true, process: "parent" }
    );

    sandbox.restore();
    cleanup_tab_pickup();
    await SpecialPowers.popPrefEnv();
  });
});

/**
 * Ensure that tabs sync when a user reloads Firefox View.
 * This is accomplished by asserting that a new set of tabs are loaded
 * on page reload.
 */
add_task(async function test_tabs_sync_on_user_page_reload() {
  const sandbox = setupRecentDeviceListMocks();
  sandbox.stub(SyncedTabs._internal, "syncTabs").resolves(true);
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs1 = getMockTabData(syncedTabsData1);
  let expectedTabsAfterReload = getMockTabData(syncedTabsData3);
  let getRecentTabsResult = mockTabs1;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    let reloadButton = browser.ownerDocument.getElementById("reload-button");

    await setupListState(browser);

    let tabLoaded = BrowserTestUtils.browserLoaded(browser);
    EventUtils.synthesizeMouseAtCenter(reloadButton, {}, browser.ownerGlobal);
    await tabLoaded;
    // Wait until the window is reloaded, then get the current instance
    // of the contentWindow
    const { document } = browser.contentWindow;
    ok(true, "Firefox View has been reloaded");
    ok(TabsSetupFlowManager.waitingForTabs, "waitingForTabs is true");

    let waitedForTabs = TestUtils.waitForCondition(() => {
      return !TabsSetupFlowManager.waitingForTabs;
    });

    getRecentTabsResult = expectedTabsAfterReload;
    Services.obs.notifyObservers(null, "services.sync.tabs.changed");

    const syncedTabsList = document.querySelector("ol.synced-tabs-list");
    // The tab pickup list has been updated
    await BrowserTestUtils.waitForMutationCondition(
      syncedTabsList,
      { childList: true, subtree: true },
      () =>
        syncedTabsList.firstChild.textContent.includes("Sandboxes - Sinon.JS")
    );
    await waitedForTabs;

    sandbox.restore();
    cleanup_tab_pickup();
  });
});

add_task(async function test_keyboard_navigation() {
  TabsSetupFlowManager.resetInternalState();

  const sandbox = setupRecentDeviceListMocks();
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs1 = getMockTabData(syncedTabsData1);
  let getRecentTabsResult = mockTabs1;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    const { document } = browser.contentWindow;
    let win = browser.ownerGlobal;

    await setupListState(browser);
    const tab = (shiftKey = false) => {
      info(`${shiftKey ? "Shift + Tab" : "Tab"}`);
      EventUtils.synthesizeKey("KEY_Tab", { shiftKey }, win);
    };
    const arrowDown = () => {
      info("Arrow Down");
      EventUtils.synthesizeKey("KEY_ArrowDown", {}, win);
    };
    const arrowUp = () => {
      info("Arrow Up");
      EventUtils.synthesizeKey("KEY_ArrowUp", {}, win);
    };
    const arrowLeft = () => {
      info("Arrow Left");
      EventUtils.synthesizeKey("KEY_ArrowLeft", {}, win);
    };
    const arrowRight = () => {
      info("Arrow Right");
      EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
    };

    let syncedTabsLinks = document
      .querySelector("ol.synced-tabs-list")
      .querySelectorAll("a");
    let summary = document
      .getElementById("tab-pickup-container")
      .querySelector("summary");
    summary.focus();
    tab();
    is(
      syncedTabsLinks[0],
      document.activeElement,
      "First synced tab should be focused"
    );
    arrowDown();
    is(
      syncedTabsLinks[1],
      document.activeElement,
      "Second synced tab should be focused"
    );
    arrowDown();
    is(
      syncedTabsLinks[2],
      document.activeElement,
      "Third synced tab should be focused"
    );
    arrowDown();
    is(
      syncedTabsLinks[2],
      document.activeElement,
      "Third synced tab should still be focused"
    );
    arrowUp();
    is(
      syncedTabsLinks[1],
      document.activeElement,
      "Second synced tab should be focused"
    );
    arrowLeft();
    is(
      syncedTabsLinks[0],
      document.activeElement,
      "First synced tab should be focused"
    );
    arrowRight();
    is(
      syncedTabsLinks[1],
      document.activeElement,
      "Second synced tab should be focused"
    );
    arrowDown();
    is(
      syncedTabsLinks[2],
      document.activeElement,
      "Third synced tab should be focused"
    );
    arrowLeft();
    is(
      syncedTabsLinks[0],
      document.activeElement,
      "First synced tab should be focused"
    );

    tab(true);
    is(
      summary,
      document.activeElement,
      "Summary element should be focused when shift tabbing away from list"
    );

    sandbox.restore();
    cleanup_tab_pickup();
  });
});

add_task(async function test_duplicate_tab_filter() {
  const sandbox = setupRecentDeviceListMocks();
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs6 = getMockTabData(syncedTabsData6);
  let getRecentTabsResult = mockTabs6;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    await setupListState(browser);

    Assert.equal(
      mockTabs6[0].title,
      "Firefox Privacy Notice",
      `First tab should be ${mockTabs6[0].title}`
    );

    Assert.equal(
      mockTabs6[0].lastUsed,
      1673991540155,
      `First tab lastUsed value should be ${mockTabs6[0].lastUsed}`
    );

    Assert.equal(
      mockTabs6[1].title,
      "Bugzilla Main Page",
      `Second tab should be ${mockTabs6[1].title}`
    );

    Assert.equal(
      mockTabs6[1].lastUsed,
      1673513538000,
      `Second tab lastUsed value should be ${mockTabs6[1].lastUsed}`
    );

    Assert.equal(
      mockTabs6[2].title,
      "Internet for people, not profits - Mozilla",
      `Third tab should be ${mockTabs6[2].title}`
    );

    Assert.equal(
      mockTabs6[2].lastUsed,
      1606510800000,
      `Third tab lastUsed value should be ${mockTabs6[2].lastUsed}`
    );

    sandbox.restore();
    cleanup_tab_pickup();
  });
});

add_task(async function test_tabs_dont_update_unnecessarily() {
  const sandbox = setupRecentDeviceListMocks();
  const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
  let mockTabs1 = getMockTabData(syncedTabsData1);
  let getRecentTabsResult = mockTabs1;
  syncedTabsMock.callsFake(() => {
    info(
      `Stubbed SyncedTabs.getRecentTabs returning a promise that resolves to ${getRecentTabsResult.length} tabs\n`
    );
    return Promise.resolve(getRecentTabsResult);
  });

  await withFirefoxView({}, async browser => {
    await setupListState(browser);

    const { document } = browser.contentWindow;
    const syncedTabsList = document.querySelector("ol.synced-tabs-list");

    Assert.ok(
      syncedTabsList.children.length === 3,
      "Tab Pickup list should have three list items"
    );

    Assert.ok(
      syncedTabsList.firstChild.textContent.includes(
        "Internet for people, not profits - Mozilla"
      ),
      `First item in the Tab Pickup list is ${mockTabs1[0].title}`
    );

    Assert.ok(
      syncedTabsList.children[1].textContent.includes("The Times"),
      `Second item in Tab Pickup list is ${mockTabs1[1].title}`
    );

    Assert.ok(
      syncedTabsList.children[2].textContent.includes("Sandboxes - Sinon.JS"),
      `Third item in Tab Pickup list is ${mockTabs1[2].title}`
    );

    let wasMutated = false;

    const callback = mutationList => {
      // some logging so if this starts to fail we have some clues as to why
      for (const mutation of mutationList) {
        if (mutation.type === "childList") {
          info(
            "A child node has been added or removed:" + mutation.target.nodeName
          );
        } else if (mutation.type === "attributes") {
          info(`The ${mutation.attributeName} attribute was modified.`);
        } else if (mutation.type === "characterData") {
          info(`The characterData was modified.`);
        }
      }
      wasMutated = true;
    };

    const observer = new MutationObserver(callback);

    observer.observe(syncedTabsList, { childList: true, subtree: true });

    getRecentTabsResult = mockTabs1;
    const tabPickupList = document.querySelector("tab-pickup-list");
    const updateTabsListSpy = sandbox.spy(tabPickupList, "updateTabsList");

    // Initiate a synced tabs update
    Services.obs.notifyObservers(null, "services.sync.tabs.changed");
    await TestUtils.waitForCondition(() => {
      return !TabsSetupFlowManager.waitingForTabs;
    });
    await TestUtils.waitForCondition(() => updateTabsListSpy.called);
    Assert.ok(!wasMutated, "The synced tabs list was not mutated");

    observer.disconnect();
    sandbox.restore();
    cleanup_tab_pickup();
  });
});
