--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -41,16 +41,18 @@ XPCOMUtils.defineLazyModuleGetter(this,
XPCOMUtils.defineLazyModuleGetter(this, "Log",
"resource://gre/modules/Log.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchGlobs",
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
"resource://gre/modules/MatchPattern.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "MessageChannel",
"resource://gre/modules/MessageChannel.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
+ "resource://gre/modules/NativeMessaging.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
"resource://gre/modules/NetUtil.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "OS",
"resource://gre/modules/osfile.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
"resource://gre/modules/PrivateBrowsingUtils.jsm");
XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
"resource://gre/modules/Preferences.jsm");
@@ -106,16 +108,17 @@ const COMMENT_REGEXP = new RegExp(String
" (?:[^"\\\n] | \\.)* "
)*?
)
//.*
`.replace(/\s+/g, ""), "gm");
var GlobalManager;
+var ParentAPIManager;
// This object loads the ext-*.js scripts that define the extension API.
var Management = new class extends SchemaAPIManager {
constructor() {
super("main");
this.initialized = null;
}
@@ -179,16 +182,31 @@ let ProxyMessenger = {
MessageChannel.addListener(messageManagers, "Extension:Connect", this);
MessageChannel.addListener(messageManagers, "Extension:Message", this);
MessageChannel.addListener(messageManagers, "Extension:Port:Disconnect", this);
MessageChannel.addListener(messageManagers, "Extension:Port:PostMessage", this);
},
receiveMessage({target, messageName, channelId, sender, recipient, data, responseType}) {
+ if (recipient.toNativeApp) {
+ let {childId, toNativeApp} = recipient;
+ if (messageName == "Extension:Message") {
+ let context = ParentAPIManager.getContextById(childId);
+ return new NativeApp(context, toNativeApp).sendMessage(data);
+ }
+ if (messageName == "Extension:Connect") {
+ let context = ParentAPIManager.getContextById(childId);
+ NativeApp.onConnectNative(context, target.messageManager, data.portId, sender, toNativeApp);
+ return true;
+ }
+ // "Extension:Port:Disconnect" and "Extension:Port:PostMessage" for
+ // native messages are handled by NativeApp.
+ return;
+ }
let extension = GlobalManager.extensionMap.get(sender.extensionId);
let receiverMM = this._getMessageManagerForRecipient(recipient);
if (!extension || !receiverMM) {
return Promise.reject({
result: MessageChannel.RESULT_NO_HANDLER,
message: "No matching message handler for the given recipient.",
});
}
@@ -224,18 +242,16 @@ let ProxyMessenger = {
// runtime.sendMessage / runtime.connect
if (extensionId) {
// TODO(robwu): map the extensionId to the addon parent process's message
// manager when they run in a separate process.
let pipmm = Services.ppmm.getChildAt(0);
return pipmm;
}
- // Note: No special handling for sendNativeMessage / connectNative because
- // native messaging runs in the chrome process, so it never needs a proxy.
return null;
},
};
class BrowserDocshellFollower {
/**
* Follows the <browser> belonging to the `xulBrowser`'s current docshell.
*
@@ -396,17 +412,17 @@ function findPathInObject(obj, path, pri
}
obj = obj[elt];
}
return obj;
}
-var ParentAPIManager = {
+ParentAPIManager = {
proxyContexts: new Map(),
init() {
Services.obs.addObserver(this, "message-manager-close", false);
Services.mm.addMessageListener("API:CreateProxyContext", this);
Services.mm.addMessageListener("API:CloseProxyContext", this, true);
Services.mm.addMessageListener("API:Call", this);
@@ -493,21 +509,17 @@ var ParentAPIManager = {
if (!context) {
return;
}
context.unload();
this.proxyContexts.delete(childId);
},
call(data, target) {
- let context = this.proxyContexts.get(data.childId);
- if (!context) {
- Cu.reportError("WebExtension context not found!");
- return;
- }
+ let context = this.getContextById(data.childId);
if (context.currentMessageManager !== target.messageManager) {
Cu.reportError("WebExtension warning: Message manager unexpectedly changed");
}
function callback(...cbArgs) {
let lastError = context.lastError;
context.currentMessageManager.sendAsyncMessage("API:CallResult", {
@@ -531,21 +543,17 @@ var ParentAPIManager = {
childId: data.childId,
callId: data.callId,
lastError: msg,
});
}
},
addListener(data, target) {
- let context = this.proxyContexts.get(data.childId);
- if (!context) {
- Cu.reportError("WebExtension context not found!");
- return;
- }
+ let context = this.getContextById(data.childId);
if (context.currentMessageManager !== target.messageManager) {
Cu.reportError("WebExtension warning: Message manager unexpectedly changed");
}
function listener(...listenerArgs) {
context.currentMessageManager.sendAsyncMessage("API:RunListener", {
childId: data.childId,
path: data.path,
@@ -555,23 +563,30 @@ var ParentAPIManager = {
context.listenerProxies.set(data.path, listener);
let args = Cu.cloneInto(data.args, context.sandbox);
findPathInObject(context.apiObj, data.path).addListener(listener, ...args);
},
removeListener(data) {
- let context = this.proxyContexts.get(data.childId);
- if (!context) {
- Cu.reportError("WebExtension context not found!");
- }
+ let context = this.getContextById(data.childId);
let listener = context.listenerProxies.get(data.path);
findPathInObject(context.apiObj, data.path).removeListener(listener);
},
+
+ getContextById(childId) {
+ let context = this.proxyContexts.get(childId);
+ if (!context) {
+ let error = new Error("WebExtension context not found!");
+ Cu.reportError(error);
+ throw error;
+ }
+ return context;
+ },
};
ParentAPIManager.init();
// All moz-extension URIs use a machine-specific UUID rather than the
// extension's own ID in the host component. This makes it more
// difficult for web pages to detect whether a user has a given add-on
// installed (by trying to load a moz-extension URI referring to a
--- a/toolkit/components/extensions/ExtensionChild.jsm
+++ b/toolkit/components/extensions/ExtensionChild.jsm
@@ -106,19 +106,17 @@ class WannabeChildAPIManager extends Chi
// Synchronously unload the ProxyContext because we synchronously create it.
this.context.callOnClose({close: proxyContext.unload.bind(proxyContext)});
}
getFallbackImplementation(namespace, name) {
// This is gross and should be removed ASAP.
let shouldSynchronouslyUseParentAPI = false;
// Incompatible APIs are listed here.
- if (namespace == "runtime" && name == "connectNative" || // Returns a custom Port.
- namespace == "runtime" && name == "sendNativeMessage" || // Fix together with connectNative.
- namespace == "webNavigation" || // ChildAPIManager is oblivious to filters.
+ if (namespace == "webNavigation" || // ChildAPIManager is oblivious to filters.
namespace == "webRequest") { // Incompatible by design (synchronous).
shouldSynchronouslyUseParentAPI = true;
}
if (shouldSynchronouslyUseParentAPI) {
let proxyContext = ParentAPIManager.proxyContexts.get(this.id);
let apiObj = findPathInObject(proxyContext.apiObj, namespace, false);
if (apiObj && name in apiObj) {
return new LocalAPIImplementation(apiObj, name, this.context);
--- a/toolkit/components/extensions/ExtensionUtils.jsm
+++ b/toolkit/components/extensions/ExtensionUtils.jsm
@@ -1175,16 +1175,17 @@ function Port(context, senderMM, receive
this.senderMM = senderMM;
this.receiverMMs = receiverMMs;
this.name = name;
this.id = id;
this.sender = sender;
this.recipient = recipient;
this.disconnected = false;
this.disconnectListeners = new Set();
+ this.unregisterMessageFuncs = new Set();
// Common options for onMessage and onDisconnect.
this.handlerBase = {
messageFilterStrict: {portId: id},
filterMessage: (sender, recipient) => {
if (!sender.contextId) {
Cu.reportError("Missing sender.contextId in message to Port");
return false;
@@ -1205,69 +1206,103 @@ Port.prototype = {
let portObj = Cu.createObjectIn(this.context.cloneScope);
let publicAPI = {
name: this.name,
disconnect: () => {
this.disconnect();
},
postMessage: json => {
- if (this.disconnected) {
- throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port");
- }
-
- this._sendMessage("Extension:Port:PostMessage", json);
+ this.postMessage(json);
},
onDisconnect: new EventManager(this.context, "Port.onDisconnect", fire => {
- let listener = () => {
- if (this.context.active && !this.disconnected) {
- fire.withoutClone(portObj);
- }
- };
-
- this.disconnectListeners.add(listener);
- return () => {
- this.disconnectListeners.delete(listener);
- };
+ return this.registerOnDisconnect(() => fire.withoutClone(portObj));
}).api(),
onMessage: new EventManager(this.context, "Port.onMessage", fire => {
- let handler = Object.assign({
- receiveMessage: ({data}) => {
- if (this.context.active && !this.disconnected) {
- fire(data);
- }
- },
- }, this.handlerBase);
-
- MessageChannel.addListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
- return () => {
- MessageChannel.removeListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
- };
+ return this.registerOnMessage(msg => {
+ msg = Cu.cloneInto(msg, this.context.cloneScope);
+ fire.withoutClone(msg, portObj);
+ });
}).api(),
};
if (this.sender) {
publicAPI.sender = this.sender;
}
injectAPI(publicAPI, portObj);
return portObj;
},
+ postMessage(json) {
+ if (this.disconnected) {
+ throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port");
+ }
+
+ this._sendMessage("Extension:Port:PostMessage", json);
+ },
+
+ /**
+ * Register a callback that is called when the port is disconnected by the
+ * *other* end. The callback is automatically unregistered when the port or
+ * context is closed.
+ *
+ * @param {function} callback Called when the other end disconnects the port.
+ * @returns {function} Function to unregister the listener.
+ */
+ registerOnDisconnect(callback) {
+ let listener = () => {
+ if (this.context.active && !this.disconnected) {
+ callback();
+ }
+ };
+ this.disconnectListeners.add(listener);
+ return () => {
+ this.disconnectListeners.delete(listener);
+ };
+ },
+
+ /**
+ * Register a callback that is called when a message is received. The callback
+ * is automatically unregistered when the port or context is closed.
+ *
+ * @param {function} callback Called when a message is received.
+ * @returns {function} Function to unregister the listener.
+ */
+ registerOnMessage(callback) {
+ let handler = Object.assign({
+ receiveMessage: ({data}) => {
+ if (this.context.active && !this.disconnected) {
+ callback(data);
+ }
+ },
+ }, this.handlerBase);
+
+ let unregister = () => {
+ this.unregisterMessageFuncs.delete(unregister);
+ MessageChannel.removeListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
+ };
+ MessageChannel.addListener(this.receiverMMs, "Extension:Port:PostMessage", handler);
+ return unregister;
+ },
+
_sendMessage(message, data) {
let options = {
recipient: Object.assign({}, this.recipient, {portId: this.id}),
responseType: MessageChannel.RESPONSE_NONE,
};
return this.context.sendMessage(this.senderMM, message, data, options);
},
handleDisconnection() {
MessageChannel.removeListener(this.receiverMMs, "Extension:Port:Disconnect", this.disconnectHandler);
+ for (let unregister of this.unregisterMessageFuncs) {
+ unregister();
+ }
this.context.forgetOnClose(this);
this.disconnected = true;
},
disconnectByOtherEnd() {
if (this.disconnected) {
return;
}
@@ -1383,22 +1418,27 @@ Messenger.prototype = {
MessageChannel.addListener(this.messageManagers, "Extension:Message", listener);
return () => {
MessageChannel.removeListener(this.messageManagers, "Extension:Message", listener);
};
}).api();
},
- connect(messageManager, name, recipient) {
+ connectGetRawPort(messageManager, name, recipient) {
let portId = `${gNextPortId++}-${Services.appinfo.uniqueProcessID}`;
let port = new Port(this.context, messageManager, this.messageManagers, name, portId, null, recipient);
let msg = {name, portId};
this._sendMessage(messageManager, "Extension:Connect", msg, recipient)
.catch(e => port.disconnectByOtherEnd());
+ return port;
+ },
+
+ connect(messageManager, name, recipient) {
+ let port = this.connectGetRawPort(messageManager, name, recipient);
return port.api();
},
onConnect(name) {
return new SingletonEventManager(this.context, name, callback => {
let listener = {
messageFilterPermissive: this.filter,
@@ -2052,15 +2092,16 @@ this.ExtensionUtils = {
BaseContext,
DefaultWeakMap,
EventEmitter,
EventManager,
IconDetails,
LocalAPIImplementation,
LocaleData,
Messenger,
+ Port,
PlatformInfo,
SchemaAPIInterface,
SingletonEventManager,
SpreadArgs,
ChildAPIManager,
SchemaAPIManager,
};
--- a/toolkit/components/extensions/NativeMessaging.jsm
+++ b/toolkit/components/extensions/NativeMessaging.jsm
@@ -1,15 +1,16 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
this.EXPORTED_SYMBOLS = ["HostManifestManager", "NativeApp"];
+/* globals NativeApp */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
const {EventEmitter} = Cu.import("resource://devtools/shared/event-emitter.js", {});
XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
@@ -157,43 +158,38 @@ this.HostManifestManager = {
if (!VALID_APPLICATION.test(application)) {
throw new Error(`Invalid application "${application}"`);
}
return this.init().then(() => this._lookup(application, context));
},
};
this.NativeApp = class extends EventEmitter {
- constructor(extension, context, application) {
+ constructor(context, application) {
super();
this.context = context;
this.name = application;
// We want a close() notification when the window is destroyed.
this.context.callOnClose(this);
- this.encoder = new TextEncoder();
this.proc = null;
this.readPromise = null;
this.sendQueue = [];
this.writePromise = null;
this.sentDisconnect = false;
- // Grab these once at startup
- XPCOMUtils.defineLazyPreferenceGetter(this, "maxRead", PREF_MAX_READ, MAX_READ);
- XPCOMUtils.defineLazyPreferenceGetter(this, "maxWrite", PREF_MAX_WRITE, MAX_WRITE);
-
this.startupPromise = HostManifestManager.lookupApplication(application, context)
.then(hostInfo => {
if (!hostInfo) {
throw new Error(`No such native application ${application}`);
}
- if (!hostInfo.manifest.allowed_extensions.includes(extension.id)) {
+ if (!hostInfo.manifest.allowed_extensions.includes(context.extension.id)) {
throw new Error(`This extension does not have permission to use native application ${application}`);
}
let command = hostInfo.manifest.path;
if (AppConstants.platform == "win") {
// OS.Path.join() ignores anything before the last absolute path
// it sees, so if command is already absolute, it remains unchanged
// here. If it is relative, we get the proper absolute path here.
@@ -215,32 +211,69 @@ this.NativeApp = class extends EventEmit
this._startStderrRead();
}).catch(err => {
this.startupPromise = null;
Cu.reportError(err instanceof Error ? err : err.message);
this._cleanup(err);
});
}
+ /**
+ * Open a connection to a native messaging host.
+ *
+ * @param {BaseContext} context The context associated with the port.
+ * @param {nsIMessageSender} messageManager The message manager used to send
+ * and receive messages from the port's creator.
+ * @param {string} portId A unique internal ID that identifies the port.
+ * @param {object} sender The object describing the creator of the connection
+ * request.
+ * @param {string} application The name of the native messaging host.
+ */
+ static onConnectNative(context, messageManager, portId, sender, application) {
+ messageManager.QueryInterface(Ci.nsIMessageSender);
+ let app = new NativeApp(context, application);
+ let port = new ExtensionUtils.Port(context, messageManager, [messageManager], "", portId, sender, sender);
+ app.once("disconnect", () => port.disconnect());
+ /* eslint-disable mozilla/balanced-listeners */
+ app.on("message", (what, msg) => port.postMessage(msg));
+ /* eslint-enable mozilla/balanced-listeners */
+ port.registerOnMessage(msg => app.send(msg));
+ port.registerOnDisconnect(msg => app.close());
+ }
+
+ /**
+ * @param {BaseContext} context The scope from where `message` originates.
+ * @param {*} message A message from the extension, meant for a native app.
+ * @returns {ArrayBuffer} An ArrayBuffer that can be sent to the native app.
+ */
+ static encodeMessage(context, message) {
+ message = context.jsonStringify(message);
+ let buffer = new TextEncoder().encode(message).buffer;
+ if (buffer.byteLength > NativeApp.maxWrite) {
+ throw new context.cloneScope.Error("Write too big");
+ }
+ return buffer;
+ }
+
// A port is definitely "alive" if this.proc is non-null. But we have
// to provide a live port object immediately when connecting so we also
// need to consider a port alive if proc is null but the startupPromise
// is still pending.
get _isDisconnected() {
return (!this.proc && !this.startupPromise);
}
_startRead() {
if (this.readPromise) {
throw new Error("Entered _startRead() while readPromise is non-null");
}
this.readPromise = this.proc.stdout.readUint32()
.then(len => {
- if (len > this.maxRead) {
- throw new Error(`Native application tried to send a message of ${len} bytes, which exceeds the limit of ${this.maxRead} bytes.`);
+ if (len > NativeApp.maxRead) {
+ throw new Error(`Native application tried to send a message of ${len} bytes, which exceeds the limit of ${NativeApp.maxRead} bytes.`);
}
return this.proc.stdout.readJSON(len);
}).then(msg => {
this.emit("message", msg);
this.readPromise = null;
this._startRead();
}).catch(err => {
if (err.errorCode != Subprocess.ERROR_END_OF_FILE) {
@@ -299,26 +332,23 @@ this.NativeApp = class extends EventEmit
}
});
}
send(msg) {
if (this._isDisconnected) {
throw new this.context.cloneScope.Error("Attempt to postMessage on disconnected port");
}
+ if (Cu.getClassName(msg, true) != "ArrayBuffer") {
+ throw new this.context.cloneScope.Error("The message is not an ArrayBuffer");
+ }
- let json;
- try {
- json = this.context.jsonStringify(msg);
- } catch (err) {
- throw new this.context.cloneScope.Error(err.message);
- }
- let buffer = this.encoder.encode(json).buffer;
+ let buffer = msg;
- if (buffer.byteLength > this.maxWrite) {
+ if (buffer.byteLength > NativeApp.maxWrite) {
throw new this.context.cloneScope.Error("Write too big");
}
this.sendQueue.push(buffer);
if (!this.startupPromise && !this.writePromise) {
this._startWrite();
}
}
@@ -371,62 +401,20 @@ this.NativeApp = class extends EventEmit
}
}
// Called from Context when the extension is shut down.
close() {
this._cleanup();
}
- portAPI() {
- let port = {
- name: this.name,
-
- disconnect: () => {
- if (this._isDisconnected) {
- throw new this.context.cloneScope.Error("Attempt to disconnect an already disconnected port");
- }
- this._cleanup();
- },
-
- postMessage: msg => {
- this.send(msg);
- },
-
- onDisconnect: new ExtensionUtils.SingletonEventManager(this.context, "native.onDisconnect", fire => {
- let listener = what => {
- this.context.runSafeWithoutClone(fire, port);
- };
- this.on("disconnect", listener);
- return () => {
- this.off("disconnect", listener);
- };
- }).api(),
-
- onMessage: new ExtensionUtils.SingletonEventManager(this.context, "native.onMessage", fire => {
- let listener = (what, msg) => {
- msg = Cu.cloneInto(msg, this.context.cloneScope);
- this.context.runSafeWithoutClone(fire, msg, port);
- };
- this.on("message", listener);
- return () => {
- this.off("message", listener);
- };
- }).api(),
- };
-
- port = Cu.cloneInto(port, this.context.cloneScope, {cloneFunctions: true});
-
- return port;
- }
-
sendMessage(msg) {
let responsePromise = new Promise((resolve, reject) => {
- this.on("message", (what, msg) => { resolve(msg); });
- this.on("disconnect", (what, err) => { reject(err); });
+ this.once("message", (what, msg) => { resolve(msg); });
+ this.once("disconnect", (what, err) => { reject(err); });
});
let result = this.startupPromise.then(() => {
this.send(msg);
return responsePromise;
});
result.then(() => {
@@ -437,8 +425,11 @@ this.NativeApp = class extends EventEmit
responsePromise.catch(() => {});
this._cleanup();
});
return result;
}
};
+
+XPCOMUtils.defineLazyPreferenceGetter(NativeApp, "maxRead", PREF_MAX_READ, MAX_READ);
+XPCOMUtils.defineLazyPreferenceGetter(NativeApp, "maxWrite", PREF_MAX_WRITE, MAX_WRITE);
--- a/toolkit/components/extensions/ext-c-runtime.js
+++ b/toolkit/components/extensions/ext-c-runtime.js
@@ -1,9 +1,11 @@
"use strict";
+XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
+ "resource://gre/modules/NativeMessaging.jsm");
function runtimeApiFactory(context) {
let {extension} = context;
return {
runtime: {
onConnect: context.messenger.onConnect("runtime.onConnect"),
@@ -50,16 +52,39 @@ function runtimeApiFactory(context) {
// TODO(robwu): Validate option keys and values when we support it.
extensionId = extensionId || extension.id;
let recipient = {extensionId};
return context.messenger.sendMessage(context.messageManager, message, recipient, responseCallback);
},
+ connectNative(application) {
+ let recipient = {
+ childId: context.childManager.id,
+ toNativeApp: application,
+ };
+ let rawPort = context.messenger.connectGetRawPort(context.messageManager, "", recipient);
+ let port = rawPort.api();
+ port.postMessage = message => {
+ message = NativeApp.encodeMessage(context, message);
+ rawPort.postMessage(message);
+ };
+ return port;
+ },
+
+ sendNativeMessage(application, message) {
+ let recipient = {
+ childId: context.childManager.id,
+ toNativeApp: application,
+ };
+ message = NativeApp.encodeMessage(context, message);
+ return context.messenger.sendMessage(context.messageManager, message, recipient);
+ },
+
get lastError() {
return context.lastError;
},
getManifest() {
return Cu.cloneInto(extension.manifest, context.cloneScope);
},
--- a/toolkit/components/extensions/ext-runtime.js
+++ b/toolkit/components/extensions/ext-runtime.js
@@ -12,19 +12,16 @@ XPCOMUtils.defineLazyModuleGetter(this,
"resource://gre/modules/ExtensionManagement.jsm");
var {
EventManager,
SingletonEventManager,
ignoreEvent,
} = ExtensionUtils;
-XPCOMUtils.defineLazyModuleGetter(this, "NativeApp",
- "resource://gre/modules/NativeMessaging.jsm");
-
extensions.registerSchemaAPI("runtime", "addon_parent", context => {
let {extension} = context;
return {
runtime: {
onStartup: new EventManager(context, "runtime.onStartup", fire => {
extension.onStartup = fire;
return () => {
extension.onStartup = null;
@@ -54,30 +51,17 @@ extensions.registerSchemaAPI("runtime",
} else {
// Otherwise, reload the current extension.
AddonManager.getAddonByID(extension.id, addon => {
addon.reload();
});
}
},
- connectNative(application) {
- 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() {
- // TODO(robwu): Figure out how to make sure that errors in the parent
- // process are propagated to the child process.
- // lastError should not be accessed from the parent.
return context.lastError;
},
getBrowserInfo: function() {
const {name, vendor, version, appBuildID} = Services.appinfo;
const info = {name, vendor, version, buildID: appBuildID};
return Promise.resolve(info);
},
--- a/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_native_messaging.js
@@ -216,18 +216,17 @@ add_task(function* test_sendNativeMessag
add_task(function* test_disconnect() {
function background() {
let port = browser.runtime.connectNative("echo");
port.onMessage.addListener((msg, msgPort) => {
browser.test.assertEq(port, msgPort, "onMessage handler should receive the port as the second argument");
browser.test.sendMessage("message", msg);
});
port.onDisconnect.addListener(msgPort => {
- browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the second argument");
- browser.test.sendMessage("disconnected");
+ browser.test.fail("onDisconnect should not be called for disconnect()");
});
browser.test.onMessage.addListener((what, payload) => {
if (what == "send") {
if (payload._json) {
let json = payload._json;
payload.toJSON = () => json;
delete payload._json;
}
@@ -264,27 +263,24 @@ add_task(function* test_disconnect() {
let procCount = yield getSubprocessCount();
equal(procCount, 1, "subprocess is running");
extension.sendMessage("disconnect");
response = yield extension.awaitMessage("disconnect-result");
equal(response.success, true, "disconnect succeeded");
- yield extension.awaitMessage("disconnected");
-
do_print("waiting for subprocess to exit");
yield waitForSubprocessExit();
procCount = yield getSubprocessCount();
equal(procCount, 0, "subprocess is no longer running");
extension.sendMessage("disconnect");
response = yield extension.awaitMessage("disconnect-result");
- equal(response.success, false, "second call to disconnect failed");
- ok(/already disconnected/.test(response.errmsg), "disconnect error message is reasonable");
+ equal(response.success, true, "second call to disconnect silently ignored");
yield extension.unload();
});
// Test the limit on message size for writing
add_task(function* test_write_limit() {
Services.prefs.setIntPref(PREF_MAX_WRITE, 10);
function clearPref() {
@@ -443,17 +439,18 @@ add_task(function* test_child_process()
let exitPromise = waitForSubprocessExit();
yield extension.unload();
yield exitPromise;
});
add_task(function* test_stderr() {
function background() {
let port = browser.runtime.connectNative("stderr");
- port.onDisconnect.addListener(() => {
+ port.onDisconnect.addListener(msgPort => {
+ browser.test.assertEq(port, msgPort, "onDisconnect handler should receive the port as the first argument");
browser.test.sendMessage("finished");
});
}
let {messages} = yield promiseConsoleOutput(function* () {
let extension = ExtensionTestUtils.loadExtension({
background,
manifest: {
--- a/toolkit/components/extensions/test/xpcshell/test_native_messaging.js
+++ b/toolkit/components/extensions/test/xpcshell/test_native_messaging.js
@@ -1,13 +1,14 @@
"use strict";
/* global OS, HostManifestManager, NativeApp */
Cu.import("resource://gre/modules/AppConstants.jsm");
Cu.import("resource://gre/modules/AsyncShutdown.jsm");
+Cu.import("resource://gre/modules/ExtensionUtils.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");
Cu.import("resource://gre/modules/Schemas.jsm");
Cu.import("resource://gre/modules/Services.jsm");
const {Subprocess, SubprocessImpl} = Cu.import("resource://gre/modules/Subprocess.jsm");
Cu.import("resource://gre/modules/NativeMessaging.jsm");
Cu.import("resource://gre/modules/osfile.jsm");
let registry = null;
@@ -251,30 +252,33 @@ while True:
} else {
yield OS.File.writeAtomic(scriptPath, `#!${PYTHON} -u\n${SCRIPT}`);
yield OS.File.setPermissions(scriptPath, {unixMode: 0o755});
manifest.path = scriptPath;
yield writeManifest(manifestPath, manifest);
}
let extension = {id: ID};
- let app = new NativeApp(extension, context, "wontdie");
+ let context = new ExtensionUtils.BaseContext("testEnv", extension);
+ let app = new NativeApp(context, "wontdie");
// send a message and wait for the reply to make sure the app is running
let MSG = "test";
let recvPromise = new Promise(resolve => {
let listener = (what, msg) => {
equal(msg, MSG, "Received test message");
app.off("message", listener);
resolve();
};
app.on("message", listener);
});
- app.send(MSG);
+ // This is equivalent to NativeApp.encodeMessage
+ let {buffer} = new TextEncoder().encode(JSON.stringify(MSG));
+ app.send(buffer);
yield recvPromise;
app._cleanup();
do_print("waiting for async shutdown");
Services.prefs.setBoolPref("toolkit.asyncshutdown.testing", true);
AsyncShutdown.profileBeforeChange._trigger();
Services.prefs.clearUserPref("toolkit.asyncshutdown.testing");