632 lines
21 KiB
JavaScript
632 lines
21 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",
|
|
SyncedTabs: "resource://services-sync/SyncedTabs.sys.mjs",
|
|
SyncedTabsErrorHandler:
|
|
"resource:///modules/firefox-view-synced-tabs-error-handler.sys.mjs",
|
|
UIState: "resource://services-sync/UIState.sys.mjs",
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "syncUtils", () => {
|
|
return ChromeUtils.importESModule("resource://services-sync/util.sys.mjs")
|
|
.Utils;
|
|
});
|
|
|
|
ChromeUtils.defineLazyGetter(lazy, "fxAccounts", () => {
|
|
return ChromeUtils.importESModule(
|
|
"resource://gre/modules/FxAccounts.sys.mjs"
|
|
).getFxAccountsSingleton();
|
|
});
|
|
|
|
const SYNC_TABS_PREF = "services.sync.engine.tabs";
|
|
const TOPIC_TABS_CHANGED = "services.sync.tabs.changed";
|
|
const LOGGING_PREF = "browser.tabs.firefox-view.logLevel";
|
|
const TOPIC_SETUPSTATE_CHANGED = "firefox-view.setupstate.changed";
|
|
const TOPIC_DEVICESTATE_CHANGED = "firefox-view.devicestate.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_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";
|
|
|
|
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.syncIsConnected = lazy.UIState.get().syncEnabled;
|
|
this.didFxaTabOpen = false;
|
|
|
|
this.registerSetupState({
|
|
uiStateIndex: 0,
|
|
name: "error-state",
|
|
exitConditions: () => {
|
|
return lazy.SyncedTabsErrorHandler.isSyncReady();
|
|
},
|
|
});
|
|
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);
|
|
}
|
|
);
|
|
|
|
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.abortWaitingForTabs();
|
|
|
|
Services.obs.notifyObservers(null, TOPIC_DEVICESTATE_CHANGED);
|
|
|
|
// keep track of what is connected so we can respond to changes
|
|
this._deviceStateSnapshot = {
|
|
mobileDeviceConnected: this.mobileDeviceConnected,
|
|
secondaryDeviceConnected: this.secondaryDeviceConnected,
|
|
};
|
|
// keep track of tab-pickup-container instance visibilities
|
|
this._viewVisibilityStates = new Map();
|
|
}
|
|
|
|
get isPrimaryPasswordLocked() {
|
|
return lazy.syncUtils.mpLocked();
|
|
}
|
|
|
|
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 hasVisibleViews() {
|
|
return Array.from(this._viewVisibilityStates.values()).reduce(
|
|
(hasVisible, visibility) => {
|
|
return hasVisible || visibility == "visible";
|
|
},
|
|
false
|
|
);
|
|
}
|
|
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 {
|
|
await this.maybeUpdateUI();
|
|
}
|
|
this._lastFxASignedIn = this.fxaSignedIn;
|
|
break;
|
|
case TOPIC_DEVICELIST_UPDATED:
|
|
this.logger.debug("Handling observer notification:", topic, data);
|
|
const { deviceStateChanged, deviceAdded } = await this.refreshDevices();
|
|
if (deviceStateChanged) {
|
|
await this.maybeUpdateUI(true);
|
|
}
|
|
if (deviceAdded && this.secondaryDeviceConnected) {
|
|
this.logger.debug("device was added");
|
|
this._deviceAddedResultsNeverSeen = true;
|
|
if (this.hasVisibleViews) {
|
|
this.startWaitingForNewDeviceTabs();
|
|
}
|
|
}
|
|
break;
|
|
case FXA_DEVICE_CONNECTED:
|
|
case FXA_DEVICE_DISCONNECTED:
|
|
await lazy.fxAccounts.device.refreshDeviceList({ ignoreCached: true });
|
|
await 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.abortWaitingForTabs();
|
|
await this.maybeUpdateUI(true);
|
|
}
|
|
break;
|
|
case NETWORK_STATUS_CHANGED:
|
|
this.abortWaitingForTabs();
|
|
await this.maybeUpdateUI(true);
|
|
break;
|
|
case SYNC_SERVICE_FINISHED:
|
|
this.logger.debug(`Handling ${SYNC_SERVICE_FINISHED}`);
|
|
// We intentionally leave any empty-tabs timestamp
|
|
// as we may be still waiting for a sync that delivers some tabs
|
|
this._waitingForNextTabSync = false;
|
|
await 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;
|
|
}
|
|
}
|
|
|
|
updateViewVisibility(instanceId, visibility) {
|
|
const wasVisible = this.hasVisibleViews;
|
|
this.logger.debug(
|
|
`updateViewVisibility for instance: ${instanceId}, visibility: ${visibility}`
|
|
);
|
|
if (visibility == "unloaded") {
|
|
this._viewVisibilityStates.delete(instanceId);
|
|
} else {
|
|
this._viewVisibilityStates.set(instanceId, visibility);
|
|
}
|
|
const isVisible = this.hasVisibleViews;
|
|
if (isVisible && !wasVisible) {
|
|
// If we're already timing waiting for tabs from a newly-added device
|
|
// we might be able to stop
|
|
if (this._noTabsVisibleFromAddedDeviceTimestamp) {
|
|
return this.stopWaitingForNewDeviceTabs();
|
|
}
|
|
if (this._deviceAddedResultsNeverSeen) {
|
|
// If this is the first time a view has been visible since a device was added
|
|
// we may want to start the empty-tabs visible timer
|
|
return this.startWaitingForNewDeviceTabs();
|
|
}
|
|
}
|
|
if (!isVisible) {
|
|
this.logger.debug(
|
|
"Resetting timestamp and tabs pending flags as there are no visible views"
|
|
);
|
|
// if there's no view visible, we're not really waiting anymore
|
|
this.abortWaitingForTabs();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
get waitingForTabs() {
|
|
return (
|
|
// signed in & at least 1 other device is syncing indicates there's something to wait for
|
|
this.secondaryDeviceConnected && this._waitingForNextTabSync
|
|
);
|
|
}
|
|
|
|
abortWaitingForTabs() {
|
|
this._waitingForNextTabSync = false;
|
|
// also clear out the device-added / tabs pending flags
|
|
this._noTabsVisibleFromAddedDeviceTimestamp = 0;
|
|
this._deviceAddedResultsNeverSeen = false;
|
|
}
|
|
|
|
startWaitingForTabs() {
|
|
if (!this._waitingForNextTabSync) {
|
|
this._waitingForNextTabSync = true;
|
|
Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED);
|
|
}
|
|
}
|
|
|
|
async stopWaitingForTabs() {
|
|
const wasWaiting = this.waitingForTabs;
|
|
if (this.hasVisibleViews && this._deviceAddedResultsNeverSeen) {
|
|
await this.stopWaitingForNewDeviceTabs();
|
|
}
|
|
this._waitingForNextTabSync = false;
|
|
if (wasWaiting) {
|
|
Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED);
|
|
}
|
|
}
|
|
|
|
async onSignedInChange() {
|
|
this.logger.debug("onSignedInChange, fxaSignedIn:", this.fxaSignedIn);
|
|
// update UI to make the state change
|
|
await this.maybeUpdateUI(true);
|
|
if (!this.fxaSignedIn) {
|
|
// As we just signed out, ensure the waiting flag is reset for next time around
|
|
this.abortWaitingForTabs();
|
|
return;
|
|
}
|
|
|
|
// 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
|
|
const { deviceStateChanged } = await this.refreshDevices();
|
|
if (deviceStateChanged) {
|
|
this.logger.debug(
|
|
"onSignedInChange, after refreshDevices, calling maybeUpdateUI"
|
|
);
|
|
// give the UI an opportunity to update as secondaryDeviceConnected or
|
|
// mobileDeviceConnected have changed value
|
|
await 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 startWaitingForNewDeviceTabs() {
|
|
// if we're already waiting for tabs, don't reset
|
|
if (this._noTabsVisibleFromAddedDeviceTimestamp) {
|
|
return;
|
|
}
|
|
|
|
// take a timestamp whenever the latest device is added and we have 0 tabs to show,
|
|
// allowing us to track how long we show an empty list after a new device is added
|
|
const hasRecentTabs = (await lazy.SyncedTabs.getRecentTabs(1)).length;
|
|
if (this.hasVisibleViews && !hasRecentTabs) {
|
|
this._noTabsVisibleFromAddedDeviceTimestamp = Date.now();
|
|
this.logger.debug(
|
|
"New device added with 0 synced tabs to show, storing timestamp:",
|
|
this._noTabsVisibleFromAddedDeviceTimestamp
|
|
);
|
|
}
|
|
}
|
|
|
|
async stopWaitingForNewDeviceTabs() {
|
|
if (!this._noTabsVisibleFromAddedDeviceTimestamp) {
|
|
return;
|
|
}
|
|
const recentTabs = await lazy.SyncedTabs.getRecentTabs(1);
|
|
if (recentTabs.length) {
|
|
// We have been waiting for > 0 tabs after a newly-added device, record
|
|
// the time elapsed
|
|
const elapsed = Date.now() - this._noTabsVisibleFromAddedDeviceTimestamp;
|
|
this.logger.debug(
|
|
"stopWaitingForTabs, resetting _noTabsVisibleFromAddedDeviceTimestamp and recording telemetry:",
|
|
Math.round(elapsed / 1000)
|
|
);
|
|
this._noTabsVisibleFromAddedDeviceTimestamp = 0;
|
|
this._deviceAddedResultsNeverSeen = false;
|
|
} else {
|
|
// we are still waiting for some tabs to show...
|
|
this.logger.debug(
|
|
"stopWaitingForTabs: Still no recent tabs, we are still waiting"
|
|
);
|
|
}
|
|
}
|
|
|
|
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;
|
|
const oldDevicesCount = this._deviceStateSnapshot?.devicesCount ?? 0;
|
|
const devicesCount = lazy.fxAccounts.device?.recentDeviceList?.length ?? 0;
|
|
|
|
this.logger.debug(
|
|
`refreshDevices, mobileDeviceConnected: ${mobileDeviceConnected}, `,
|
|
`secondaryDeviceConnected: ${secondaryDeviceConnected}`
|
|
);
|
|
|
|
let deviceStateChanged =
|
|
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
|
|
this._shouldShowSuccessConfirmation = false;
|
|
}
|
|
this._deviceStateSnapshot = {
|
|
mobileDeviceConnected,
|
|
secondaryDeviceConnected,
|
|
devicesCount,
|
|
};
|
|
if (deviceStateChanged) {
|
|
this.logger.debug("refreshDevices: device state did change");
|
|
if (!secondaryDeviceConnected) {
|
|
this.logger.debug(
|
|
"We lost a device, now claim sync hasn't worked before."
|
|
);
|
|
Services.obs.notifyObservers(null, TOPIC_DEVICESTATE_CHANGED);
|
|
}
|
|
} else {
|
|
this.logger.debug("refreshDevices: no device state change");
|
|
}
|
|
return {
|
|
deviceStateChanged,
|
|
deviceAdded: oldDevicesCount < devicesCount,
|
|
};
|
|
}
|
|
|
|
async 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) {
|
|
// Use idleDispatch() to give observers a chance to resolve before
|
|
// determining the new state.
|
|
errorState = await new Promise(resolve => {
|
|
ChromeUtils.idleDispatch(() => {
|
|
resolve(lazy.SyncedTabsErrorHandler.getErrorType());
|
|
});
|
|
});
|
|
this.logger.debug("maybeUpdateUI, in error state:", errorState);
|
|
}
|
|
Services.obs.notifyObservers(null, TOPIC_SETUPSTATE_CHANGED, errorState);
|
|
}
|
|
if ("function" == typeof setupState.enter) {
|
|
setupState.enter();
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
async openFxAPairDevice(window) {
|
|
const url = await lazy.fxAccounts.constructor.config.promisePairingURI({
|
|
entrypoint: "fx-view",
|
|
});
|
|
this.didFxaTabOpen = true;
|
|
openTabInWindow(window, url, true);
|
|
}
|
|
|
|
syncOpenTabs() {
|
|
// 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();
|
|
if (this.isPrimaryPasswordLocked) {
|
|
lazy.syncUtils.ensureMPUnlocked();
|
|
}
|
|
this.logger.debug("tryToClearError: triggering new tab sync");
|
|
this.syncTabs();
|
|
Services.tm.dispatchToMainThread(() => {});
|
|
} 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);
|
|
}
|
|
})();
|