Backed out changeset 7b2ffe9a4d06 (bug 1869605) for causing bc failures on browser_no...
[gecko.git] / browser / components / firefoxview / tests / browser / browser_setup_state.js
blob9f34e3861405b882a9faa53df766e6f0362e4463
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" }],
8 ];
10 var gMockFxaDevices = null;
11 var gUIStateStatus;
13 function promiseSyncReady() {
14   let service = Cc["@mozilla.org/weave/service;1"].getService(
15     Ci.nsISupports
16   ).wrappedJSObject;
17   return service.whenLoaded();
20 var gSandbox;
22 async function setupWithDesktopDevices() {
23   const sandbox = setupMocks({
24     state: UIState.STATUS_SIGNED_IN,
25     fxaDevices: [
26       {
27         id: 1,
28         name: "This Device",
29         isCurrentDevice: true,
30         type: "desktop",
31         tabs: [],
32       },
33       {
34         id: 2,
35         name: "Other Device",
36         type: "desktop",
37         tabs: [],
38       },
39     ],
40   });
42   await SpecialPowers.pushPrefEnv({
43     set: [["services.sync.engine.tabs", true]],
44   });
45   return sandbox;
47 add_setup(async function () {
48   registerCleanupFunction(() => {
49     // reset internal state so it doesn't affect the next tests
50     TabsSetupFlowManager.resetInternalState();
51   });
53   // gSync.init() is called in a requestIdleCallback. Force its initialization.
54   gSync.init();
56   registerCleanupFunction(async function () {
57     Services.prefs.clearUserPref("services.sync.engine.tabs");
58     await tearDown(gSandbox);
59   });
60   // set tab sync false so we don't skip setup states
61   await SpecialPowers.pushPrefEnv({
62     set: [["services.sync.engine.tabs", false]],
63   });
64 });
66 add_task(async function test_unconfigured_initial_state() {
67   const sandbox = setupMocks({
68     state: UIState.STATUS_NOT_CONFIGURED,
69     syncEnabled: false,
70   });
71   await withFirefoxView({ openNewWindow: true }, async browser => {
72     Services.obs.notifyObservers(null, UIState.ON_UPDATE);
73     await waitForVisibleSetupStep(browser, {
74       expectedVisible: "#tabpickup-steps-view1",
75     });
76     checkMobilePromo(browser, {
77       mobilePromo: false,
78       mobileConfirmation: false,
79     });
80     await clearAllParentTelemetryEvents();
81     await BrowserTestUtils.synthesizeMouseAtCenter(
82       'button[data-action="view1-primary-action"]',
83       {},
84       browser
85     );
87     await TestUtils.waitForCondition(
88       () => {
89         let events = Services.telemetry.snapshotEvents(
90           Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
91           false
92         ).parent;
93         return events && events.length >= 1;
94       },
95       "Waiting for fxa_continue firefoxview telemetry events.",
96       200,
97       100
98     );
100     TelemetryTestUtils.assertEvents(
101       FXA_CONTINUE_EVENT,
102       { category: "firefoxview" },
103       { clear: true, process: "parent" }
104     );
105   });
106   await tearDown(sandbox);
109 add_task(async function test_signed_in() {
110   const sandbox = setupMocks({
111     state: UIState.STATUS_SIGNED_IN,
112     fxaDevices: [
113       {
114         id: 1,
115         name: "This Device",
116         isCurrentDevice: true,
117         type: "desktop",
118         tabs: [],
119       },
120     ],
121   });
123   await withFirefoxView({ openNewWindow: true }, async browser => {
124     Services.obs.notifyObservers(null, UIState.ON_UPDATE);
125     await waitForVisibleSetupStep(browser, {
126       expectedVisible: "#tabpickup-steps-view2",
127     });
128     is(
129       fxAccounts.device.recentDeviceList?.length,
130       1,
131       "Just 1 device connected"
132     );
133     checkMobilePromo(browser, {
134       mobilePromo: false,
135       mobileConfirmation: false,
136     });
138     await clearAllParentTelemetryEvents();
140     await BrowserTestUtils.synthesizeMouseAtCenter(
141       'button[data-action="view2-primary-action"]',
142       {},
143       browser
144     );
146     await TestUtils.waitForCondition(
147       () => {
148         let events = Services.telemetry.snapshotEvents(
149           Ci.nsITelemetry.DATASET_PRERELEASE_CHANNELS,
150           false
151         ).parent;
152         return events && events.length >= 1;
153       },
154       "Waiting for fxa_mobile firefoxview telemetry events.",
155       200,
156       100
157     );
159     TelemetryTestUtils.assertEvents(
160       FXA_MOBILE_EVENT,
161       { category: "firefoxview" },
162       { clear: true, process: "parent" }
163     );
164   });
165   await tearDown(sandbox);
168 add_task(async function test_support_links() {
169   await clearAllParentTelemetryEvents();
170   setupMocks({
171     state: UIState.STATUS_SIGNED_IN,
172     fxaDevices: [
173       {
174         id: 1,
175         name: "This Device",
176         isCurrentDevice: true,
177         type: "desktop",
178         tabs: [],
179       },
180     ],
181   });
182   await withFirefoxView({}, async browser => {
183     Services.obs.notifyObservers(null, UIState.ON_UPDATE);
184     await waitForVisibleSetupStep(browser, {
185       expectedVisible: "#tabpickup-steps-view2",
186     });
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");
193   });
196 add_task(async function test_2nd_desktop_connected() {
197   const sandbox = setupMocks({
198     state: UIState.STATUS_SIGNED_IN,
199     fxaDevices: [
200       {
201         id: 1,
202         name: "This Device",
203         isCurrentDevice: true,
204         type: "desktop",
205         tabs: [],
206       },
207       {
208         id: 2,
209         name: "Other Device",
210         type: "desktop",
211         tabs: [],
212       },
213     ],
214   });
215   await withFirefoxView({}, async browser => {
216     // ensure tab sync is false so we don't skip onto next step
217     ok(
218       !Services.prefs.getBoolPref("services.sync.engine.tabs", false),
219       "services.sync.engine.tabs is initially false"
220     );
222     Services.obs.notifyObservers(null, UIState.ON_UPDATE);
223     await waitForVisibleSetupStep(browser, {
224       expectedVisible: "#tabpickup-steps-view3",
225     });
227     is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
228     ok(
229       fxAccounts.device.recentDeviceList?.every(
230         device => device.type !== "mobile" && device.type !== "tablet"
231       ),
232       "No connected device is type:mobile or type:tablet"
233     );
234     checkMobilePromo(browser, {
235       mobilePromo: false,
236       mobileConfirmation: false,
237     });
238   });
239   await tearDown(sandbox);
242 add_task(async function test_mobile_connected() {
243   const sandbox = setupMocks({
244     state: UIState.STATUS_SIGNED_IN,
245     fxaDevices: [
246       {
247         id: 1,
248         name: "This Device",
249         isCurrentDevice: true,
250         type: "desktop",
251         tabs: [],
252       },
253       {
254         id: 2,
255         name: "Other Device",
256         type: "mobile",
257         tabs: [],
258       },
259     ],
260   });
261   await withFirefoxView({}, async browser => {
262     // ensure tab sync is false so we don't skip onto next step
263     ok(
264       !Services.prefs.getBoolPref("services.sync.engine.tabs", false),
265       "services.sync.engine.tabs is initially false"
266     );
268     Services.obs.notifyObservers(null, UIState.ON_UPDATE);
269     await waitForVisibleSetupStep(browser, {
270       expectedVisible: "#tabpickup-steps-view3",
271     });
273     is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
274     ok(
275       fxAccounts.device.recentDeviceList?.some(
276         device => device.type == "mobile"
277       ),
278       "A connected device is type:mobile"
279     );
280     checkMobilePromo(browser, {
281       mobilePromo: false,
282       mobileConfirmation: false,
283     });
284   });
285   await tearDown(sandbox);
288 add_task(async function test_tablet_connected() {
289   const sandbox = setupMocks({
290     state: UIState.STATUS_SIGNED_IN,
291     fxaDevices: [
292       {
293         id: 1,
294         name: "This Device",
295         isCurrentDevice: true,
296         type: "desktop",
297         tabs: [],
298       },
299       {
300         id: 2,
301         name: "Other Device",
302         type: "tablet",
303         tabs: [],
304       },
305     ],
306   });
307   await withFirefoxView({}, async browser => {
308     // ensure tab sync is false so we don't skip onto next step
309     ok(
310       !Services.prefs.getBoolPref("services.sync.engine.tabs", false),
311       "services.sync.engine.tabs is initially false"
312     );
314     Services.obs.notifyObservers(null, UIState.ON_UPDATE);
315     await waitForVisibleSetupStep(browser, {
316       expectedVisible: "#tabpickup-steps-view3",
317     });
319     is(fxAccounts.device.recentDeviceList?.length, 2, "2 devices connected");
320     ok(
321       fxAccounts.device.recentDeviceList?.some(
322         device => device.type == "tablet"
323       ),
324       "A connected device is type:tablet"
325     );
326     checkMobilePromo(browser, {
327       mobilePromo: false,
328       mobileConfirmation: false,
329     });
330   });
331   await tearDown(sandbox);
334 add_task(async function test_tab_sync_enabled() {
335   const sandbox = setupMocks({
336     state: UIState.STATUS_SIGNED_IN,
337     fxaDevices: [
338       {
339         id: 1,
340         name: "This Device",
341         isCurrentDevice: true,
342         type: "desktop",
343         tabs: [],
344       },
345       {
346         id: 2,
347         name: "Other Device",
348         type: "mobile",
349         tabs: [],
350       },
351     ],
352   });
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",
359     });
360     checkMobilePromo(browser, {
361       mobilePromo: false,
362       mobileConfirmation: false,
363     });
365     // test with the pref toggled on
366     await SpecialPowers.pushPrefEnv({
367       set: [["services.sync.engine.tabs", true]],
368     });
369     await waitForElementVisible(browser, "#tabpickup-steps", false);
370     checkMobilePromo(browser, {
371       mobilePromo: false,
372       mobileConfirmation: false,
373     });
375     // reset and test clicking the action button
376     await SpecialPowers.popPrefEnv();
377     await waitForVisibleSetupStep(browser, {
378       expectedVisible: "#tabpickup-steps-view3",
379     });
380     checkMobilePromo(browser, {
381       mobilePromo: false,
382       mobileConfirmation: false,
383     });
385     const actionButton = browser.contentWindow.document.querySelector(
386       "#tabpickup-steps-view3 button.primary"
387     );
388     actionButton.click();
390     await waitForElementVisible(browser, "#tabpickup-steps", false);
391     checkMobilePromo(browser, {
392       mobilePromo: false,
393       mobileConfirmation: false,
394     });
395     ok(true, "Tab pickup product tour screen renders when sync is enabled");
396     ok(
397       Services.prefs.getBoolPref("services.sync.engine.tabs", false),
398       "tab sync pref should be enabled after button click"
399     );
400   });
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, {
416       mobilePromo: true,
417       mobileConfirmation: false,
418     });
420     gMockFxaDevices.push({
421       id: 3,
422       name: "Mobile Device",
423       type: "mobile",
424       tabs: [],
425     });
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(
432       browser,
433       "#tab-pickup-container > .promo-box",
434       false
435     );
436     is(fxAccounts.device.recentDeviceList?.length, 3, "3 devices connected");
437     checkMobilePromo(browser, {
438       mobilePromo: false,
439       mobileConfirmation: true,
440     });
442     info("checking mobile promo disappears on log out");
443     gMockFxaDevices.pop();
444     Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
445     await waitForElementVisible(
446       browser,
447       "#tab-pickup-container > .promo-box",
448       true
449     );
450     checkMobilePromo(browser, {
451       mobilePromo: true,
452       mobileConfirmation: false,
453     });
455     // Set the UIState to what we expect when the user signs out
456     gUIStateStatus = UIState.STATUS_NOT_CONFIGURED;
457     gUIStateSyncEnabled = undefined;
459     info(
460       "notifying that we've signed out of fxa, UIState.get().status:" +
461         UIState.get().status
462     );
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",
467     });
468     checkMobilePromo(browser, {
469       mobilePromo: false,
470       mobileConfirmation: false,
471     });
472   });
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]],
480   });
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, {
493       mobilePromo: false,
494       mobileConfirmation: false,
495     });
497     // reset the dismissed pref, which should case the promo to get shown
498     await SpecialPowers.pushPrefEnv({
499       set: [[MOBILE_PROMO_DISMISSED_PREF, false]],
500     });
501     await waitForElementVisible(
502       browser,
503       "#tab-pickup-container > .promo-box",
504       true
505     );
507     const promoElem = browser.contentWindow.document.querySelector(
508       "#tab-pickup-container > .promo-box"
509     );
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);
518     ok(
519       SpecialPowers.getBoolPref(MOBILE_PROMO_DISMISSED_PREF),
520       "Promo pref is updated when close is clicked"
521     );
522   });
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, {
540       mobilePromo: true,
541       mobileConfirmation: false,
542     });
544     info(
545       "opening new window, pref is: " +
546         SpecialPowers.getBoolPref("browser.tabs.firefox-view")
547     );
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, {
557           mobilePromo: true,
558           mobileConfirmation: false,
559         });
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({
564           id: 3,
565           name: "Mobile Device",
566           type: "mobile",
567           tabs: [],
568         });
570         Services.obs.notifyObservers(null, "fxaccounts:devicelist_updated");
571         is(
572           fxAccounts.device.recentDeviceList?.length,
573           3,
574           "3 devices connected"
575         );
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(
581           win2Browser,
582           "#tab-pickup-container > .promo-box",
583           false
584         );
586         for (let fxviewBrowser of [browser, win2Browser]) {
587           info(
588             "checking promo is hidden and confirmation is visible in each window"
589           );
590           checkMobilePromo(fxviewBrowser, {
591             mobilePromo: false,
592             mobileConfirmation: true,
593           });
594         }
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"
599         );
600         const closeButton = confirmBox.querySelector(".close");
601         ok(closeButton.hasAttribute("aria-label"), "Button has an a11y name");
602         EventUtils.sendMouseEvent(
603           { type: "click" },
604           closeButton,
605           win2Browser.ownerGlobal
606         );
607         BrowserTestUtils.is_hidden(confirmBox);
609         for (let fxviewBrowser of [browser, win2Browser]) {
610           checkMobilePromo(fxviewBrowser, {
611             mobilePromo: false,
612             mobileConfirmation: false,
613           });
614         }
615       }
616     );
617   });
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,
629     url
630   );
632   await BrowserTestUtils.browserLoaded(
633     win.gBrowser.selectedTab.linkedBrowser,
634     null,
635     url
636   );
638   is(
639     win.gBrowser.selectedTab.linkedBrowser.currentURI.filePath,
640     "/pair/auth/complete",
641     "/pair/auth/complete is the selected tab"
642   );
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/"]],
653   });
654   let win = await BrowserTestUtils.openNewBrowserWindow();
655   let fxViewTab = await openFirefoxViewTab(win);
657   await waitForVisibleSetupStep(win.gBrowser, {
658     expectedVisible: "#tabpickup-steps-view1",
659   });
661   let actionButton = win.gBrowser.contentWindow.document.querySelector(
662     "#tabpickup-steps-view1 button.primary"
663   );
664   // initiate the sign in flow from Firefox View, to check that didFxaTabOpen is set
665   let tabSwitched = BrowserTestUtils.waitForEvent(
666     win.gBrowser,
667     "TabSwitchDone"
668   );
669   actionButton.click();
670   await tabSwitched;
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
680   );
681   let removedTab = BrowserTestUtils.waitForTabClosing(newTab);
682   BrowserTestUtils.removeTab(newTab);
683   await removedTab;
685   is(win.gBrowser.tabs.length, 2, "Tabs strip should only contain two tabs");
687   is(
688     win.gBrowser.selectedTab.linkedBrowser.currentURI.filePath,
689     "/pair/auth/complete",
690     "/pair/auth/complete is the selected tab"
691   );
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" },
700     win
701   );
703   is(win.gBrowser.tabs.length, 2, "Tabs strip should only contain two tabs");
705   is(
706     win.gBrowser.tabs[0].linkedBrowser.currentURI.filePath,
707     "firefoxview",
708     "First tab is Firefox view"
709   );
711   is(
712     win.gBrowser.tabs[1].linkedBrowser.currentURI.filePath,
713     "newtab",
714     "Second tab is about:newtab"
715   );
717   // now simulate the signed-in state with the prompt to download
718   // and sync mobile
719   const sandbox = setupMocks({
720     state: UIState.STATUS_SIGNED_IN,
721     fxaDevices: [
722       {
723         id: 1,
724         name: "This Device",
725         isCurrentDevice: true,
726         type: "desktop",
727         tabs: [],
728       },
729     ],
730   });
732   Services.obs.notifyObservers(null, UIState.ON_UPDATE);
734   await waitForVisibleSetupStep(win.gBrowser, {
735     expectedVisible: "#tabpickup-steps-view2",
736   });
738   actionButton = win.gBrowser.contentWindow.document.querySelector(
739     "#tabpickup-steps-view2 button.primary"
740   );
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();
744   await tabSwitched;
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" },
751     win
752   );
753   is(win.gBrowser.tabs.length, 2, "Tabs strip should only contain two tabs");
755   is(
756     win.gBrowser.tabs[0].linkedBrowser.currentURI.filePath,
757     "firefoxview",
758     "First tab is Firefox view"
759   );
761   is(
762     win.gBrowser.tabs[1].linkedBrowser.currentURI.filePath,
763     "newtab",
764     "Second tab is about:newtab"
765   );
767   // cleanup time
768   await tearDown(sandbox);
769   await BrowserTestUtils.closeWindow(win);