tor-browser

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

commit dc24b8290977f4bc8186f6f4c21522e421287c02
parent 9b1c48431ea80e6b0098ddfc111b210ee8c00e31
Author: Benjamin VanderSloot <bvandersloot@mozilla.com>
Date:   Fri,  3 Oct 2025 14:04:25 +0000

Bug 1989281 - Elevate the permission dialog for wallet custom schemes on Desktop - r=fluent-reviewers,emz,bolsson

Manual testing is easiest if you add `mailto` or `zoomus` to
`WALLET_SCHEMES` to find an easy example of a link with a registered
protocol handler on your system.

Differential Revision: https://phabricator.services.mozilla.com/D265275

Diffstat:
Mbrowser/app/profile/firefox.js | 3+++
Mtoolkit/components/nimbus/FeatureManifest.yaml | 13+++++++++++++
Mtoolkit/locales/en-US/toolkit/global/handlerDialog.ftl | 16++++++++++++++++
Mtoolkit/mozapps/handling/content/handler.css | 1+
Mtoolkit/mozapps/handling/content/permissionDialog.js | 51+++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/mozapps/handling/content/permissionDialog.xhtml | 7+++++++
Muriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_permission.js | 119+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------
7 files changed, 201 insertions(+), 9 deletions(-)

diff --git a/browser/app/profile/firefox.js b/browser/app/profile/firefox.js @@ -1242,6 +1242,9 @@ pref("privacy.globalprivacycontrol.functionality.enabled", true); // Enable GPC in private browsing mode pref("privacy.globalprivacycontrol.pbmode.enabled", true); +// What custom schemes to treat as accessing digital wallets, comma separated +pref("privacy.wallet_schemes", "openid4vp,mdoc,mdoc-openid4vp,haip,eudi-wallet,eudi-openid4vp,openid-credential-offer"); + pref("network.proxy.share_proxy_settings", false); // use the same proxy settings for all protocols // simple gestures support diff --git a/toolkit/components/nimbus/FeatureManifest.yaml b/toolkit/components/nimbus/FeatureManifest.yaml @@ -5623,3 +5623,16 @@ image: setPref: branch: user pref: "image.mem.decode_bytes_at_a_time" + +walletCustomSchemes: + description: Defines which custom schemes are treated as digital identity requests + owner: bvandersloot@mozilla.com + hasExposure: false + variables: + enabledSchemes: + type: string + setPref: + branch: user + pref: privacy.wallet_schemes + description: >- + Comma separated lists of schemes diff --git a/toolkit/locales/en-US/toolkit/global/handlerDialog.ftl b/toolkit/locales/en-US/toolkit/global/handlerDialog.ftl @@ -39,6 +39,22 @@ permission-dialog-description-system-app = permission-dialog-description-system-noapp = Open the { $scheme } link? +# Header on a permission prompt, asking the user if they should allow the website to continue. +wallet-custom-scheme-warning-heading = + .heading = Allow this site to open your digital wallet? + +# Warning given to the user that the current page is trying to open their digital wallet app +wallet-custom-scheme-warning-host-app = Opening a <strong>{ $scheme }</strong> link allows <strong>{ $host }</strong> to request your real identity with <strong>{ $appName }</strong>. Only continue if you trust this site. + +# Warning given to the user that the current page is trying to open their digital wallet app +wallet-custom-scheme-warning-app = Opening a <strong>{ $scheme }</strong> link allows this site to request your real identity with <strong>{ $appName }</strong>. Only continue if you trust this site. + +# Warning given to the user that the current page is trying to open their digital wallet app +wallet-custom-scheme-warning-host = Opening a <strong>{ $scheme }</strong> link allows <strong>{ $host }</strong> to request your real identity from your digital wallet. Only continue if you trust this site. + +# Warning given to the user that the current page is trying to open their digital wallet app +wallet-custom-scheme-warning = Opening a <strong>{ $scheme }</strong> link allows this site to request your real identity from your digital wallet. Only continue if you trust this site. + ## Please keep the emphasis around the hostname and scheme (ie the ## `<strong>` HTML tags). Please also keep the hostname as close to the start ## of the sentence as your language's grammar allows. diff --git a/toolkit/mozapps/handling/content/handler.css b/toolkit/mozapps/handling/content/handler.css @@ -28,6 +28,7 @@ description { #description, #description-box, +#warning-bar, #rememberContainer, #chooser { margin: 0 4px 16px; diff --git a/toolkit/mozapps/handling/content/permissionDialog.js b/toolkit/mozapps/handling/content/permissionDialog.js @@ -5,6 +5,20 @@ const { EnableDelayHelper } = ChromeUtils.importESModule( "resource://gre/modules/PromptUtils.sys.mjs" ); +const { XPCOMUtils } = ChromeUtils.importESModule( + "resource://gre/modules/XPCOMUtils.sys.mjs" +); + +// These schemes are for digital identitity documents. +// We show a warning card for them in the permission dialog, +// so the user is made aware the site may be requesting a real +// identity. +const lazy = {}; +XPCOMUtils.defineLazyPreferenceGetter( + lazy, + "walletSchemes", + "privacy.wallet_schemes" +); let dialog = { /** @@ -145,6 +159,26 @@ let dialog = { }, /** + * Determines the l10n ID to use for the dangerous scheme warning, + * depending on the triggering principal and the preferred application + * handler. + */ + get walletWarningL10nId() { + if (this.shouldShowPrincipal() && this.userReadablePrincipal) { + if (this._preferredHandlerName) { + return "wallet-custom-scheme-warning-host-app"; + } + return "wallet-custom-scheme-warning-host"; + } + + if (this._preferredHandlerName) { + return "wallet-custom-scheme-warning-app"; + } + + return "wallet-custom-scheme-warning"; + }, + + /** * Computes text to show in the prompt that is a user-understandable * version of what is asking to open the external protocol. * It's usually the prePath of the site that wants to navigate to @@ -214,6 +248,23 @@ let dialog = { scheme, }); } + + let walletSchemeList = lazy.walletSchemes.split(","); + if (walletSchemeList.includes(scheme)) { + let warning = document.getElementById("warning-bar"); + document.l10n.setAttributes( + warning, + "wallet-custom-scheme-warning-heading" + ); + warning.messageL10nId = this.walletWarningL10nId; + warning.messageL10nArgs = { + host, + scheme, + appName: this._preferredHandlerName, + }; + warning.hidden = false; + description.hidden = true; + } }, onAccept() { diff --git a/toolkit/mozapps/handling/content/permissionDialog.xhtml b/toolkit/mozapps/handling/content/permissionDialog.xhtml @@ -32,8 +32,15 @@ type="application/javascript" /> + <script + type="module" + src="chrome://global/content/elements/moz-message-bar.mjs" + ></script> + <vbox id="description-box"> <description id="description"></description> + <html:moz-message-bar type="warning" id="warning-bar" hidden="true"> + </html:moz-message-bar> <label id="change-app" hidden="true" diff --git a/uriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_permission.js b/uriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_permission.js @@ -32,6 +32,7 @@ const PROTOCOL_HANDLER_OPEN_PERM_KEY = "open-protocol-handler"; const PERMISSION_KEY_DELIMITER = "^"; const TEST_PROTOS = ["foo", "bar"]; +const WALLET_PROTO = "moz-test-wallet"; let testDir = getChromeDir(getResolvedURI(gTestPath)); @@ -76,7 +77,9 @@ function getSystemProtocol() { * Creates dummy web protocol handlers used for testing. */ function initTestHandlers() { - TEST_PROTOS.forEach(scheme => { + const allProtos = structuredClone(TEST_PROTOS); + allProtos.push(WALLET_PROTO); + allProtos.forEach(scheme => { let webHandler = Cc[ "@mozilla.org/uriloader/web-handler-app;1" ].createInstance(Ci.nsIWebHandlerApp); @@ -223,6 +226,7 @@ async function testOpenProto( actionConfirm, actionChangeApp, checkContents, + hasWalletWarning = false, } = permDialogOptions; if (actionChangeApp) { @@ -230,15 +234,35 @@ async function testOpenProto( } let descriptionEl = dialogEl.querySelector("#description"); - ok( - descriptionEl && BrowserTestUtils.isVisible(descriptionEl), - "Has a visible description element." - ); + let warningEl = dialogEl.querySelector("#warning-bar"); + if (hasWalletWarning) { + ok( + descriptionEl && !BrowserTestUtils.isVisible(descriptionEl), + "Has an invisible description element." + ); + ok( + warningEl && BrowserTestUtils.isVisible(warningEl), + "Has a visible warning element." + ); + ok( + !warningEl.innerHTML.toLowerCase().includes(NULL_PRINCIPAL_SCHEME), + "Warning does not include NullPrincipal scheme." + ); + } else { + ok( + descriptionEl && BrowserTestUtils.isVisible(descriptionEl), + "Has a visible description element." + ); - ok( - !descriptionEl.innerHTML.toLowerCase().includes(NULL_PRINCIPAL_SCHEME), - "Description does not include NullPrincipal scheme." - ); + ok( + !descriptionEl.innerHTML.toLowerCase().includes(NULL_PRINCIPAL_SCHEME), + "Description does not include NullPrincipal scheme." + ); + ok( + warningEl && !BrowserTestUtils.isVisible(warningEl), + "Has an invisible warning element." + ); + } await testCheckbox(dialogEl, dialogType, { hasCheckbox, @@ -466,6 +490,9 @@ registerCleanupFunction(function () { add_setup(async function () { initTestHandlers(); + await SpecialPowers.pushPrefEnv({ + set: [["privacy.wallet_schemes", WALLET_PROTO]], + }); }); /** @@ -1378,3 +1405,77 @@ add_task(async function test_unloaded_iframe() { }); }); }); + +/** + * Tests that we show a warning UI element for a wallet scheme + */ +add_task(async function test_prompt_warning_for_wallet_scheme() { + // Test that we show the warning in the simple case. + await BrowserTestUtils.withNewTab(ORIGIN1, async browser => { + await testOpenProto(browser, WALLET_PROTO, { + permDialogOptions: { + hasCheckbox: true, + hasChangeApp: false, + chooserIsNext: true, + actionConfirm: true, + hasWalletWarning: true, + }, + chooserDialogOptions: { hasCheckbox: true, actionConfirm: true }, + }); + }); + + // Test that we show the warning with a null principal + await BrowserTestUtils.withNewTab(ORIGIN1, async browser => { + await testOpenProto(browser, WALLET_PROTO, { + triggerLoad: () => { + let uri = `${WALLET_PROTO}://test`; + ContentTask.spawn(browser, { uri }, args => { + let frame = content.document.createElement("iframe"); + frame.src = `data:text/html,<script>location.href="${args.uri}"</script>`; + content.document.body.appendChild(frame); + }); + }, + permDialogOptions: { + hasCheckbox: false, + chooserIsNext: true, + hasChangeApp: false, + actionConfirm: true, + hasWalletWarning: true, + }, + chooserDialogOptions: { + hasCheckbox: true, + actionConfirm: false, // Cancel dialog + }, + }); + }); + + // Test that we show the warning with a system principal + await BrowserTestUtils.withNewTab(ORIGIN1, async browser => { + await testOpenProto(browser, WALLET_PROTO, { + permDialogOptions: { + hasCheckbox: false, + hasChangeApp: false, + chooserIsNext: true, + actionChangeApp: false, + hasWalletWarning: true, + }, + triggerLoad: useTriggeringPrincipal( + Services.scriptSecurityManager.getSystemPrincipal() + ), + }); + }); + + // Test that we don't show the warning for another scheme. + await BrowserTestUtils.withNewTab(ORIGIN1, async browser => { + await testOpenProto(browser, TEST_PROTOS[0], { + permDialogOptions: { + hasCheckbox: true, + hasChangeApp: false, + chooserIsNext: true, + actionConfirm: true, + hasWalletWarning: false, + }, + chooserDialogOptions: { hasCheckbox: true, actionConfirm: true }, + }); + }); +});