Rietveld Code Review Tool
Help | Bug tracker | Discussion group | Source code

Side by Side Diff: lib/messaging.js

Issue 29338275: Issue 3499 - Create a clean messaging API for internal use (Closed)
Patch Set: Fixed copyright year Created March 16, 2016, 11:04 a.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
View unified diff | Download patch
« no previous file with comments | « lib/child/bootstrap.js ('k') | no next file » | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
(Empty)
1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH
4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation.
8 *
9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */
17
18 "use strict";
19
20 const MESSAGE_NAME = "AdblockPlus:Message";
21 const RESPONSE_NAME = "AdblockPlus:Response";
22
23 function sendMessage(messageManager, messageName, payload, callbackID)
24 {
25 let request = {messageName, payload, callbackID};
26 if (messageManager instanceof Ci.nsIMessageSender)
27 {
28 messageManager.sendAsyncMessage(MESSAGE_NAME, request);
29 return 1;
30 }
31 else if (messageManager instanceof Ci.nsIMessageBroadcaster)
32 {
33 messageManager.broadcastAsyncMessage(MESSAGE_NAME, request);
34 return messageManager.childCount;
35 }
36 else
37 {
38 Cu.reportError("Unexpected message manager, impossible to send message");
39 return 0;
40 }
41 }
42
43 function sendSyncMessage(messageManager, messageName, payload)
44 {
45 let request = {messageName, payload};
46 let responses = messageManager.sendRpcMessage(MESSAGE_NAME, request);
47 for (let response of responses)
48 if (typeof response != "undefined")
49 return response;
Thomas Greiner 2016/03/17 12:09:26 Detail: What's the reason for not using `flattenRe
Wladimir Palant 2016/03/21 15:31:31 No real reason, flattenResponses() was simply intr
50 return undefined;
51 }
52
53 function flattenResponses(responses, messageName)
54 {
55 let result = undefined;
56 for (let response of responses)
57 {
58 if (typeof response == "undefined")
Erik 2016/03/17 05:02:04 nit `==` -> `===`
Wladimir Palant 2016/03/21 15:31:32 We generally don't use === unless it makes a diffe
59 continue;
60
61 if (typeof result == "undefined")
Erik 2016/03/17 05:02:04 nit `==` -> `===`
Wladimir Palant 2016/03/21 15:31:33 Same as above.
62 result = response;
63 else
64 Cu.reportError("Got multiple responses to message '" + messageName + "', o nly first response was accepted.");
65 }
66 return result;
67 }
68
69 function getSender(origin)
70 {
71 if (origin instanceof Ci.nsIDOMXULElement)
72 origin = origin.messageManager;
73
74 if (origin instanceof Ci.nsIMessageSender)
75 return new LightWeightPort(origin);
76 else
77 return null;
78 }
79
80 /**
81 * Lightweight communication port allowing only sending messages.
82 * @param {nsIMessageManager} messageManager
83 * @constructor
84 */
85 function LightWeightPort(messageManager)
86 {
87 this._messageManager = messageManager;
88 }
89 LightWeightPort.prototype =
90 {
91 /**
92 * @see Port#emit
93 */
94 emit: function(messageName, payload)
95 {
96 sendMessage(this._messageManager, messageName, payload);
97 },
98
99 /**
100 * @see Port#emitSync
101 */
102 emitSync: function(messageName, payload)
103 {
104 return sendSyncMessage(this._messageManager, messageName, payload);
105 }
106 };
107
108 /**
109 * Communication port wrapping the message manager API to send and receive
110 * messages.
111 * @param {nsIMessageManager} messageManager
112 * @constructor
113 */
114 function Port(messageManager)
115 {
116 this._messageManager = messageManager;
117
118 this._callbacks = new Map();
119 this._responseCallbacks = new Map();
120 this._responseCallbackCounter = 0;
121
122 this._handleRequest = this._handleRequest.bind(this);
123 this._handleResponse = this._handleResponse.bind(this);
124 this._messageManager.addMessageListener(MESSAGE_NAME, this._handleRequest);
125 this._messageManager.addMessageListener(RESPONSE_NAME, this._handleResponse);
126 }
127 Port.prototype = {
128 /**
129 * Disables the port and makes it stop listening to incoming messages.
130 */
131 disconnect: function()
132 {
133 this._messageManager.removeMessageListener(MESSAGE_NAME, this._handleRequest );
134 this._messageManager.removeMessageListener(RESPONSE_NAME, this._handleRespon se);
135 },
136
137 _sendResponse: function(sender, callbackID, payload)
138 {
139 if (!sender || typeof callbackID == "undefined")
140 return;
141
142 var response = {callbackID, payload};
Thomas Greiner 2016/03/17 12:09:26 Detail: Unless there's a particular reason not to,
Wladimir Palant 2016/03/21 15:31:33 Done.
143 sender._messageManager.sendAsyncMessage(RESPONSE_NAME, response);
144 },
145
146 _handleRequest: function(message)
147 {
148 let sender = getSender(message.target);
149 let {callbackID, messageName, payload} = message.data;
150
151 let result = this._dispatch(messageName, payload, sender);
152 if (result && typeof result.then == "function")
Erik 2016/03/17 05:02:04 It'd be nice to have a `isPromise` function to reu
Thomas Greiner 2016/03/17 12:09:26 You mean `result instanceof Promise`?
Wladimir Palant 2016/03/21 15:31:33 No, `result instanceof Promise` would be wrong - t
153 {
154 // This is a promise - asynchronous response
155 if (message.sync)
156 {
157 Cu.reportError("Asynchronous response to the synchronous message '" + me ssageName + "' is not possible");
158 return undefined;
159 }
160
161 result.then(result => {
162 this._sendResponse(sender, callbackID, result)
163 }, e => {
164 Cu.reportError(e);
165 this._sendResponse(sender, callbackID, undefined);
166 });
167 }
168 else
169 this._sendResponse(sender, callbackID, result);
170
171 return result;
172 },
173
174 _handleResponse: function(message)
175 {
176 let {callbackID, payload} = message.data;
177 if (!this._responseCallbacks.has(callbackID))
178 return;
179
180 let [callback, messageName, responses, expectedResponses] =
181 this._responseCallbacks.get(callbackID);
182 responses.push(payload);
Thomas Greiner 2016/03/17 12:09:26 Why do you wait for and store all the responses if
Wladimir Palant 2016/03/21 15:31:31 Because otherwise this code gets rather complicate
Thomas Greiner 2016/03/21 18:43:22 So decrementing `expectedResponses` is not an opti
Wladimir Palant 2016/03/21 20:42:34 Without storing all the values in an array we cann
183 if (responses.length == expectedResponses)
Erik 2016/03/17 05:02:04 nit `==` -> `===`
Wladimir Palant 2016/03/21 15:31:32 Same as above.
184 {
185 this._responseCallbacks.delete(callbackID);
186 callback(flattenResponses(responses, messageName));
187 }
188 },
189
190 _dispatch: function(messageName, payload, sender)
191 {
192 let callbacks = this._callbacks.get(messageName);
193 if (!callbacks)
194 return undefined;
195
196 callbacks = callbacks.slice();
Thomas Greiner 2016/03/17 12:09:26 Why do you copy the array? I don't see that a call
Wladimir Palant 2016/03/21 15:31:32 It sure could. Classic example is a callback that
Thomas Greiner 2016/03/21 18:43:22 Of course, in theory it could happen because it's
Wladimir Palant 2016/03/21 20:42:34 No, it would merely have to call port.off(argument
Thomas Greiner 2016/03/22 11:15:07 Ah, right. Nevermind then.
197 let responses = [];
198 for (let callback of callbacks)
199 {
200 try
201 {
202 responses.push(callback(payload, sender));
203 }
204 catch (e)
205 {
206 Cu.reportError(e);
207 }
208 }
209 return flattenResponses(responses, messageName);
210 },
211
212 /**
213 * Function to be called when a particular message is received
214 * @callback Port~messageHandler
215 * @param payload data attached to the message if any
216 * @param {LightWeightPort} sender object that can be used to communicate with
Erik 2016/03/17 05:02:05 this param appears to just accept a function, and
Wladimir Palant 2016/03/21 15:31:32 Explanation below.
217 * the sender of the message, could be null
218 * @return the handler can return undefined (no response), a value (response
219 * to be sent to sender immediately) or a promise (asynchronous
220 * response).
Erik 2016/03/17 05:02:05 `on` doesn't appear to return a promise, or anythi
Wladimir Palant 2016/03/21 15:31:32 Explanation below.
221 */
222
223 /**
224 * Adds a handler for the specified message.
225 * @param {string} messageName message that would trigger the callback
226 * @param {Port~messageHandler} callback
Erik 2016/03/17 05:02:04 same comment as above, this appears to just accept
Wladimir Palant 2016/03/21 15:31:32 Please see http://usejsdoc.org/tags-callback.html
227 */
228 on: function(messageName, callback)
229 {
230 if (!this._callbacks.has(messageName))
231 this._callbacks.set(messageName, []);
Thomas Greiner 2016/03/17 12:09:26 Detail: Since you're using `Map` I wonder why you'
Wladimir Palant 2016/03/21 15:31:31 As Sebastian pointed out, Node.js (unlike Add-on S
Thomas Greiner 2016/03/21 18:43:23 Interesting, I wonder why they thought that'd be a
Wladimir Palant 2016/03/21 20:42:34 Probably because it's simpler/more efficient to im
232
233 let callbacks = this._callbacks.get(messageName);
234 if (callbacks.indexOf(callback) < 0)
235 callbacks.push(callback);
236 },
237
238 /**
239 * Removes a handler for the specified message.
240 * @param {string} messageName message that would trigger the callback
241 * @param {Port~messageHandler} callback
Erik 2016/03/17 05:02:04 if I am not mistaken about the above comments for
Wladimir Palant 2016/03/21 15:31:32 Same as above.
242 */
243 off: function(messageName, callback)
244 {
245 let callbacks = this._callbacks.get(messageName);
246 if (!callbacks)
247 return;
248
249 let index = callbacks.indexOf(callback);
250 if (index >= 0)
251 callbacks.splice(index, 1);
252 },
253
254 /**
255 * Sends a message.
256 * @param {string} messageName message identifier
257 * @param [payload] data to attach to the message
Thomas Greiner 2016/03/17 12:09:26 Detail: Is this valid JSDoc syntax even with the t
Wladimir Palant 2016/03/21 15:31:32 Yes, it is.
258 */
259 emit: function(messageName, payload)
260 {
261 sendMessage(this._messageManager, messageName, payload, undefined);
262 },
263
264 /**
265 * Sends a message and expects a response.
266 * @param {string} messageName message identifier
267 * @param [payload] data to attach to the message
268 * @return {Promise} promise that will be resolved with the response
269 */
270 emitWithResponse: function(messageName, payload)
271 {
272 let callbackID = ++this._responseCallbackCounter;
273 let expectedResponses = sendMessage(
274 this._messageManager, messageName, payload, callbackID);
275 return new Promise((resolve, reject) => {
Erik 2016/03/17 05:02:04 nit add a newline here?
Wladimir Palant 2016/03/21 15:31:32 Done.
276 this._responseCallbacks.set(callbackID,
277 [resolve, messageName, [], expectedResponses]);
278 });
279 },
280
281 /**
282 * Sends a synchonous message (DO NOT USE unless absolutely unavoidable).
283 * @param {string} messageName message identifier
284 * @param [payload] data to attach to the message
285 * @return response returned by the handler
286 */
287 emitSync: function(messageName, payload)
288 {
289 return sendSyncMessage(this._messageManager, messageName, payload);
290 }
291 };
292 exports.Port = Port;
293
294 let messageManager;
295 try
296 {
297 // Child
298 messageManager = require("messageManager");
299 }
300 catch (e)
301 {
302 // Parent
303 messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"]
304 .getService(Ci.nsIMessageListenerManager);
305 }
306
307 let port = new Port(messageManager);
308 onShutdown.add(() => port.disconnect());
309 exports.port = port;
OLDNEW
« no previous file with comments | « lib/child/bootstrap.js ('k') | no next file » | no next file with comments »

Powered by Google App Engine
This is Rietveld