summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html')
-rw-r--r--toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html580
1 files changed, 580 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html b/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html
new file mode 100644
index 0000000000..41db82eff7
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_protocolHandlers.html
@@ -0,0 +1,580 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Test for protocol handlers</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
+ <script type="text/javascript" src="head.js"></script>
+ <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
+</head>
+<body>
+
+<script type="text/javascript">
+"use strict";
+
+/* eslint-disable mozilla/balanced-listeners */
+
+function protocolChromeScript() {
+ const PROTOCOL_HANDLER_OPEN_PERM_KEY = "open-protocol-handler";
+ const PERMISSION_KEY_DELIMITER = "^";
+
+ /* eslint-env mozilla/chrome-script */
+ addMessageListener("setup", ({ protocol, principalOrigins }) => {
+ let data = {};
+ const protoSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo(protocol);
+ data.preferredAction = protoInfo.preferredAction == protoInfo.useHelperApp;
+
+ let handlers = protoInfo.possibleApplicationHandlers;
+ data.handlers = handlers.length;
+
+ let handler = handlers.queryElementAt(0, Ci.nsIHandlerApp);
+ data.isWebHandler = handler instanceof Ci.nsIWebHandlerApp;
+ data.uriTemplate = handler.uriTemplate;
+
+ // ext+ protocols should be set as default when there is only one
+ data.preferredApplicationHandler =
+ protoInfo.preferredApplicationHandler == handler;
+ data.alwaysAskBeforeHandling = protoInfo.alwaysAskBeforeHandling;
+ const handlerSvc = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+ ].getService(Ci.nsIHandlerService);
+ handlerSvc.store(protoInfo);
+
+ for (let origin of principalOrigins) {
+ let principal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(origin),
+ {}
+ );
+ let pbPrincipal = Services.scriptSecurityManager.createContentPrincipal(
+ Services.io.newURI(origin),
+ {
+ privateBrowsingId: 1,
+ }
+ );
+ let permKey =
+ PROTOCOL_HANDLER_OPEN_PERM_KEY + PERMISSION_KEY_DELIMITER + protocol;
+ Services.perms.addFromPrincipal(
+ principal,
+ permKey,
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_NEVER
+ );
+ Services.perms.addFromPrincipal(
+ pbPrincipal,
+ permKey,
+ Services.perms.ALLOW_ACTION,
+ Services.perms.EXPIRE_NEVER
+ );
+ }
+
+ sendAsyncMessage("handlerData", data);
+ });
+ addMessageListener("setPreferredAction", data => {
+ let { protocol, template } = data;
+ const protoSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo(protocol);
+
+ for (let handler of protoInfo.possibleApplicationHandlers.enumerate()) {
+ if (handler.uriTemplate.startsWith(template)) {
+ protoInfo.preferredApplicationHandler = handler;
+ protoInfo.preferredAction = protoInfo.useHelperApp;
+ protoInfo.alwaysAskBeforeHandling = false;
+ }
+ }
+ const handlerSvc = Cc[
+ "@mozilla.org/uriloader/handler-service;1"
+ ].getService(Ci.nsIHandlerService);
+ handlerSvc.store(protoInfo);
+ sendAsyncMessage("set");
+ });
+}
+
+add_task(async function test_protocolHandler() {
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "ext+foo",
+ name: "a foo protocol handler",
+ uriTemplate: "foo.html?val=%s",
+ },
+ ],
+ },
+
+ background() {
+ browser.test.onMessage.addListener(async (msg, arg) => {
+ if (msg == "open") {
+ let tab = await browser.tabs.create({ url: arg });
+ browser.test.sendMessage("opened", tab.id);
+ } else if (msg == "close") {
+ await browser.tabs.remove(arg);
+ browser.test.sendMessage("closed");
+ }
+ });
+ browser.test.sendMessage("test-url", browser.runtime.getURL("foo.html"));
+ },
+
+ 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>
+ <head>
+ <meta charset="utf-8">
+ <script src="foo.js"><\/script>
+ </head>
+ </html>`,
+ },
+ };
+
+ let pb_extension = ExtensionTestUtils.loadExtension({
+ background() {
+ browser.test.onMessage.addListener(async (msg, arg) => {
+ if (msg == "open") {
+ let win = await browser.windows.create({ url: arg, incognito: true });
+ browser.test.sendMessage("opened", {
+ windowId: win.id,
+ tabId: win.tabs[0].id,
+ });
+ } else if (msg == "nav") {
+ await browser.tabs.update(arg.tabId, { url: arg.url });
+ browser.test.sendMessage("navigated");
+ } else if (msg == "close") {
+ await browser.windows.remove(arg);
+ browser.test.sendMessage("closed");
+ }
+ });
+ },
+ incognitoOverride: "spanning",
+ });
+ await pb_extension.startup();
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ let handlerUrl = await extension.awaitMessage("test-url");
+
+ // Ensure that the protocol handler is configured, and set it as default to
+ // bypass the dialog.
+ let chromeScript = SpecialPowers.loadChromeScript(protocolChromeScript);
+
+ let msg = chromeScript.promiseOneMessage("handlerData");
+ chromeScript.sendAsyncMessage("setup", {
+ protocol: "ext+foo",
+ principalOrigins: [
+ `moz-extension://${extension.uuid}/`,
+ `moz-extension://${pb_extension.uuid}/`,
+ ],
+ });
+ let data = await msg;
+ ok(
+ data.preferredAction,
+ "using a helper application is the preferred action"
+ );
+ ok(data.preferredApplicationHandler, "handler was set as default handler");
+ is(data.handlers, 1, "one handler is set");
+ ok(!data.alwaysAskBeforeHandling, "will not show dialog");
+ ok(data.isWebHandler, "the handler is a web handler");
+ is(data.uriTemplate, `${handlerUrl}?val=%s`, "correct url template");
+ chromeScript.destroy();
+
+ extension.sendMessage("open", "ext+foo:test");
+ let id = await extension.awaitMessage("opened");
+
+ 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(() => {
+ /* eslint-env mozilla/chrome-script */
+ let cmdLineHandler = Cc["@mozilla.org/browser/final-clh;1"].getService(
+ Ci.nsICommandLineHandler
+ );
+ let fakeCmdLine = Cu.createCommandLine(
+ ["-url", "ext+foo:cmdline"],
+ null,
+ Ci.nsICommandLine.STATE_REMOTE_EXPLICIT
+ );
+ 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/ }]);
+
+ // Expect the chooser window to be open, close it.
+ chromeScript = SpecialPowers.loadChromeScript(async () => {
+ /* eslint-env mozilla/chrome-script */
+ const CONTENT_HANDLING_URL =
+ "chrome://mozapps/content/handling/appChooser.xhtml";
+ const { BrowserTestUtils } = ChromeUtils.import(
+ "resource://testing-common/BrowserTestUtils.jsm"
+ );
+
+ let windowOpen = BrowserTestUtils.domWindowOpenedAndLoaded();
+
+ sendAsyncMessage("listenWindow");
+
+ let window = await windowOpen;
+ let gBrowser = window.gBrowser;
+ let tabDialogBox = gBrowser.getTabDialogBox(gBrowser.selectedBrowser);
+ let dialogStack = tabDialogBox.getTabDialogManager()._dialogStack;
+
+ let checkFn = dialogEvent =>
+ dialogEvent.detail.dialog?._openedURL == CONTENT_HANDLING_URL;
+
+ let eventPromise = BrowserTestUtils.waitForEvent(
+ dialogStack,
+ "dialogopen",
+ true,
+ checkFn
+ );
+
+ sendAsyncMessage("listenDialog");
+
+ let event = await eventPromise;
+
+ let { dialog } = event.detail;
+
+ let entry = dialog._frame.contentDocument.getElementById("items")
+ .firstChild;
+ sendAsyncMessage("handling", {
+ name: entry.getAttribute("name"),
+ disabled: entry.disabled,
+ });
+
+ dialog.close();
+ });
+
+ // Wait for the chrome script to attach window listener
+ await chromeScript.promiseOneMessage("listenWindow");
+
+ let listenDialog = chromeScript.promiseOneMessage("listenDialog");
+ let windowOpen = pb_extension.awaitMessage("opened");
+
+ pb_extension.sendMessage("open", "ext+foo:test");
+
+ // Wait for chrome script to attach dialog listener
+ await listenDialog;
+ let { tabId, windowId } = await windowOpen;
+
+ let testData = chromeScript.promiseOneMessage("handling");
+ let navPromise = pb_extension.awaitMessage("navigated");
+ pb_extension.sendMessage("nav", { url: "ext+foo:test", tabId });
+ await navPromise;
+ await consoleMonitor.finished();
+ let entry = await testData;
+
+ is(entry.name, "a foo protocol handler", "entry is correct");
+ ok(entry.disabled, "handler is disabled");
+
+ let promiseClosed = pb_extension.awaitMessage("closed");
+ pb_extension.sendMessage("close", windowId);
+ await promiseClosed;
+ await pb_extension.unload();
+
+ // Shutdown the addon, then ensure the protocol was removed.
+ await extension.unload();
+ chromeScript = SpecialPowers.loadChromeScript(() => {
+ /* eslint-env mozilla/chrome-script */
+ addMessageListener("setup", () => {
+ const protoSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo("ext+foo");
+ sendAsyncMessage(
+ "preferredApplicationHandler",
+ !protoInfo.preferredApplicationHandler
+ );
+ let handlers = protoInfo.possibleApplicationHandlers;
+
+ sendAsyncMessage("handlerData", {
+ preferredApplicationHandler: !protoInfo.preferredApplicationHandler,
+ handlers: handlers.length,
+ });
+ });
+ });
+
+ msg = chromeScript.promiseOneMessage("handlerData");
+ chromeScript.sendAsyncMessage("setup");
+ data = await msg;
+ ok(data.preferredApplicationHandler, "no preferred handler is set");
+ is(data.handlers, 0, "no handler is set");
+ chromeScript.destroy();
+});
+
+add_task(async function test_protocolHandler_two() {
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "ext+foo",
+ name: "a foo protocol handler",
+ uriTemplate: "foo.html?val=%s",
+ },
+ {
+ protocol: "ext+foo",
+ name: "another foo protocol handler",
+ uriTemplate: "foo2.html?val=%s",
+ },
+ ],
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+
+ // Ensure that the protocol handler is configured, and set it as default,
+ // but because there are two handlers, the dialog is not bypassed. We
+ // don't test the actual dialog ui, it's been here forever and works based
+ // on the alwaysAskBeforeHandling value.
+ let chromeScript = SpecialPowers.loadChromeScript(protocolChromeScript);
+
+ let msg = chromeScript.promiseOneMessage("handlerData");
+ chromeScript.sendAsyncMessage("setup", {
+ protocol: "ext+foo",
+ principalOrigins: [],
+ });
+ let data = await msg;
+ ok(
+ data.preferredAction,
+ "using a helper application is the preferred action"
+ );
+ ok(data.preferredApplicationHandler, "preferred handler is set");
+ is(data.handlers, 2, "two handlers are set");
+ ok(data.alwaysAskBeforeHandling, "will show dialog");
+ ok(data.isWebHandler, "the handler is a web handler");
+ chromeScript.destroy();
+ await extension.unload();
+});
+
+add_task(async function test_protocolHandler_https_target() {
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "ext+foo",
+ name: "http target",
+ uriTemplate: "https://example.com/foo.html?val=%s",
+ },
+ ],
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ ok(true, "https uriTemplate target works");
+ await extension.unload();
+});
+
+add_task(async function test_protocolHandler_http_target() {
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "ext+foo",
+ name: "http target",
+ uriTemplate: "http://example.com/foo.html?val=%s",
+ },
+ ],
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ ok(true, "http uriTemplate target works");
+ await extension.unload();
+});
+
+add_task(async function test_protocolHandler_restricted_protocol() {
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "http",
+ name: "take over the http protocol",
+ uriTemplate: "http.html?val=%s",
+ },
+ ],
+ },
+ };
+
+ consoleMonitor.start([
+ { message: /processing protocol_handlers\.0\.protocol/ },
+ ]);
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await Assert.rejects(
+ extension.startup(),
+ /startup failed/,
+ "unable to register restricted handler protocol"
+ );
+
+ await consoleMonitor.finished();
+});
+
+add_task(async function test_protocolHandler_restricted_uriTemplate() {
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "ext+foo",
+ name: "take over the http protocol",
+ uriTemplate: "ftp://example.com/file.txt",
+ },
+ ],
+ },
+ };
+
+ consoleMonitor.start([
+ { message: /processing protocol_handlers\.0\.uriTemplate/ },
+ ]);
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await Assert.rejects(
+ extension.startup(),
+ /startup failed/,
+ "unable to register restricted handler uriTemplate"
+ );
+
+ await consoleMonitor.finished();
+});
+
+add_task(async function test_protocolHandler_duplicate() {
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "ext+foo",
+ name: "foo protocol",
+ uriTemplate: "foo.html?val=%s",
+ },
+ {
+ protocol: "ext+foo",
+ name: "foo protocol",
+ uriTemplate: "foo.html?val=%s",
+ },
+ ],
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+
+ // Get the count of handlers installed.
+ let chromeScript = SpecialPowers.loadChromeScript(() => {
+ /* eslint-env mozilla/chrome-script */
+ addMessageListener("setup", () => {
+ const protoSvc = Cc[
+ "@mozilla.org/uriloader/external-protocol-service;1"
+ ].getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo("ext+foo");
+ let handlers = protoInfo.possibleApplicationHandlers;
+ sendAsyncMessage("handlerData", handlers.length);
+ });
+ });
+
+ let msg = chromeScript.promiseOneMessage("handlerData");
+ chromeScript.sendAsyncMessage("setup");
+ let data = await msg;
+ is(data, 1, "cannot re-register the same handler config");
+ chromeScript.destroy();
+ await extension.unload();
+});
+
+// Test that a protocol handler will work if ftp is enabled
+add_task(async function test_ftp_protocolHandler() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ // Disabling the external protocol permission prompt. We don't need it
+ // for this test.
+ ["security.external_protocol_requires_permission", false],
+ ],
+ });
+ let extensionData = {
+ manifest: {
+ protocol_handlers: [
+ {
+ protocol: "ftp",
+ name: "an ftp protocol handler",
+ uriTemplate: "ftp.html?val=%s",
+ },
+ ],
+ },
+
+ async background() {
+ let url = "ftp://example.com/file.txt";
+ browser.test.onMessage.addListener(async () => {
+ await browser.tabs.create({ url });
+ });
+ },
+
+ files: {
+ "ftp.js": function() {
+ browser.test.sendMessage("test-query", location.search);
+ },
+ "ftp.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="ftp.js"><\/script>
+ </head>
+ </html>`,
+ },
+ };
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ await extension.startup();
+ const handlerUrl = `moz-extension://${extension.uuid}/ftp.html`;
+
+ let chromeScript = SpecialPowers.loadChromeScript(protocolChromeScript);
+
+ // Set the preferredAction to this extension as ftp will default to system. If
+ // we didn't bypass the dialog for this test, the user would get asked in this case.
+ let msg = chromeScript.promiseOneMessage("set");
+ chromeScript.sendAsyncMessage("setPreferredAction", {
+ protocol: "ftp",
+ template: handlerUrl,
+ });
+ await msg;
+
+ msg = chromeScript.promiseOneMessage("handlerData");
+ chromeScript.sendAsyncMessage("setup", { protocol: "ftp", principalOrigins: [] });
+ let data = await msg;
+ ok(
+ data.preferredAction,
+ "using a helper application is the preferred action"
+ );
+ ok(data.preferredApplicationHandler, "handler was set as default handler");
+ is(data.handlers, 1, "one handler is set");
+ ok(!data.alwaysAskBeforeHandling, "will not show dialog");
+ ok(data.isWebHandler, "the handler is a web handler");
+ is(data.uriTemplate, `${handlerUrl}?val=%s`, "correct url template");
+
+ chromeScript.destroy();
+
+ extension.sendMessage("run");
+ let query = await extension.awaitMessage("test-query");
+ is(query, "?val=ftp%3A%2F%2Fexample.com%2Ffile.txt", "test query ok");
+ await extension.unload();
+});
+</script>
+
+</body>
+</html>