forked from mirrors/gecko-dev
		
	 ee5be13d7b
			
		
	
	
		ee5be13d7b
		
	
	
	
	
		
			
			There are two things I've added here: - The observers for when FxA devices are connected/disconnected were not added/removed as part of this update: https://phabricator.services.mozilla.com/D153069. - When a mobile device is the only synced device beyond the current one (desktop) and you remove the current device (desktop), then sign back in from Fx View Tab Pickup banner, `fxAccounts.device.recentDeviceList` only returns the mobile device for some reason (possibly due to device cache). This causes our checks for a secondary device to fail (as we now only have access to the mobile device from `recentDeviceList`, and we're assuming the one device we DO have access to is our current device - which is not the case). This is why Tab Pickup was incorrectly displaying the "Connect a mobile device" message. I've added a check at the start of `refreshDevices()` to manually refresh the device list (ignoring device cache) if the `recentDeviceList` doesn't contain a device with `isCurrentDevice` set to `true`. This is really a workaround for the caching stuff going on behind the scenes, but this does seem to fix things from our end. Differential Revision: https://phabricator.services.mozilla.com/D165960
		
			
				
	
	
		
			604 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			604 lines
		
	
	
	
		
			19 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
| /* This Source Code Form is subject to the terms of the Mozilla Public
 | |
|  * License, v. 2.0. If a copy of the MPL was not distributed with this
 | |
|  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | |
| 
 | |
| /**
 | |
|  * This module exports the TabsSetupFlowManager singleton, which manages the state and
 | |
|  * diverse inputs which drive the Firefox View synced tabs setup flow
 | |
|  */
 | |
| 
 | |
| import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";
 | |
| 
 | |
| const lazy = {};
 | |
| 
 | |
