tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

browser_sync.js (34656B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 "use strict";
      5 
      6 Services.scriptloader.loadSubScript(
      7  "chrome://mochitests/content/browser/browser/components/profiles/tests/browser/head.js",
      8  this
      9 );
     10 Services.scriptloader.loadSubScript(
     11  "chrome://mochitests/content/browser/browser/components/customizableui/test/head.js",
     12  this
     13 );
     14 
     15 const { FX_RELAY_OAUTH_CLIENT_ID } = ChromeUtils.importESModule(
     16  "resource://gre/modules/FxAccountsCommon.sys.mjs"
     17 );
     18 
     19 ChromeUtils.defineESModuleGetters(this, {
     20  CustomizableUITestUtils:
     21    "resource://testing-common/CustomizableUITestUtils.sys.mjs",
     22  ExperimentAPI: "resource://nimbus/ExperimentAPI.sys.mjs",
     23  NimbusTestUtils: "resource://testing-common/NimbusTestUtils.sys.mjs",
     24 });
     25 
     26 add_setup(async function () {
     27  // gSync.init() is called in a requestIdleCallback. Force its initialization.
     28  gSync.init();
     29  // This preference gets set the very first time that the FxA menu gets opened,
     30  // which can cause a state write to occur, which can confuse this test, since
     31  // when in the signed-out state, we need to set the state _before_ opening
     32  // the FxA menu (since the panel cannot be opened) in the signed out state.
     33  await SpecialPowers.pushPrefEnv({
     34    set: [
     35      ["browser.urlbar.trustPanel.featureGate", false],
     36      ["identity.fxaccounts.toolbar.accessed", true],
     37    ],
     38  });
     39 });
     40 
     41 add_task(async function test_ui_state_notification_calls_updateAllUI() {
     42  let called = false;
     43  let updateAllUI = gSync.updateAllUI;
     44  gSync.updateAllUI = () => {
     45    called = true;
     46  };
     47 
     48  Services.obs.notifyObservers(null, UIState.ON_UPDATE);
     49  ok(called);
     50 
     51  gSync.updateAllUI = updateAllUI;
     52 });
     53 
     54 add_task(async function test_navBar_button_visibility() {
     55  const button = document.getElementById("fxa-toolbar-menu-button");
     56  ok(button.closest("#nav-bar"), "button is in the #nav-bar");
     57 
     58  const state = {
     59    status: UIState.STATUS_NOT_CONFIGURED,
     60    syncEnabled: true,
     61  };
     62  gSync.updateAllUI(state);
     63  ok(
     64    BrowserTestUtils.isVisible(button),
     65    "Check button visibility with STATUS_NOT_CONFIGURED"
     66  );
     67 
     68  state.email = "foo@bar.com";
     69  state.status = UIState.STATUS_NOT_VERIFIED;
     70  gSync.updateAllUI(state);
     71  ok(
     72    BrowserTestUtils.isVisible(button),
     73    "Check button visibility with STATUS_NOT_VERIFIED"
     74  );
     75 
     76  state.status = UIState.STATUS_LOGIN_FAILED;
     77  gSync.updateAllUI(state);
     78  ok(
     79    BrowserTestUtils.isVisible(button),
     80    "Check button visibility with STATUS_LOGIN_FAILED"
     81  );
     82 
     83  state.status = UIState.STATUS_SIGNED_IN;
     84  gSync.updateAllUI(state);
     85  ok(
     86    BrowserTestUtils.isVisible(button),
     87    "Check button visibility with STATUS_SIGNED_IN"
     88  );
     89 
     90  state.syncEnabled = false;
     91  gSync.updateAllUI(state);
     92  is(
     93    BrowserTestUtils.isVisible(button),
     94    true,
     95    "Check button visibility when signed in, but sync disabled"
     96  );
     97 });
     98 
     99 add_task(async function test_overflow_navBar_button_visibility() {
    100  const button = document.getElementById("fxa-toolbar-menu-button");
    101 
    102  let overflowPanel = document.getElementById("widget-overflow");
    103  overflowPanel.setAttribute("animate", "false");
    104  let navbar = document.getElementById(CustomizableUI.AREA_NAVBAR);
    105  let originalWindowWidth;
    106 
    107  registerCleanupFunction(function () {
    108    overflowPanel.removeAttribute("animate");
    109    unensureToolbarOverflow(window, originalWindowWidth);
    110    return TestUtils.waitForCondition(
    111      () => !navbar.hasAttribute("overflowing")
    112    );
    113  });
    114 
    115  // As of bug 1960002, overflowing the navbar requires adding buttons.
    116  originalWindowWidth = ensureToolbarOverflow(window, false);
    117  await TestUtils.waitForCondition(() => navbar.hasAttribute("overflowing"));
    118  ok(navbar.hasAttribute("overflowing"), "Should have an overflowing toolbar.");
    119 
    120  let chevron = document.getElementById("nav-bar-overflow-button");
    121  let shownPanelPromise = BrowserTestUtils.waitForEvent(
    122    overflowPanel,
    123    "popupshown"
    124  );
    125  chevron.click();
    126  await shownPanelPromise;
    127 
    128  ok(button, "fxa-toolbar-menu-button was found");
    129 
    130  const state = {
    131    status: UIState.STATUS_NOT_CONFIGURED,
    132    syncEnabled: true,
    133  };
    134  gSync.updateAllUI(state);
    135 
    136  ok(
    137    BrowserTestUtils.isVisible(button),
    138    "Button should still be visable even if user sync not configured"
    139  );
    140 
    141  let hidePanelPromise = BrowserTestUtils.waitForEvent(
    142    overflowPanel,
    143    "popuphidden"
    144  );
    145  chevron.click();
    146  await hidePanelPromise;
    147 });
    148 
    149 add_task(async function setupForPanelTests() {
    150  /* Proton hides the FxA toolbar button when in the nav-bar and unconfigured.
    151     To test the panel in all states, we move it to the tabstrip toolbar where
    152     it will always be visible.
    153   */
    154  CustomizableUI.addWidgetToArea(
    155    "fxa-toolbar-menu-button",
    156    CustomizableUI.AREA_TABSTRIP
    157  );
    158 
    159  // make sure it gets put back at the end of the tests
    160  registerCleanupFunction(() => {
    161    CustomizableUI.addWidgetToArea(
    162      "fxa-toolbar-menu-button",
    163      CustomizableUI.AREA_NAVBAR
    164    );
    165  });
    166 });
    167 
    168 add_task(async function test_ui_state_signedin() {
    169  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    170 
    171  // Setup profiles db
    172  await SpecialPowers.pushPrefEnv({
    173    set: [["browser.profiles.enabled", true]],
    174  });
    175  await initGroupDatabase();
    176  let profile = SelectableProfileService.currentProfile;
    177  Assert.ok(profile, "Should have a profile now");
    178 
    179  const relativeDateAnchor = new Date();
    180  let state = {
    181    status: UIState.STATUS_SIGNED_IN,
    182    syncEnabled: true,
    183    email: "foo@bar.com",
    184    displayName: "Foo Bar",
    185    avatarURL: "https://foo.bar",
    186    lastSync: new Date(),
    187    syncing: false,
    188  };
    189 
    190  const origRelativeTimeFormat = gSync.relativeTimeFormat;
    191  gSync.relativeTimeFormat = {
    192    formatBestUnit(date) {
    193      return origRelativeTimeFormat.formatBestUnit(date, {
    194        now: relativeDateAnchor,
    195      });
    196    },
    197  };
    198 
    199  gSync.updateAllUI(state);
    200 
    201  await openFxaPanel();
    202 
    203  checkMenuBarItem("sync-syncnowitem");
    204  checkPanelHeader();
    205  ok(
    206    BrowserTestUtils.isVisible(
    207      document.getElementById("fxa-menu-header-title")
    208    ),
    209    "expected toolbar to be visible after opening"
    210  );
    211  checkFxaToolbarButtonPanel({
    212    headerTitle: "Manage account",
    213    headerDescription: state.displayName,
    214    enabledItems: [
    215      "PanelUI-fxa-menu-sendtab-button",
    216      "PanelUI-fxa-menu-connect-device-button",
    217      "PanelUI-fxa-menu-syncnow-button",
    218      "PanelUI-fxa-menu-sync-prefs-button",
    219      "PanelUI-fxa-menu-account-signout-button",
    220    ],
    221    disabledItems: [],
    222    hiddenItems: ["PanelUI-fxa-menu-setup-sync-container"],
    223    visibleItems: [],
    224  });
    225 
    226  await checkProfilesButtons(
    227    document.getElementById("fxa-manage-account-button"),
    228    true
    229  );
    230 
    231  checkFxAAvatar("signedin");
    232  gSync.relativeTimeFormat = origRelativeTimeFormat;
    233  await closeFxaPanel();
    234 
    235  await openMainPanel();
    236 
    237  checkPanelUIStatusBar({
    238    description: "Foo Bar",
    239    titleHidden: true,
    240    hideFxAText: true,
    241  });
    242 
    243  // Ensure the profiles menuitem is not shown in the FxA panel when it is
    244  // displayed as a subpanel of the app menu.
    245  let appMenuFxAStatusButton = document.getElementById("appMenu-fxa-label2");
    246  appMenuFxAStatusButton.click();
    247  let fxaView = PanelMultiView.getViewNode(document, "PanelUI-fxa");
    248  await BrowserTestUtils.waitForEvent(fxaView, "ViewShown");
    249 
    250  // Verify the manage button is shown, just as a basic check.
    251  let manageButton = fxaView.querySelector("#fxa-manage-account-button");
    252  ok(
    253    BrowserTestUtils.isVisible(manageButton),
    254    "expected manage button to be visible after opening"
    255  );
    256  let profilesButton = fxaView.querySelector(
    257    "PanelUI-fxa-menu-profiles-button"
    258  );
    259  ok(!profilesButton, "expected profiles button to not be present");
    260 
    261  await closeTabAndMainPanel();
    262 });
    263 
    264 add_task(async function test_ui_state_syncing_panel_closed() {
    265  let state = {
    266    status: UIState.STATUS_SIGNED_IN,
    267    syncEnabled: true,
    268    email: "foo@bar.com",
    269    displayName: "Foo Bar",
    270    avatarURL: "https://foo.bar",
    271    lastSync: new Date(),
    272    syncing: true,
    273  };
    274 
    275  gSync.updateAllUI(state);
    276 
    277  checkSyncNowButtons(true);
    278 
    279  // Be good citizens and remove the "syncing" state.
    280  gSync.updateAllUI({
    281    status: UIState.STATUS_SIGNED_IN,
    282    syncEnabled: true,
    283    email: "foo@bar.com",
    284    lastSync: new Date(),
    285    syncing: false,
    286  });
    287  // Because we switch from syncing to non-syncing, and there's a timeout involved.
    288  await promiseObserver("test:browser-sync:activity-stop");
    289 });
    290 
    291 add_task(async function test_ui_state_syncing_panel_open() {
    292  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    293 
    294  let state = {
    295    status: UIState.STATUS_SIGNED_IN,
    296    syncEnabled: true,
    297    email: "foo@bar.com",
    298    displayName: "Foo Bar",
    299    avatarURL: "https://foo.bar",
    300    lastSync: new Date(),
    301    syncing: false,
    302  };
    303 
    304  gSync.updateAllUI(state);
    305 
    306  await openFxaPanel();
    307 
    308  checkSyncNowButtons(false);
    309 
    310  state = {
    311    status: UIState.STATUS_SIGNED_IN,
    312    syncEnabled: true,
    313    email: "foo@bar.com",
    314    displayName: "Foo Bar",
    315    avatarURL: "https://foo.bar",
    316    lastSync: new Date(),
    317    syncing: true,
    318  };
    319 
    320  gSync.updateAllUI(state);
    321 
    322  checkSyncNowButtons(true);
    323 
    324  // Be good citizens and remove the "syncing" state.
    325  gSync.updateAllUI({
    326    status: UIState.STATUS_SIGNED_IN,
    327    syncEnabled: true,
    328    email: "foo@bar.com",
    329    lastSync: new Date(),
    330    syncing: false,
    331  });
    332  // Because we switch from syncing to non-syncing, and there's a timeout involved.
    333  await promiseObserver("test:browser-sync:activity-stop");
    334 
    335  await closeFxaPanel();
    336  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    337 });
    338 
    339 add_task(async function test_ui_state_panel_open_after_syncing() {
    340  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    341 
    342  let state = {
    343    status: UIState.STATUS_SIGNED_IN,
    344    syncEnabled: true,
    345    email: "foo@bar.com",
    346    displayName: "Foo Bar",
    347    avatarURL: "https://foo.bar",
    348    lastSync: new Date(),
    349    syncing: true,
    350  };
    351 
    352  gSync.updateAllUI(state);
    353 
    354  await openFxaPanel();
    355 
    356  checkSyncNowButtons(true);
    357 
    358  // Be good citizens and remove the "syncing" state.
    359  gSync.updateAllUI({
    360    status: UIState.STATUS_SIGNED_IN,
    361    syncEnabled: true,
    362    email: "foo@bar.com",
    363    lastSync: new Date(),
    364    syncing: false,
    365  });
    366  // Because we switch from syncing to non-syncing, and there's a timeout involved.
    367  await promiseObserver("test:browser-sync:activity-stop");
    368 
    369  await closeFxaPanel();
    370  BrowserTestUtils.removeTab(gBrowser.selectedTab);
    371 });
    372 
    373 add_task(async function test_ui_state_unconfigured() {
    374  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    375 
    376  // Setup profiles db
    377  await SpecialPowers.pushPrefEnv({
    378    set: [["browser.profiles.enabled", true]],
    379  });
    380  await initGroupDatabase();
    381  let profile = SelectableProfileService.currentProfile;
    382  Assert.ok(profile, "Should have a profile now");
    383 
    384  let state = {
    385    status: UIState.STATUS_NOT_CONFIGURED,
    386  };
    387 
    388  gSync.updateAllUI(state);
    389 
    390  checkMenuBarItem("sync-setup");
    391 
    392  checkFxAAvatar("not_configured");
    393 
    394  let signedOffLabel = gSync.fluentStrings.formatValueSync(
    395    "appmenu-fxa-signed-in-label"
    396  );
    397 
    398  await openMainPanel();
    399 
    400  checkPanelUIStatusBar({
    401    description: signedOffLabel,
    402    titleHidden: true,
    403    hideFxAText: false,
    404  });
    405  await closeTabAndMainPanel();
    406 
    407  await openFxaPanel();
    408 
    409  await checkProfilesButtons(
    410    document.getElementById("PanelUI-signedin-panel"),
    411    false
    412  );
    413 
    414  await closeFxaPanel();
    415 });
    416 
    417 add_task(async function test_ui_state_signed_in() {
    418  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    419 
    420  let state = {
    421    status: UIState.STATUS_SIGNED_IN,
    422    syncEnabled: false,
    423    email: "foo@bar.com",
    424    displayName: "Foo Bar",
    425    avatarURL: "https://foo.bar",
    426  };
    427 
    428  gSync.updateAllUI(state);
    429 
    430  await openFxaPanel();
    431 
    432  checkMenuBarItem("sync-enable");
    433  checkPanelHeader();
    434  checkFxaToolbarButtonPanel({
    435    headerTitle: "Manage account",
    436    headerDescription: "Foo Bar",
    437    enabledItems: [
    438      "PanelUI-fxa-menu-sendtab-button",
    439      "PanelUI-fxa-menu-connect-device-button",
    440      "PanelUI-fxa-menu-account-signout-button",
    441    ],
    442    disabledItems: [],
    443    hiddenItems: [
    444      "PanelUI-fxa-menu-syncnow-button",
    445      "PanelUI-fxa-menu-sync-prefs-button",
    446    ],
    447    visibleItems: ["PanelUI-fxa-menu-setup-sync-container"],
    448  });
    449  checkFxAAvatar("signedin");
    450  await closeFxaPanel();
    451 
    452  await openMainPanel();
    453 
    454  checkPanelUIStatusBar({
    455    description: "Foo Bar",
    456    titleHidden: true,
    457    hideFxAText: true,
    458  });
    459 
    460  await closeTabAndMainPanel();
    461 });
    462 
    463 add_task(async function test_ui_state_signed_in_no_display_name() {
    464  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    465 
    466  let state = {
    467    status: UIState.STATUS_SIGNED_IN,
    468    syncEnabled: false,
    469    email: "foo@bar.com",
    470    avatarURL: "https://foo.bar",
    471  };
    472 
    473  gSync.updateAllUI(state);
    474 
    475  await openFxaPanel();
    476 
    477  checkMenuBarItem("sync-enable");
    478  checkPanelHeader();
    479  checkFxaToolbarButtonPanel({
    480    headerTitle: "Manage account",
    481    headerDescription: "foo@bar.com",
    482    enabledItems: [
    483      "PanelUI-fxa-menu-sendtab-button",
    484      "PanelUI-fxa-menu-connect-device-button",
    485      "PanelUI-fxa-menu-account-signout-button",
    486    ],
    487    disabledItems: [],
    488    hiddenItems: [
    489      "PanelUI-fxa-menu-syncnow-button",
    490      "PanelUI-fxa-menu-sync-prefs-button",
    491    ],
    492    visibleItems: ["PanelUI-fxa-menu-setup-sync-container"],
    493  });
    494  checkFxAAvatar("signedin");
    495  await closeFxaPanel();
    496 
    497  await openMainPanel();
    498 
    499  checkPanelUIStatusBar({
    500    description: "foo@bar.com",
    501    titleHidden: true,
    502    hideFxAText: true,
    503  });
    504 
    505  await closeTabAndMainPanel();
    506 });
    507 
    508 add_task(async function test_ui_state_unverified() {
    509  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    510 
    511  let state = {
    512    status: UIState.STATUS_NOT_VERIFIED,
    513    email: "foo@bar.com",
    514    syncing: false,
    515  };
    516 
    517  gSync.updateAllUI(state);
    518 
    519  await openFxaPanel();
    520 
    521  const expectedLabel = gSync.fluentStrings.formatValueSync(
    522    "account-finish-account-setup"
    523  );
    524 
    525  checkMenuBarItem("sync-unverifieditem");
    526  checkPanelHeader();
    527  checkFxaToolbarButtonPanel({
    528    headerTitle: expectedLabel,
    529    headerDescription: state.email,
    530    enabledItems: [
    531      "PanelUI-fxa-menu-sendtab-button",
    532      "PanelUI-fxa-menu-account-signout-button",
    533    ],
    534    disabledItems: ["PanelUI-fxa-menu-connect-device-button"],
    535    hiddenItems: [
    536      "PanelUI-fxa-menu-syncnow-button",
    537      "PanelUI-fxa-menu-sync-prefs-button",
    538    ],
    539    visibleItems: ["PanelUI-fxa-menu-setup-sync-container"],
    540  });
    541  checkFxAAvatar("unverified");
    542  await closeFxaPanel();
    543  await openMainPanel();
    544 
    545  checkPanelUIStatusBar({
    546    description: state.email,
    547    title: expectedLabel,
    548    titleHidden: false,
    549    hideFxAText: true,
    550  });
    551 
    552  await closeTabAndMainPanel();
    553 });
    554 
    555 add_task(async function test_ui_state_loginFailed() {
    556  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    557 
    558  let state = {
    559    status: UIState.STATUS_LOGIN_FAILED,
    560    email: "foo@bar.com",
    561    displayName: "Foo Bar",
    562  };
    563 
    564  gSync.updateAllUI(state);
    565 
    566  await openFxaPanel();
    567 
    568  const expectedLabel = gSync.fluentStrings.formatValueSync(
    569    "account-disconnected2"
    570  );
    571 
    572  checkMenuBarItem("sync-reauthitem");
    573  checkPanelHeader();
    574  checkFxaToolbarButtonPanel({
    575    headerTitle: expectedLabel,
    576    headerDescription: state.displayName,
    577    enabledItems: [
    578      "PanelUI-fxa-menu-sendtab-button",
    579      "PanelUI-fxa-menu-account-signout-button",
    580    ],
    581    disabledItems: ["PanelUI-fxa-menu-connect-device-button"],
    582    hiddenItems: [
    583      "PanelUI-fxa-menu-syncnow-button",
    584      "PanelUI-fxa-menu-sync-prefs-button",
    585    ],
    586    visibleItems: ["PanelUI-fxa-menu-setup-sync-container"],
    587  });
    588  checkFxAAvatar("login-failed");
    589  await closeFxaPanel();
    590  await openMainPanel();
    591 
    592  checkPanelUIStatusBar({
    593    description: state.displayName,
    594    title: expectedLabel,
    595    titleHidden: false,
    596    hideFxAText: true,
    597  });
    598 
    599  await closeTabAndMainPanel();
    600 });
    601 
    602 add_task(async function test_app_menu_fxa_disabled() {
    603  const newWin = await BrowserTestUtils.openNewBrowserWindow();
    604 
    605  Services.prefs.setBoolPref("identity.fxaccounts.enabled", true);
    606  newWin.gSync.onFxaDisabled();
    607 
    608  let menuButton = newWin.document.getElementById("PanelUI-menu-button");
    609  menuButton.click();
    610  await BrowserTestUtils.waitForEvent(newWin.PanelUI.mainView, "ViewShown");
    611 
    612  [...newWin.document.querySelectorAll(".sync-ui-item")].forEach(
    613    e => (e.hidden = false)
    614  );
    615 
    616  let hidden = BrowserTestUtils.waitForEvent(
    617    newWin.document,
    618    "popuphidden",
    619    true
    620  );
    621  newWin.PanelUI.hide();
    622  await hidden;
    623  await BrowserTestUtils.closeWindow(newWin);
    624 });
    625 
    626 add_task(async function test_history_menu_fxa_disabled() {
    627  if (AppConstants.platform === "macosx") {
    628    info(
    629      "skipping test because the history menu can't be opened in tests on mac"
    630    );
    631    return;
    632  }
    633 
    634  const newWin = await BrowserTestUtils.openNewBrowserWindow();
    635 
    636  Services.prefs.setBoolPref("identity.fxaccounts.enabled", true);
    637  newWin.gSync.onFxaDisabled();
    638 
    639  const historyMenubarItem = window.document.getElementById("history-menu");
    640  const historyMenu = window.document.getElementById("historyMenuPopup");
    641  const syncedTabsItem = historyMenu.querySelector("#sync-tabs-menuitem");
    642  const menuShown = BrowserTestUtils.waitForEvent(historyMenu, "popupshown");
    643  historyMenubarItem.openMenu(true);
    644  await menuShown;
    645 
    646  Assert.equal(
    647    syncedTabsItem.hidden,
    648    true,
    649    "Synced Tabs item should not be displayed when FxAccounts is disabled"
    650  );
    651  const menuHidden = BrowserTestUtils.waitForEvent(historyMenu, "popuphidden");
    652  historyMenu.hidePopup();
    653  await menuHidden;
    654  await BrowserTestUtils.closeWindow(newWin);
    655 });
    656 
    657 // If the PXI experiment is enabled, we need to ensure we can see the CTAs when signed out
    658 add_task(async function test_experiment_ui_state_unconfigured() {
    659  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    660 
    661  // The experiment enables this bool, found in FeatureManifest.yaml
    662  Services.prefs.setBoolPref(
    663    "identity.fxaccounts.toolbar.pxiToolbarEnabled",
    664    true
    665  );
    666  let state = {
    667    status: UIState.STATUS_NOT_CONFIGURED,
    668  };
    669 
    670  gSync.updateAllUI(state);
    671 
    672  checkMenuBarItem("sync-setup");
    673 
    674  checkFxAAvatar("not_configured");
    675 
    676  let expectedLabel = gSync.fluentStrings.formatValueSync(
    677    "synced-tabs-fxa-sign-in"
    678  );
    679 
    680  let expectedDescriptionLabel = gSync.fluentStrings.formatValueSync(
    681    "fxa-menu-sync-description"
    682  );
    683 
    684  await openMainPanel();
    685 
    686  checkFxaToolbarButtonPanel({
    687    headerTitle: expectedLabel,
    688    headerDescription: expectedDescriptionLabel,
    689    enabledItems: [
    690      "PanelUI-fxa-cta-menu",
    691      "PanelUI-fxa-menu-monitor-button",
    692      "PanelUI-fxa-menu-relay-button",
    693      "PanelUI-fxa-menu-vpn-button",
    694    ],
    695    disabledItems: [],
    696    hiddenItems: [
    697      "PanelUI-fxa-menu-syncnow-button",
    698      "PanelUI-fxa-menu-sync-prefs-button",
    699    ],
    700    visibleItems: [],
    701  });
    702 
    703  // Revert the pref at the end of the test
    704  Services.prefs.setBoolPref(
    705    "identity.fxaccounts.toolbar.pxiToolbarEnabled",
    706    false
    707  );
    708  await closeTabAndMainPanel();
    709 });
    710 
    711 // Ensure we can see the regular signed in flow + the extra PXI CTAs when
    712 // the experiment is enabled
    713 add_task(async function test_experiment_ui_state_signedin() {
    714  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    715 
    716  // The experiment enables this bool, found in FeatureManifest.yaml
    717  Services.prefs.setBoolPref(
    718    "identity.fxaccounts.toolbar.pxiToolbarEnabled",
    719    true
    720  );
    721 
    722  const relativeDateAnchor = new Date();
    723  let state = {
    724    status: UIState.STATUS_SIGNED_IN,
    725    syncEnabled: true,
    726    email: "foo@bar.com",
    727    displayName: "Foo Bar",
    728    avatarURL: "https://foo.bar",
    729    lastSync: new Date(),
    730    syncing: false,
    731  };
    732 
    733  const origRelativeTimeFormat = gSync.relativeTimeFormat;
    734  gSync.relativeTimeFormat = {
    735    formatBestUnit(date) {
    736      return origRelativeTimeFormat.formatBestUnit(date, {
    737        now: relativeDateAnchor,
    738      });
    739    },
    740  };
    741 
    742  gSync.updateAllUI(state);
    743 
    744  await openFxaPanel();
    745 
    746  checkMenuBarItem("sync-syncnowitem");
    747  checkPanelHeader();
    748  ok(
    749    BrowserTestUtils.isVisible(
    750      document.getElementById("fxa-menu-header-title")
    751    ),
    752    "expected toolbar to be visible after opening"
    753  );
    754  checkFxaToolbarButtonPanel({
    755    headerTitle: "Manage account",
    756    headerDescription: state.displayName,
    757    enabledItems: [
    758      "PanelUI-fxa-menu-sendtab-button",
    759      "PanelUI-fxa-menu-connect-device-button",
    760      "PanelUI-fxa-menu-syncnow-button",
    761      "PanelUI-fxa-menu-sync-prefs-button",
    762      "PanelUI-fxa-menu-account-signout-button",
    763      "PanelUI-fxa-cta-menu",
    764      "PanelUI-fxa-menu-monitor-button",
    765      "PanelUI-fxa-menu-relay-button",
    766      "PanelUI-fxa-menu-vpn-button",
    767    ],
    768    disabledItems: [],
    769    hiddenItems: ["PanelUI-fxa-menu-setup-sync-container"],
    770    visibleItems: [],
    771  });
    772  checkFxAAvatar("signedin");
    773  gSync.relativeTimeFormat = origRelativeTimeFormat;
    774  await closeFxaPanel();
    775 
    776  await openMainPanel();
    777 
    778  checkPanelUIStatusBar({
    779    description: "Foo Bar",
    780    titleHidden: true,
    781    hideFxAText: true,
    782  });
    783 
    784  // Revert the pref at the end of the test
    785  Services.prefs.setBoolPref(
    786    "identity.fxaccounts.toolbar.pxiToolbarEnabled",
    787    false
    788  );
    789  await closeTabAndMainPanel();
    790 });
    791 
    792 add_task(async function test_new_sync_setup_ui() {
    793  let state = {
    794    status: UIState.STATUS_SIGNED_IN,
    795    syncEnabled: false,
    796    hasSyncKeys: true,
    797    email: "foo@bar.com",
    798    displayName: "Foo Bar",
    799    avatarURL: "https://foo.bar",
    800  };
    801 
    802  gSync.updateAllUI(state);
    803 
    804  await openFxaPanel();
    805 
    806  checkMenuBarItem("sync-enable");
    807  checkPanelHeader();
    808 
    809  checkFxaToolbarButtonPanel({
    810    headerTitle: "Manage account",
    811    headerDescription: "Foo Bar",
    812    enabledItems: [
    813      "PanelUI-fxa-menu-sendtab-button",
    814      "PanelUI-fxa-menu-account-signout-button",
    815      "PanelUI-fxa-menu-connect-device-button",
    816    ],
    817    disabledItems: [],
    818    hiddenItems: [
    819      "PanelUI-fxa-menu-syncnow-button",
    820      "PanelUI-fxa-menu-sync-prefs-button",
    821    ],
    822    visibleItems: [
    823      "PanelUI-fxa-menu-setup-sync-container",
    824      "PanelUI-fxa-menu-connect-device-button",
    825    ],
    826  });
    827 
    828  await closeFxaPanel();
    829 
    830  // We need to reset the panel back to hidden since in the code we flip between the old and new sync setup ids
    831  // so subsequent tests will fail if checking this new container
    832  let newSyncSetup = document.getElementById(
    833    "PanelUI-fxa-menu-setup-sync-container"
    834  );
    835  newSyncSetup.setAttribute("hidden", true);
    836 });
    837 
    838 // Ensure we can see the new "My services" section if the user has enabled relay on their account
    839 add_task(async function test_ui_my_services_signedin() {
    840  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
    841 
    842  const relativeDateAnchor = new Date();
    843  let state = {
    844    status: UIState.STATUS_SIGNED_IN,
    845    syncEnabled: true,
    846    hasSyncKeys: true,
    847    email: "foo@bar.com",
    848    displayName: "Foo Bar",
    849    avatarURL: "https://foo.bar",
    850    lastSync: new Date(),
    851    syncing: false,
    852  };
    853 
    854  const origRelativeTimeFormat = gSync.relativeTimeFormat;
    855  gSync.relativeTimeFormat = {
    856    formatBestUnit(date) {
    857      return origRelativeTimeFormat.formatBestUnit(date, {
    858        now: relativeDateAnchor,
    859      });
    860    },
    861  };
    862 
    863  gSync.updateAllUI(state);
    864 
    865  // pretend that the user has relay enabled
    866  gSync._attachedClients = [
    867    {
    868      id: FX_RELAY_OAUTH_CLIENT_ID,
    869    },
    870  ];
    871 
    872  await openFxaPanel();
    873 
    874  checkMenuBarItem("sync-syncnowitem");
    875  checkPanelHeader();
    876  ok(
    877    BrowserTestUtils.isVisible(
    878      document.getElementById("fxa-menu-header-title")
    879    ),
    880    "expected toolbar to be visible after opening"
    881  );
    882  checkFxaToolbarButtonPanel({
    883    headerTitle: "Manage account",
    884    headerDescription: state.displayName,
    885    enabledItems: [
    886      "PanelUI-fxa-menu-sendtab-button",
    887      "PanelUI-fxa-menu-connect-device-button",
    888      "PanelUI-fxa-menu-syncnow-button",
    889      "PanelUI-fxa-menu-sync-prefs-button",
    890      "PanelUI-fxa-menu-account-signout-button",
    891      "PanelUI-fxa-cta-menu",
    892      "PanelUI-fxa-menu-monitor-button",
    893      "PanelUI-fxa-menu-vpn-button",
    894    ],
    895    disabledItems: [],
    896    hiddenItems: [
    897      "PanelUI-fxa-menu-setup-sync-container",
    898      "PanelUI-fxa-menu-relay-button", // the relay button in the "other protections" side should be hidden
    899    ],
    900    visibleItems: [],
    901  });
    902  checkFxAAvatar("signedin");
    903  gSync.relativeTimeFormat = origRelativeTimeFormat;
    904  await closeFxaPanel();
    905 
    906  await openMainPanel();
    907 
    908  checkPanelUIStatusBar({
    909    description: "Foo Bar",
    910    titleHidden: true,
    911    hideFxAText: true,
    912  });
    913 
    914  // Revert the pref at the end of the test
    915  Services.prefs.setBoolPref(
    916    "identity.fxaccounts.toolbar.pxiToolbarEnabled",
    917    false
    918  );
    919  await closeTabAndMainPanel();
    920 });
    921 
    922 add_task(async function test_experiment_signin_button_signed_out() {
    923  // Enroll in Nimbus experiment
    924  await ExperimentAPI.ready();
    925  let cleanupNimbus = await NimbusTestUtils.enrollWithFeatureConfig({
    926    featureId: NimbusFeatures.expandSignInButton.featureId,
    927    value: {
    928      ctaCopyVariant: "fxa-avatar-sign-in",
    929    },
    930  });
    931 
    932  // Set UI state to STATUS_NOT_CONFIGURED (signed out)
    933  let state = { status: UIState.STATUS_NOT_CONFIGURED };
    934  gSync.updateAllUI(state);
    935 
    936  const fxaAvatarLabel = document.getElementById("fxa-avatar-label");
    937  ok(fxaAvatarLabel, "Avatar label element should exist");
    938  is(
    939    fxaAvatarLabel.hidden,
    940    false,
    941    "Avatar label should be visible when Nimbus experiment is enabled"
    942  );
    943 
    944  const expectedLabel =
    945    gSync.fluentStrings.formatValueSync("fxa-avatar-sign-in");
    946 
    947  is(
    948    fxaAvatarLabel.getAttribute("value"),
    949    expectedLabel,
    950    `Avatar label should have the expected localized value: ${expectedLabel}`
    951  );
    952 
    953  // Clean up experiment
    954  await cleanupNimbus();
    955 
    956  // Reset UI state after experiment cleanup
    957  gSync.updateAllUI(state);
    958 
    959  is(
    960    fxaAvatarLabel.hidden,
    961    true,
    962    "Avatar label should be hidden after Nimbus experiment is cleaned up"
    963  );
    964 });
    965 
    966 add_task(async function test_experiment_signin_button_signed_in() {
    967  // Enroll in Nimbus experiment
    968  await ExperimentAPI.ready();
    969  let cleanupNimbus = await NimbusTestUtils.enrollWithFeatureConfig({
    970    featureId: NimbusFeatures.expandSignInButton.featureId,
    971    value: {
    972      ctaCopyVariant: "fxa-avatar-sign-in",
    973    },
    974  });
    975 
    976  let state = {
    977    status: UIState.STATUS_SIGNED_IN,
    978    syncEnabled: true,
    979    email: "foo@bar.com",
    980    displayName: "Foo Bar",
    981    avatarURL: "https://foo.bar",
    982    lastSync: new Date(),
    983    syncing: false,
    984  };
    985  gSync.updateAllUI(state);
    986 
    987  const fxaAvatarLabel = document.getElementById("fxa-avatar-label");
    988  is(
    989    fxaAvatarLabel.hidden,
    990    true,
    991    "Avatar label should never be visible when signed in"
    992  );
    993 
    994  // Clean up experiment
    995  await cleanupNimbus();
    996 
    997  // Reset UI state after experiment cleanup
    998  gSync.updateAllUI(state);
    999 
   1000  is(
   1001    fxaAvatarLabel.hidden,
   1002    true,
   1003    "Avatar label should still be hidden when signed in without experiment"
   1004  );
   1005 });
   1006 
   1007 function checkPanelUIStatusBar({
   1008  description,
   1009  title,
   1010  titleHidden,
   1011  hideFxAText,
   1012 }) {
   1013  checkAppMenuFxAText(hideFxAText);
   1014  let appMenuHeaderTitle = PanelMultiView.getViewNode(
   1015    document,
   1016    "appMenu-header-title"
   1017  );
   1018  let appMenuHeaderDescription = PanelMultiView.getViewNode(
   1019    document,
   1020    "appMenu-header-description"
   1021  );
   1022  is(
   1023    appMenuHeaderDescription.value,
   1024    description,
   1025    "app menu description has correct value"
   1026  );
   1027  is(appMenuHeaderTitle.hidden, titleHidden, "title has correct hidden status");
   1028  if (!titleHidden) {
   1029    is(appMenuHeaderTitle.value, title, "title has correct value");
   1030  }
   1031 }
   1032 
   1033 function checkMenuBarItem(expectedShownItemId) {
   1034  checkItemsVisibilities(
   1035    [
   1036      "sync-setup",
   1037      "sync-enable",
   1038      "sync-syncnowitem",
   1039      "sync-reauthitem",
   1040      "sync-unverifieditem",
   1041    ],
   1042    expectedShownItemId
   1043  );
   1044 }
   1045 
   1046 function checkPanelHeader() {
   1047  let fxaPanelView = PanelMultiView.getViewNode(document, "PanelUI-fxa");
   1048  is(
   1049    fxaPanelView.getAttribute("title"),
   1050    gSync.fluentStrings.formatValueSync("appmenu-account-header"),
   1051    "Panel title is correct"
   1052  );
   1053 }
   1054 
   1055 function checkSyncNowButtons(syncing, tooltip = null) {
   1056  const syncButtons = document.querySelectorAll(".syncNowBtn");
   1057 
   1058  for (const syncButton of syncButtons) {
   1059    is(
   1060      syncButton.getAttribute("syncstatus"),
   1061      syncing ? "active" : null,
   1062      "button active has the right value"
   1063    );
   1064    if (tooltip) {
   1065      is(
   1066        syncButton.getAttribute("tooltiptext"),
   1067        tooltip,
   1068        "button tooltiptext is set to the right value"
   1069      );
   1070    }
   1071  }
   1072 
   1073  const syncLabels = document.querySelectorAll(".syncnow-label");
   1074 
   1075  for (const syncLabel of syncLabels) {
   1076    if (syncing) {
   1077      is(
   1078        syncLabel.getAttribute("data-l10n-id"),
   1079        syncLabel.getAttribute("syncing-data-l10n-id"),
   1080        "label is set to the right value"
   1081      );
   1082    } else {
   1083      is(
   1084        syncLabel.getAttribute("data-l10n-id"),
   1085        syncLabel.getAttribute("sync-now-data-l10n-id"),
   1086        "label is set to the right value"
   1087      );
   1088    }
   1089  }
   1090 }
   1091 
   1092 async function checkFxaToolbarButtonPanel({
   1093  headerTitle,
   1094  headerDescription,
   1095  enabledItems,
   1096  disabledItems,
   1097  hiddenItems,
   1098  visibleItems,
   1099 }) {
   1100  is(
   1101    document.getElementById("fxa-menu-header-title").value,
   1102    headerTitle,
   1103    "has correct title"
   1104  );
   1105  is(
   1106    document.getElementById("fxa-menu-header-description").value,
   1107    headerDescription,
   1108    "has correct description"
   1109  );
   1110 
   1111  for (const id of enabledItems) {
   1112    const el = document.getElementById(id);
   1113    is(el.hasAttribute("disabled"), false, id + " is enabled");
   1114  }
   1115 
   1116  for (const id of disabledItems) {
   1117    const el = document.getElementById(id);
   1118    is(el.getAttribute("disabled"), "true", id + " is disabled");
   1119  }
   1120 
   1121  for (const id of hiddenItems) {
   1122    const el = document.getElementById(id);
   1123    ok(el.hasAttribute("hidden"), id + " is hidden");
   1124  }
   1125 
   1126  for (const id of visibleItems) {
   1127    const el = document.getElementById(id);
   1128    ok(isElementVisible(el), `${id} is visible`);
   1129  }
   1130 }
   1131 
   1132 function isElementVisible(el) {
   1133  if (!el) {
   1134    return false;
   1135  }
   1136  let style = window.getComputedStyle(el);
   1137  // The “hidden” property on the element itself
   1138  // might not exist or might be false, so we also
   1139  // check that the computed style is not hiding it
   1140  // (display: none or visibility: hidden).
   1141  return (
   1142    !el.hidden && style.display !== "none" && style.visibility !== "hidden"
   1143  );
   1144 }
   1145 
   1146 async function checkProfilesButtons(
   1147  previousElementSibling,
   1148  separatorVisible = false
   1149 ) {
   1150  const profilesButton = document.getElementById(
   1151    "PanelUI-fxa-menu-profiles-button"
   1152  );
   1153  const emptyProfilesButton = document.getElementById(
   1154    "PanelUI-fxa-menu-empty-profiles-button"
   1155  );
   1156  const profilesSeparator = document.getElementById(
   1157    "PanelUI-fxa-menu-profiles-separator"
   1158  );
   1159 
   1160  ok(
   1161    (profilesButton.hidden || emptyProfilesButton.hidden) &&
   1162      !(profilesButton.hidden && emptyProfilesButton.hidden),
   1163    "Only one of the profiles button is visible"
   1164  );
   1165 
   1166  is(
   1167    !profilesSeparator.hidden,
   1168    separatorVisible,
   1169    "The profile separator is visible"
   1170  );
   1171 
   1172  is(
   1173    previousElementSibling,
   1174    emptyProfilesButton.previousElementSibling,
   1175    "The profiles button is displayed after " +
   1176      emptyProfilesButton.previousElementSibling.id
   1177  );
   1178 }
   1179 
   1180 async function checkFxABadged() {
   1181  const button = document.getElementById("fxa-toolbar-menu-button");
   1182  await BrowserTestUtils.waitForCondition(() => {
   1183    return button.querySelector("label.feature-callout");
   1184  });
   1185  const badge = button.querySelector("label.feature-callout");
   1186  ok(badge, "expected feature-callout style badge");
   1187  ok(BrowserTestUtils.isVisible(badge), "expected the badge to be visible");
   1188 }
   1189 
   1190 // fxaStatus is one of 'not_configured', 'unverified', 'login-failed', or 'signedin'.
   1191 function checkFxAAvatar(fxaStatus) {
   1192  // Unhide the panel so computed styles can be read
   1193  document.querySelector("#appMenu-popup").hidden = false;
   1194 
   1195  const avatarContainers = [document.getElementById("fxa-avatar-image")];
   1196  for (const avatar of avatarContainers) {
   1197    const avatarURL = getComputedStyle(avatar).listStyleImage;
   1198    const expected = {
   1199      not_configured: 'url("chrome://browser/skin/fxa/avatar-empty.svg")',
   1200      unverified: 'url("chrome://browser/skin/fxa/avatar.svg")',
   1201      signedin: 'url("chrome://browser/skin/fxa/avatar.svg")',
   1202      "login-failed": 'url("chrome://browser/skin/fxa/avatar.svg")',
   1203    };
   1204    Assert.equal(
   1205      avatarURL,
   1206      expected[fxaStatus],
   1207      `expected avatar URL to be ${expected[fxaStatus]}, got ${avatarURL}`
   1208    );
   1209  }
   1210 }
   1211 
   1212 function checkAppMenuFxAText(hideStatus) {
   1213  let fxaText = document.getElementById("appMenu-fxa-text");
   1214  let isHidden = fxaText.hidden || fxaText.style.visibility == "collapse";
   1215  Assert.equal(isHidden, hideStatus, "FxA text has correct hidden state");
   1216 }
   1217 
   1218 // Only one item visible at a time.
   1219 function checkItemsVisibilities(itemsIds, expectedShownItemId) {
   1220  for (let id of itemsIds) {
   1221    if (id == expectedShownItemId) {
   1222      ok(
   1223        !document.getElementById(id).hidden,
   1224        "menuitem " + id + " should be visible"
   1225      );
   1226    } else {
   1227      ok(
   1228        document.getElementById(id).hidden,
   1229        "menuitem " + id + " should be hidden"
   1230      );
   1231    }
   1232  }
   1233 }
   1234 
   1235 function promiseObserver(topic) {
   1236  return new Promise(resolve => {
   1237    let obs = (aSubject, aTopic) => {
   1238      Services.obs.removeObserver(obs, aTopic);
   1239      resolve(aSubject);
   1240    };
   1241    Services.obs.addObserver(obs, topic);
   1242  });
   1243 }
   1244 
   1245 async function openTabAndFxaPanel() {
   1246  await BrowserTestUtils.openNewForegroundTab(gBrowser, "https://example.com/");
   1247  await openFxaPanel();
   1248 }
   1249 
   1250 async function openFxaPanel() {
   1251  let fxaButton = document.getElementById("fxa-toolbar-menu-button");
   1252  fxaButton.click();
   1253 
   1254  let fxaView = PanelMultiView.getViewNode(document, "PanelUI-fxa");
   1255  await BrowserTestUtils.waitForEvent(fxaView, "ViewShown");
   1256 }
   1257 
   1258 async function closeFxaPanel() {
   1259  let fxaView = PanelMultiView.getViewNode(document, "PanelUI-fxa");
   1260  let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
   1261  fxaView.closest("panel").hidePopup();
   1262  await hidden;
   1263 }
   1264 
   1265 async function openMainPanel() {
   1266  let menuButton = document.getElementById("PanelUI-menu-button");
   1267  menuButton.click();
   1268  await BrowserTestUtils.waitForEvent(window.PanelUI.mainView, "ViewShown");
   1269 }
   1270 
   1271 async function closeTabAndMainPanel() {
   1272  await gCUITestUtils.hideMainMenu();
   1273 
   1274  BrowserTestUtils.removeTab(gBrowser.selectedTab);
   1275 }