summaryrefslogtreecommitdiffstats
path: root/browser/components/firefoxview/firefox-view-synced-tabs-error-handler.sys.mjs
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--browser/components/firefoxview/firefox-view-synced-tabs-error-handler.sys.mjs187
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();