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

Delta Between Two Patch Sets: lib/messaging.js

Issue 29338275: Issue 3499 - Create a clean messaging API for internal use (Closed)
Left Patch Set: Fixed copyright year Created March 16, 2016, 11:04 a.m.
Right Patch Set: Don't store all responses unnecessarily Created March 21, 2016, 8:40 p.m.
Left:
Right:
Use n/p to move between diff chunks; N/P to move between comments.
Jump to:
Left: Side by side diff | Download
Right: Side by side diff | Download
« no previous file with change/comment | « lib/child/bootstrap.js ('k') | no next file » | no next file with change/comment »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
LEFTRIGHT
1 /* 1 /*
2 * This file is part of Adblock Plus <https://adblockplus.org/>, 2 * This file is part of Adblock Plus <https://adblockplus.org/>,
3 * Copyright (C) 2006-2016 Eyeo GmbH 3 * Copyright (C) 2006-2016 Eyeo GmbH
4 * 4 *
5 * Adblock Plus is free software: you can redistribute it and/or modify 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 6 * it under the terms of the GNU General Public License version 3 as
7 * published by the Free Software Foundation. 7 * published by the Free Software Foundation.
8 * 8 *
9 * Adblock Plus is distributed in the hope that it will be useful, 9 * Adblock Plus is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details. 12 * GNU General Public License for more details.
13 * 13 *
14 * You should have received a copy of the GNU General Public License 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/>. 15 * along with Adblock Plus. If not, see <http://www.gnu.org/licenses/>.
16 */ 16 */
17 17
18 "use strict"; 18 "use strict";
19 19
20 const MESSAGE_NAME = "AdblockPlus:Message"; 20 const MESSAGE_NAME = "AdblockPlus:Message";
21 const RESPONSE_NAME = "AdblockPlus:Response"; 21 const RESPONSE_NAME = "AdblockPlus:Response";
22 22
23 function isPromise(value)
24 {
25 // value instanceof Promise won't work - there can be different Promise
26 // classes (e.g. in different contexts) and there can also be promise-like
27 // classes (e.g. Task).
28 return (value && typeof value.then == "function");
29 }
30
23 function sendMessage(messageManager, messageName, payload, callbackID) 31 function sendMessage(messageManager, messageName, payload, callbackID)
24 { 32 {
25 let request = {messageName, payload, callbackID}; 33 let request = {messageName, payload, callbackID};
26 if (messageManager instanceof Ci.nsIMessageSender) 34 if (messageManager instanceof Ci.nsIMessageSender)
27 { 35 {
28 messageManager.sendAsyncMessage(MESSAGE_NAME, request); 36 messageManager.sendAsyncMessage(MESSAGE_NAME, request);
29 return 1; 37 return 1;
30 } 38 }
31 else if (messageManager instanceof Ci.nsIMessageBroadcaster) 39 else if (messageManager instanceof Ci.nsIMessageBroadcaster)
32 { 40 {
33 messageManager.broadcastAsyncMessage(MESSAGE_NAME, request); 41 messageManager.broadcastAsyncMessage(MESSAGE_NAME, request);
34 return messageManager.childCount; 42 return messageManager.childCount;
35 } 43 }
36 else 44 else
37 { 45 {
38 Cu.reportError("Unexpected message manager, impossible to send message"); 46 Cu.reportError("Unexpected message manager, impossible to send message");
39 return 0; 47 return 0;
40 } 48 }
41 } 49 }
42 50
43 function sendSyncMessage(messageManager, messageName, payload) 51 function sendSyncMessage(messageManager, messageName, payload)
44 { 52 {
45 let request = {messageName, payload}; 53 let request = {messageName, payload};
46 let responses = messageManager.sendRpcMessage(MESSAGE_NAME, request); 54 let responses = messageManager.sendRpcMessage(MESSAGE_NAME, request);
55 let processor = new ResponseProcessor(messageName);
47 for (let response of responses) 56 for (let response of responses)
48 if (typeof response != "undefined") 57 processor.add(response);
49 return response; 58 return processor.value;
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; 59 }
51 } 60
52 61 function ResponseProcessor(messageName)
53 function flattenResponses(responses, messageName) 62 {
54 { 63 this.value = undefined;
55 let result = undefined; 64 this.add = function(response)
56 for (let response of responses)
57 { 65 {
58 if (typeof response == "undefined") 66 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; 67 return;
60 68
61 if (typeof result == "undefined") 69 if (typeof this.value == "undefined")
Erik 2016/03/17 05:02:04 nit `==` -> `===`
Wladimir Palant 2016/03/21 15:31:33 Same as above.
62 result = response; 70 this.value = response;
63 else 71 else
64 Cu.reportError("Got multiple responses to message '" + messageName + "', o nly first response was accepted."); 72 Cu.reportError("Got multiple responses to message '" + messageName + "', o nly first response was accepted.");
65 } 73 };
66 return result;
67 } 74 }
68 75
69 function getSender(origin) 76 function getSender(origin)
70 { 77 {
71 if (origin instanceof Ci.nsIDOMXULElement) 78 if (origin instanceof Ci.nsIDOMXULElement)
72 origin = origin.messageManager; 79 origin = origin.messageManager;
73 80
74 if (origin instanceof Ci.nsIMessageSender) 81 if (origin instanceof Ci.nsIMessageSender)
75 return new LightWeightPort(origin); 82 return new LightWeightPort(origin);
76 else 83 else
(...skipping 55 matching lines...) Expand 10 before | Expand all | Expand 10 after
132 { 139 {
133 this._messageManager.removeMessageListener(MESSAGE_NAME, this._handleRequest ); 140 this._messageManager.removeMessageListener(MESSAGE_NAME, this._handleRequest );
134 this._messageManager.removeMessageListener(RESPONSE_NAME, this._handleRespon se); 141 this._messageManager.removeMessageListener(RESPONSE_NAME, this._handleRespon se);
135 }, 142 },
136 143
137 _sendResponse: function(sender, callbackID, payload) 144 _sendResponse: function(sender, callbackID, payload)
138 { 145 {
139 if (!sender || typeof callbackID == "undefined") 146 if (!sender || typeof callbackID == "undefined")
140 return; 147 return;
141 148
142 var response = {callbackID, payload}; 149 let 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); 150 sender._messageManager.sendAsyncMessage(RESPONSE_NAME, response);
144 }, 151 },
145 152
146 _handleRequest: function(message) 153 _handleRequest: function(message)
147 { 154 {
148 let sender = getSender(message.target); 155 let sender = getSender(message.target);
149 let {callbackID, messageName, payload} = message.data; 156 let {callbackID, messageName, payload} = message.data;
150 157
151 let result = this._dispatch(messageName, payload, sender); 158 let result = this._dispatch(messageName, payload, sender);
152 if (result && typeof result.then == "function") 159 if (isPromise(result))
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 { 160 {
154 // This is a promise - asynchronous response 161 // This is a promise - asynchronous response
155 if (message.sync) 162 if (message.sync)
156 { 163 {
157 Cu.reportError("Asynchronous response to the synchronous message '" + me ssageName + "' is not possible"); 164 Cu.reportError("Asynchronous response to the synchronous message '" + me ssageName + "' is not possible");
158 return undefined; 165 return undefined;
159 } 166 }
160 167
161 result.then(result => { 168 result.then(result =>
169 {
162 this._sendResponse(sender, callbackID, result) 170 this._sendResponse(sender, callbackID, result)
163 }, e => { 171 }, e =>
172 {
164 Cu.reportError(e); 173 Cu.reportError(e);
165 this._sendResponse(sender, callbackID, undefined); 174 this._sendResponse(sender, callbackID, undefined);
166 }); 175 });
167 } 176 }
168 else 177 else
169 this._sendResponse(sender, callbackID, result); 178 this._sendResponse(sender, callbackID, result);
170 179
171 return result; 180 return result;
172 }, 181 },
173 182
174 _handleResponse: function(message) 183 _handleResponse: function(message)
175 { 184 {
176 let {callbackID, payload} = message.data; 185 let {callbackID, payload} = message.data;
177 if (!this._responseCallbacks.has(callbackID)) 186 let callbackData = this._responseCallbacks.get(callbackID);
187 if (!callbackData)
178 return; 188 return;
179 189
180 let [callback, messageName, responses, expectedResponses] = 190 let [callback, processor, expectedResponses] = callbackData;
181 this._responseCallbacks.get(callbackID); 191
182 responses.push(payload); 192 try
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) 193 {
Erik 2016/03/17 05:02:04 nit `==` -> `===`
Wladimir Palant 2016/03/21 15:31:32 Same as above.
194 processor.add(payload);
195 }
196 catch (e)
197 {
198 Cu.reportError(e);
199 }
200
201 callbackData[2] = --expectedResponses;
202 if (expectedResponses <= 0)
184 { 203 {
185 this._responseCallbacks.delete(callbackID); 204 this._responseCallbacks.delete(callbackID);
186 callback(flattenResponses(responses, messageName)); 205 callback(processor.value);
187 } 206 }
188 }, 207 },
189 208
190 _dispatch: function(messageName, payload, sender) 209 _dispatch: function(messageName, payload, sender)
191 { 210 {
192 let callbacks = this._callbacks.get(messageName); 211 let callbacks = this._callbacks.get(messageName);
193 if (!callbacks) 212 if (!callbacks)
194 return undefined; 213 return undefined;
195 214
196 callbacks = callbacks.slice(); 215 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 = []; 216 let processor = new ResponseProcessor(messageName);
198 for (let callback of callbacks) 217 for (let callback of callbacks)
199 { 218 {
200 try 219 try
201 { 220 {
202 responses.push(callback(payload, sender)); 221 processor.add(callback(payload, sender));
203 } 222 }
204 catch (e) 223 catch (e)
205 { 224 {
206 Cu.reportError(e); 225 Cu.reportError(e);
207 } 226 }
208 } 227 }
209 return flattenResponses(responses, messageName); 228 return processor.value;
210 }, 229 },
211 230
212 /** 231 /**
213 * Function to be called when a particular message is received 232 * Function to be called when a particular message is received
214 * @callback Port~messageHandler 233 * @callback Port~messageHandler
215 * @param payload data attached to the message if any 234 * @param payload data attached to the message if any
216 * @param {LightWeightPort} sender object that can be used to communicate with 235 * @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 236 * the sender of the message, could be null
218 * @return the handler can return undefined (no response), a value (response 237 * @return the handler can return undefined (no response), a value (response
219 * to be sent to sender immediately) or a promise (asynchronous 238 * to be sent to sender immediately) or a promise (asynchronous
220 * response). 239 * 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 */ 240 */
222 241
223 /** 242 /**
224 * Adds a handler for the specified message. 243 * Adds a handler for the specified message.
225 * @param {string} messageName message that would trigger the callback 244 * @param {string} messageName message that would trigger the callback
226 * @param {Port~messageHandler} callback 245 * @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 */ 246 */
228 on: function(messageName, callback) 247 on: function(messageName, callback)
229 { 248 {
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); 249 let callbacks = this._callbacks.get(messageName);
234 if (callbacks.indexOf(callback) < 0) 250 if (callbacks)
235 callbacks.push(callback); 251 callbacks.push(callback);
252 else
253 this._callbacks.set(messageName, [callback]);
236 }, 254 },
237 255
238 /** 256 /**
239 * Removes a handler for the specified message. 257 * Removes a handler for the specified message.
240 * @param {string} messageName message that would trigger the callback 258 * @param {string} messageName message that would trigger the callback
241 * @param {Port~messageHandler} callback 259 * @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 */ 260 */
243 off: function(messageName, callback) 261 off: function(messageName, callback)
244 { 262 {
245 let callbacks = this._callbacks.get(messageName); 263 let callbacks = this._callbacks.get(messageName);
246 if (!callbacks) 264 if (!callbacks)
247 return; 265 return;
248 266
249 let index = callbacks.indexOf(callback); 267 let index = callbacks.indexOf(callback);
250 if (index >= 0) 268 if (index >= 0)
251 callbacks.splice(index, 1); 269 callbacks.splice(index, 1);
252 }, 270 },
253 271
254 /** 272 /**
255 * Sends a message. 273 * Sends a message.
256 * @param {string} messageName message identifier 274 * @param {string} messageName message identifier
257 * @param [payload] data to attach to the message 275 * @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 */ 276 */
259 emit: function(messageName, payload) 277 emit: function(messageName, payload)
260 { 278 {
261 sendMessage(this._messageManager, messageName, payload, undefined); 279 sendMessage(this._messageManager, messageName, payload, undefined);
262 }, 280 },
263 281
264 /** 282 /**
265 * Sends a message and expects a response. 283 * Sends a message and expects a response.
266 * @param {string} messageName message identifier 284 * @param {string} messageName message identifier
267 * @param [payload] data to attach to the message 285 * @param [payload] data to attach to the message
268 * @return {Promise} promise that will be resolved with the response 286 * @return {Promise} promise that will be resolved with the response
269 */ 287 */
270 emitWithResponse: function(messageName, payload) 288 emitWithResponse: function(messageName, payload)
271 { 289 {
272 let callbackID = ++this._responseCallbackCounter; 290 let callbackID = ++this._responseCallbackCounter;
273 let expectedResponses = sendMessage( 291 let expectedResponses = sendMessage(
274 this._messageManager, messageName, payload, callbackID); 292 this._messageManager, messageName, payload, callbackID);
275 return new Promise((resolve, reject) => { 293 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.
294 {
276 this._responseCallbacks.set(callbackID, 295 this._responseCallbacks.set(callbackID,
277 [resolve, messageName, [], expectedResponses]); 296 [resolve, new ResponseProcessor(messageName), expectedResponses]);
278 }); 297 });
279 }, 298 },
280 299
281 /** 300 /**
282 * Sends a synchonous message (DO NOT USE unless absolutely unavoidable). 301 * Sends a synchonous message (DO NOT USE unless absolutely unavoidable).
283 * @param {string} messageName message identifier 302 * @param {string} messageName message identifier
284 * @param [payload] data to attach to the message 303 * @param [payload] data to attach to the message
285 * @return response returned by the handler 304 * @return response returned by the handler
286 */ 305 */
287 emitSync: function(messageName, payload) 306 emitSync: function(messageName, payload)
(...skipping 12 matching lines...) Expand all
300 catch (e) 319 catch (e)
301 { 320 {
302 // Parent 321 // Parent
303 messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"] 322 messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"]
304 .getService(Ci.nsIMessageListenerManager); 323 .getService(Ci.nsIMessageListenerManager);
305 } 324 }
306 325
307 let port = new Port(messageManager); 326 let port = new Port(messageManager);
308 onShutdown.add(() => port.disconnect()); 327 onShutdown.add(() => port.disconnect());
309 exports.port = port; 328 exports.port = port;
LEFTRIGHT

Powered by Google App Engine
This is Rietveld