diff options
Diffstat (limited to 'data/extensions/spyblock@gnu.org/lib/messaging.js')
-rw-r--r-- | data/extensions/spyblock@gnu.org/lib/messaging.js | 316 |
1 files changed, 316 insertions, 0 deletions
diff --git a/data/extensions/spyblock@gnu.org/lib/messaging.js b/data/extensions/spyblock@gnu.org/lib/messaging.js new file mode 100644 index 0000000..63d061e --- /dev/null +++ b/data/extensions/spyblock@gnu.org/lib/messaging.js @@ -0,0 +1,316 @@ +/* + * This file is part of Adblock Plus <https://adblockplus.org/>, + * Copyright (C) 2006-2017 eyeo GmbH + * + * 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"; + +let {EventEmitter} = require("events"); + +const MESSAGE_NAME = "AdblockPlus:Message"; +const RESPONSE_NAME = "AdblockPlus:Response"; + +function isPromise(value) +{ + // value instanceof Promise won't work - there can be different Promise + // classes (e.g. in different contexts) and there can also be promise-like + // classes (e.g. Task). + return (value && typeof value.then == "function"); +} + +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) + { + messageManager.broadcastAsyncMessage(MESSAGE_NAME, request); + return messageManager.childCount; + } + else + { + 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); + let processor = new ResponseProcessor(messageName); + for (let response of responses) + processor.add(response); + return processor.value; +} + +function ResponseProcessor(messageName) +{ + this.value = undefined; + this.add = function(response) + { + if (typeof response == "undefined") + return; + + if (typeof this.value == "undefined") + this.value = response; + else + Cu.reportError("Got multiple responses to message '" + messageName + "', only first response was accepted."); + }; +} + +function getSender(origin) +{ + 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._eventEmitter = new EventEmitter(); + + 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; + + let 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 (isPromise(result)) + { + // 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; + let callbackData = this._responseCallbacks.get(callbackID); + if (!callbackData) + return; + + let [callback, processor, expectedResponses] = callbackData; + + try + { + processor.add(payload); + } + catch (e) + { + Cu.reportError(e); + } + + callbackData[2] = --expectedResponses; + if (expectedResponses <= 0) + { + this._responseCallbacks.delete(callbackID); + callback(processor.value); + } + }, + + _dispatch: function(messageName, payload, sender) + { + let callbacks = this._eventEmitter.listeners(messageName); + let processor = new ResponseProcessor(messageName); + for (let callback of callbacks) + { + try + { + processor.add(callback(payload, sender)); + } + catch (e) + { + Cu.reportError(e); + } + } + return processor.value; + }, + + /** + * 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) + { + this._eventEmitter.on(messageName, 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) + { + this._eventEmitter.off(messageName, callback); + }, + + /** + * 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, new ResponseProcessor(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; |