Bug 1271553 support programatic protocol handlers, r?kmag
MozReview-Commit-ID: A0ORZCnzSQF
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/ext-handlers.js
@@ -0,0 +1,59 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+var handlers = new WeakMap();
+
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("manifest_handlers", (type, directive, extension, manifest) => {
+ if (!extension.permissions.has("protocol_handler")) {
+ return;
+ }
+ for (let handlerConfig of manifest.handlers) {
+ handlerConfig.uriTemplate = extension.baseURI.resolve(handlerConfig.uriTemplate);
+
+ let handler = Cc["@mozilla.org/uriloader/web-handler-app;1"]
+ .createInstance(Ci.nsIWebHandlerApp);
+ handler.name = handlerConfig.name;
+ handler.uriTemplate = handlerConfig.uriTemplate;
+
+ let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ let protoInfo = eps.getProtocolHandlerInfo(handlerConfig.protocol);
+ protoInfo.possibleApplicationHandlers.appendElement(handler, false);
+
+ let hs = Cc["@mozilla.org/uriloader/handler-service;1"]
+ .getService(Ci.nsIHandlerService);
+ hs.store(protoInfo);
+ }
+ handlers.set(extension, manifest.handlers);
+});
+
+extensions.on("shutdown", (type, extension) => {
+ if (!handlers.has(extension)) {
+ return;
+ }
+ for (let handlerConfig of handlers.get(extension)) {
+ let eps = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ let protoInfo = eps.getProtocolHandlerInfo(handlerConfig.protocol);
+ let appHandlers = protoInfo.possibleApplicationHandlers;
+ for (let i = 0; i < appHandlers.length; i++) {
+ try {
+ let handler = appHandlers.queryElementAt(i, Ci.nsIWebHandlerApp);
+ if (handler && handler.uriTemplate == handlerConfig.uriTemplate) {
+ appHandlers.removeElementAt(i);
+ if (protoInfo.preferredApplicationHandler == handler) {
+ protoInfo.preferredApplicationHandler = null;
+ protoInfo.alwaysAskBeforeHandling = true;
+ }
+ break;
+ }
+ } catch (e) { continue; }
+ }
+ let hs = Cc["@mozilla.org/uriloader/handler-service;1"]
+ .getService(Ci.nsIHandlerService);
+ hs.store(protoInfo);
+ }
+ handlers.delete(extension);
+});
--- a/toolkit/components/extensions/extensions-toolkit.manifest
+++ b/toolkit/components/extensions/extensions-toolkit.manifest
@@ -1,16 +1,17 @@
# scripts
category webextension-scripts alarms chrome://extensions/content/ext-alarms.js
category webextension-scripts backgroundPage chrome://extensions/content/ext-backgroundPage.js
category webextension-scripts contextualIdentities chrome://extensions/content/ext-contextualIdentities.js
category webextension-scripts cookies chrome://extensions/content/ext-cookies.js
category webextension-scripts downloads chrome://extensions/content/ext-downloads.js
category webextension-scripts management chrome://extensions/content/ext-management.js
category webextension-scripts notifications chrome://extensions/content/ext-notifications.js
+category webextension-scripts handlers chrome://extensions/content/ext-handlers.js
category webextension-scripts i18n chrome://extensions/content/ext-i18n.js
category webextension-scripts idle chrome://extensions/content/ext-idle.js
category webextension-scripts webRequest chrome://extensions/content/ext-webRequest.js
category webextension-scripts webNavigation chrome://extensions/content/ext-webNavigation.js
category webextension-scripts runtime chrome://extensions/content/ext-runtime.js
category webextension-scripts extension chrome://extensions/content/ext-extension.js
category webextension-scripts storage chrome://extensions/content/ext-storage.js
category webextension-scripts topSites chrome://extensions/content/ext-topSites.js
@@ -43,16 +44,17 @@ category webextension-scripts-addon stor
# schemas
category webextension-schemas alarms chrome://extensions/content/schemas/alarms.json
category webextension-schemas contextualIdentities chrome://extensions/content/schemas/contextual_identities.json
category webextension-schemas cookies chrome://extensions/content/schemas/cookies.json
category webextension-schemas downloads chrome://extensions/content/schemas/downloads.json
category webextension-schemas events chrome://extensions/content/schemas/events.json
category webextension-schemas extension chrome://extensions/content/schemas/extension.json
category webextension-schemas extension_types chrome://extensions/content/schemas/extension_types.json
+category webextension-schemas handlers chrome://extensions/content/schemas/extension_handler.json
category webextension-schemas i18n chrome://extensions/content/schemas/i18n.json
#ifndef ANDROID
category webextension-schemas identity chrome://extensions/content/schemas/identity.json
#endif
category webextension-schemas idle chrome://extensions/content/schemas/idle.json
category webextension-schemas management chrome://extensions/content/schemas/management.json
category webextension-schemas native_host_manifest chrome://extensions/content/schemas/native_host_manifest.json
category webextension-schemas notifications chrome://extensions/content/schemas/notifications.json
--- a/toolkit/components/extensions/jar.mn
+++ b/toolkit/components/extensions/jar.mn
@@ -7,16 +7,17 @@ toolkit.jar:
content/extensions/ext-alarms.js
content/extensions/ext-backgroundPage.js
content/extensions/ext-browser-content.js
content/extensions/ext-contextualIdentities.js
content/extensions/ext-cookies.js
content/extensions/ext-downloads.js
content/extensions/ext-management.js
content/extensions/ext-notifications.js
+ content/extensions/ext-handlers.js
content/extensions/ext-i18n.js
content/extensions/ext-idle.js
content/extensions/ext-webRequest.js
content/extensions/ext-webNavigation.js
content/extensions/ext-runtime.js
content/extensions/ext-extension.js
content/extensions/ext-storage.js
content/extensions/ext-topSites.js
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/schemas/extension_handler.json
@@ -0,0 +1,37 @@
+// Copyright (c) 2012 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+[
+ {
+ "namespace": "manifest",
+ "types": [
+ {
+ "$extend": "Permission",
+ "choices": [{
+ "type": "string",
+ "enum": [
+ "protocol_handler"
+ ]
+ }]
+ },
+ {
+ "$extend": "WebExtensionManifest",
+ "properties": {
+ "handlers": {
+ "optional": true,
+ "type": "array",
+ "items": {
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "protocol": {"type": "string"},
+ "uriTemplate": {"type": "string"}
+ }
+ }
+ }
+ }
+ }
+ ]
+ }
+]
--- a/toolkit/components/extensions/schemas/jar.mn
+++ b/toolkit/components/extensions/schemas/jar.mn
@@ -7,16 +7,17 @@ toolkit.jar:
content/extensions/schemas/alarms.json
content/extensions/schemas/contextual_identities.json
content/extensions/schemas/cookies.json
content/extensions/schemas/downloads.json
content/extensions/schemas/events.json
content/extensions/schemas/experiments.json
content/extensions/schemas/extension.json
content/extensions/schemas/extension_types.json
+ content/extensions/schemas/extension_handler.json
content/extensions/schemas/i18n.json
#ifndef ANDROID
content/extensions/schemas/identity.json
#endif
content/extensions/schemas/idle.json
content/extensions/schemas/management.json
content/extensions/schemas/manifest.json
content/extensions/schemas/native_host_manifest.json
--- a/toolkit/components/extensions/test/mochitest/mochitest-common.ini
+++ b/toolkit/components/extensions/test/mochitest/mochitest-common.ini
@@ -59,16 +59,17 @@ skip-if = os == 'android' # Android does
skip-if = os == 'android' # Android does not support tabs API. Bug 1260250
[test_ext_contentscript_teardown.html]
skip-if = (os == 'android') # Android does not support tabs API. Bug 1260250
[test_ext_exclude_include_globs.html]
[test_ext_i18n_css.html]
[test_ext_generate.html]
[test_ext_notifications.html]
[test_ext_permission_xhr.html]
+[test_ext_rph.html]
[test_ext_runtime_connect.html]
skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975).
[test_ext_runtime_connect_twoway.html]
skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975).
[test_ext_runtime_connect2.html]
skip-if = os == 'android' # port.sender.tab is undefined on Android (bug 1258975).
[test_ext_runtime_disconnect.html]
[test_ext_runtime_id.html]
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/mochitest/test_ext_rph.html
@@ -0,0 +1,110 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+ <title>Test for content script</title>
+ <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+ <script type="text/javascript" src="/tests/SimpleTest/SpawnTask.js"></script>
+ <script type="text/javascript" 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 */
+
+add_task(function* test_rph() {
+ let extensionData = {
+ manifest: {
+ "permissions": [
+ "protocol_handler",
+ ],
+ "handlers": [
+ {
+ "protocol": "foo",
+ "name": "a foo protocol handler",
+ "uriTemplate": "foo.html?val=%s",
+ },
+ ],
+ },
+
+ background() {
+ browser.test.sendMessage("test-url", browser.runtime.getURL("foo.html"));
+ },
+
+ files: {
+ "foo.js": function() {
+ browser.test.sendMessage("test-query", location.search);
+ },
+ "foo.html": `<!DOCTYPE html>
+ <html>
+ <head>
+ <meta charset="utf-8">
+ <script src="foo.js"><\/script>
+ </head>
+ </html>`,
+ },
+ };
+
+ let extension = ExtensionTestUtils.loadExtension(extensionData);
+ yield extension.startup();
+ let handlerUrl = yield extension.awaitMessage("test-url");
+
+ // Ensure that the protocol handler is configured, and set it as default to
+ // bypass the dialog.
+ let chromeScript = SpecialPowers.loadChromeScript(() => {
+ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+ const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo("foo");
+ sendAsyncMessage("preferredAction", protoInfo.preferredAction == protoInfo.useHelperApp);
+ sendAsyncMessage("preferredApplicationHandler", !protoInfo.preferredApplicationHandler);
+
+ let handlers = protoInfo.possibleApplicationHandlers;
+ sendAsyncMessage("handlers", handlers.length);
+ let handler = handlers.queryElementAt(0, Ci.nsIHandlerApp);
+ sendAsyncMessage("isWebHandler", handler instanceof Ci.nsIWebHandlerApp);
+ sendAsyncMessage("uriTemplate", handler.uriTemplate);
+ protoInfo.preferredApplicationHandler = handler;
+ protoInfo.alwaysAskBeforeHandling = false;
+ const handlerSvc = Cc["@mozilla.org/uriloader/handler-service;1"]
+ .getService(Ci.nsIHandlerService);
+ handlerSvc.store(protoInfo);
+ });
+
+ ok(yield chromeScript.promiseOneMessage("preferredAction"), "using a helper application is the preferred action");
+ ok(yield chromeScript.promiseOneMessage("preferredApplicationHandler"), "no preferred handler is set");
+ is(yield chromeScript.promiseOneMessage("handlers"), 1, "one handler is set");
+ ok(yield chromeScript.promiseOneMessage("isWebHandler"), "the handler is a web handler");
+ is(yield chromeScript.promiseOneMessage("uriTemplate"), `${handlerUrl}?val=%s`, "correct url template");
+ chromeScript.destroy();
+
+ let win = window.open("foo:test");
+ let query = yield extension.awaitMessage("test-query");
+ is(query, "?val=foo%3Atest", "test query ok");
+ win.close();
+
+ // Shutdown the addon, then ensure the protocol was removed.
+ yield extension.unload();
+ chromeScript = SpecialPowers.loadChromeScript(() => {
+ const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+ const protoSvc = Cc["@mozilla.org/uriloader/external-protocol-service;1"]
+ .getService(Ci.nsIExternalProtocolService);
+ let protoInfo = protoSvc.getProtocolHandlerInfo("foo");
+ sendAsyncMessage("preferredApplicationHandler", !protoInfo.preferredApplicationHandler);
+ let handlers = protoInfo.possibleApplicationHandlers;
+ sendAsyncMessage("handlers", handlers.length);
+ });
+
+ ok(yield chromeScript.promiseOneMessage("preferredApplicationHandler"), "no preferred handler is set");
+ is(yield chromeScript.promiseOneMessage("handlers"), 0, "no handler is set");
+ chromeScript.destroy();
+});
+</script>
+
+</body>
+</html>