1 /* Any copyright is dedicated to the Public Domain.
2 * http://creativecommons.org/publicdomain/zero/1.0/ */
4 const FXA_CONTINUE_EVENT = [["firefoxview", "fxa_continue", "sync", undefined]];
6 const FXA_MOBILE_EVENT = [
7 ["firefoxview", "fxa_mobile", "sync", undefined, { has_devices: "false" }],
10 var gMockFxaDevices = null;
13 function promiseSyncReady() {
14 let service = Cc["@mozilla.org/weave/service;1"].getService(
17 return service.whenLoaded();
22 async function setupWithDesktopDevices() {
23 const sandbox = setupMocks({
24 state: UIState.STATUS_SIGNED_IN,
29 isCurrentDevice: true,
42 await SpecialPowers.pushPrefEnv({
43 set: [["services.sync.engine.tabs", true]],
47 add_setup(async function () {
48 registerCleanupFunction(() => {
49 // reset internal state so it doesn't affect the next tests
50 TabsSetupFlowManager.resetInternalState();
53 // gSync.init() is called in a requestIdleCallback. Force its initialization.
56 registerCleanupFunction(async function () {
57 Services.prefs.clearUserPref("services.sync.engine.tabs");
58 await tearDown(gSandbox);
60 // set tab sync false so we don't skip setup states
61 await SpecialPowers.pushPrefEnv({
62 set: [["services.sync.engine.tabs", false]],
66 add_task(async function test_unconfigured_initial_state() {
67 const sandbox = setupMocks({
68 state: UIState.STATUS_NOT_CONFIGURED,
71 await withFirefoxView({ openNewWindow: true }, async browser => {
72 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
73 await waitForVisibleSetupStep(browser, {
74 expectedVisible: "#tabpickup-steps-view1",
76 checkMobilePromo(browser, {
78 mobileConfirmation: false,
80 await clearAllParentTelemetryEvents();
81 await BrowserTestUtils.synthesizeMouseAtCenter(
82 'button[data-action="view1-primary-action"]',
87 await TestUtils.waitForCondition(
89 let events = Services.telemetry.snapshotEvents(
90 Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
93 return events && events.length >= 1;
95 "Waiting for fxa_continue firefoxview telemetry events.",
100 TelemetryTestUtils.assertEvents(
102 { category: "firefoxview" },
103 { clear: true, process: "parent" }
106 await tearDown(sandbox);
109 add_task(async function test_signed_in() {
110 const sandbox = setupMocks({
111 state: UIState.STATUS_SIGNED_IN,
116 isCurrentDevice: true,
123 await withFirefoxView({ openNewWindow: true }, async browser => {
124 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
125 await waitForVisibleSetupStep(browser, {
126 expectedVisible: "#tabpickup-steps-view2",
129 fxAccounts.device.recentDeviceList?.length,
131 "Just 1 device connected"
133 checkMobilePromo(browser, {
135 mobileConfirmation: false,
138 await clearAllParentTelemetryEvents();
140 await BrowserTestUtils.synthesizeMouseAtCenter(
141 'button[data-action="view2-primary-action"]',
146 await TestUtils.waitForCondition(
148 let events = Services.telemetry.snapshotEvents(
149 Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
152 return events && events.length >= 1;
154 "Waiting for fxa_mobile firefoxview telemetry events.",
159 TelemetryTestUtils.assertEvents(
161 { category: "firefoxview" },
162 { clear: true, process: "parent" }
165 await tearDown(sandbox);
168 add_task(async function test_support_links() {
169 await clearAllParentTelemetryEvents();
171 state: UIState.STATUS_SIGNED_IN,
176 isCurrentDevice: true,
182 await withFirefoxView({}, async browser => {
183 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
184 await waitForVisibleSetupStep(browser, {
185 expectedVisible: "#tabpickup-steps-view2",
187 const { document } = browser.contentWindow;
188 const container = document.getElementById("tab-pickup-container");
189 const supportLinks = Array.from(
190 container.querySelectorAll("a[href]")
191 ).filter(a => !!a.href);
192 is(supportLinks.length, 2, "Support links have non-empty hrefs");
196 add_task(async function test_2nd_desktop_connected() {
197 const sandbox = setupMocks({
198 state: UIState.STATUS_SIGNED_IN,
203 isCurrentDevice: true,
209 name: "Other Device",
215 await withFirefoxView({}, async browser => {
216 // ensure tab sync is false so we don't skip onto next step
218 !Services.prefs.getBoolPref("services.sync.engine.tabs", false),
219 "services.sync.engine.tabs is initially false"
222 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
223 await waitForVisibleSetupStep(browser, {
224 expectedVisible: "#tabpickup-steps-view3",
227 is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
229 fxAccounts.device.recentDeviceList?.every(
230 device => device.type !== "mobile" && device.type !== "tablet"
232 "No connected device is type:mobile or type:tablet"
234 checkMobilePromo(browser, {
236 mobileConfirmation: false,
239 await tearDown(sandbox);
242 add_task(async function test_mobile_connected() {
243 const sandbox = setupMocks({
244 state: UIState.STATUS_SIGNED_IN,
249 isCurrentDevice: true,
255 name: "Other Device",
261 await withFirefoxView({}, async browser => {
262 // ensure tab sync is false so we don't skip onto next step
264 !Services.prefs.getBoolPref("services.sync.engine.tabs", false),
265 "services.sync.engine.tabs is initially false"
268 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
269 await waitForVisibleSetupStep(browser, {
270 expectedVisible: "#tabpickup-steps-view3",
273 is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
275 fxAccounts.device.recentDeviceList?.some(
276 device => device.type == "mobile"
278 "A connected device is type:mobile"
280 checkMobilePromo(browser, {
282 mobileConfirmation: false,
285 await tearDown(sandbox);
288 add_task(async function test_tablet_connected() {
289 const sandbox = setupMocks({
290 state: UIState.STATUS_SIGNED_IN,
295 isCurrentDevice: true,
301 name: "Other Device",
307 await withFirefoxView({}, async browser => {
308 // ensure tab sync is false so we don't skip onto next step
310 !Services.prefs.getBoolPref("services.sync.engine.tabs", false),
311 "services.sync.engine.tabs is initially false"
314 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
315 await waitForVisibleSetupStep(browser, {
316 expectedVisible: "#tabpickup-steps-view3",
319 is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
321 fxAccounts.device.recentDeviceList?.some(
322 device => device.type == "tablet"
324 "A connected device is type:tablet"
326 checkMobilePromo(browser, {
328 mobileConfirmation: false,
331 await tearDown(sandbox);
334 add_task(async function test_tab_sync_enabled() {
335 const sandbox = setupMocks({
336 state: UIState.STATUS_SIGNED_IN,
341 isCurrentDevice: true,
347 name: "Other Device",
353 await withFirefoxView({}, async browser => {
354 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
356 // test initial state, with the pref not enabled
357 await waitForVisibleSetupStep(browser, {
358 expectedVisible: "#tabpickup-steps-view3",
360 checkMobilePromo(browser, {
362 mobileConfirmation: false,
365 // test with the pref toggled on
366 await SpecialPowers.pushPrefEnv({
367 set: [["services.sync.engine.tabs", true]],
369 await waitForElementVisible(browser, "#tabpickup-steps", false);
370 checkMobilePromo(browser, {
372 mobileConfirmation: false,
375 // reset and test clicking the action button
376 await SpecialPowers.popPrefEnv();
377 await waitForVisibleSetupStep(browser, {
378 expectedVisible: "#tabpickup-steps-view3",
380 checkMobilePromo(browser, {
382 mobileConfirmation: false,
385 const actionButton = browser.contentWindow.document.querySelector(
386 "#tabpickup-steps-view3 button.primary"
388 actionButton.click();
390 await waitForElementVisible(browser, "#tabpickup-steps", false);
391 checkMobilePromo(browser, {
393 mobileConfirmation: false,
395 ok(true, "Tab pickup product tour screen renders when sync is enabled");
397 Services.prefs.getBoolPref("services.sync.engine.tabs", false),
398 "tab sync pref should be enabled after button click"
401 await tearDown(sandbox);
404 add_task(async function test_mobile_promo() {
405 const sandbox = await setupWithDesktopDevices();
406 await withFirefoxView({}, async browser => {
407 // ensure last tab fetch was just now so we don't get the loading state
408 await touchLastTabFetch();
410 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
411 await waitForElementVisible(browser, ".synced-tabs-container");
412 is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
414 info("checking mobile promo, should be visible now");
415 checkMobilePromo(browser, {
417 mobileConfirmation: false,
420 gMockFxaDevices.push({
422 name: "Mobile Device",
427 Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
429 // Wait for the async refreshDeviceList(),
430 // which should result in the promo being hidden
431 await waitForElementVisible(
433 "#tab-pickup-container > .promo-box",
436 is(fxAccounts.device.recentDeviceList?.length, 3, "3 devices connected");
437 checkMobilePromo(browser, {
439 mobileConfirmation: true,
442 info("checking mobile promo disappears on log out");
443 gMockFxaDevices.pop();
444 Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
445 await waitForElementVisible(
447 "#tab-pickup-container > .promo-box",
450 checkMobilePromo(browser, {
452 mobileConfirmation: false,
455 // Set the UIState to what we expect when the user signs out
456 gUIStateStatus = UIState.STATUS_NOT_CONFIGURED;
457 gUIStateSyncEnabled = undefined;
460 "notifying that we've signed out of fxa, UIState.get().status:" +
463 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
464 info("waiting for setup card 1 to appear again");
465 await waitForVisibleSetupStep(browser, {
466 expectedVisible: "#tabpickup-steps-view1",
468 checkMobilePromo(browser, {
470 mobileConfirmation: false,
473 await tearDown(sandbox);
476 add_task(async function test_mobile_promo_pref() {
477 const sandbox = await setupWithDesktopDevices();
478 await SpecialPowers.pushPrefEnv({
479 set: [[MOBILE_PROMO_DISMISSED_PREF, true]],
481 await withFirefoxView({}, async browser => {
482 // ensure tab sync is false so we don't skip onto next step
483 info("starting test, will notify of UIState update");
484 // ensure last tab fetch was just now so we don't get the loading state
485 await touchLastTabFetch();
487 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
488 await waitForElementVisible(browser, ".synced-tabs-container");
489 is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
491 info("checking mobile promo, should be still hidden because of the pref");
492 checkMobilePromo(browser, {
494 mobileConfirmation: false,
497 // reset the dismissed pref, which should case the promo to get shown
498 await SpecialPowers.pushPrefEnv({
499 set: [[MOBILE_PROMO_DISMISSED_PREF, false]],
501 await waitForElementVisible(
503 "#tab-pickup-container > .promo-box",
507 const promoElem = browser.contentWindow.document.querySelector(
508 "#tab-pickup-container > .promo-box"
510 const promoElemClose = promoElem.querySelector(".close");
511 ok(promoElemClose.hasAttribute("aria-label"), "Button has an a11y name");
512 // check that dismissing the promo sets the pref
513 info("Clicking the promo close button: " + promoElemClose);
514 EventUtils.sendMouseEvent({ type: "click" }, promoElemClose);
516 info("Check the promo box got hidden");
517 BrowserTestUtils.is_hidden(promoElem);
519 SpecialPowers.getBoolPref(MOBILE_PROMO_DISMISSED_PREF),
520 "Promo pref is updated when close is clicked"
523 await tearDown(sandbox);
526 add_task(async function test_mobile_promo_windows() {
527 // make sure interacting with the promo and success confirmation in one window
528 // also updates the others
529 const sandbox = await setupWithDesktopDevices();
530 await withFirefoxView({}, async browser => {
531 // ensure last tab fetch was just now so we don't get the loading state
532 await touchLastTabFetch();
534 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
535 await waitForElementVisible(browser, ".synced-tabs-container");
536 is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
538 info("checking mobile promo is visible");
539 checkMobilePromo(browser, {
541 mobileConfirmation: false,
545 "opening new window, pref is: " +
546 SpecialPowers.getBoolPref("browser.tabs.firefox-view")
549 info("Got window, now opening Firefox View in it");
550 await withFirefoxView(
551 { openNewWindow: true, resetFlowManager: false },
552 async win2Browser => {
553 info("In withFirefoxView taskFn for win2");
554 // promo should be visible in the 2nd window too
555 info("check mobile promo is visible in the new window");
556 checkMobilePromo(win2Browser, {
558 mobileConfirmation: false,
561 // add the mobile device to get the success confirmation in both instances
562 info("add a mobile device and send device_connected notification");
563 gMockFxaDevices.push({
565 name: "Mobile Device",
570 Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
572 fxAccounts.device.recentDeviceList?.length,
574 "3 devices connected"
577 // Wait for the async refreshDevices(),
578 // which should result in the promo being hidden
579 info("waiting for the confirmation box to be visible");
580 await waitForElementVisible(
582 "#tab-pickup-container > .promo-box",
586 for (let fxviewBrowser of [browser, win2Browser]) {
588 "checking promo is hidden and confirmation is visible in each window"
590 checkMobilePromo(fxviewBrowser, {
592 mobileConfirmation: true,
596 // dismiss the confirmation and check its gone from both instances
597 const confirmBox = win2Browser.contentWindow.document.querySelector(
598 "#tab-pickup-container > .confirmation-message-box"
600 const closeButton = confirmBox.querySelector(".close");
601 ok(closeButton.hasAttribute("aria-label"), "Button has an a11y name");
602 EventUtils.sendMouseEvent(
605 win2Browser.ownerGlobal
607 BrowserTestUtils.is_hidden(confirmBox);
609 for (let fxviewBrowser of [browser, win2Browser]) {
610 checkMobilePromo(fxviewBrowser, {
612 mobileConfirmation: false,
618 await tearDown(sandbox);
621 async function mockFxaDeviceConnected(win) {
622 // We use an existing tab to navigate to the final "device connected" url
623 // in order to fake the fxa device sync process
624 const url = "https://example.org/pair/auth/complete";
625 is(win.gBrowser.tabs.length, 3, "Tabs strip should contain three tabs");
627 BrowserTestUtils.startLoadingURIString(
628 win.gBrowser.selectedTab.linkedBrowser,
632 await BrowserTestUtils.browserLoaded(
633 win.gBrowser.selectedTab.linkedBrowser,
639 win.gBrowser.selectedTab.linkedBrowser.currentURI.filePath,
640 "/pair/auth/complete",
641 "/pair/auth/complete is the selected tab"
645 add_task(async function test_close_device_connected_tab() {
646 // test that when a device has been connected to sync we close
647 // that tab after the user is directed back to firefox view
649 // Ensure we are in the correct state to start the task.
650 TabsSetupFlowManager.resetInternalState();
651 await SpecialPowers.pushPrefEnv({
652 set: [["identity.fxaccounts.remote.root", "https://example.org/"]],
654 let win = await BrowserTestUtils.openNewBrowserWindow();
655 let fxViewTab = await openFirefoxViewTab(win);
657 await waitForVisibleSetupStep(win.gBrowser, {
658 expectedVisible: "#tabpickup-steps-view1",
661 let actionButton = win.gBrowser.contentWindow.document.querySelector(
662 "#tabpickup-steps-view1 button.primary"
664 // initiate the sign in flow from Firefox View, to check that didFxaTabOpen is set
665 let tabSwitched = BrowserTestUtils.waitForEvent(
669 actionButton.click();
672 // fake the end point of the device syncing flow
673 await mockFxaDeviceConnected(win);
674 let deviceConnectedTab = win.gBrowser.tabs[2];
676 // remove the blank tab opened with the browser to check that we don't
677 // close the window when the "Device connected" tab is closed
678 const newTab = win.gBrowser.tabs.find(
679 tab => tab != deviceConnectedTab && tab != fxViewTab
681 let removedTab = BrowserTestUtils.waitForTabClosing(newTab);
682 BrowserTestUtils.removeTab(newTab);
685 is(win.gBrowser.tabs.length, 2, "Tabs strip should only contain two tabs");
688 win.gBrowser.selectedTab.linkedBrowser.currentURI.filePath,
689 "/pair/auth/complete",
690 "/pair/auth/complete is the selected tab"
693 // we use this instead of BrowserTestUtils.switchTab to get back to the firefox view tab
694 // because this more accurately reflects how this tab is selected - via a custom onmousedown
695 // and command that calls FirefoxViewHandler.openTab (both when the user manually clicks the tab
696 // and when navigating from the fxa Device Connected tab, which also calls FirefoxViewHandler.openTab)
697 await EventUtils.synthesizeMouseAtCenter(
698 win.document.getElementById("firefox-view-button"),
699 { type: "mousedown" },
703 is(win.gBrowser.tabs.length, 2, "Tabs strip should only contain two tabs");
706 win.gBrowser.tabs[0].linkedBrowser.currentURI.filePath,
708 "First tab is Firefox view"
712 win.gBrowser.tabs[1].linkedBrowser.currentURI.filePath,
714 "Second tab is about:newtab"
717 // now simulate the signed-in state with the prompt to download
719 const sandbox = setupMocks({
720 state: UIState.STATUS_SIGNED_IN,
725 isCurrentDevice: true,
732 Services.obs.notifyObservers(null, UIState.ON_UPDATE);
734 await waitForVisibleSetupStep(win.gBrowser, {
735 expectedVisible: "#tabpickup-steps-view2",
738 actionButton = win.gBrowser.contentWindow.document.querySelector(
739 "#tabpickup-steps-view2 button.primary"
741 // initiate the connect device (mobile) flow from Firefox View, to check that didFxaTabOpen is set
742 tabSwitched = BrowserTestUtils.waitForEvent(win.gBrowser, "TabSwitchDone");
743 actionButton.click();
745 // fake the end point of the device syncing flow
746 await mockFxaDeviceConnected(win);
748 await EventUtils.synthesizeMouseAtCenter(
749 win.document.getElementById("firefox-view-button"),
750 { type: "mousedown" },
753 is(win.gBrowser.tabs.length, 2, "Tabs strip should only contain two tabs");
756 win.gBrowser.tabs[0].linkedBrowser.currentURI.filePath,
758 "First tab is Firefox view"
762 win.gBrowser.tabs[1].linkedBrowser.currentURI.filePath,
764 "Second tab is about:newtab"
768 await tearDown(sandbox);
769 await BrowserTestUtils.closeWindow(win);