commit 566e4283c6b13cec5a7837c93822ba4b64e3fae1
parent 75573040ed7579f36fa22ac0432ff8f7e8a5041a
Author: Micah Killoran <mtigley@mozilla.com>
Date: Tue, 18 Nov 2025 21:02:00 +0000
Bug 1971621 - Start converting Passwords section to config based prefs. r=dimi,akulyk,fluent-reviewers,desktop-theme-reviewers,tgiles,bolsson
Differential Revision: https://phabricator.services.mozilla.com/D265170
Diffstat:
6 files changed, 317 insertions(+), 4 deletions(-)
diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js
@@ -1843,6 +1843,111 @@ SettingGroupManager.registerGroups({
},
],
},
+ passwords: {
+ inProgress: true,
+ id: "passwordsGroup",
+ l10nId: "forms-passwords-header",
+ headingLevel: 2,
+ items: [
+ {
+ id: "savePasswords",
+ l10nId: "forms-ask-to-save-passwords",
+ items: [
+ {
+ id: "managePasswordExceptions",
+ l10nId: "forms-manage-password-exceptions",
+ control: "moz-box-button",
+ controlAttrs: {
+ "search-l10n-ids":
+ "permissions-address,permissions-exceptions-saved-passwords-window.title,permissions-exceptions-saved-passwords-desc,",
+ },
+ },
+ {
+ id: "fillUsernameAndPasswords",
+ l10nId: "forms-fill-usernames-and-passwords-2",
+ controlAttrs: {
+ "search-l10n-ids": "forms-saved-passwords-searchkeywords",
+ },
+ },
+ {
+ id: "suggestStrongPasswords",
+ l10nId: "forms-suggest-passwords",
+ supportPage: "how-generate-secure-password-firefox",
+ },
+ ],
+ },
+ {
+ id: "requireOSAuthForPasswords",
+ l10nId: "forms-os-reauth",
+ },
+ {
+ id: "manageSavedPasswords",
+ l10nId: "forms-saved-passwords-2",
+ control: "moz-box-button",
+ },
+ {
+ id: "additionalProtectionsGroup",
+ l10nId: "forms-additional-protections-header",
+ control: "moz-fieldset",
+ controlAttrs: {
+ headingLevel: 2,
+ },
+ items: [
+ {
+ id: "primaryPasswordNotSet",
+ control: "moz-box-group",
+ items: [
+ {
+ id: "usePrimaryPassword",
+ l10nId: "forms-primary-pw-use",
+ control: "moz-box-item",
+ supportPage: "primary-password-stored-logins",
+ },
+ {
+ id: "addPrimaryPassword",
+ l10nId: "forms-primary-pw-set",
+ control: "moz-box-button",
+ },
+ ],
+ },
+ {
+ id: "primaryPasswordSet",
+ control: "moz-box-group",
+ items: [
+ {
+ id: "statusPrimaryPassword",
+ l10nId: "forms-primary-pw-on",
+ control: "moz-box-item",
+ controlAttrs: {
+ iconsrc: "chrome://global/skin/icons/check-filled.svg",
+ },
+ options: [
+ {
+ id: "turnOffPrimaryPassword",
+ l10nId: "forms-primary-pw-turn-off",
+ control: "moz-button",
+ controlAttrs: {
+ slot: "actions",
+ },
+ },
+ ],
+ },
+ {
+ id: "changePrimaryPassword",
+ l10nId: "forms-primary-pw-change-2",
+ control: "moz-box-button",
+ },
+ ],
+ },
+ {
+ id: "breachAlerts",
+ l10nId: "forms-breach-alerts",
+ supportPage: "lockwise-alerts",
+ },
+ ],
+ },
+ ],
+ },
history: {
items: [
{
diff --git a/browser/components/preferences/privacy.inc.xhtml b/browser/components/preferences/privacy.inc.xhtml
@@ -426,7 +426,7 @@
</groupbox>
<!-- Passwords -->
-<groupbox id="passwordsGroup" orient="vertical" data-category="panePrivacy" data-subcategory="logins" hidden="true">
+<groupbox data-srd-groupid="passwords" id="passwordsGroup" orient="vertical" data-category="panePrivacy" data-subcategory="logins" hidden="true">
<label><html:h2 data-l10n-id="pane-privacy-passwords-header" data-l10n-attrs="searchkeywords"/></label>
<vbox id="passwordSettings">
@@ -555,6 +555,8 @@
<label id="fips-desc" hidden="true" data-l10n-id="forms-master-pw-fips-desc"></label>
</groupbox>
+<html:setting-group groupid="passwords" hidden="true" data-category="panePrivacy" />
+
<!-- The form autofill section is inserted in to this box
after the form autofill extension has initialized. -->
<groupbox id="formAutofillGroupBox"
diff --git a/browser/components/preferences/privacy.js b/browser/components/preferences/privacy.js
@@ -210,6 +210,7 @@ Preferences.addAll([
// Popups
{ id: "dom.disable_open_during_load", type: "bool" },
+
// Passwords
{ id: "signon.rememberSignons", type: "bool" },
{ id: "signon.generation.enabled", type: "bool" },
@@ -628,6 +629,144 @@ if (Services.prefs.getBoolPref("privacy.ui.status_card", false)) {
})
);
}
+
+Preferences.addSetting({
+ id: "savePasswords",
+ pref: "signon.rememberSignons",
+ controllingExtensionInfo: {
+ storeId: "services.passwordSavingEnabled",
+ l10nId: "extension-controlling-password-saving",
+ },
+});
+
+Preferences.addSetting({
+ id: "managePasswordExceptions",
+ onUserClick: () => {
+ gPrivacyPane.showPasswordExceptions();
+ },
+});
+
+Preferences.addSetting({
+ id: "fillUsernameAndPasswords",
+ pref: "signon.autofillForms",
+});
+
+Preferences.addSetting({
+ id: "suggestStrongPasswords",
+ pref: "signon.generation.enabled",
+ visible: () => Services.prefs.getBoolPref("signon.generation.available"),
+});
+
+Preferences.addSetting({
+ id: "requireOSAuthForPasswords",
+ visible: () => OSKeyStore.canReauth(),
+ get: () => LoginHelper.getOSAuthEnabled(),
+ async set(checked) {
+ const [messageText, captionText] = await Promise.all([
+ lazy.AboutLoginsL10n.formatValue("about-logins-os-auth-dialog-message"),
+ lazy.AboutLoginsL10n.formatValue("about-logins-os-auth-dialog-caption"),
+ ]);
+
+ await LoginHelper.trySetOSAuthEnabled(
+ window,
+ checked,
+ messageText,
+ captionText
+ );
+
+ // Trigger change event to keep checkbox UI in sync with pref value
+ Services.obs.notifyObservers(null, "PasswordsOSAuthEnabledChange");
+ },
+ setup: emitChange => {
+ Services.obs.addObserver(emitChange, "PasswordsOSAuthEnabledChange");
+ return () =>
+ Services.obs.removeObserver(emitChange, "PasswordsOSAuthEnabledChange");
+ },
+});
+
+Preferences.addSetting({
+ id: "manageSavedPasswords",
+ onUserClick: ({ target }) => {
+ target.ownerGlobal.gPrivacyPane.showPasswords();
+ },
+});
+
+Preferences.addSetting({
+ id: "additionalProtectionsGroup",
+});
+
+Preferences.addSetting({
+ id: "primaryPasswordNotSet",
+ setup(emitChange) {
+ const topic = "passwordmgr-primary-pw-changed";
+ Services.obs.addObserver(emitChange, topic);
+ return () => Services.obs.removeObserver(emitChange, topic);
+ },
+ visible: () => {
+ return !LoginHelper.isPrimaryPasswordSet();
+ },
+});
+
+Preferences.addSetting({
+ id: "usePrimaryPassword",
+ deps: ["primaryPasswordNotSet"],
+});
+
+Preferences.addSetting({
+ id: "addPrimaryPassword",
+ deps: ["primaryPasswordNotSet"],
+ onUserClick: ({ target }) => {
+ target.ownerGlobal.gPrivacyPane.changeMasterPassword();
+ },
+ disabled: () => {
+ return !Services.policies.isAllowed("createMasterPassword");
+ },
+});
+
+Preferences.addSetting({
+ id: "primaryPasswordSet",
+ setup(emitChange) {
+ const topic = "passwordmgr-primary-pw-changed";
+ Services.obs.addObserver(emitChange, topic);
+ return () => Services.obs.removeObserver(emitChange, topic);
+ },
+ visible: () => {
+ return LoginHelper.isPrimaryPasswordSet();
+ },
+});
+
+Preferences.addSetting({
+ id: "statusPrimaryPassword",
+ deps: ["primaryPasswordSet"],
+ onUserClick: e => {
+ if (e.target.localName == "moz-button") {
+ e.target.ownerGlobal.gPrivacyPane._removeMasterPassword();
+ }
+ },
+ getControlConfig(config) {
+ config.options[0].controlAttrs = {
+ ...config.options[0].controlAttrs,
+ ...(!Services.policies.isAllowed("removeMasterPassword")
+ ? { disabled: "" }
+ : {}),
+ };
+ return config;
+ },
+});
+
+Preferences.addSetting({
+ id: "changePrimaryPassword",
+ deps: ["primaryPasswordSet"],
+ onUserClick: ({ target }) => {
+ target.ownerGlobal.gPrivacyPane.changeMasterPassword();
+ },
+});
+
+Preferences.addSetting({
+ id: "breachAlerts",
+ pref: "signon.management.page.breach-alerts.enabled",
+});
+
/**
* This class is used to create Settings that are used to warn the user about
* potential misconfigurations. It should be passed into Preferences.addSetting
@@ -3285,6 +3424,9 @@ var gPrivacyPane = {
this._initMasterPasswordUI();
this._initOSAuthentication();
+ // Init passwords settings group
+ initSettingGroup("passwords");
+
this.initListenersForExtensionControllingPasswordManager();
setSyncFromPrefListener("contentBlockingBlockCookiesCheckbox", () =>
@@ -4590,7 +4732,10 @@ var gPrivacyPane = {
this._initMasterPasswordUI();
} else {
gSubDialog.open("chrome://mozapps/content/preferences/removemp.xhtml", {
- closingCallback: this._initMasterPasswordUI.bind(this),
+ closingCallback: () => {
+ Services.obs.notifyObservers(null, "passwordmgr-primary-pw-changed");
+ this._initMasterPasswordUI();
+ },
});
}
},
@@ -4639,7 +4784,10 @@ var gPrivacyPane = {
gSubDialog.open("chrome://mozapps/content/preferences/changemp.xhtml", {
features: "resizable=no",
- closingCallback: this._initMasterPasswordUI.bind(this),
+ closingCallback: () => {
+ Services.obs.notifyObservers(null, "passwordmgr-primary-pw-changed");
+ this._initMasterPasswordUI();
+ },
});
},
diff --git a/browser/locales/en-US/browser/preferences/preferences.ftl b/browser/locales/en-US/browser/preferences/preferences.ftl
@@ -1109,10 +1109,17 @@ privacy-header = Browser Privacy
pane-privacy-passwords-header = Passwords
.searchkeywords = logins
+forms-passwords-header =
+ .label = Passwords
+ .aria-label = Passwords
+
# Checkbox to control whether UI is shown to users to save or fill logins/passwords.
forms-ask-to-save-passwords =
.label = Ask to save passwords
.accesskey = A
+forms-manage-password-exceptions =
+ .label = Manage password exceptions
+ .accesskey = M
forms-exceptions =
.label = Exceptions…
.accesskey = x
@@ -1132,12 +1139,32 @@ relay-integration-learn-more-link = Learn more
forms-fill-usernames-and-passwords =
.label = Fill usernames and passwords automatically
.accesskey = F
+forms-fill-usernames-and-passwords-2 =
+ .label = Save and autofill usernames and passwords
+ .accesskey = f
forms-saved-passwords =
.label = Saved passwords
.accesskey = d
+forms-saved-passwords-2 =
+ .label = Manage saved passwords
+ .accesskey = d
+forms-saved-passwords-searchkeywords = Logins for the following sites are stored on your computer
+
+# Header for additional protections when managing password settings.
+forms-additional-protections-header =
+ .label = Additional protections
forms-primary-pw-use =
- .label = Use a Primary Password
+ .label = Use a primary password
.accesskey = U
+forms-primary-pw-set =
+ .label = Set primary password
+forms-primary-pw-on =
+ .label = Primary password is ON
+forms-primary-pw-change-2 =
+ .label = Change primary password
+# Label for button to disable primary password.
+forms-primary-pw-turn-off =
+ .label = Turn it off
# This operation requires the user to authenticate with the operating system (device sign-in)
forms-os-reauth =
.label = Require device sign in to fill and manage passwords
diff --git a/browser/themes/shared/preferences/privacy.css b/browser/themes/shared/preferences/privacy.css
@@ -334,3 +334,7 @@
#openWindowsPasskeySettings {
margin-block-start: 2em;
}
+
+#statusPrimaryPassword {
+ --box-icon-fill: var(--icon-color-success);
+}
diff --git a/toolkit/components/passwordmgr/LoginHelper.sys.mjs b/toolkit/components/passwordmgr/LoginHelper.sys.mjs
@@ -1571,6 +1571,33 @@ export const LoginHelper = {
Services.prefs.lockPref(prefName);
},
+ async trySetOSAuthEnabled(win, checked, messageText, captionText) {
+ // Calling OSKeyStore.ensureLoggedIn() instead of LoginHelper.verifyOSAuth()
+ // since we want to authenticate user each time this setting is changed.
+
+ // Note on Glean collection: because OSKeyStore.ensureLoggedIn() is not wrapped in
+ // verifyOSAuth(), it will be documenting "success" for unsupported platforms
+ // and won't record "fail_error", only "fail_user_canceled"
+ let isAuthorized = (
+ await lazy.OSKeyStore.ensureLoggedIn(messageText, captionText, win, false)
+ ).authenticated;
+
+ Glean.pwmgr.promptShownOsReauth.record({
+ trigger: "toggle_pref_os_auth",
+ result: isAuthorized ? "success" : "fail_user_canceled",
+ });
+
+ if (!isAuthorized) {
+ return;
+ }
+
+ // If osReauthCheckbox is checked enable osauth.
+ LoginHelper.setOSAuthEnabled(checked);
+ Glean.pwmgr.requireOsReauthToggle.record({
+ toggle_state: checked,
+ });
+ },
+
async verifyUserOSAuth(
prefName,
promptMessage,