diff options
Diffstat (limited to 'browser/components/firefoxview/firefox-view-synced-tabs-error-handler.sys.mjs')
-rw-r--r-- | browser/components/firefoxview/firefox-view-synced-tabs-error-handler.sys.mjs | 187 |
1 files changed, 187 insertions, 0 deletions
diff --git a/browser/components/firefoxview/firefox-view-synced-tabs-error-handler.sys.mjs b/browser/components/firefoxview/firefox-view-synced-tabs-error-handler.sys.mjs new file mode 100644 index 0000000000..414a0dab78 --- /dev/null +++ b/browser/components/firefoxview/firefox-view-synced-tabs-error-handler.sys.mjs @@ -0,0 +1,187 @@ +/* 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 SyncedTabsErrorHandler singleton, which handles + * error states for synced tabs. + */ + +import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"; + +const lazy = {}; + +ChromeUtils.defineESModuleGetters(lazy, { + UIState: "resource://services-sync/UIState.sys.mjs", +}); + +XPCOMUtils.defineLazyGetter(lazy, "syncUtils", () => { + return ChromeUtils.importESModule("resource://services-sync/util.sys.mjs") + .Utils; +}); + +XPCOMUtils.defineLazyServiceGetter( + lazy, + "gNetworkLinkService", + "@mozilla.org/network/network-link-service;1", + "nsINetworkLinkService" +); + +const FXA_ENABLED = "identity.fxaccounts.enabled"; +const NETWORK_STATUS_CHANGED = "network:offline-status-changed"; +const SYNC_SERVICE_ERROR = "weave:service:sync:error"; +const SYNC_SERVICE_FINISHED = "weave:service:sync:finish"; +const TOPIC_DEVICESTATE_CHANGED = "firefox-view.devicestate.changed"; + +const ErrorType = Object.freeze({ + SYNC_ERROR: "sync-error", + FXA_ADMIN_DISABLED: "fxa-admin-disabled", + NETWORK_OFFLINE: "network-offline", + SYNC_DISCONNECTED: "sync-disconnected", + PASSWORD_LOCKED: "password-locked", + SIGNED_OUT: "signed-out", +}); + +export const SyncedTabsErrorHandler = { + init() { + this.networkIsOnline = + lazy.gNetworkLinkService.linkStatusKnown && + lazy.gNetworkLinkService.isLinkUp; + this.syncIsConnected = lazy.UIState.get().syncEnabled; + this.syncIsWorking = true; + + Services.obs.addObserver(this, NETWORK_STATUS_CHANGED); + Services.obs.addObserver(this, lazy.UIState.ON_UPDATE); + Services.obs.addObserver(this, SYNC_SERVICE_ERROR); + Services.obs.addObserver(this, SYNC_SERVICE_FINISHED); + Services.obs.addObserver(this, TOPIC_DEVICESTATE_CHANGED); + + return this; + }, + + 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 + ); + }, + + getErrorType() { + // this ordering is important for dealing with multiple errors at once + const errorStates = { + [ErrorType.NETWORK_OFFLINE]: !this.networkIsOnline, + [ErrorType.FXA_ADMIN_DISABLED]: Services.prefs.prefIsLocked(FXA_ENABLED), + [ErrorType.PASSWORD_LOCKED]: this.isPrimaryPasswordLocked, + [ErrorType.SIGNED_OUT]: + lazy.UIState.get().status === lazy.UIState.STATUS_LOGIN_FAILED, + [ErrorType.SYNC_DISCONNECTED]: !this.syncIsConnected, + [ErrorType.SYNC_ERROR]: !this.syncIsWorking && !this.syncHasWorked, + }; + + for (let [type, value] of Object.entries(errorStates)) { + if (value) { + return type; + } + } + return null; + }, + + getFluentStringsForErrorType(type) { + return Object.freeze(this._errorStateStringMappings[type]); + }, + + get isPrimaryPasswordLocked() { + return lazy.syncUtils.mpLocked(); + }, + + isSyncReady() { + 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) + ); + }, + + observe(_, topic, data) { + switch (topic) { + case NETWORK_STATUS_CHANGED: + this.networkIsOnline = data == "online"; + break; + case lazy.UIState.ON_UPDATE: + this.syncIsConnected = lazy.UIState.get().syncEnabled; + break; + case SYNC_SERVICE_ERROR: + if (lazy.UIState.get().status == lazy.UIState.STATUS_SIGNED_IN) { + this.syncIsWorking = false; + } + break; + case SYNC_SERVICE_FINISHED: + if (!this.syncIsWorking) { + this.syncIsWorking = true; + this.syncHasWorked = true; + } + break; + case TOPIC_DEVICESTATE_CHANGED: + this.syncHasWorked = false; + } + }, + + ErrorType, + + // We map the error state strings to Fluent string IDs so that it's easier + // to change strings in the future without having to update all of the + // error state strings. + _errorStateStringMappings: { + [ErrorType.SYNC_ERROR]: { + header: "firefoxview-tabpickup-sync-error-header", + description: "firefoxview-tabpickup-generic-sync-error-description", + buttonLabel: "firefoxview-tabpickup-sync-error-primarybutton", + }, + [ErrorType.FXA_ADMIN_DISABLED]: { + header: "firefoxview-tabpickup-fxa-admin-disabled-header", + description: "firefoxview-tabpickup-fxa-admin-disabled-description", + // The button is hidden for this errorState, so we don't include the + // buttonLabel property. + }, + [ErrorType.NETWORK_OFFLINE]: { + header: "firefoxview-tabpickup-network-offline-header", + description: "firefoxview-tabpickup-network-offline-description", + buttonLabel: "firefoxview-tabpickup-network-offline-primarybutton", + }, + [ErrorType.SYNC_DISCONNECTED]: { + header: "firefoxview-tabpickup-sync-disconnected-header", + description: "firefoxview-tabpickup-sync-disconnected-description", + buttonLabel: "firefoxview-tabpickup-sync-disconnected-primarybutton", + }, + [ErrorType.PASSWORD_LOCKED]: { + header: "firefoxview-tabpickup-password-locked-header", + description: "firefoxview-tabpickup-password-locked-description", + buttonLabel: "firefoxview-tabpickup-password-locked-primarybutton", + link: { + label: "firefoxview-tabpickup-password-locked-link", + href: + Services.urlFormatter.formatURLPref("app.support.baseURL") + + "primary-password-stored-logins", + }, + }, + [ErrorType.SIGNED_OUT]: { + header: "firefoxview-tabpickup-signed-out-header", + description: "firefoxview-tabpickup-signed-out-description", + buttonLabel: "firefoxview-tabpickup-signed-out-primarybutton", + }, + }, +}.init(); |