Left: | ||
Right: |
OLD | NEW |
---|---|
(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; | |
OLD | NEW |