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: 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:
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 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
31 function sendMessage(messageManager, messageName, payload, callbackID)
32 {
33 let request = {messageName, payload, callbackID};
34 if (messageManager instanceof Ci.nsIMessageSender)
35 {
36 messageManager.sendAsyncMessage(MESSAGE_NAME, request);
37 return 1;
38 }
39 else if (messageManager instanceof Ci.nsIMessageBroadcaster)
40 {
41 messageManager.broadcastAsyncMessage(MESSAGE_NAME, request);
42 return messageManager.childCount;
43 }
44 else
45 {
46 Cu.reportError("Unexpected message manager, impossible to send message");
47 return 0;
48 }
49 }
50
51 function sendSyncMessage(messageManager, messageName, payload)
52 {
53 let request = {messageName, payload};
54 let responses = messageManager.sendRpcMessage(MESSAGE_NAME, request);
55 let processor = new ResponseProcessor(messageName);
56 for (let response of responses)
57 processor.add(response);
58 return processor.value;
59 }
60
61 function ResponseProcessor(messageName)
62 {
63 this.value = undefined;
64 this.add = function(response)
65 {
66 if (typeof response == "undefined")
67 return;
68
69 if (typeof this.value == "undefined")
70 this.value = response;
71 else
72 Cu.reportError("Got multiple responses to message '" + messageName + "', o nly first response was accepted.");
73 };
74 }
75
76 function getSender(origin)
77 {
78 if (origin instanceof Ci.nsIDOMXULElement)
79 origin = origin.messageManager;
80
81 if (origin instanceof Ci.nsIMessageSender)
82 return new LightWeightPort(origin);
83 else
84 return null;
85 }
86
87 /**
88 * Lightweight communication port allowing only sending messages.
89 * @param {nsIMessageManager} messageManager
90 * @constructor
91 */
92 function LightWeightPort(messageManager)
93 {
94 this._messageManager = messageManager;
95 }
96 LightWeightPort.prototype =
97 {
98 /**
99 * @see Port#emit
100 */
101 emit: function(messageName, payload)
102 {
103 sendMessage(this._messageManager, messageName, payload);
104 },
105
106 /**
107 * @see Port#emitSync
108 */
109 emitSync: function(messageName, payload)
110 {
111 return sendSyncMessage(this._messageManager, messageName, payload);
112 }
113 };
114
115 /**
116 * Communication port wrapping the message manager API to send and receive
117 * messages.
118 * @param {nsIMessageManager} messageManager
119 * @constructor
120 */
121 function Port(messageManager)
122 {
123 this._messageManager = messageManager;
124
125 this._callbacks = new Map();
126 this._responseCallbacks = new Map();
127 this._responseCallbackCounter = 0;
128
129 this._handleRequest = this._handleRequest.bind(this);
130 this._handleResponse = this._handleResponse.bind(this);
131 this._messageManager.addMessageListener(MESSAGE_NAME, this._handleRequest);
132 this._messageManager.addMessageListener(RESPONSE_NAME, this._handleResponse);
133 }
134 Port.prototype = {
135 /**
136 * Disables the port and makes it stop listening to incoming messages.
137 */
138 disconnect: function()
139 {
140 this._messageManager.removeMessageListener(MESSAGE_NAME, this._handleRequest );
141 this._messageManager.removeMessageListener(RESPONSE_NAME, this._handleRespon se);
142 },
143
144 _sendResponse: function(sender, callbackID, payload)
145 {
146 if (!sender || typeof callbackID == "undefined")
147 return;
148
149 let response = {callbackID, payload};
150 sender._messageManager.sendAsyncMessage(RESPONSE_NAME, response);
151 },
152
153 _handleRequest: function(message)
154 {
155 let sender = getSender(message.target);
156 let {callbackID, messageName, payload} = message.data;
157
158 let result = this._dispatch(messageName, payload, sender);
159 if (isPromise(result))
160 {
161 // This is a promise - asynchronous response
162 if (message.sync)
163 {
164 Cu.reportError("Asynchronous response to the synchronous message '" + me ssageName + "' is not possible");
165 return undefined;
166 }
167
168 result.then(result =>
169 {
170 this._sendResponse(sender, callbackID, result)
171 }, e =>
172 {
173 Cu.reportError(e);
174 this._sendResponse(sender, callbackID, undefined);
175 });
176 }
177 else
178 this._sendResponse(sender, callbackID, result);
179
180 return result;
181 },
182
183 _handleResponse: function(message)
184 {
185 let {callbackID, payload} = message.data;
186 let callbackData = this._responseCallbacks.get(callbackID);
187 if (!callbackData)
188 return;
189
190 let [callback, processor, expectedResponses] = callbackData;
191
192 try
193 {
194 processor.add(payload);
195 }
196 catch (e)
197 {
198 Cu.reportError(e);
199 }
200
201 callbackData[2] = --expectedResponses;
202 if (expectedResponses <= 0)
203 {
204 this._responseCallbacks.delete(callbackID);
205 callback(processor.value);
206 }
207 },
208
209 _dispatch: function(messageName, payload, sender)
210 {
211 let callbacks = this._callbacks.get(messageName);
212 if (!callbacks)
213 return undefined;
214
215 callbacks = callbacks.slice();
216 let processor = new ResponseProcessor(messageName);
217 for (let callback of callbacks)
218 {
219 try
220 {
221 processor.add(callback(payload, sender));
222 }
223 catch (e)
224 {
225 Cu.reportError(e);
226 }
227 }
228 return processor.value;
229 },
230
231 /**
232 * Function to be called when a particular message is received
233 * @callback Port~messageHandler
234 * @param payload data attached to the message if any
235 * @param {LightWeightPort} sender object that can be used to communicate with
236 * the sender of the message, could be null
237 * @return the handler can return undefined (no response), a value (response
238 * to be sent to sender immediately) or a promise (asynchronous
239 * response).
240 */
241
242 /**
243 * Adds a handler for the specified message.
244 * @param {string} messageName message that would trigger the callback
245 * @param {Port~messageHandler} callback
246 */
247 on: function(messageName, callback)
248 {
249 let callbacks = this._callbacks.get(messageName);
250 if (callbacks)
251 callbacks.push(callback);
252 else
253 this._callbacks.set(messageName, [callback]);
254 },
255
256 /**
257 * Removes a handler for the specified message.
258 * @param {string} messageName message that would trigger the callback
259 * @param {Port~messageHandler} callback
260 */
261 off: function(messageName, callback)
262 {
263 let callbacks = this._callbacks.get(messageName);
264 if (!callbacks)
265 return;
266
267 let index = callbacks.indexOf(callback);
268 if (index >= 0)
269 callbacks.splice(index, 1);
270 },
271
272 /**
273 * Sends a message.
274 * @param {string} messageName message identifier
275 * @param [payload] data to attach to the message
276 */
277 emit: function(messageName, payload)
278 {
279 sendMessage(this._messageManager, messageName, payload, undefined);
280 },
281
282 /**
283 * Sends a message and expects a response.
284 * @param {string} messageName message identifier
285 * @param [payload] data to attach to the message
286 * @return {Promise} promise that will be resolved with the response
287 */
288 emitWithResponse: function(messageName, payload)
289 {
290 let callbackID = ++this._responseCallbackCounter;
291 let expectedResponses = sendMessage(
292 this._messageManager, messageName, payload, callbackID);
293 return new Promise((resolve, reject) =>
294 {
295 this._responseCallbacks.set(callbackID,
296 [resolve, new ResponseProcessor(messageName), expectedResponses]);
297 });
298 },
299
300 /**
301 * Sends a synchonous message (DO NOT USE unless absolutely unavoidable).
302 * @param {string} messageName message identifier
303 * @param [payload] data to attach to the message
304 * @return response returned by the handler
305 */
306 emitSync: function(messageName, payload)
307 {
308 return sendSyncMessage(this._messageManager, messageName, payload);
309 }
310 };
311 exports.Port = Port;
312
313 let messageManager;
314 try
315 {
316 // Child
317 messageManager = require("messageManager");
318 }
319 catch (e)
320 {
321 // Parent
322 messageManager = Cc["@mozilla.org/parentprocessmessagemanager;1"]
323 .getService(Ci.nsIMessageListenerManager);
324 }
325
326 let port = new Port(messageManager);
327 onShutdown.add(() => port.disconnect());
328 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