| ChromeUtils.defineESModuleGetters(lazy, {
 | |
|   Log: "resource://gre/modules/Log.sys.mjs",
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyModuleGetters(lazy, {
 | |
|   UIState: "resource://services-sync/UIState.jsm",
 | |
|   SyncedTabs: "resource://services-sync/SyncedTabs.jsm",
 | |
|   Weave: "resource://services-sync/main.js",
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(lazy, "syncUtils", () => {
 | |
|   return ChromeUtils.import("resource://services-sync/util.js").Utils;
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyGetter(lazy, "fxAccounts", () => {
 | |
|   return ChromeUtils.import(
 | |
|     "resource://gre/modules/FxAccounts.jsm"
 | |
|   ).getFxAccountsSingleton();
 | |
| });
 | |
| 
 | |
| XPCOMUtils.defineLazyServiceGetter(
 | |
|   lazy,
 | |
|   "gNetworkLinkService",
 | |
|   "@mozilla.org/network/network-link-service;1",
 | |
|   "nsINetworkLinkService"
 | |
| );
 | |
| 
 | |
| const SYNC_TABS_PREF = "services.sync.engine.tabs";
 | |
| const TOPIC_TABS_CHANGED = "services.sync.tabs.changed";
 | |
| const MOBILE_PROMO_DISMISSED_PREF =
 | |
|   "browser.tabs.firefox-view.mobilePromo.dismissed";
 | |
| const LOGGING_PREF = "browser.tabs.firefox-view.logLevel";
 | |
| const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed";
 | |
| const TOPIC_DEVICELIST_UPDATED = "fxaccounts:devicelist_updated";
 | |
| const NETWORK_STATUS_CHANGED = "network:offline-status-changed";
 | |
| const SYNC_SERVICE_ERROR = "weave:service:sync:error";
 | |
| const FXA_ENABLED = "identity.fxaccounts.enabled";
 | |
| const FXA_DEVICE_CONNECTED = "fxaccounts:device_connected";
 | |
| const FXA_DEVICE_DISCONNECTED = "fxaccounts:device_disconnected";
 | |
| const SYNC_SERVICE_FINISHED = "weave:service:sync:finish";
 | |
| const PRIMARY_PASSWORD_UNLOCKED = "passwordmgr-crypto-login";
 | |
| const TAB_PICKUP_OPEN_STATE_PREF =
 | |
|   "browser.tabs.firefox-view.ui-state.tab-pickup.open";
 | |
| 
 | |
| function openTabInWindow(window, url) {
 | |
|   const {
 | |
|     switchToTabHavingURI,
 | |
|   } = window.docShell.chromeEventHandler.ownerGlobal;
 | |
|   switchToTabHavingURI(url, true, {});
 | |
| }
 | |
| 
 | |
| export const TabsSetupFlowManager = new (class {
 | |
|   constructor() {
 | |
|     this.QueryInterface = ChromeUtils.generateQI(["nsIObserver"]);
 | |
| 
 | |
|     this.setupState = new Map();
 | |
|     this.resetInternalState();
 | |
|     this._currentSetupStateName = "";
 | |
|     this.networkIsOnline =
 | |
|       lazy.gNetworkLinkService.linkStatusKnown &&
 | |
|       lazy.gNetworkLinkService.isLinkUp;
 | |
|     this.syncIsWorking = true;
 | |
|     this.syncIsConnected = lazy.UIState.get().syncEnabled;
 | |
|     this.didFxaTabOpen = false;
 | |
| 
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 0,
 | |
|       name: "error-state",
 | |
|       exitConditions: () => {
 | |
|         const fxaStatus = lazy.UIState.get().status;
 | |
|         return (
 | |
|           this.networkIsOnline &&
 | |
|           (this.syncIsWorking || this.syncHasWorked) &&
 | |
|           !Services.prefs.prefIsLocked(FXA_ENABLED) &&
 | |
|           // it's an error for sync to not be connected if we are signed-in,
 | |
|           // or for sync to be connected if the FxA status is "login_failed",
 | |
|           // which can happen if a user updates their password on another device
 | |
|           ((!this.syncIsConnected &&
 | |
|             fxaStatus !== lazy.UIState.STATUS_SIGNED_IN) ||
 | |
|             (this.syncIsConnected &&
 | |
|               fxaStatus !== lazy.UIState.STATUS_LOGIN_FAILED)) &&
 | |
|           // We treat a locked primary password as an error if we are signed-in.
 | |
|           // If the user dismisses the prompt to unlock, they can use the "Try again" button to prompt again
 | |
|           (!this.isPrimaryPasswordLocked || !this.fxaSignedIn)
 | |
|         );
 | |
|       },
 | |
|     });
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 1,
 | |
|       name: "not-signed-in",
 | |
|       exitConditions: () => {
 | |
|         return this.fxaSignedIn;
 | |
|       },
 | |
|     });
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 2,
 | |
|       name: "connect-secondary-device",
 | |
|       exitConditions: () => {
 | |
|         return this.secondaryDeviceConnected;
 | |
|       },
 | |
|     });
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 3,
 | |
|       name: "disabled-tab-sync",
 | |
|       exitConditions: () => {
 | |
|         return this.syncTabsPrefEnabled;
 | |
|       },
 | |
|     });
 | |
|     this.registerSetupState({
 | |
|       uiStateIndex: 4,
 | |
|       name: "synced-tabs-loaded",
 | |
|       exitConditions: () => {
 | |
|         // This is the end state
 | |
|         return false;
 | |
|       },
 | |
|     });
 | |
| 
 | |
|     Services.obs.addObserver(this, lazy.UIState.ON_UPDATE);
 | |
|     Services.obs.addObserver(this, TOPIC_DEVICELIST_UPDATED);
 | |
|     Services.obs.addObserver(this, NETWORK_STATUS_CHANGED);
 | |
|     Services.obs.addObserver(this, SYNC_SERVICE_ERROR);
 | |
|     Services.obs.addObserver(this, SYNC_SERVICE_FINISHED);
 | |
|     Services.obs.addObserver(this, TOPIC_TABS_CHANGED);
 | |
|     Services.obs.addObserver(this, PRIMARY_PASSWORD_UNLOCKED);
 | |
|     Services.obs.addObserver(this, FXA_DEVICE_CONNECTED);
 | |
|     Services.obs.addObserver(this, FXA_DEVICE_DISCONNECTED);
 | |
| 
 | |
|     // this.syncTabsPrefEnabled will track the value of the tabs pref
 | |
|     XPCOMUtils.defineLazyPreferenceGetter(
 | |
|       this,
 | |
|       "syncTabsPrefEnabled",
 | |
|       SYNC_TABS_PREF,
 | |
|       false,
 | |
|       () => {
 | |
|         this.maybeUpdateUI(true);
 | |
|       }
 | |
|     );
 | |
|     XPCOMUtils.defineLazyPreferenceGetter(
 | |
|       this,
 | |
|       "mobilePromoDismissedPref",
 | |
|       MOBILE_PROMO_DISMISSED_PREF,
 | |
|       false,
 | |
|       () => {
 | |
|         this.maybeUpdateUI(true);
 | |
|       }
 | |
|     );
 | |
| 
 | |
|     this._lastFxASignedIn = this.fxaSignedIn;
 | |
|     this.logger.debug(
 | |
|       "TabsSetupFlowManager constructor, fxaSignedIn:",
 | |
|       this._lastFxASignedIn
 | |
|     );
 | |
|     this.onSignedInChange();
 | |
|   }
 | |
