/* 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/. */ /* import-globals-from preferences.js */ ChromeUtils.defineESModuleGetters(this, { UIState: "resource://services-sync/UIState.sys.mjs", Weave: "resource://services-sync/main.sys.mjs", }); var { FxAccounts, getFxAccountsSingleton } = ChromeUtils.importESModule( "resource://gre/modules/FxAccounts.sys.mjs" ); var fxAccounts = getFxAccountsSingleton(); var gSyncPane = { init() { this._setupEventListeners(); this.setupEnginesUI(); Weave.Svc.Obs.add(UIState.ON_UPDATE, this.updateWeavePrefs, this); window.addEventListener("unload", () => { Weave.Svc.Obs.remove(UIState.ON_UPDATE, this.updateWeavePrefs, this); }); let cachedComputerName = Services.prefs.getStringPref( "identity.fxaccounts.account.device.name", "" ); if (cachedComputerName) { this._populateComputerName(cachedComputerName); } this.updateWeavePrefs(); }, /** * Update the UI based on the current state. */ updateWeavePrefs() { let state = UIState.get(); let noFxaAccount = document.getElementById("noFxaAccount"); let hasFxaAccount = document.getElementById("hasFxaAccount"); if (state.status == UIState.STATUS_NOT_CONFIGURED) { noFxaAccount.hidden = false; hasFxaAccount.hidden = true; return; } noFxaAccount.hidden = true; hasFxaAccount.hidden = false; let syncReady = false; // Is sync able to actually sync? let fxaLoginUnverified = document.getElementById("fxaLoginUnverified"); let fxaLoginRejected = document.getElementById("fxaLoginRejected"); let fxaLoginVerified = document.getElementById("fxaLoginVerified"); if (state.status == UIState.STATUS_LOGIN_FAILED) { fxaLoginUnverified.hidden = true; fxaLoginRejected.hidden = false; fxaLoginVerified.hidden = true; } else if (state.status == UIState.STATUS_NOT_VERIFIED) { fxaLoginUnverified.hidden = false; fxaLoginRejected.hidden = true; fxaLoginVerified.hidden = true; } else { fxaLoginUnverified.hidden = true; fxaLoginRejected.hidden = true; fxaLoginVerified.hidden = false; syncReady = true; } this._populateComputerName(Weave.Service.clientsEngine.localName); for (let elt of document.querySelectorAll(".needs-account-ready")) { elt.disabled = !syncReady; } let syncConnected = document.getElementById("syncConnected"); let syncDisconnected = document.getElementById("syncDisconnected"); syncConnected.hidden = !syncReady || !state.syncEnabled; syncDisconnected.hidden = !syncReady || state.syncEnabled; document.l10n.setAttributes( document.getElementById("fxaAccountMailNotVerified"), "sync-pane-email-not-verified", { userEmail: state.email } ); document.l10n.setAttributes( document.getElementById("fxaAccountLoginRejected"), "sync-signedin-login-failure", { userEmail: state.email } ); document.getElementById("fxaAvatar").src = state.avatarURL && !state.avatarIsDefault ? state.avatarURL : ""; document.getElementById("fxaDisplayName").textContent = state.displayName; document.getElementById("fxaEmailAddress").textContent = state.email; this._updateSyncNow(state.syncing); }, _toggleComputerNameControls(editMode) { let textbox = document.getElementById("fxaDeviceNameInput"); textbox.readOnly = !editMode; document.getElementById("fxaDeviceNameChangeDeviceName").hidden = editMode; document.getElementById("fxaDeviceNameCancel").hidden = !editMode; document.getElementById("fxaDeviceNameSave").hidden = !editMode; }, _focusComputerNameTextbox() { let textbox = document.getElementById("fxaDeviceNameInput"); let valLength = textbox.value.length; textbox.focus(); textbox.setSelectionRange(valLength, valLength); }, _blurComputerNameTextbox() { document.getElementById("fxaDeviceNameInput").blur(); }, _focusAfterComputerNameTextbox() { // Focus the most appropriate element that's *not* the "computer name" box. Services.focus.moveFocus( window, document.getElementById("fxaDeviceNameInput"), Services.focus.MOVEFOCUS_FORWARD, 0 ); }, _updateComputerNameValue(save) { if (save) { let textbox = document.getElementById("fxaDeviceNameInput"); Weave.Service.clientsEngine.localName = textbox.value; } this._populateComputerName(Weave.Service.clientsEngine.localName); }, _setupEventListeners() { function setEventListener(id, eventType, callback) { document .getElementById(id) .addEventListener(eventType, callback.bind(gSyncPane)); } setEventListener("noFxaSignIn", "click", function () { window.browsingContext.topChromeWindow.gSync.initFxA(); return false; }); setEventListener( "fxaResendVerification", "click", gSyncPane.verifyFirefoxAccount ); setEventListener("fxaUnverifiedRemoveAccount", "click", function () { /* No warning as account can't have previously synced. */ gSyncPane.unlinkFirefoxAccount(false); }); setEventListener("fxaRejectedSignIn", "click", gSyncPane.reSignIn); setEventListener("fxaRejectedRemoveAccount", "click", function () { gSyncPane.unlinkFirefoxAccount(true); }); setEventListener("photoButton", "click", function (event) { window.browsingContext.topChromeWindow.gSync.openFxAAvatarPage( "preferences" ); }); setEventListener("verifiedManage", "click", function (event) { window.browsingContext.topChromeWindow.gSync.openFxAManagePage( "preferences" ); event.preventDefault(); // Stop attempts to open this link in an external browser. event.stopPropagation(); }); setEventListener("fxaAccountSignOut", "click", function () { gSyncPane.unlinkFirefoxAccount(true); }); setEventListener("fxaDeviceNameCancel", "click", function () { // We explicitly blur the textbox because of bug 75324, then after // changing the state of the buttons, force focus to whatever the focus // manager thinks should be next (which on the mac, depends on an OSX // keyboard access preference) this._blurComputerNameTextbox(); this._toggleComputerNameControls(false); this._updateComputerNameValue(false); this._focusAfterComputerNameTextbox(); }); setEventListener("fxaDeviceNameSave", "click", function () { // Work around bug 75324 - see above. this._blurComputerNameTextbox(); this._toggleComputerNameControls(false); this._updateComputerNameValue(true); this._focusAfterComputerNameTextbox(); }); setEventListener("fxaDeviceNameChangeDeviceName", "click", function () { this._toggleComputerNameControls(true); this._focusComputerNameTextbox(); }); setEventListener("syncShowSyncedSyncNow", "click", function () { // syncing can take a little time to send the "started" notification, so // pretend we already got it. this._updateSyncNow(true); Weave.Service.sync({ why: "aboutprefs" }); }); setEventListener("enginesLearnMore", "click", function (event) { // TODO: A real page. window.browsingContext.topChromeWindow.openContentTab( "https://example.org/?page=learnMore" ); event.preventDefault(); // Stop attempts to open this link in an external browser. event.stopPropagation(); }); setEventListener("syncChangeOptions", "click", function () { gSyncPane._chooseWhatToSync(true); }); setEventListener("syncSetup", "click", function () { gSyncPane._chooseWhatToSync(false); }); }, async _chooseWhatToSync(isAlreadySyncing) { // Assuming another device is syncing and we're not, we update the engines // selection so the correct checkboxes are pre-filled. if (!isAlreadySyncing) { try { await Weave.Service.updateLocalEnginesState(); } catch (err) { console.error("Error updating the local engines state", err); } } let params = {}; if (isAlreadySyncing) { // If we are already syncing then we also offer to disconnect. params.disconnectFun = () => this.disconnectSync(); } gSubDialog.open( "chrome://messenger/content/preferences/syncDialog.xhtml", { features: "resizable=no", closingCallback: event => { if (!isAlreadySyncing && event.detail.button == "accept") { // We weren't syncing but the user has accepted the dialog - so we // want to start! fxAccounts.telemetry .recordConnection(["sync"], "ui") .then(() => { return Weave.Service.configure(); }) .catch(err => { console.error("Failed to enable sync", err); }); } }, }, params ); }, _updateSyncNow(syncing) { let button = document.getElementById("syncShowSyncedSyncNow"); if (syncing) { document.l10n.setAttributes(button, "sync-panel-sync-now-syncing"); button.disabled = true; } else { document.l10n.setAttributes(button, "sync-pane-sync-now"); button.disabled = false; } }, /** * If connecting to Firefox Accounts failed, try again. */ async reSignIn() { // There's a bit of an edge-case here - we might be forcing reauth when we've // lost the FxA account data - in which case we'll not get a URL as the re-auth // URL embeds account info and the server endpoint complains if we don't // supply it - so we just use the regular "sign in" URL in that case. if (!(await FxAccounts.canConnectAccount())) { return; } const url = (await FxAccounts.config.promiseForceSigninURI("preferences")) || (await FxAccounts.config.promiseConnectAccountURI("preferences")); window.browsingContext.topChromeWindow.openContentTab(url); }, /** * Send a confirmation email to the account's email address. */ verifyFirefoxAccount() { let onError = async () => { let [title, body] = await document.l10n.formatValues([ "fxa-verification-not-sent-title", "fxa-verification-not-sent-body", ]); new Notification(title, { body }); }; let onSuccess = async data => { if (data) { let [title, body] = await document.l10n.formatValues([ "fxa-verification-sent-title", { id: "fxa-verification-sent-body", args: { userEmail: data.email } }, ]); new Notification(title, { body }); } else { onError(); } }; fxAccounts .resendVerificationEmail() .then(() => fxAccounts.getSignedInUser(), onError) .then(onSuccess, onError); }, /** * Disconnect the account, including everything linked. * * @param {boolean} confirm - If true, asks the user if they're sure. */ unlinkFirefoxAccount(confirm) { window.browsingContext.topChromeWindow.gSync.disconnect({ confirm }); }, /** * Disconnect sync, leaving the FxA account connected. */ disconnectSync() { return window.browsingContext.topChromeWindow.gSync.disconnect({ confirm: true, disconnectAccount: false, }); }, _populateComputerName(value) { let textbox = document.getElementById("fxaDeviceNameInput"); if (!textbox.hasAttribute("placeholder")) { textbox.setAttribute( "placeholder", fxAccounts.device.getDefaultLocalName() ); } textbox.value = value; }, /** * Arranges to dynamically show or hide sync engine name elements based on * the preferences used for the engines. */ setupEnginesUI() { let observe = (element, prefName) => { element.hidden = !Services.prefs.getBoolPref(prefName, false); }; let engineItems = { showSyncAccount: "services.sync.engine.accounts", showSyncIdentity: "services.sync.engine.identities", showSyncAddress: "services.sync.engine.addressbooks", showSyncCalendar: "services.sync.engine.calendars", showSyncPasswords: "services.sync.engine.passwords", }; for (let [id, prefName] of Object.entries(engineItems)) { let obs = observe.bind(null, document.getElementById(id), prefName); obs(); Services.prefs.addObserver(prefName, obs); window.addEventListener("unload", () => { Services.prefs.removeObserver(prefName, obs); }); } }, };