Bug 1270360 Implement runtime.sendNativeMessage() r?kmag
MozReview-Commit-ID: 93FaGaYto5w
--- a/toolkit/components/extensions/NativeMessaging.jsm
+++ b/toolkit/components/extensions/NativeMessaging.jsm
@@ -351,9 +351,29 @@ this.NativeApp = class extends EventEmit
return () => {
this.off("message", listener);
};
}).api(),
};
return Cu.cloneInto(api, this.context.cloneScope, {cloneFunctions: true});
}
+
+ sendMessage(msg) {
+ let responsePromise = new Promise((resolve, reject) => {
+ this.on("message", (what, msg) => { resolve(msg); });
+ this.on("disconnect", (what, err) => { reject(err); });
+ });
+
+ let result = this.startupPromise.then(() => {
+ this.send(msg);
+ return responsePromise;
+ });
+
+ result.then(() => {
+ this._cleanup();
+ }, () => {
+ this._cleanup();
+ });
+
+ return result;
+ }
};
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -51,24 +51,25 @@ extensions.registerSchemaAPI("runtime",
if (!GlobalManager.extensionMap.has(recipient.extensionId)) {
return context.wrapPromise(Promise.reject({message: "Invalid extension ID"}),
responseCallback);
}
return context.messenger.sendMessage(Services.cpmm, message, recipient, responseCallback);
},
connectNative(application) {
- if (!extension.hasPermission("nativeMessaging")) {
- throw new context.cloneScope.Error("Permission denied because 'nativeMessaging' permission is missing.");
- }
-
let app = new NativeApp(extension, context, application);
return app.portAPI();
},
+ sendNativeMessage(application, message) {
+ let app = new NativeApp(extension, context, application);
+ return app.sendMessage(message);
+ },
+
get lastError() {
return context.lastError;
},
getManifest() {
return Cu.cloneInto(extension.manifest, context.cloneScope);
},
--- a/toolkit/components/extensions/schemas/runtime.json
+++ b/toolkit/components/extensions/schemas/runtime.json
@@ -273,16 +273,17 @@
"$ref": "Port",
"description": "Port through which messages can be sent and received. The port's $(ref:runtime.Port onDisconnect) event is fired if the extension/app does not exist. "
}
},
{
"name": "connectNative",
"type": "function",
"description": "Connects to a native application in the host machine.",
+ "permissions": ["nativeMessaging"],
"parameters": [
{
"type": "string",
"name": "application",
"description": "The name of the registered application to connect to."
}
],
"returns": {
@@ -317,32 +318,30 @@
"description": "The JSON response object sent by the handler of the message. If an error occurs while connecting to the extension, the callback will be called with no arguments and $(ref:runtime.lastError) will be set to the error message."
}
]
}
]
},
{
"name": "sendNativeMessage",
- "unsupported": true,
"type": "function",
"description": "Send a single message to a native application.",
+ "permissions": ["nativeMessaging"],
+ "async": "responseCallback",
"parameters": [
{
"name": "application",
"description": "The name of the native messaging host.",
"type": "string"
},
{
"name": "message",
"description": "The message that will be passed to the native messaging host.",
- "type": "object",
- "additionalProperties": {
- "type": "any"
- }
+ "type": "any"
},
{
"type": "function",
"name": "responseCallback",
"optional": true,
"parameters": [
{
"name": "response",
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_native_messaging.html
@@ -230,16 +230,54 @@ add_task(function* test_happy_path() {
let procCount = yield getSubprocessCount();
is(procCount, 1, "subprocess is still running");
let exitPromise = waitForSubprocessExit();
yield extension.unload();
yield exitPromise;
});
+// Test sendNativeMessage()
+add_task(function* test_sendNativeMessage() {
+ function background() {
+ let MSG = {test: "hello world"};
+
+ // Check error handling
+ browser.runtime.sendNativeMessage("nonexistent", MSG).then(() => {
+ browser.test.fail("sendNativeMessage() to a nonexistent app should have failed");
+ }, err => {
+ browser.test.succeed("sendNativeMessage() to a nonexistent app failed");
+ }).then(() => {
+ // Check regular message exchange
+ return browser.runtime.sendNativeMessage("echo", MSG);
+ }).then(reply => {
+ let expected = JSON.stringify(MSG);
+ let received = JSON.stringify(reply);
+ browser.test.assertEq(expected, received, "Received echoed native message");
+ browser.test.sendMessage("finished");
+ });
+ }
+
+ let extension = ExtensionTestUtils.loadExtension({
+ background: `(${background})()`,
+ manifest: {
+ permissions: ["nativeMessaging"],
+ },
+ }, ID);
+
+ yield extension.startup();
+ yield extension.awaitMessage("finished");
+
+ // With sendNativeMessage(), the subprocess should be disconnected
+ // after exchanging a single message.
+ yield waitForSubprocessExit();
+
+ yield extension.unload();
+});
+
// Test calling Port.disconnect()
add_task(function* test_disconnect() {
function background() {
let port = browser.runtime.connectNative("echo");
port.onMessage.addListener(msg => {
browser.test.sendMessage("message", msg);
});
browser.test.onMessage.addListener((what, payload) => {
@@ -373,39 +411,31 @@ add_task(function* test_read_limit() {
clearPref();
});
// Test that an extension without the nativeMessaging permission cannot
// use native messaging.
add_task(function* test_ext_permission() {
function background() {
- try {
- browser.runtime.connectNative("test");
- browser.test.sendMessage("result", null);
- } catch (ex) {
- browser.test.sendMessage("result", ex.message);
- }
+ browser.test.assertFalse("connectNative" in chrome.runtime, "chrome.runtime.connectNative does not exist without nativeMessaging permission");
+ browser.test.assertFalse("connectNative" in browser.runtime, "browser.runtime.connectNative does not exist without nativeMessaging permission");
+ browser.test.assertFalse("sendNativeMessage" in chrome.runtime, "chrome.runtime.sendNativeMessage does not exist without nativeMessaging permission");
+ browser.test.assertFalse("sendNativeMessage" in browser.runtime, "browser.runtime.sendNativeMessage does not exist without nativeMessaging permission");
+ browser.test.sendMessage("finished");
}
let extension = ExtensionTestUtils.loadExtension({
background: `(${background})()`,
manifest: {},
});
yield extension.startup();
-
- let errmsg = yield extension.awaitMessage("result");
- isnot(errmsg, null, "connectNative() failed without nativeMessaging permission");
- ok(/Permission denied/.test(errmsg), "error message for missing extension permission is reasonable");
-
+ yield extension.awaitMessage("finished");
yield extension.unload();
-
- let procCount = yield getSubprocessCount();
- is(procCount, 0, "No child process was started");
});
// Test that an extension that is not listed in allowed_extensions for
// a native application cannot use that application.
add_task(function* test_app_permission() {
function background() {
let port = browser.runtime.connectNative("echo");
port.onDisconnect.addListener(() => {