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