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 }