Bug 1700976 - do not prompt for externally-opened web- or extension-handled 'external' protocols, r=zombie

Differential Revision: https://phabricator.services.mozilla.com/D115478
This commit is contained in:
Gijs Kruitbosch 2021-05-24 16:02:50 +00:00
parent fa827a1cbe
commit 2d2e51253d
3 changed files with 157 additions and 29 deletions

View File

@ -69,9 +69,6 @@ add_task(async function test_protocolHandler() {
await SpecialPowers.pushPrefEnv({
set: [
["extensions.allowPrivateBrowsingByDefault", false],
// Disabling the external protocol permission prompt. We don't need it
// for this test.
["security.external_protocol_requires_permission", false],
],
});
let extensionData = {
@ -101,6 +98,7 @@ add_task(async function test_protocolHandler() {
files: {
"foo.js": function() {
browser.test.sendMessage("test-query", location.search);
browser.tabs.getCurrent().then(tab => browser.test.sendMessage("test-tab", tab.id));
},
"foo.html": `<!DOCTYPE html>
<html>
@ -161,10 +159,62 @@ add_task(async function test_protocolHandler() {
let query = await extension.awaitMessage("test-query");
is(query, "?val=ext%2Bfoo%3Atest", "test query ok");
is(id, await extension.awaitMessage("test-tab"), "id should match opened tab");
extension.sendMessage("close", id);
await extension.awaitMessage("closed");
// Test that handling a URL from the commandline works.
chromeScript = SpecialPowers.loadChromeScript(() => {
let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService(
Ci.nsICommandLineHandler
);
let fakeCmdLine = {
length: 1,
_arg: "ext+foo:cmdline",
getArgument(index) {
if (index == 0) {
return this._arg;
}
throw Components.Exception("", Cr.NS_ERROR_INVALID_ARG);
},
findFlag() {
return -1;
},
handleFlagWithParam() {
if (this._argCount) {
this._argCount = 0;
return this._arg;
}
return "";
},
state: 2,
STATE_INITIAL_LAUNCH: 0,
STATE_REMOTE_AUTO: 1,
STATE_REMOTE_EXPLICIT: 2,
preventDefault: false,
resolveURI() {
return Services.io.newURI(this._arg);
},
QueryInterface: ChromeUtils.generateQI(["nsICommandLine"]),
};
cmdLineHandler.handle(fakeCmdLine);
});
query = await extension.awaitMessage("test-query");
is(query, "?val=ext%2Bfoo%3Acmdline", "cmdline query ok");
id = await extension.awaitMessage("test-tab");
extension.sendMessage("close", id);
await extension.awaitMessage("closed");
chromeScript.destroy();
// Test the protocol in a private window, watch for the
// console error.
consoleMonitor.start([{ message: /NS_ERROR_FILE_NOT_FOUND/ }]);

View File

@ -263,7 +263,15 @@ class nsContentDispatchChooser {
// Force showing the dialog for links passed from outside the application.
// This avoids infinite loops, see bug 1678255, bug 1667468, etc.
if (aTriggeredExternally && gPrefs.promptForExternal) {
if (
aTriggeredExternally &&
gPrefs.promptForExternal &&
// ... unless we intend to open the link with a website or extension:
!(
aHandler.preferredAction == Ci.nsIHandlerInfo.useHelperApp &&
aHandler.preferredApplicationHandler instanceof Ci.nsIWebHandlerApp
)
) {
aHandler.alwaysAskBeforeHandling = true;
}

View File

@ -11,42 +11,45 @@ let gHandlerService = Cc["@mozilla.org/uriloader/handler-service;1"].getService(
* Creates dummy protocol handler
*/
function initTestHandlers() {
let handlerInfo = HandlerServiceTestUtils.getBlankHandlerInfo("yoink");
let handlerInfoThatAsks = HandlerServiceTestUtils.getBlankHandlerInfo(
"should-ask"
);
let appHandler = Cc[
"@mozilla.org/uriloader/local-handler-app;1"
].createInstance(Ci.nsILocalHandlerApp);
// This is a dir and not executable, but that's enough for here.
appHandler.executable = Services.dirsvc.get("XCurProcD", Ci.nsIFile);
handlerInfo.possibleApplicationHandlers.appendElement(appHandler);
handlerInfo.preferredApplicationHandler = appHandler;
handlerInfo.preferredAction = handlerInfo.useHelperApp;
handlerInfo.alwaysAskBeforeHandling = false;
gHandlerService.store(handlerInfo);
handlerInfoThatAsks.possibleApplicationHandlers.appendElement(appHandler);
handlerInfoThatAsks.preferredApplicationHandler = appHandler;
handlerInfoThatAsks.preferredAction = handlerInfoThatAsks.useHelperApp;
handlerInfoThatAsks.alwaysAskBeforeHandling = false;
gHandlerService.store(handlerInfoThatAsks);
let webHandlerInfo = HandlerServiceTestUtils.getBlankHandlerInfo(
"web+somesite"
);
let webHandler = Cc[
"@mozilla.org/uriloader/web-handler-app;1"
].createInstance(Ci.nsIWebHandlerApp);
webHandler.name = "Somesite";
webHandler.uriTemplate = "https://example.com/handle_url?u=%s";
webHandlerInfo.possibleApplicationHandlers.appendElement(webHandler);
webHandlerInfo.preferredApplicationHandler = webHandler;
webHandlerInfo.preferredAction = webHandlerInfo.useHelperApp;
webHandlerInfo.alwaysAskBeforeHandling = false;
gHandlerService.store(webHandlerInfo);
registerCleanupFunction(() => {
gHandlerService.remove(handlerInfo);
gHandlerService.remove(webHandlerInfo);
gHandlerService.remove(handlerInfoThatAsks);
});
}
/**
* Check that if we get a direct request from another app / the OS to open a
* link, we always prompt, even if we think we know what the correct answer
* is. This is to avoid infinite loops in such situations where the OS and
* Firefox have conflicting ideas about the default handler, or where our
* checks with the OS don't work (Linux and/or Snap, at time of this comment).
*/
add_task(async function test_external_asks_anyway() {
await SpecialPowers.pushPrefEnv({
set: [["network.protocol-handler.prompt-from-external", true]],
});
initTestHandlers();
let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService(
Ci.nsICommandLineHandler
);
let fakeCmdLine = {
function makeCmdLineHelper(url) {
return {
length: 1,
_arg: "yoink:yoink",
_arg: url,
getArgument(aIndex) {
if (aIndex == 0) {
@ -81,10 +84,31 @@ add_task(async function test_external_asks_anyway() {
},
QueryInterface: ChromeUtils.generateQI(["nsICommandLine"]),
};
}
add_task(async function setup() {
await SpecialPowers.pushPrefEnv({
set: [["network.protocol-handler.prompt-from-external", true]],
});
initTestHandlers();
});
/**
* Check that if we get a direct request from another app / the OS to open a
* link, we always prompt, even if we think we know what the correct answer
* is. This is to avoid infinite loops in such situations where the OS and
* Firefox have conflicting ideas about the default handler, or where our
* checks with the OS don't work (Linux and/or Snap, at time of this comment).
*/
add_task(async function test_external_asks_anyway() {
let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService(
Ci.nsICommandLineHandler
);
let chooserDialogOpenPromise = waitForProtocolAppChooserDialog(
gBrowser,
true
);
let fakeCmdLine = makeCmdLineHelper("should-ask:dummy");
cmdLineHandler.handle(fakeCmdLine);
let dialog = await chooserDialogOpenPromise;
ok(dialog, "Should have prompted.");
@ -99,3 +123,49 @@ add_task(async function test_external_asks_anyway() {
// We will have opened a tab; close it.
BrowserTestUtils.removeTab(gBrowser.selectedTab);
});
/**
* Like the previous test, but avoid asking for web and extension handlers,
* as we can open those ourselves without looping.
*/
add_task(async function test_web_app_doesnt_ask() {
// Listen for a dialog open and fail the test if it does:
let dialogOpenListener = () => ok(false, "Shouldn't have opened a dialog!");
document.documentElement.addEventListener("dialogopen", dialogOpenListener);
registerCleanupFunction(() =>
document.documentElement.removeEventListener(
"dialogopen",
dialogOpenListener
)
);
// Set up a promise for a tab to open with the right URL:
const kURL = "web+somesite:dummy";
const kLoadedURL =
"https://example.com/handle_url?u=" + encodeURIComponent(kURL);
let tabPromise = BrowserTestUtils.waitForNewTab(gBrowser, kLoadedURL);
// Load the URL:
let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService(
Ci.nsICommandLineHandler
);
let fakeCmdLine = makeCmdLineHelper(kURL);
cmdLineHandler.handle(fakeCmdLine);
// Check that the tab loaded. If instead the dialog opened, the dialogopen handler
// will fail the test.
let tab = await tabPromise;
is(
tab.linkedBrowser.currentURI.spec,
kLoadedURL,
"Should have opened the right URL."
);
BrowserTestUtils.removeTab(tab);
// We do this both here and in cleanup so it's easy to add tasks to this test,
// and so we clean up correctly if the test aborts before we get here.
document.documentElement.removeEventListener(
"dialogopen",
dialogOpenListener
);
});