commit 054316229cb666e286a8c9746623e2e44509b084
parent 6635506128b3227346d012389dd994aae44e94a8
Author: hannajones <hjones@mozilla.com>
Date: Tue, 16 Dec 2025 18:36:53 +0000
Bug 1971835 - add config-based account settings r=akulyk,fluent-reviewers,desktop-theme-reviewers,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D275086
Diffstat:
7 files changed, 419 insertions(+), 31 deletions(-)
diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
@@ -3511,6 +3511,111 @@ SettingGroupManager.registerGroups({
},
],
},
+ account: {
+ inProgress: true,
+ l10nId: "account-group-label",
+ headingLevel: 2,
+ items: [
+ {
+ id: "noFxaAccountGroup",
+ control: "moz-box-group",
+ items: [
+ {
+ id: "noFxaAccount",
+ control: "placeholder-message",
+ l10nId: "account-placeholder",
+ controlAttrs: {
+ imagesrc: "chrome://global/skin/illustrations/security-error.svg",
+ },
+ },
+ {
+ id: "noFxaSignIn",
+ control: "moz-box-link",
+ l10nId: "sync-signedout-account-short",
+ },
+ ],
+ },
+ {
+ id: "fxaSignedInGroup",
+ control: "moz-box-group",
+ items: [
+ {
+ id: "fxaLoginVerified",
+ control: "moz-box-item",
+ l10nId: "sync-account-signed-in",
+ l10nArgs: { email: "" },
+ iconSrc: "chrome://browser/skin/fxa/avatar-color.svg",
+ controlAttrs: {
+ layout: "large-icon",
+ },
+ },
+ {
+ id: "verifiedManage",
+ control: "moz-box-link",
+ l10nId: "sync-manage-account2",
+ controlAttrs: {
+ href: "https://accounts.firefox.com/settings",
+ },
+ },
+ {
+ id: "fxaUnlinkButton",
+ control: "moz-box-button",
+ l10nId: "sync-sign-out2",
+ },
+ ],
+ },
+ {
+ id: "fxaUnverifiedGroup",
+ control: "moz-box-group",
+ items: [
+ {
+ id: "fxaLoginUnverified",
+ control: "placeholder-message",
+ l10nId: "sync-signedin-unverified2",
+ l10nArgs: { email: "" },
+ controlAttrs: {
+ imagesrc: "chrome://global/skin/illustrations/security-error.svg",
+ },
+ },
+ {
+ id: "verifyFxaAccount",
+ control: "moz-box-link",
+ l10nId: "sync-verify-account",
+ },
+ {
+ id: "unverifiedUnlinkFxaAccount",
+ control: "moz-box-button",
+ l10nId: "sync-remove-account",
+ },
+ ],
+ },
+ {
+ id: "fxaLoginRejectedGroup",
+ control: "moz-box-group",
+ items: [
+ {
+ id: "fxaLoginRejected",
+ control: "placeholder-message",
+ l10nId: "sync-signedin-login-failure2",
+ l10nArgs: { email: "" },
+ controlAttrs: {
+ imagesrc: "chrome://global/skin/illustrations/security-error.svg",
+ },
+ },
+ {
+ id: "rejectReSignIn",
+ control: "moz-box-link",
+ l10nId: "sync-sign-in",
+ },
+ {
+ id: "rejectUnlinkFxaAccount",
+ control: "moz-box-button",
+ l10nId: "sync-remove-account",
+ },
+ ],
+ },
+ ],
+ },
});
/**
diff --git a/browser/components/preferences/sync.inc.xhtml b/browser/components/preferences/sync.inc.xhtml
@@ -246,6 +246,9 @@
</vbox>
</deck>
+<!-- Mozilla account -->
+<html:setting-group groupid="account" hidden="true" data-category="paneSync"/>
+
<!-- Sync -->
<html:setting-group groupid="sync" hidden="true" data-category="paneSync"/>
diff --git a/browser/components/preferences/sync.js b/browser/components/preferences/sync.js
@@ -225,9 +225,37 @@ var SyncHelpers = new (class SyncHelpers {
);
this.replaceTabWithUrl(url);
}
+
+ /**
+ * Attempts to take the user through the sign in flow by opening the web content
+ * with the given entrypoint as a query parameter
+ *
+ * @param {string} entrypoint
+ * An string appended to the query parameters, used in telemetry to differentiate
+ * different entrypoints to accounts
+ */
+ async reSignIn(entrypoint) {
+ const url = await FxAccounts.config.promiseConnectAccountURI(entrypoint);
+ this.replaceTabWithUrl(url);
+ }
+
+ async verifyFirefoxAccount() {
+ return this.reSignIn("preferences-reverify");
+ }
+
+ /**
+ * Disconnect the account, including everything linked.
+ *
+ * @param {boolean} confirm
+ * Whether to show a confirmation dialog before disconnecting
+ */
+ unlinkFirefoxAccount(confirm) {
+ window.browsingContext.topChromeWindow.gSync.disconnect({
+ confirm,
+ });
+ }
})();
-// Sync section
Preferences.addSetting({
id: "uiStateUpdate",
setup(emitChange) {
@@ -236,7 +264,171 @@ Preferences.addSetting({
},
});
-// Sync section - no Firefox account
+// Mozilla accounts section
+
+// Logged out of Mozilla account
+Preferences.addSetting({
+ id: "noFxaAccountGroup",
+ deps: ["uiStateUpdate"],
+ visible() {
+ return SyncHelpers.uiStateStatus == UIState.STATUS_NOT_CONFIGURED;
+ },
+});
+Preferences.addSetting({
+ id: "noFxaAccount",
+});
+Preferences.addSetting({
+ id: "noFxaSignIn",
+ onUserClick: () => {
+ SyncHelpers.signIn();
+ },
+});
+
+// Logged in and verified and all is good
+Preferences.addSetting({
+ id: "fxaSignedInGroup",
+ deps: ["uiStateUpdate"],
+ visible() {
+ return SyncHelpers.uiStateStatus == UIState.STATUS_SIGNED_IN;
+ },
+});
+Preferences.addSetting({
+ id: "fxaLoginVerified",
+ deps: ["uiStateUpdate"],
+ _failedAvatarURLs: new Set(),
+ getControlConfig(config, _, setting) {
+ let state = SyncHelpers.uiState;
+
+ if (state.displayName) {
+ config.l10nId = "sync-account-signed-in-display-name";
+ config.l10nArgs = {
+ name: state.displayName,
+ email: state.email || "",
+ };
+ } else {
+ config.l10nId = "sync-account-signed-in";
+ config.l10nArgs = {
+ email: state.email || "",
+ };
+ }
+
+ // Reset the image to default avatar if we encounter an error.
+ if (this._failedAvatarURLs.has(state.avatarURL)) {
+ config.iconSrc = "chrome://browser/skin/fxa/avatar-color.svg";
+ return config;
+ }
+
+ if (state.avatarURL && !state.avatarIsDefault) {
+ config.iconSrc = state.avatarURL;
+ let img = new Image();
+ img.onerror = () => {
+ this._failedAvatarURLs.add(state.avatarURL);
+ setting.onChange();
+ };
+ img.src = state.avatarURL;
+ }
+ return config;
+ },
+});
+Preferences.addSetting(
+ class extends Preferences.AsyncSetting {
+ static id = "verifiedManage";
+
+ setup() {
+ Weave.Svc.Obs.add(UIState.ON_UPDATE, this.emitChange);
+ return () => Weave.Svc.Obs.remove(UIState.ON_UPDATE, this.emitChange);
+ }
+
+ // The "manage account" link embeds the uid, so we need to update this
+ // if the account state changes.
+ async getControlConfig() {
+ let href = await FxAccounts.config.promiseManageURI(
+ SyncHelpers.getEntryPoint()
+ );
+ return {
+ controlAttrs: {
+ href: href ?? "https://accounts.firefox.com/settings",
+ },
+ };
+ }
+ }
+);
+
+Preferences.addSetting({
+ id: "fxaUnlinkButton",
+ onUserClick: () => {
+ SyncHelpers.unlinkFirefoxAccount(true);
+ },
+});
+
+// Logged in to an unverified account
+Preferences.addSetting({
+ id: "fxaUnverifiedGroup",
+ deps: ["uiStateUpdate"],
+ visible() {
+ return SyncHelpers.uiStateStatus == UIState.STATUS_NOT_VERIFIED;
+ },
+});
+Preferences.addSetting({
+ id: "fxaLoginUnverified",
+ deps: ["uiStateUpdate"],
+ getControlConfig(config) {
+ let state = SyncHelpers.uiState;
+ config.l10nArgs = {
+ email: state.email || "",
+ };
+ return config;
+ },
+});
+Preferences.addSetting({
+ id: "verifyFxaAccount",
+ onUserClick: () => {
+ SyncHelpers.verifyFirefoxAccount();
+ },
+});
+Preferences.addSetting({
+ id: "unverifiedUnlinkFxaAccount",
+ onUserClick: () => {
+ /* no warning as account can't have previously synced */
+ SyncHelpers.unlinkFirefoxAccount(false);
+ },
+});
+
+// Logged in locally but server rejected credentials
+Preferences.addSetting({
+ id: "fxaLoginRejectedGroup",
+ deps: ["uiStateUpdate"],
+ visible() {
+ return SyncHelpers.uiStateStatus == UIState.STATUS_LOGIN_FAILED;
+ },
+});
+Preferences.addSetting({
+ id: "fxaLoginRejected",
+ deps: ["uiStateUpdate"],
+ getControlConfig(config) {
+ let state = SyncHelpers.uiState;
+ config.l10nArgs = {
+ email: state.email || "",
+ };
+ return config;
+ },
+});
+Preferences.addSetting({
+ id: "rejectReSignIn",
+ onUserClick: () => {
+ SyncHelpers.reSignIn(SyncHelpers.getEntryPoint());
+ },
+});
+Preferences.addSetting({
+ id: "rejectUnlinkFxaAccount",
+ onUserClick: () => {
+ SyncHelpers.unlinkFirefoxAccount(true);
+ },
+});
+
+//Sync section
+
+//Sync section - no Firefox account
Preferences.addSetting({
id: "syncNoFxaSignIn",
deps: ["uiStateUpdate"],
@@ -505,6 +697,7 @@ var gSyncPane = {
_init() {
initSettingGroup("sync");
+ initSettingGroup("account");
Weave.Svc.Obs.add(UIState.ON_UPDATE, this.updateWeavePrefs, this);
@@ -631,22 +824,20 @@ var gSyncPane = {
return false;
});
setEventListener("fxaUnlinkButton", "command", function () {
- gSyncPane.unlinkFirefoxAccount(true);
+ SyncHelpers.unlinkFirefoxAccount(true);
});
- setEventListener(
- "verifyFxaAccount",
- "command",
- gSyncPane.verifyFirefoxAccount
+ setEventListener("verifyFxaAccount", "command", () =>
+ SyncHelpers.verifyFirefoxAccount()
);
setEventListener("unverifiedUnlinkFxaAccount", "command", function () {
/* no warning as account can't have previously synced */
- gSyncPane.unlinkFirefoxAccount(false);
+ SyncHelpers.unlinkFirefoxAccount(false);
});
setEventListener("rejectReSignIn", "command", function () {
- gSyncPane.reSignIn(SyncHelpers.getEntryPoint());
+ SyncHelpers.reSignIn(SyncHelpers.getEntryPoint());
});
setEventListener("rejectUnlinkFxaAccount", "command", function () {
- gSyncPane.unlinkFirefoxAccount(true);
+ SyncHelpers.unlinkFirefoxAccount(true);
});
setEventListener("fxaSyncComputerName", "keypress", function (e) {
if (e.keyCode == KeyEvent.DOM_VK_RETURN) {
@@ -814,16 +1005,16 @@ var gSyncPane = {
win.switchToTabHavingURI(url, true, options);
},
- /**
- * Attempts to take the user through the sign in flow by opening the web content
- * with the given entrypoint as a query parameter
- *
- * @param entrypoint: An string appended to the query parameters, used in telemtry to differentiate
- * different entrypoints to accounts
- */
- async reSignIn(entrypoint) {
- const url = await FxAccounts.config.promiseConnectAccountURI(entrypoint);
- SyncHelpers.replaceTabWithUrl(url);
+ // Replace the current tab with the specified URL.
+ replaceTabWithUrl(url) {
+ // Get the <browser> element hosting us.
+ let browser = window.docShell.chromeEventHandler;
+ // And tell it to load our URL.
+ browser.loadURI(Services.io.newURI(url), {
+ triggeringPrincipal: Services.scriptSecurityManager.createNullPrincipal(
+ {}
+ ),
+ });
},
clickOrSpaceOrEnterPressed(event) {
@@ -853,17 +1044,6 @@ var gSyncPane = {
}
},
- async verifyFirefoxAccount() {
- return this.reSignIn("preferences-reverify");
- },
-
- // Disconnect the account, including everything linked.
- unlinkFirefoxAccount(confirm) {
- window.browsingContext.topChromeWindow.gSync.disconnect({
- confirm,
- });
- },
-
pairAnotherDevice() {
gSubDialog.open(
"chrome://browser/content/preferences/fxaPairDevice.xhtml",
diff --git a/browser/locales/en-US/browser/preferences/preferences.ftl b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -997,6 +997,13 @@ containers-remove-button =
sync-group-label =
.label = Sync
+account-group-label =
+ .label = { -vendor-short-name } account
+
+account-placeholder =
+ .label = You’re not signed in
+ .description = Sign in to keep your data private, encrypted, and synced across devices.
+
## Firefox account - Signed out. Note that "Sync" and "Firefox account" are now
## more discrete ("signed in" no longer means "and sync is connected").
@@ -1011,6 +1018,10 @@ sync-signedout-account-signin-4 =
.label = Sign in to your account to start syncing
.accesskey = i
+sync-signedout-account-short =
+ .label = Sign in
+ .accesskey = i
+
# This message contains two links and two icon images.
# `<img data-l10n-name="android-icon"/>` - Android logo icon
# `<a data-l10n-name="android-link">` - Link to Android Download
@@ -1037,15 +1048,40 @@ sync-sign-out =
.label = Sign Out…
.accesskey = g
+sync-sign-out2 =
+ .label = Sign out
+ .accesskey = g
+
sync-manage-account = Manage account
.accesskey = o
+sync-manage-account2 =
+ .label = Manage account
+ .accesskey = o
+
## Variables
## $email (string) - Email used for Firefox account
+## $name (string) - Name used for Firefox account
+
+sync-account-signed-in =
+ .label = { $email }
+
+sync-account-signed-in-display-name =
+ .label = { $name }
+ .description = { $email }
sync-signedin-unverified = { $email } is not verified.
+
+sync-signedin-unverified2 =
+ .label = { $email } isn’t confirmed yet
+ .description = Check your inbox to confirm your account and make it official.
+
sync-signedin-login-failure = Please sign in to reconnect { $email }
+sync-signedin-login-failure2 =
+ .label = You’re signed out of { $email }
+ .description = Sign back in to reconnect and start syncing your data.
+
##
sync-verify-account =
diff --git a/browser/themes/shared/preferences/preferences.css b/browser/themes/shared/preferences/preferences.css
@@ -792,6 +792,16 @@ button#noFxaSignIn {
margin-inline: 4px 8px;
}
+#fxaLoginVerified {
+ --box-icon-size: calc(var(--icon-size-xlarge) * 2);
+ --box-icon-border-radius: var(--border-radius-circle);
+
+ &[description] {
+ --box-label-alignment: end;
+ --box-description-alignment: start;
+ }
+}
+
#syncStatusContainer {
margin-top: 32px;
}
diff --git a/python/l10n/fluent_migrations/bug_1971835_sync_preferences_update.py b/python/l10n/fluent_migrations/bug_1971835_sync_preferences_update.py
@@ -0,0 +1,51 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+import re
+from fluent.migrate.transforms import TransformPattern, COPY_PATTERN
+import fluent.syntax.ast as FTL
+
+
+class STRIP_ELLIPSIS(TransformPattern):
+ def visit_TextElement(self, node):
+ node.value = re.sub(r"(?:…|\.\.\.)$", "", node.value)
+ return node
+
+
+def migrate(ctx):
+ """Bug 1971835 - Update account preferences strings, part {index}."""
+
+ source = "browser/browser/preferences/preferences.ftl"
+
+ ctx.add_transforms(
+ source,
+ source,
+ [
+ FTL.Message(
+ id=FTL.Identifier("sync-sign-out2"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=STRIP_ELLIPSIS(source, "sync-sign-out.label"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY_PATTERN(source, "sync-sign-out.accesskey"),
+ ),
+ ],
+ ),
+ FTL.Message(
+ id=FTL.Identifier("sync-manage-account2"),
+ attributes=[
+ FTL.Attribute(
+ id=FTL.Identifier("label"),
+ value=COPY_PATTERN(source, "sync-manage-account"),
+ ),
+ FTL.Attribute(
+ id=FTL.Identifier("accesskey"),
+ value=COPY_PATTERN(source, "sync-manage-account.accesskey"),
+ ),
+ ],
+ ),
+ ],
+ )
diff --git a/toolkit/content/widgets/moz-box-common.css b/toolkit/content/widgets/moz-box-common.css
@@ -52,6 +52,7 @@
.label {
grid-area: label;
font-weight: var(--box-label-font-weight, normal);
+ align-self: var(--box-label-alignment);
}
.icon {
@@ -61,6 +62,7 @@
-moz-context-properties: fill, stroke;
fill: var(--box-icon-fill);
stroke: var(--box-icon-stroke);
+ border-radius: var(--box-icon-border-radius);
&:not(.nav-icon) {
fill: var(--box-icon-start-fill, var(--box-icon-fill));
@@ -73,6 +75,7 @@
display: flex;
justify-content: center;
gap: var(--space-small);
+ align-self: var(--box-description-alignment);
}
}