tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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:
Mbrowser/components/preferences/main.js | 105+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mbrowser/components/preferences/privacy.inc.xhtml | 4+++-
Mbrowser/components/preferences/privacy.js | 152+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++--
Mbrowser/locales/en-US/browser/preferences/preferences.ftl | 29++++++++++++++++++++++++++++-
Mbrowser/themes/shared/preferences/privacy.css | 4++++
Mtoolkit/components/passwordmgr/LoginHelper.sys.mjs | 27+++++++++++++++++++++++++++
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,