| 
 | |
|   resetInternalState() {
 | |
|     // assign initial values for all the managed internal properties
 | |
|     delete this._lastFxASignedIn;
 | |
|     this._currentSetupStateName = "not-signed-in";
 | |
|     this._shouldShowSuccessConfirmation = false;
 | |
|     this._didShowMobilePromo = false;
 | |
|     this._waitingForTabs = false;
 | |
| 
 | |
|     this.syncHasWorked = false;
 | |
| 
 | |
|     // keep track of what is connected so we can respond to changes
 | |
|     this._deviceStateSnapshot = {
 | |
|       mobileDeviceConnected: this.mobileDeviceConnected,
 | |
|       secondaryDeviceConnected: this.secondaryDeviceConnected,
 | |
|     };
 | |
|   }
 | |
| 
 | |
|   get isPrimaryPasswordLocked() {
 | |
|     return lazy.syncUtils.mpLocked();
 | |
|   }
 | |
| 
 | |
|   getErrorType() {
 | |
|     // this ordering is important for dealing with multiple errors at once
 | |
|     const errorStates = {
 | |
|       "network-offline": !this.networkIsOnline,
 | |
|       "fxa-admin-disabled": Services.prefs.prefIsLocked(FXA_ENABLED),
 | |
|       "password-locked": this.isPrimaryPasswordLocked,
 | |
|       "signed-out":
 | |
|         lazy.UIState.get().status === lazy.UIState.STATUS_LOGIN_FAILED,
 | |
|       "sync-disconnected": !this.syncIsConnected,
 | |
|       "sync-error": !this.syncIsWorking && !this.syncHasWorked,
 | |
|     };
 | |
| 
 | |
|     for (let [type, value] of Object.entries(errorStates)) {
 | |
|       if (value) {
 | |
|         return type;
 | |
|       }
 | |
|     }
 | |
|     return null;
 | |
|   }
 | |
| 
 | |
|   uninit() {
 | |
|     Services.obs.removeObserver(this, lazy.UIState.ON_UPDATE);
 | |
|     Services.obs.removeObserver(this, TOPIC_DEVICELIST_UPDATED);
 | |
|     Services.obs.removeObserver(this, NETWORK_STATUS_CHANGED);
 | |
|     Services.obs.removeObserver(this, SYNC_SERVICE_ERROR);
 | |
|     Services.obs.removeObserver(this, SYNC_SERVICE_FINISHED);
 | |
|     Services.obs.removeObserver(this, TOPIC_TABS_CHANGED);
 | |
|     Services.obs.removeObserver(this, PRIMARY_PASSWORD_UNLOCKED);
 | |
|     Services.obs.removeObserver(this, FXA_DEVICE_CONNECTED);
 | |
|     Services.obs.removeObserver(this, FXA_DEVICE_DISCONNECTED);
 | |
|   }
 | |
|   get currentSetupState() {
 | |
|     return this.setupState.get(this._currentSetupStateName);
 | |
|   }
 | |
|   get isTabSyncSetupComplete() {
 | |
|     return this.currentSetupState.uiStateIndex >= 4;
 | |
|   }
 | |
|   get uiStateIndex() {
 | |
|     return this.currentSetupState.uiStateIndex;
 | |
|   }
 | |
