| Index: lib/messaging.js |
| =================================================================== |
| new file mode 100644 |
| --- /dev/null |
| +++ b/lib/messaging.js |
| @@ -0,0 +1,309 @@ |
| +/* |
| + * This file is part of Adblock Plus <https://adblockplus.org/>, |
| + * Copyright (C) 2006-2015 Eyeo GmbH |
|
Sebastian Noack
2016/03/16 10:44:13
It's 2016.
Wladimir Palant
2016/03/16 11:04:49
Right, this patch is older... Fixed.
|
| + * |
| + * Adblock Plus is free software: you can redistribute it and/or modify |
| + * it under the terms of the GNU General Public License version 3 as |
| + * published by the Free Software Foundation. |
| + * |
| + * Adblock Plus is distributed in the hope that it will be useful, |
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of |
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| + * GNU General Public License for more details. |
| + * |
| + * You should have received a copy of the GNU General Public License |
| + * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>. |
| + */ |
| + |
| +"use strict"; |
| + |
| +const MESSAGE_NAME = "AdblockPlus:Message"; |
| +const RESPONSE_NAME = "AdblockPlus:Response"; |
| + |
| +function sendMessage(messageManager, messageName, payload, callbackID) |
| +{ |
| + let request = {messageName, payload, callbackID}; |
| + if (messageManager instanceof Ci.nsIMessageSender) |
| + { |
| + messageManager.sendAsyncMessage(MESSAGE_NAME, request); |
| + return 1; |
| + } |
| + else if (messageManager instanceof Ci.nsIMessageBroadcaster) |
|
Sebastian Noack
2016/03/16 10:44:13
Nit: Redundant else
Wladimir Palant
2016/03/16 11:04:50
As you know, I like redundant else clauses :). Whi
|
| + { |
| + messageManager.broadcastAsyncMessage(MESSAGE_NAME, request); |
| + return messageManager.childCount; |
| + } |
| + else |
|
Sebastian Noack
2016/03/16 10:44:13
Nit: Redundant else
Wladimir Palant
2016/03/16 11:04:49
Same as above.
|
| + { |
| + Cu.reportError("Unexpected message manager, impossible to send message"); |
| + return 0; |
| + } |
| +} |
| + |
| +function sendSyncMessage(messageManager, messageName, payload) |
| +{ |
| + let request = {messageName, payload}; |
| + let responses = messageManager.sendRpcMessage(MESSAGE_NAME, request); |
| + for (let response of responses) |
| + if (typeof response != "undefined") |
| + return response; |
| + return undefined; |
| +} |
| + |
| +function flattenResponses(responses, messageName) |
| +{ |
| + let result = undefined; |
| + for (let response of responses) |
| + { |
| + if (typeof response == "undefined") |
| + continue; |
| + |
| + if (typeof result == "undefined") |
| + result = response; |
| + else |
| + Cu.reportError("Got multiple responses to message '" + messageName + "', only first response was accepted."); |
| + } |
| + return result; |
| +} |
| + |
| +function getSender(origin) |
|
Erik
2016/03/15 22:56:35
I'm not quire sure when this would be used, since
Wladimir Palant
2016/03/16 09:45:17
This is something that the first-run page will nee
|
| +{ |
| + if (origin instanceof Ci.nsIDOMXULElement) |
| + origin = origin.messageManager; |
| + |
| + if (origin instanceof Ci.nsIMessageSender) |
| + return new LightWeightPort(origin); |
| + else |
| + return null; |
| +} |
| + |
| +/** |
| + * Lightweight communication port allowing only sending messages. |
| + * @param {nsIMessageManager} messageManager |
| + * @constructor |
| + */ |
| +function LightWeightPort(messageManager) |
| +{ |
| + this._messageManager = messageManager; |
| +} |
| +LightWeightPort.prototype = |
| +{ |
| + /** |
| + * @see Port#emit |
| + */ |
| + emit: function(messageName, payload) |
| + { |
| + sendMessage(this._messageManager, messageName, payload); |
| + }, |
| + |
| + /** |
| + * @see Port#emitSync |
| + */ |
| + emitSync: function(messageName, payload) |
| + { |
| + return sendSyncMessage(this._messageManager, messageName, payload); |
| + } |
| +}; |
| + |
| +/** |
| + * Communication port wrapping the message manager API to send and receive |
| + * messages. |
| + * @param {nsIMessageManager} messageManager |
| + * @constructor |
| + */ |
| +function Port(messageManager) |
| +{ |
| + this._messageManager = messageManager; |
| + |
| + this._callbacks = new Map(); |
| + this._responseCallbacks = new Map(); |
| + this._responseCallbackCounter = 0; |
| + |
| + this._handleRequest = this._handleRequest.bind(this); |
| + this._handleResponse = this._handleResponse.bind(this); |
| + this._messageManager.addMessageListener(MESSAGE_NAME, this._handleRequest); |
| + this._messageManager.addMessageListener(RESPONSE_NAME, this._handleResponse); |
| +} |
| +Port.prototype = { |
| + /** |
| + * Disables the port and makes it stop listening to incoming messages. |
| + */ |
| + disconnect: function() |
| + { |
| + this._messageManager.removeMessageListener(MESSAGE_NAME, this._handleRequest); |
| + this._messageManager.removeMessageListener(RESPONSE_NAME, this._handleResponse); |
| + }, |
| + |
| + _sendResponse: function(sender, callbackID, payload) |
| + { |
| + if (!sender || typeof callbackID == "undefined") |
| + return; |
| + |
| + var response = {callbackID, payload}; |
| + sender._messageManager.sendAsyncMessage(RESPONSE_NAME, response); |
| + }, |
| + |
| + _handleRequest: function(message) |
| + { |
| + let sender = getSender(message.target); |
| + let {callbackID, messageName, payload} = message.data; |
| + |
| + let result = this._dispatch(messageName, payload, sender); |
| + if (result && typeof result.then == "function") |
| + { |
| + // This is a promise - asynchronous response |
| + if (message.sync) |
| + { |
| + Cu.reportError("Asynchronous response to the synchronous message '" + messageName + "' is not possible"); |
| + return undefined; |
| + } |
| + |
| + result.then(result => { |
| + this._sendResponse(sender, callbackID, result) |
| + }, e => { |
| + Cu.reportError(e); |
| + this._sendResponse(sender, callbackID, undefined); |
| + }); |
| + } |
| + else |
| + this._sendResponse(sender, callbackID, result); |
| + |
| + return result; |
| + }, |
| + |
| + _handleResponse: function(message) |
| + { |
| + let {callbackID, payload} = message.data; |
| + if (!this._responseCallbacks.has(callbackID)) |
| + return; |
| + |
| + let [callback, messageName, responses, expectedResponses] = |
| + this._responseCallbacks.get(callbackID); |
| + responses.push(payload); |
| + if (responses.length == expectedResponses) |
| + { |
| + this._responseCallbacks.delete(callbackID); |
| + callback(flattenResponses(responses, messageName)); |
| + } |
| + }, |
| + |
| + _dispatch: function(messageName, payload, sender) |
| + { |
| + let callbacks = this._callbacks.get(messageName); |
| + if (!callbacks) |
| + return undefined; |
| + |
| + callbacks = callbacks.slice(); |
| + let responses = []; |
| + for (let callback of callbacks) |
| + { |
| + try |
| + { |
| + responses.push(callback(payload, sender)); |
| + } |
| + catch (e) |
| + { |
| + Cu.reportError(e); |
| + } |
| + } |
| + return flattenResponses(responses, messageName); |
| + }, |
| + |
| + /** |
| + * Function to be called when a particular message is received |
| + * @callback Port~messageHandler |
| + * @param payload data attached to the message if any |
| + * @param {LightWeightPort} sender object that can be used to communicate with |
| + * the sender of the message, could be null |
| + * @return the handler can return undefined (no response), a value (response |
| + * to be sent to sender immediately) or a promise (asynchronous |
| + * response). |
| + */ |
| + |
| + /** |
| + * Adds a handler for the specified message. |
| + * @param {string} messageName message that would trigger the callback |
| + * @param {Port~messageHandler} callback |
| + */ |
| + on: function(messageName, callback) |
| + { |
| + if (!this._callbacks.has(messageName)) |
| + this._callbacks.set(messageName, []); |
| + |
| + let callbacks = this._callbacks.get(messageName); |
| + if (callbacks.indexOf(callback) < 0) |
| + callbacks.push(callback); |
| + }, |
| + |
| + /** |
| + * Removes a handler for the specified message. |
| + * @param {string} messageName message that would trigger the callback |
| + * @param {Port~messageHandler} callback |
| + */ |
| + off: function(messageName, callback) |
| + { |
| + let callbacks = this._callbacks.get(messageName); |
| + if (!callbacks) |
| + return; |
| + |
| + let index = callbacks.indexOf(callback); |
| + if (index >= 0) |
| + callbacks.splice(index, 1); |
|
Sebastian Noack
2016/03/16 11:53:32
If a listener is temporarily added we leak the key
Wladimir Palant
2016/03/16 13:12:54
IMHO not really, so far we don't have a single cal
|
| + }, |
| + |
| + /** |
| + * Sends a message. |
| + * @param {string} messageName message identifier |
| + * @param [payload] data to attach to the message |
| + */ |
| + emit: function(messageName, payload) |
| + { |
| + sendMessage(this._messageManager, messageName, payload, undefined); |
| + }, |
| + |
| + /** |
| + * Sends a message and expects a response. |
| + * @param {string} messageName message identifier |
| + * @param [payload] data to attach to the message |
| + * @return {Promise} promise that will be resolved with the response |
| + */ |
| + emitWithResponse: function(messageName, payload) |
| + { |
| + let callbackID = ++this._responseCallbackCounter; |
| + let expectedResponses = sendMessage( |
| + this._messageManager, messageName, payload, callbackID); |
| + return new Promise((resolve, reject) => { |
| + this._responseCallbacks.set(callbackID, |
| + [resolve, messageName, [], expectedResponses]); |
| + }); |
| + }, |
| + |
| + /** |
| + * Sends a synchonous message (DO NOT USE unless absolutely unavoidable). |
| + * @param {string} messageName message identifier |
| + * @param [payload] data to attach to the message |
| + * @return response returned by the handler |
| + */ |
| + emitSync: function(messageName, payload) |
| + { |
| + return sendSyncMessage(this._messageManager, messageName, payload); |
| + } |
| +}; |
| +exports.Port = Port; |
| + |
| +let messageManager; |
| +try |
| +{ |
| + // Child |
| + messageManager = require("messageManager"); |
| +} |
| +catch (e) |
| +{ |
| + // Parent |
| + messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"] |
| + .getService(Ci.nsIMessageListenerManager); |
| +} |
| + |
| +let port = new Port(messageManager); |
| +onShutdown.add(() => port.disconnect()); |
| +exports.port = port; |