Source code

Revision control

Copy as Markdown

Other Tools

/* 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, {
});
ChromeUtils.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-disabled-by-policy-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-description2",
buttonLabel: "firefoxview-tabpickup-signed-out-primarybutton",
},
},
}.init();