tor-browser

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

commit f22505510c37019f14bdeb676b63989e64787469
parent 2f46db151480d329d3512befea4df75ccf6538c7
Author: Sarah Clements <sclements@mozilla.com>
Date:   Wed, 22 Oct 2025 17:21:21 +0000

Bug 1972326 - Permission should show when opening external helper app r=Gijs

* Improve _hasProtocolHandlerPermission to handle external + non-standard protocol + hasHelper scenario
* Add test case and move protocol helper into head.js

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

Diffstat:
Mtoolkit/mozapps/handling/ContentDispatchChooser.sys.mjs | 27+++++++++++++++++++++------
Muriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_external.js | 37+++++++++++++++++++++++++++++++++++++
Muriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_permission.js | 12------------
Muriloader/exthandler/tests/mochitest/head.js | 13+++++++++++++
4 files changed, 71 insertions(+), 18 deletions(-)

diff --git a/toolkit/mozapps/handling/ContentDispatchChooser.sys.mjs b/toolkit/mozapps/handling/ContentDispatchChooser.sys.mjs @@ -45,10 +45,14 @@ export class nsContentDispatchChooser { aBrowsingContext, aTriggeredExternally = false ) { + const isStandardProtocol = E10SUtils.STANDARD_SAFE_PROTOCOLS.includes( + aURI.scheme + ); let callerHasPermission = this._hasProtocolHandlerPermission( - aHandler.type, + aHandler, aPrincipal, - aTriggeredExternally + aTriggeredExternally, + isStandardProtocol ); // Force showing the dialog for links passed from outside the application. @@ -276,11 +280,18 @@ export class nsContentDispatchChooser { * @param {nsIPrincipal} aPrincipal - Principal to test for permission. * @returns {boolean} - true if permission is set, false otherwise. */ - _hasProtocolHandlerPermission(scheme, aPrincipal, aTriggeredExternally) { + _hasProtocolHandlerPermission( + aHandler, + aPrincipal, + aTriggeredExternally, + isStandardProtocol + ) { // If a handler is set to open externally by default we skip the dialog. + const { type, hasDefaultHandler, preferredApplicationHandler } = aHandler; + if ( Services.prefs.getBoolPref( - "network.protocol-handler.external." + scheme, + "network.protocol-handler.external." + type, false ) ) { @@ -289,12 +300,16 @@ export class nsContentDispatchChooser { if ( !aPrincipal || - (aPrincipal.isSystemPrincipal && !aTriggeredExternally) + (aPrincipal.isSystemPrincipal && !aTriggeredExternally) || + (!isStandardProtocol && + hasDefaultHandler && + !preferredApplicationHandler && + aTriggeredExternally) ) { return false; } - let key = this._getSkipProtoDialogPermissionKey(scheme); + let key = this._getSkipProtoDialogPermissionKey(type); return ( Services.perms.testPermissionFromPrincipal(aPrincipal, key) === Services.perms.ALLOW_ACTION diff --git a/uriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_external.js b/uriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_external.js @@ -197,3 +197,40 @@ add_task(async function external_https_redirect_doesnt_ask() { ); gHandlerService.wrappedJSObject.mockProtocolHandler(); }); + +/** + * Tests that if a URI scheme is launched externally, has a non-standard protocol + * and a default exists the permission dialog is shown. + */ +add_task(async function test_external_non_standard_protocol() { + let scheme = getSystemProtocol(); + if (!scheme) { + return; + } + let uri = `${scheme}://test`; + let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService( + Ci.nsICommandLineHandler + ); + let permissionDialogOpenPromise = waitForProtocolPermissionDialog( + gBrowser, + true + ); + let fakeCmdLine = Cu.createCommandLine( + ["-url", uri], + null, + Ci.nsICommandLine.STATE_REMOTE_EXPLICIT + ); + cmdLineHandler.handle(fakeCmdLine); + let dialog = await permissionDialogOpenPromise; + ok(dialog, "Should have prompted."); + + let dialogClosedPromise = waitForProtocolPermissionDialog( + gBrowser.selectedBrowser, + false + ); + let dialogEl = dialog._frame.contentDocument.querySelector("dialog"); + dialogEl.cancelDialog(); + await dialogClosedPromise; + // We will have opened a tab; close it. + BrowserTestUtils.removeTab(gBrowser.selectedTab); +}); diff --git a/uriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_permission.js b/uriloader/exthandler/tests/mochitest/browser_protocol_ask_dialog_permission.js @@ -61,18 +61,6 @@ function getSkipProtoDialogPermissionKey(aProtocolScheme) { ); } -function getSystemProtocol() { - // TODO add a scheme for Windows 10 or greater once support is added (see bug 1764599). - if (AppConstants.platform == "macosx") { - return "itunes"; - } - - info( - "Skipping this test since there isn't a suitable default protocol on this platform" - ); - return null; -} - /** * Creates dummy web protocol handlers used for testing. */ diff --git a/uriloader/exthandler/tests/mochitest/head.js b/uriloader/exthandler/tests/mochitest/head.js @@ -570,3 +570,16 @@ function runExtProtocolSandboxTest(options) { } ); } + +function getSystemProtocol() { + if (AppConstants.platform == "macosx") { + return "itunes"; + } else if (AppConstants.platform == "win") { + return "mms"; + } + + info( + "Skipping this test since there isn't a suitable default protocol on this platform" + ); + return null; +}