/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ const { LoginTestUtils } = ChromeUtils.importESModule( "resource://testing-common/LoginTestUtils.sys.mjs" ); async function setupWithDesktopDevices(state = UIState.STATUS_SIGNED_IN) { const sandbox = setupSyncFxAMocks({ state, fxaDevices: [ { id: 1, name: "This Device", isCurrentDevice: true, type: "desktop", }, { id: 2, name: "Other Device", type: "desktop", }, ], }); return sandbox; } async function tearDown(sandbox) { sandbox?.restore(); Services.prefs.clearUserPref("services.sync.lastTabFetch"); Services.prefs.clearUserPref("identity.fxaccounts.enabled"); } add_setup(async function () { // gSync.init() is called in a requestIdleCallback. Force its initialization. gSync.init(); await SpecialPowers.pushPrefEnv({ set: [ ["services.sync.engine.tabs", true], ["identity.fxaccounts.enabled", true], ], }); registerCleanupFunction(async function () { // reset internal state so it doesn't affect the next tests TabsSetupFlowManager.resetInternalState(); await tearDown(gSandbox); }); }); add_task(async function test_network_offline() { const sandbox = await setupWithDesktopDevices(); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers( null, "network:offline-status-changed", "offline" ); await waitForElementVisible(browser, "#tabpickup-steps", true); await waitForVisibleSetupStep(browser, { expectedVisible: "#tabpickup-steps-view0", }); const errorStateHeader = document.querySelector( "#tabpickup-steps-view0-header" ); await BrowserTestUtils.waitForMutationCondition( errorStateHeader, { childList: true }, () => errorStateHeader.textContent.includes("connection") ); ok( errorStateHeader.getAttribute("data-l10n-id").includes("network-offline"), "Correct message should show when network connection is lost" ); Services.obs.notifyObservers( null, "network:offline-status-changed", "online" ); await waitForElementVisible(browser, "#tabpickup-tabs-container", true); }); await tearDown(sandbox); }); add_task(async function test_sync_error() { const sandbox = await setupWithDesktopDevices(); sandbox.spy(TabsSetupFlowManager, "tryToClearError"); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, "weave:service:sync:error"); await waitForElementVisible(browser, "#tabpickup-steps", true); await waitForVisibleSetupStep(browser, { expectedVisible: "#tabpickup-steps-view0", }); const errorStateHeader = document.querySelector( "#tabpickup-steps-view0-header" ); await BrowserTestUtils.waitForMutationCondition( errorStateHeader, { childList: true }, () => errorStateHeader.textContent.includes("trouble syncing") ); ok( errorStateHeader.getAttribute("data-l10n-id").includes("sync-error"), "Correct message should show when there's a sync service error" ); await BrowserTestUtils.synthesizeMouseAtCenter( "#error-state-button", {}, browser ); await BrowserTestUtils.waitForCondition(() => { return TabsSetupFlowManager.tryToClearError.calledOnce; }); ok( TabsSetupFlowManager.tryToClearError.calledOnce, "TabsSetupFlowManager.tryToClearError() was called once" ); // Clear the error. Services.obs.notifyObservers(null, "weave:service:sync:finish"); }); // Now reopen the tab and check that sending an error state does not // start showing the error: Services.obs.notifyObservers(null, UIState.ON_UPDATE); const recentFetchTime = Math.floor(Date.now() / 1000); info("updating lastFetch:" + recentFetchTime); Services.prefs.setIntPref("services.sync.lastTabFetch", recentFetchTime); await withFirefoxView({ resetFlowManager: false }, async browser => { const { document } = browser.contentWindow; await waitForElementVisible(browser, "#synced-tabs-placeholder", true); Services.obs.notifyObservers(null, "weave:service:sync:error"); await TestUtils.waitForTick(); ok( BrowserTestUtils.is_visible( document.getElementById("synced-tabs-placeholder") ), "Should still be showing the placeholder content." ); let stepHeader = document.getElementById("tabpickup-steps-view0-header"); ok( !stepHeader || BrowserTestUtils.is_hidden(stepHeader), "Should not be showing an error state if we had previously synced successfully." ); // Now drop a device: let someDevice = gMockFxaDevices.pop(); Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated"); // This will trip a UI update where we decide we can't rely on // previously synced tabs anymore (they may be from the device // that was removed!), so we still show an error: await waitForElementVisible(browser, "#tabpickup-steps", true); await waitForVisibleSetupStep(browser, { expectedVisible: "#tabpickup-steps-view0", }); const errorStateHeader = document.querySelector( "#tabpickup-steps-view0-header" ); await BrowserTestUtils.waitForMutationCondition( errorStateHeader, { childList: true }, () => errorStateHeader.textContent.includes("trouble syncing") ); ok( errorStateHeader.getAttribute("data-l10n-id").includes("sync-error"), "Correct message should show when there's an error and tab information is outdated." ); // Sneak device back in so as not to break other tests: gMockFxaDevices.push(someDevice); // Clear the error. Services.obs.notifyObservers(null, "weave:service:sync:finish"); }); Services.prefs.clearUserPref("services.sync.lastTabFetch"); await tearDown(sandbox); }); add_task(async function test_sync_error_signed_out() { // sync error should not show if user is not signed in let sandbox = await setupWithDesktopDevices(UIState.STATUS_NOT_CONFIGURED); await withFirefoxView({}, async browser => { Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, "weave:service:sync:error"); await waitForElementVisible(browser, "#tabpickup-steps", true); await waitForVisibleSetupStep(browser, { expectedVisible: "#tabpickup-steps-view1", }); }); await tearDown(sandbox); }); add_task(async function test_sync_disconnected_error() { // it's possible for fxa to be enabled but sync not enabled. const sandbox = setupSyncFxAMocks({ state: UIState.STATUS_SIGNED_IN, syncEnabled: false, }); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; // triggered when user disconnects sync in about:preferences Services.obs.notifyObservers(null, UIState.ON_UPDATE); await waitForElementVisible(browser, "#tabpickup-steps", true); info("Waiting for the tabpickup error step to be visible"); await waitForVisibleSetupStep(browser, { expectedVisible: "#tabpickup-steps-view0", }); const errorStateHeader = document.querySelector( "#tabpickup-steps-view0-header" ); info( "Waiting for a mutation condition to ensure the right syncing error message" ); await BrowserTestUtils.waitForMutationCondition( errorStateHeader, { childList: true }, () => errorStateHeader.textContent.includes("Turn on syncing to continue") ); ok( errorStateHeader .getAttribute("data-l10n-id") .includes("sync-disconnected"), "Correct message should show when sync's been disconnected error" ); let preferencesTabPromise = BrowserTestUtils.waitForNewTab( browser.getTabBrowser(), "about:preferences?action=choose-what-to-sync#sync", true ); await BrowserTestUtils.synthesizeMouseAtCenter( "#error-state-button", {}, browser ); let preferencesTab = await preferencesTabPromise; await BrowserTestUtils.removeTab(preferencesTab); }); await tearDown(sandbox); }); add_task(async function test_password_change_disconnect_error() { // When the user changes their password on another device, we get into a state // where the user is signed out but sync is still enabled. const sandbox = setupSyncFxAMocks({ state: UIState.STATUS_LOGIN_FAILED, syncEnabled: true, }); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; // triggered by the user changing fxa password on another device Services.obs.notifyObservers(null, UIState.ON_UPDATE); await waitForElementVisible(browser, "#tabpickup-steps", true); await waitForVisibleSetupStep(browser, { expectedVisible: "#tabpickup-steps-view0", }); const errorStateHeader = document.querySelector( "#tabpickup-steps-view0-header" ); await BrowserTestUtils.waitForMutationCondition( errorStateHeader, { childList: true }, () => errorStateHeader.textContent.includes("Sign in to reconnect") ); ok( errorStateHeader.getAttribute("data-l10n-id").includes("signed-out"), "Correct message should show when user has been logged out due to external password change." ); }); await tearDown(sandbox); }); add_task(async function test_multiple_errors() { let sandbox = await setupWithDesktopDevices(); await withFirefoxView({}, async browser => { const { document } = browser.contentWindow; // Simulate conditions in which both the locked password and sync error // messages could be shown LoginTestUtils.primaryPassword.enable(); Services.obs.notifyObservers(null, UIState.ON_UPDATE); Services.obs.notifyObservers(null, "weave:service:sync:error"); info("Waiting for the primary password error message to be shown"); await waitForElementVisible(browser, "#tabpickup-steps", true); await waitForVisibleSetupStep(browser, { expectedVisible: "#tabpickup-steps-view0", }); const errorStateHeader = document.querySelector( "#tabpickup-steps-view0-header" ); await BrowserTestUtils.waitForMutationCondition( errorStateHeader, { childList: true }, () => errorStateHeader.textContent.includes("Enter your Primary Password") ); ok( errorStateHeader.getAttribute("data-l10n-id").includes("password-locked"), "Password locked error message is shown" ); const errorLink = document.querySelector("#error-state-link"); ok( errorLink && BrowserTestUtils.is_visible(errorLink), "Error link is visible" ); // Clear the primary password error message LoginTestUtils.primaryPassword.disable(); Services.obs.notifyObservers(null, UIState.ON_UPDATE); info("Waiting for the sync error message to be shown"); await BrowserTestUtils.waitForMutationCondition( errorStateHeader, { childList: true }, () => errorStateHeader.textContent.includes("trouble syncing") ); ok( errorStateHeader.getAttribute("data-l10n-id").includes("sync-error"), "Sync error message is now shown" ); ok( errorLink && BrowserTestUtils.is_hidden(errorLink), "Error link is now hidden" ); // Clear the sync error Services.obs.notifyObservers(null, "weave:service:sync:finish"); }); await tearDown(sandbox); });