|   get fxaSignedIn() {
 | |
|     let { UIState } = lazy;
 | |
|     let syncState = UIState.get();
 | |
|     return (
 | |
|       UIState.isReady() &&
 | |
|       syncState.status === UIState.STATUS_SIGNED_IN &&
 | |
|       // syncEnabled just checks the "services.sync.username" pref has a value
 | |
|       syncState.syncEnabled
 | |
|     );
 | |
|   }
 | |
|   get secondaryDeviceConnected() {
 | |
|     if (!this.fxaSignedIn) {
 | |
|       return false;
 | |
|     }
 | |
|     let recentDevices = lazy.fxAccounts.device?.recentDeviceList?.length;
 | |
|     return recentDevices > 1;
 | |
|   }
 | |
|   get mobileDeviceConnected() {
 | |
|     if (!this.fxaSignedIn) {
 | |
|       return false;
 | |
|     }
 | |
|     let mobileClients = lazy.fxAccounts.device.recentDeviceList?.filter(
 | |
|       device => device.type == "mobile" || device.type == "tablet"
 | |
|     );
 | |
|     return mobileClients?.length > 0;
 | |
|   }
 | |
|   get shouldShowMobilePromo() {
 | |
|     return (
 | |
|       this.syncIsConnected &&
 | |
|       this.fxaSignedIn &&
 | |
|       this.currentSetupState.uiStateIndex >= 4 &&
 | |
|       !this.mobileDeviceConnected &&
 | |
|       !this.mobilePromoDismissedPref
 | |
|     );
 | |
|   }
 | |
|   get shouldShowMobileConnectedSuccess() {
 | |
|     return (
 | |
|       this.currentSetupState.uiStateIndex >= 3 &&
 | |
|       this._shouldShowSuccessConfirmation &&
 | |
|       this.mobileDeviceConnected
 | |
|     );
 | |
|   }
 | |
|   get logger() {
 | |
|     if (!this._log) {
 | |
|       let setupLog = lazy.Log.repository.getLogger("FirefoxView.TabsSetup");
 | |
|       setupLog.manageLevelFromPref(LOGGING_PREF);
 | |
|       setupLog.addAppender(
 | |
|         new lazy.Log.ConsoleAppender(new lazy.Log.BasicFormatter())
 | |
|       );
 | |
|       this._log = setupLog;
 | |
|     }
 | |
|     return this._log;
 | |
|   }
 | |
| 
 | |
|   registerSetupState(state) {
 | |
|     this.setupState.set(state.name, state);
 | |
|   }
 | |
| 
 | |
|   async observe(subject, topic, data) {
 | |
|     switch (topic) {
 | |
|       case lazy.UIState.ON_UPDATE:
 | |
|         this.logger.debug("Handling UIState update");
 | |
|         this.syncIsConnected = lazy.UIState.get().syncEnabled;
 | |
|         if (this._lastFxASignedIn !== this.fxaSignedIn) {
 | |
|           this.onSignedInChange();
 | |
|         } else {
 | |
|           this.maybeUpdateUI();
 | |
|         }
 | |
|         this._lastFxASignedIn = this.fxaSignedIn;
 | |
|         break;
 | |
|       case TOPIC_DEVICELIST_UPDATED:
 | |
|         this.logger.debug("Handling observer notification:", topic, data);
 | |
|         if (await this.refreshDevices()) {
 | |
|           this.logger.debug(
 | |
|             "refreshDevices made changes, calling maybeUpdateUI"
 | |
|           );
 | |
|           this.maybeUpdateUI(true);
 | |
|         }
 | |
|         break;
 | |
|       case FXA_DEVICE_CONNECTED:
 | |
|       case FXA_DEVICE_DISCONNECTED:
 | |
|         await lazy.fxAccounts.device.refreshDeviceList({ ignoreCached: true });
 | |
|         this.maybeUpdateUI(true);
 | |
|         break;
 | |
|       case SYNC_SERVICE_ERROR:
 | |
|         this.logger.debug(`Handling ${SYNC_SERVICE_ERROR}`);
 | |
|         if (lazy.UIState.get().status == lazy.UIState.STATUS_SIGNED_IN) {
 | |
|           this._waitingForTabs = false;
 | |
|           this.syncIsWorking = false;
 | |
|           this.maybeUpdateUI(true);
 | |
|         }
 | |
|         break;
 | |
|       case NETWORK_STATUS_CHANGED:
 | |
|         this.networkIsOnline = data == "online";
 | |
|         this._waitingForTabs = false;
 | |
|         this.maybeUpdateUI(true);
 | |
|         break;
 | |
|       case SYNC_SERVICE_FINISHED:
 | |
|         this.logger.debug(`Handling ${SYNC_SERVICE_FINISHED}`);
 | |
|         this._waitingForTabs = false;
 | |
|         if (!this.syncIsWorking) {
 | |
|           this.syncIsWorking = true;
 | |
|           this.syncHasWorked = true;
 | |
|         }
 | |
|         this.maybeUpdateUI(true);
 | |
|         break;
 | |
|       case TOPIC_TABS_CHANGED:
 | |
|         this.stopWaitingForTabs();
 | |
|         break;
 | |
|       case PRIMARY_PASSWORD_UNLOCKED:
 | |
|         this.logger.debug(`Handling ${PRIMARY_PASSWORD_UNLOCKED}`);
 | |
|         this.tryToClearError();
 | |
|         break;
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   get waitingForTabs() {
 | |
|     return (
 | |
|       // signed in & at least 1 other device is sycning indicates there's something to wait for
 | |
|       this.secondaryDeviceConnected &&
 | |
|       // last recent tabs request came back empty and we've not had a sync finish (or error) yet
 | |
|       this._waitingForTabs
 | |
|     );
 | |
|   }
 | |
| 
 | |
|   startWaitingForTabs() {
 | |
|     if (!this._waitingForTabs) {
 | |
|       this._waitingForTabs = true;
 | |
|       Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   stopWaitingForTabs() {
 | |
|     if (this._waitingForTabs) {
 | |
|       this._waitingForTabs = false;
 | |
|       Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async onSignedInChange() {
 | |
|     this.logger.debug("onSignedInChange, fxaSignedIn:", this.fxaSignedIn);
 | |
|     // update UI to make the state change
 | |
|     this.maybeUpdateUI(true);
 | |
|     if (!this.fxaSignedIn) {
 | |
|       // As we just signed out, ensure the waiting flag is reset for next time around
 | |
|       this._waitingForTabs = false;
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // Set Tab pickup open state pref to true when signing in
 | |
|     Services.prefs.setBoolPref(TAB_PICKUP_OPEN_STATE_PREF, true);
 | |
| 
 | |
|     // Now we need to figure out if we have recently synced tabs to show
 | |
|     // Or, if we are going to need to trigger a tab sync for them
 | |
|     const recentTabs = await lazy.SyncedTabs.getRecentTabs(50);
 | |
| 
 | |
|     if (!this.fxaSignedIn) {
 | |
|       // We got signed-out in the meantime. We should get an ON_UPDATE which will put us
 | |
|       // back in the right state, so we just do nothing here
 | |
|       return;
 | |
|     }
 | |
| 
 | |
|     // When SyncedTabs has resolved the getRecentTabs promise,
 | |
|     // we also know we can update devices-related internal state
 | |
|     if (await this.refreshDevices()) {
 | |
|       this.logger.debug(
 | |
|         "onSignedInChange, after refreshDevices, calling maybeUpdateUI"
 | |
|       );
 | |
|       // give the UI an opportunity to update as secondaryDeviceConnected or
 | |
|       // mobileDeviceConnected have changed value
 | |
|       this.maybeUpdateUI(true);
 | |
|     }
 | |
| 
 | |
|     // If we can't get recent tabs, we need to trigger a request for them
 | |
|     const tabSyncNeeded = !recentTabs?.length;
 | |
|     this.logger.debug("onSignedInChange, tabSyncNeeded:", tabSyncNeeded);
 | |
| 
 | |
|     if (tabSyncNeeded) {
 | |
|       this.startWaitingForTabs();
 | |
|       this.logger.debug(
 | |
|         "isPrimaryPasswordLocked:",
 | |
|         this.isPrimaryPasswordLocked
 | |
|       );
 | |
|       this.logger.debug("onSignedInChange, no recentTabs, calling syncTabs");
 | |
|       // If the syncTabs call rejects or resolves false we need to clear the waiting
 | |
|       // flag and update UI
 | |
|       this.syncTabs()
 | |
|         .catch(ex => {
 | |
|           this.logger.debug("onSignedInChange, syncTabs rejected:", ex);
 | |
|           this.stopWaitingForTabs();
 | |
|         })
 | |
|         .then(willSync => {
 | |
|           if (!willSync) {
 | |
|             this.logger.debug("onSignedInChange, no tab sync expected");
 | |
|             this.stopWaitingForTabs();
 | |
|           }
 | |
|         });
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   async refreshDevices() {
 | |
|     // If current device not found in recent device list, refresh device list
 | |
|     if (
 | |
|       !lazy.fxAccounts.device.recentDeviceList?.some(
 | |
|         device => device.isCurrentDevice
 | |
|       )
 | |
|     ) {
 | |
|       await lazy.fxAccounts.device.refreshDeviceList({ ignoreCached: true });
 | |
|     }
 | |
| 
 | |
|     // compare new values to the previous values
 | |
|     const mobileDeviceConnected = this.mobileDeviceConnected;
 | |
|     const secondaryDeviceConnected = this.secondaryDeviceConnected;
 | |
| 
 | |
|     this.logger.debug(
 | |
|       `refreshDevices, mobileDeviceConnected: ${mobileDeviceConnected}, `,
 | |
|       `secondaryDeviceConnected: ${secondaryDeviceConnected}`
 | |
|     );
 | |
| 
 | |
|     let didDeviceStateChange =
 | |
|       this._deviceStateSnapshot.mobileDeviceConnected !=
 | |
|         mobileDeviceConnected ||
 | |
|       this._deviceStateSnapshot.secondaryDeviceConnected !=
 | |
|         secondaryDeviceConnected;
 | |
|     if (
 | |
|       mobileDeviceConnected &&
 | |
|       !this._deviceStateSnapshot.mobileDeviceConnected
 | |
|     ) {
 | |
|       // a mobile device was added, show success if we previously showed the promo
 | |
|       this._shouldShowSuccessConfirmation = this._didShowMobilePromo;
 | |
|     } else if (
 | |
|       !mobileDeviceConnected &&
 | |
|       this._deviceStateSnapshot.mobileDeviceConnected
 | |
|     ) {
 | |
|       // no mobile device connected now, reset
 | |
|       Services.prefs.clearUserPref(MOBILE_PROMO_DISMISSED_PREF);
 | |
|       this._shouldShowSuccessConfirmation = false;
 | |
|     }
 | |
|     this._deviceStateSnapshot = {
 | |
|       mobileDeviceConnected,
 | |
|       secondaryDeviceConnected,
 | |
|     };
 | |
|     if (didDeviceStateChange) {
 | |
|       this.logger.debug("refreshDevices: device state did change");
 | |
|       if (!secondaryDeviceConnected) {
 | |
|         this.logger.debug(
 | |
|           "We lost a device, now claim sync hasn't worked before."
 | |
|         );
 | |
|         this.syncHasWorked = false;
 | |
|       }
 | |
|     } else {
 | |
|       this.logger.debug("refreshDevices: no device state change");
 | |
|     }
 | |
|     return didDeviceStateChange;
 | |
|   }
 | |
| 
 | |
|   maybeUpdateUI(forceUpdate = false) {
 | |
|     let nextSetupStateName = this._currentSetupStateName;
 | |
|     let errorState = null;
 | |
|     let stateChanged = false;
 | |
| 
 | |
|     // state transition conditions
 | |
|     for (let state of this.setupState.values()) {
 | |
|       nextSetupStateName = state.name;
 | |
|       if (!state.exitConditions()) {
 | |
|         this.logger.debug(
 | |
|           "maybeUpdateUI, conditions not met to exit state: ",
 | |
|           nextSetupStateName
 | |
|         );
 | |
|         break;
 | |
|       }
 | |
|     }
 | |
| 
 | |
|     let setupState = this.currentSetupState;
 | |
|     const state = this.setupState.get(nextSetupStateName);
 | |
|     const uiStateIndex = state.uiStateIndex;
 | |
| 
 | |
|     if (
 | |
|       uiStateIndex == 0 ||
 | |
|       nextSetupStateName != this._currentSetupStateName
 | |
|     ) {
 | |
|       setupState = state;
 | |
|       this._currentSetupStateName = nextSetupStateName;
 | |
|       stateChanged = true;
 | |
|     }
 | |
|     this.logger.debug(
 | |
|       "maybeUpdateUI, will notify update?:",
 | |
|       stateChanged,
 | |
|       forceUpdate
 | |
|     );
 | |
|     if (stateChanged || forceUpdate) {
 | |
|       if (this.shouldShowMobilePromo) {
 | |
|         this._didShowMobilePromo = true;
 | |
|       }
 | |
|       if (uiStateIndex == 0) {
 | |
|         errorState = this.getErrorType();
 | |
|         this.logger.debug("maybeUpdateUI, in error state:", errorState);
 | |
|       }
 | |
|       Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED, errorState);
 | |
|     }
 | |
|     if ("function" == typeof setupState.enter) {
 | |
|       setupState.enter();
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   dismissMobilePromo() {
 | |
|     Services.prefs.setBoolPref(MOBILE_PROMO_DISMISSED_PREF, true);
 | |
|   }
 | |
| 
 | |
|   dismissMobileConfirmation() {
 | |
|     this._shouldShowSuccessConfirmation = false;
 | |
|     this._didShowMobilePromo = false;
 | |
|     this.maybeUpdateUI(true);
 | |
|   }
 | |
| 
 | |
|   async openFxASignup(window) {
 | |
|     if (!(await lazy.fxAccounts.constructor.canConnectAccount())) {
 | |
|       return;
 | |
|     }
 | |
|     const url = await lazy.fxAccounts.constructor.config.promiseConnectAccountURI(
 | |
|       "fx-view"
 | |
|     );
 | |
|     this.didFxaTabOpen = true;
 | |
|     openTabInWindow(window, url, true);
 | |
|     Services.telemetry.recordEvent("firefoxview", "fxa_continue", "sync", null);
 | |
|   }
 | |
| 
 | |
|   async openFxAPairDevice(window) {
 | |
|     const url = await lazy.fxAccounts.constructor.config.promisePairingURI({
 | |
|       entrypoint: "fx-view",
 | |
|     });
 | |
|     this.didFxaTabOpen = true;
 | |
|     openTabInWindow(window, url, true);
 | |
|     Services.telemetry.recordEvent("firefoxview", "fxa_mobile", "sync", null, {
 | |
|       has_devices: this.secondaryDeviceConnected.toString(),
 | |
|     });
 | |
|   }
 | |
| 
 | |
|   syncOpenTabs(containerElem) {
 | |
|     // Flip the pref on.
 | |
|     // The observer should trigger re-evaluating state and advance to next step
 | |
|     Services.prefs.setBoolPref(SYNC_TABS_PREF, true);
 | |
|   }
 | |
| 
 | |
|   async syncOnPageReload() {
 | |
|     if (lazy.UIState.isReady() && this.fxaSignedIn) {
 | |
|       this.startWaitingForTabs();
 | |
|       await this.syncTabs(true);
 | |
|     }
 | |
|   }
 | |
| 
 | |
|   tryToClearError() {
 | |
|     if (lazy.UIState.isReady() && this.fxaSignedIn) {
 | |
|       this.startWaitingForTabs();
 | |
|       Services.tm.dispatchToMainThread(() => {
 | |
|         this.logger.debug("tryToClearError: triggering new tab sync");
 | |
|         this.startFullTabsSync();
 | |
|       });
 | |
|     } else {
 | |
|       this.logger.debug(
 | |
|         `tryToClearError: unable to sync, isReady: ${lazy.UIState.isReady()}, fxaSignedIn: ${
 | |
|           this.fxaSignedIn
 | |
|         }`
 | |
|       );
 | |
|     }
 | |
|   }
 | |
|   // For easy overriding in tests
 | |
|   syncTabs(force = false) {
 | |
|     return lazy.SyncedTabs.syncTabs(force);
 | |
|   }
 | |
| 
 | |
|   startFullTabsSync() {
 | |
|     lazy.Weave.Service.sync({ why: "tabs", engines: ["tabs"] });
 | |
|   }
 | |
| })();
 |