|
1 /* This Source Code Form is subject to the terms of the Mozilla Public |
|
2 * License, v. 2.0. If a copy of the MPL was not distributed with this file, |
|
3 * You can obtain one at http://mozilla.org/MPL/2.0/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cu = Components.utils; |
|
10 const Cr = Components.results; |
|
11 |
|
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
13 Cu.import("resource://gre/modules/Services.jsm"); |
|
14 Cu.import("resource://gre/modules/SystemMessagePermissionsChecker.jsm"); |
|
15 |
|
16 XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
|
17 "@mozilla.org/parentprocessmessagemanager;1", |
|
18 "nsIMessageBroadcaster"); |
|
19 |
|
20 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", |
|
21 "@mozilla.org/uuid-generator;1", |
|
22 "nsIUUIDGenerator"); |
|
23 |
|
24 XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService", |
|
25 "@mozilla.org/power/powermanagerservice;1", |
|
26 "nsIPowerManagerService"); |
|
27 |
|
28 // Limit the number of pending messages for a given page. |
|
29 let kMaxPendingMessages; |
|
30 try { |
|
31 kMaxPendingMessages = |
|
32 Services.prefs.getIntPref("dom.messages.maxPendingMessages"); |
|
33 } catch(e) { |
|
34 // getIntPref throws when the pref is not set. |
|
35 kMaxPendingMessages = 5; |
|
36 } |
|
37 |
|
38 const kMessages =["SystemMessageManager:GetPendingMessages", |
|
39 "SystemMessageManager:HasPendingMessages", |
|
40 "SystemMessageManager:Register", |
|
41 "SystemMessageManager:Unregister", |
|
42 "SystemMessageManager:Message:Return:OK", |
|
43 "SystemMessageManager:AskReadyToRegister", |
|
44 "SystemMessageManager:HandleMessagesDone", |
|
45 "child-process-shutdown"] |
|
46 |
|
47 function debug(aMsg) { |
|
48 // dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n"); |
|
49 } |
|
50 |
|
51 |
|
52 let defaultMessageConfigurator = { |
|
53 get mustShowRunningApp() { |
|
54 return false; |
|
55 } |
|
56 }; |
|
57 |
|
58 const MSG_SENT_SUCCESS = 0; |
|
59 const MSG_SENT_FAILURE_PERM_DENIED = 1; |
|
60 const MSG_SENT_FAILURE_APP_NOT_RUNNING = 2; |
|
61 |
|
62 // Implementation of the component used by internal users. |
|
63 |
|
64 function SystemMessageInternal() { |
|
65 // The set of pages registered by installed apps. We keep the |
|
66 // list of pending messages for each page here also. |
|
67 this._pages = []; |
|
68 |
|
69 // The set of listeners. This is a multi-dimensional object. The _listeners |
|
70 // object itself is a map from manifest URL -> an array mapping proccesses to |
|
71 // windows. We do this so that we can track both what processes we have to |
|
72 // send system messages to as well as supporting the single-process case |
|
73 // where we track windows instead. |
|
74 this._listeners = {}; |
|
75 |
|
76 this._webappsRegistryReady = false; |
|
77 this._bufferedSysMsgs = []; |
|
78 |
|
79 this._cpuWakeLocks = {}; |
|
80 |
|
81 this._configurators = {}; |
|
82 |
|
83 Services.obs.addObserver(this, "xpcom-shutdown", false); |
|
84 Services.obs.addObserver(this, "webapps-registry-start", false); |
|
85 Services.obs.addObserver(this, "webapps-registry-ready", false); |
|
86 kMessages.forEach(function(aMsg) { |
|
87 ppmm.addMessageListener(aMsg, this); |
|
88 }, this); |
|
89 |
|
90 Services.obs.notifyObservers(this, "system-message-internal-ready", null); |
|
91 } |
|
92 |
|
93 SystemMessageInternal.prototype = { |
|
94 |
|
95 _getMessageConfigurator: function(aType) { |
|
96 debug("_getMessageConfigurator for type: " + aType); |
|
97 if (this._configurators[aType] === undefined) { |
|
98 let contractID = |
|
99 "@mozilla.org/dom/system-messages/configurator/" + aType + ";1"; |
|
100 if (contractID in Cc) { |
|
101 debug(contractID + " is registered, creating an instance"); |
|
102 this._configurators[aType] = |
|
103 Cc[contractID].createInstance(Ci.nsISystemMessagesConfigurator); |
|
104 } else { |
|
105 debug(contractID + "is not registered, caching the answer"); |
|
106 this._configurators[aType] = null; |
|
107 } |
|
108 } |
|
109 return this._configurators[aType] || defaultMessageConfigurator; |
|
110 }, |
|
111 |
|
112 _cancelCpuWakeLock: function(aPageKey) { |
|
113 let cpuWakeLock = this._cpuWakeLocks[aPageKey]; |
|
114 if (cpuWakeLock) { |
|
115 debug("Releasing the CPU wake lock for page key = " + aPageKey); |
|
116 cpuWakeLock.wakeLock.unlock(); |
|
117 cpuWakeLock.timer.cancel(); |
|
118 delete this._cpuWakeLocks[aPageKey]; |
|
119 } |
|
120 }, |
|
121 |
|
122 _acquireCpuWakeLock: function(aPageKey) { |
|
123 let cpuWakeLock = this._cpuWakeLocks[aPageKey]; |
|
124 if (!cpuWakeLock) { |
|
125 // We have to ensure the CPU doesn't sleep during the process of the page |
|
126 // handling the system messages, so that they can be handled on time. |
|
127 debug("Acquiring a CPU wake lock for page key = " + aPageKey); |
|
128 cpuWakeLock = this._cpuWakeLocks[aPageKey] = { |
|
129 wakeLock: powerManagerService.newWakeLock("cpu"), |
|
130 timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), |
|
131 lockCount: 1 |
|
132 }; |
|
133 } else { |
|
134 // We've already acquired the CPU wake lock for this page, |
|
135 // so just add to the lock count and extend the timeout. |
|
136 cpuWakeLock.lockCount++; |
|
137 } |
|
138 |
|
139 // Set a watchdog to avoid locking the CPU wake lock too long, |
|
140 // because it'd exhaust the battery quickly which is very bad. |
|
141 // This could probably happen if the app failed to launch or |
|
142 // handle the system messages due to any unexpected reasons. |
|
143 cpuWakeLock.timer.initWithCallback(function timerCb() { |
|
144 debug("Releasing the CPU wake lock because the system messages " + |
|
145 "were not handled by its registered page before time out."); |
|
146 this._cancelCpuWakeLock(aPageKey); |
|
147 }.bind(this), 30000, Ci.nsITimer.TYPE_ONE_SHOT); |
|
148 }, |
|
149 |
|
150 _releaseCpuWakeLock: function _releaseCpuWakeLock(aPageKey, aHandledCount) { |
|
151 let cpuWakeLock = this._cpuWakeLocks[aPageKey]; |
|
152 if (cpuWakeLock) { |
|
153 cpuWakeLock.lockCount -= aHandledCount; |
|
154 if (cpuWakeLock.lockCount <= 0) { |
|
155 debug("Unlocking the CPU wake lock now that the system messages " + |
|
156 "have been successfully handled by its registered page."); |
|
157 this._cancelCpuWakeLock(aPageKey); |
|
158 } |
|
159 } |
|
160 }, |
|
161 |
|
162 _findPage: function(aType, aPageURL, aManifestURL) { |
|
163 let page = null; |
|
164 this._pages.some(function(aPage) { |
|
165 if (this._isPageMatched(aPage, aType, aPageURL, aManifestURL)) { |
|
166 page = aPage; |
|
167 } |
|
168 return page !== null; |
|
169 }, this); |
|
170 return page; |
|
171 }, |
|
172 |
|
173 sendMessage: function(aType, aMessage, aPageURI, aManifestURI, aExtra) { |
|
174 // Buffer system messages until the webapps' registration is ready, |
|
175 // so that we can know the correct pages registered to be sent. |
|
176 if (!this._webappsRegistryReady) { |
|
177 this._bufferedSysMsgs.push({ how: "send", |
|
178 type: aType, |
|
179 msg: aMessage, |
|
180 pageURI: aPageURI, |
|
181 manifestURI: aManifestURI, |
|
182 extra: aExtra }); |
|
183 return; |
|
184 } |
|
185 |
|
186 // Give this message an ID so that we can identify the message and |
|
187 // clean it up from the pending message queue when apps receive it. |
|
188 let messageID = gUUIDGenerator.generateUUID().toString(); |
|
189 |
|
190 debug("Sending " + aType + " " + JSON.stringify(aMessage) + |
|
191 " for " + aPageURI.spec + " @ " + aManifestURI.spec + |
|
192 '; extra: ' + JSON.stringify(aExtra)); |
|
193 |
|
194 let result = this._sendMessageCommon(aType, |
|
195 aMessage, |
|
196 messageID, |
|
197 aPageURI.spec, |
|
198 aManifestURI.spec, |
|
199 aExtra); |
|
200 debug("Returned status of sending message: " + result); |
|
201 |
|
202 // Don't need to open the pages and queue the system message |
|
203 // which was not allowed to be sent. |
|
204 if (result === MSG_SENT_FAILURE_PERM_DENIED) { |
|
205 return; |
|
206 } |
|
207 |
|
208 let page = this._findPage(aType, aPageURI.spec, aManifestURI.spec); |
|
209 if (page) { |
|
210 // Queue this message in the corresponding pages. |
|
211 this._queueMessage(page, aMessage, messageID); |
|
212 |
|
213 this._openAppPage(page, aMessage, aExtra, result); |
|
214 } |
|
215 }, |
|
216 |
|
217 broadcastMessage: function(aType, aMessage, aExtra) { |
|
218 // Buffer system messages until the webapps' registration is ready, |
|
219 // so that we can know the correct pages registered to be broadcasted. |
|
220 if (!this._webappsRegistryReady) { |
|
221 this._bufferedSysMsgs.push({ how: "broadcast", |
|
222 type: aType, |
|
223 msg: aMessage, |
|
224 extra: aExtra }); |
|
225 return; |
|
226 } |
|
227 |
|
228 // Give this message an ID so that we can identify the message and |
|
229 // clean it up from the pending message queue when apps receive it. |
|
230 let messageID = gUUIDGenerator.generateUUID().toString(); |
|
231 |
|
232 debug("Broadcasting " + aType + " " + JSON.stringify(aMessage) + |
|
233 '; extra = ' + JSON.stringify(aExtra)); |
|
234 // Find pages that registered an handler for this type. |
|
235 this._pages.forEach(function(aPage) { |
|
236 if (aPage.type == aType) { |
|
237 let result = this._sendMessageCommon(aType, |
|
238 aMessage, |
|
239 messageID, |
|
240 aPage.pageURL, |
|
241 aPage.manifestURL, |
|
242 aExtra); |
|
243 debug("Returned status of sending message: " + result); |
|
244 |
|
245 |
|
246 // Don't need to open the pages and queue the system message |
|
247 // which was not allowed to be sent. |
|
248 if (result === MSG_SENT_FAILURE_PERM_DENIED) { |
|
249 return; |
|
250 } |
|
251 |
|
252 // Queue this message in the corresponding pages. |
|
253 this._queueMessage(aPage, aMessage, messageID); |
|
254 |
|
255 this._openAppPage(aPage, aMessage, aExtra, result); |
|
256 } |
|
257 }, this); |
|
258 }, |
|
259 |
|
260 registerPage: function(aType, aPageURI, aManifestURI) { |
|
261 if (!aPageURI || !aManifestURI) { |
|
262 throw Cr.NS_ERROR_INVALID_ARG; |
|
263 } |
|
264 |
|
265 let pageURL = aPageURI.spec; |
|
266 let manifestURL = aManifestURI.spec; |
|
267 |
|
268 // Don't register duplicates for this tuple. |
|
269 let page = this._findPage(aType, pageURL, manifestURL); |
|
270 if (page) { |
|
271 debug("Ignoring duplicate registration of " + |
|
272 [aType, pageURL, manifestURL]); |
|
273 return; |
|
274 } |
|
275 |
|
276 this._pages.push({ type: aType, |
|
277 pageURL: pageURL, |
|
278 manifestURL: manifestURL, |
|
279 pendingMessages: [] }); |
|
280 }, |
|
281 |
|
282 _findTargetIndex: function(aTargets, aTarget) { |
|
283 if (!aTargets || !aTarget) { |
|
284 return -1; |
|
285 } |
|
286 for (let index = 0; index < aTargets.length; ++index) { |
|
287 let target = aTargets[index]; |
|
288 if (target.target === aTarget) { |
|
289 return index; |
|
290 } |
|
291 } |
|
292 return -1; |
|
293 }, |
|
294 |
|
295 _isEmptyObject: function(aObj) { |
|
296 for (let name in aObj) { |
|
297 return false; |
|
298 } |
|
299 return true; |
|
300 }, |
|
301 |
|
302 _removeTargetFromListener: function(aTarget, |
|
303 aManifestURL, |
|
304 aRemoveListener, |
|
305 aPageURL) { |
|
306 let targets = this._listeners[aManifestURL]; |
|
307 if (!targets) { |
|
308 return false; |
|
309 } |
|
310 |
|
311 let index = this._findTargetIndex(targets, aTarget); |
|
312 if (index === -1) { |
|
313 return false; |
|
314 } |
|
315 |
|
316 if (aRemoveListener) { |
|
317 debug("remove the listener for " + aManifestURL); |
|
318 delete this._listeners[aManifestURL]; |
|
319 return true; |
|
320 } |
|
321 |
|
322 let target = targets[index]; |
|
323 if (aPageURL && target.winCounts[aPageURL] !== undefined && |
|
324 --target.winCounts[aPageURL] === 0) { |
|
325 delete target.winCounts[aPageURL]; |
|
326 } |
|
327 |
|
328 if (this._isEmptyObject(target.winCounts)) { |
|
329 if (targets.length === 1) { |
|
330 // If it's the only one, get rid of the entry of manifest URL entirely. |
|
331 debug("remove the listener for " + aManifestURL); |
|
332 delete this._listeners[aManifestURL]; |
|
333 } else { |
|
334 // If more than one left, remove this one and leave the rest. |
|
335 targets.splice(index, 1); |
|
336 } |
|
337 } |
|
338 return true; |
|
339 }, |
|
340 |
|
341 receiveMessage: function(aMessage) { |
|
342 let msg = aMessage.json; |
|
343 |
|
344 // To prevent the hacked child process from sending commands to parent |
|
345 // to manage system messages, we need to check its manifest URL. |
|
346 if (["SystemMessageManager:Register", |
|
347 // TODO: fix bug 988142 to re-enable. |
|
348 // "SystemMessageManager:Unregister", |
|
349 "SystemMessageManager:GetPendingMessages", |
|
350 "SystemMessageManager:HasPendingMessages", |
|
351 "SystemMessageManager:Message:Return:OK", |
|
352 "SystemMessageManager:HandleMessagesDone"].indexOf(aMessage.name) != -1) { |
|
353 if (!aMessage.target.assertContainApp(msg.manifestURL)) { |
|
354 debug("Got message from a child process containing illegal manifest URL."); |
|
355 return null; |
|
356 } |
|
357 } |
|
358 |
|
359 switch(aMessage.name) { |
|
360 case "SystemMessageManager:AskReadyToRegister": |
|
361 return true; |
|
362 break; |
|
363 case "SystemMessageManager:Register": |
|
364 { |
|
365 debug("Got Register from " + msg.pageURL + " @ " + msg.manifestURL); |
|
366 let pageURL = msg.pageURL; |
|
367 let targets, index; |
|
368 if (!(targets = this._listeners[msg.manifestURL])) { |
|
369 let winCounts = {}; |
|
370 winCounts[pageURL] = 1; |
|
371 this._listeners[msg.manifestURL] = [{ target: aMessage.target, |
|
372 winCounts: winCounts }]; |
|
373 } else if ((index = this._findTargetIndex(targets, |
|
374 aMessage.target)) === -1) { |
|
375 let winCounts = {}; |
|
376 winCounts[pageURL] = 1; |
|
377 targets.push({ target: aMessage.target, |
|
378 winCounts: winCounts }); |
|
379 } else { |
|
380 let winCounts = targets[index].winCounts; |
|
381 if (winCounts[pageURL] === undefined) { |
|
382 winCounts[pageURL] = 1; |
|
383 } else { |
|
384 winCounts[pageURL]++; |
|
385 } |
|
386 } |
|
387 |
|
388 debug("listeners for " + msg.manifestURL + |
|
389 " innerWinID " + msg.innerWindowID); |
|
390 break; |
|
391 } |
|
392 case "child-process-shutdown": |
|
393 { |
|
394 debug("Got child-process-shutdown from " + aMessage.target); |
|
395 for (let manifestURL in this._listeners) { |
|
396 // See if any processes in this manifest URL have this target. |
|
397 this._removeTargetFromListener(aMessage.target, |
|
398 manifestURL, |
|
399 true, |
|
400 null); |
|
401 } |
|
402 break; |
|
403 } |
|
404 case "SystemMessageManager:Unregister": |
|
405 { |
|
406 debug("Got Unregister from " + aMessage.target + |
|
407 " innerWinID " + msg.innerWindowID); |
|
408 this._removeTargetFromListener(aMessage.target, |
|
409 msg.manifestURL, |
|
410 false, |
|
411 msg.pageURL); |
|
412 break; |
|
413 } |
|
414 case "SystemMessageManager:GetPendingMessages": |
|
415 { |
|
416 debug("received SystemMessageManager:GetPendingMessages " + msg.type + |
|
417 " for " + msg.pageURL + " @ " + msg.manifestURL); |
|
418 |
|
419 // This is a sync call used to return the pending messages for a page. |
|
420 // Find the right page to get its corresponding pending messages. |
|
421 let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); |
|
422 if (!page) { |
|
423 return; |
|
424 } |
|
425 |
|
426 // Return the |msg| of each pending message (drop the |msgID|). |
|
427 let pendingMessages = []; |
|
428 page.pendingMessages.forEach(function(aMessage) { |
|
429 pendingMessages.push(aMessage.msg); |
|
430 }); |
|
431 |
|
432 // Clear the pending queue for this page. This is OK since we'll store |
|
433 // pending messages in the content process (|SystemMessageManager|). |
|
434 page.pendingMessages.length = 0; |
|
435 |
|
436 // Send the array of pending messages. |
|
437 aMessage.target |
|
438 .sendAsyncMessage("SystemMessageManager:GetPendingMessages:Return", |
|
439 { type: msg.type, |
|
440 manifestURL: msg.manifestURL, |
|
441 pageURL: msg.pageURL, |
|
442 msgQueue: pendingMessages }); |
|
443 break; |
|
444 } |
|
445 case "SystemMessageManager:HasPendingMessages": |
|
446 { |
|
447 debug("received SystemMessageManager:HasPendingMessages " + msg.type + |
|
448 " for " + msg.pageURL + " @ " + msg.manifestURL); |
|
449 |
|
450 // This is a sync call used to return if a page has pending messages. |
|
451 // Find the right page to get its corresponding pending messages. |
|
452 let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); |
|
453 if (!page) { |
|
454 return false; |
|
455 } |
|
456 |
|
457 return page.pendingMessages.length != 0; |
|
458 break; |
|
459 } |
|
460 case "SystemMessageManager:Message:Return:OK": |
|
461 { |
|
462 debug("received SystemMessageManager:Message:Return:OK " + msg.type + |
|
463 " for " + msg.pageURL + " @ " + msg.manifestURL); |
|
464 |
|
465 // We need to clean up the pending message since the app has already |
|
466 // received it, thus avoiding the re-lanunched app handling it again. |
|
467 let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); |
|
468 if (page) { |
|
469 let pendingMessages = page.pendingMessages; |
|
470 for (let i = 0; i < pendingMessages.length; i++) { |
|
471 if (pendingMessages[i].msgID === msg.msgID) { |
|
472 pendingMessages.splice(i, 1); |
|
473 break; |
|
474 } |
|
475 } |
|
476 } |
|
477 break; |
|
478 } |
|
479 case "SystemMessageManager:HandleMessagesDone": |
|
480 { |
|
481 debug("received SystemMessageManager:HandleMessagesDone " + msg.type + |
|
482 " with " + msg.handledCount + " for " + msg.pageURL + |
|
483 " @ " + msg.manifestURL); |
|
484 |
|
485 // A page has finished handling some of its system messages, so we try |
|
486 // to release the CPU wake lock we acquired on behalf of that page. |
|
487 this._releaseCpuWakeLock(this._createKeyForPage(msg), msg.handledCount); |
|
488 break; |
|
489 } |
|
490 } |
|
491 }, |
|
492 |
|
493 observe: function(aSubject, aTopic, aData) { |
|
494 switch (aTopic) { |
|
495 case "xpcom-shutdown": |
|
496 kMessages.forEach(function(aMsg) { |
|
497 ppmm.removeMessageListener(aMsg, this); |
|
498 }, this); |
|
499 Services.obs.removeObserver(this, "xpcom-shutdown"); |
|
500 Services.obs.removeObserver(this, "webapps-registry-start"); |
|
501 Services.obs.removeObserver(this, "webapps-registry-ready"); |
|
502 ppmm = null; |
|
503 this._pages = null; |
|
504 this._bufferedSysMsgs = null; |
|
505 break; |
|
506 case "webapps-registry-start": |
|
507 this._webappsRegistryReady = false; |
|
508 break; |
|
509 case "webapps-registry-ready": |
|
510 // After the webapps' registration has been done for sure, |
|
511 // re-fire the buffered system messages if there is any. |
|
512 this._webappsRegistryReady = true; |
|
513 this._bufferedSysMsgs.forEach(function(aSysMsg) { |
|
514 switch (aSysMsg.how) { |
|
515 case "send": |
|
516 this.sendMessage( |
|
517 aSysMsg.type, aSysMsg.msg, |
|
518 aSysMsg.pageURI, aSysMsg.manifestURI, aSysMsg.extra); |
|
519 break; |
|
520 case "broadcast": |
|
521 this.broadcastMessage(aSysMsg.type, aSysMsg.msg, aSysMsg.extra); |
|
522 break; |
|
523 } |
|
524 }, this); |
|
525 this._bufferedSysMsgs.length = 0; |
|
526 break; |
|
527 } |
|
528 }, |
|
529 |
|
530 _queueMessage: function(aPage, aMessage, aMessageID) { |
|
531 // Queue the message for this page because we've never known if an app is |
|
532 // opened or not. We'll clean it up when the app has already received it. |
|
533 aPage.pendingMessages.push({ msg: aMessage, msgID: aMessageID }); |
|
534 if (aPage.pendingMessages.length > kMaxPendingMessages) { |
|
535 aPage.pendingMessages.splice(0, 1); |
|
536 } |
|
537 }, |
|
538 |
|
539 _openAppPage: function(aPage, aMessage, aExtra, aMsgSentStatus) { |
|
540 // This means the app must be brought to the foreground. |
|
541 let showApp = this._getMessageConfigurator(aPage.type).mustShowRunningApp; |
|
542 |
|
543 // We should send the open-app message if the system message was |
|
544 // not sent, or if it was sent but we should show the app anyway. |
|
545 if ((aMsgSentStatus === MSG_SENT_SUCCESS) && !showApp) { |
|
546 return; |
|
547 } |
|
548 |
|
549 // This flag means the app must *only* be brought to the foreground |
|
550 // and we don't need to load the app to handle messages. |
|
551 let onlyShowApp = (aMsgSentStatus === MSG_SENT_SUCCESS) && showApp; |
|
552 |
|
553 // We don't need to send the full object to observers. |
|
554 let page = { pageURL: aPage.pageURL, |
|
555 manifestURL: aPage.manifestURL, |
|
556 type: aPage.type, |
|
557 extra: aExtra, |
|
558 target: aMessage.target, |
|
559 onlyShowApp: onlyShowApp, |
|
560 showApp: showApp }; |
|
561 debug("Asking to open " + JSON.stringify(page)); |
|
562 Services.obs.notifyObservers(this, |
|
563 "system-messages-open-app", |
|
564 JSON.stringify(page)); |
|
565 }, |
|
566 |
|
567 _isPageMatched: function(aPage, aType, aPageURL, aManifestURL) { |
|
568 return (aPage.type === aType && |
|
569 aPage.manifestURL === aManifestURL && |
|
570 aPage.pageURL === aPageURL) |
|
571 }, |
|
572 |
|
573 _createKeyForPage: function _createKeyForPage(aPage) { |
|
574 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
|
575 .createInstance(Ci.nsIScriptableUnicodeConverter); |
|
576 converter.charset = "UTF-8"; |
|
577 |
|
578 let hasher = Cc["@mozilla.org/security/hash;1"] |
|
579 .createInstance(Ci.nsICryptoHash); |
|
580 hasher.init(hasher.SHA1); |
|
581 |
|
582 // add manifest/page URL and action to the hash |
|
583 ["type", "manifestURL", "pageURL"].forEach(function(aProp) { |
|
584 let data = converter.convertToByteArray(aPage[aProp], {}); |
|
585 hasher.update(data, data.length); |
|
586 }); |
|
587 |
|
588 return hasher.finish(true); |
|
589 }, |
|
590 |
|
591 _sendMessageCommon: function(aType, aMessage, aMessageID, |
|
592 aPageURL, aManifestURL, aExtra) { |
|
593 // Don't send the system message not granted by the app's permissions. |
|
594 if (!SystemMessagePermissionsChecker |
|
595 .isSystemMessagePermittedToSend(aType, |
|
596 aPageURL, |
|
597 aManifestURL)) { |
|
598 return MSG_SENT_FAILURE_PERM_DENIED; |
|
599 } |
|
600 |
|
601 let appPageIsRunning = false; |
|
602 let pageKey = this._createKeyForPage({ type: aType, |
|
603 manifestURL: aManifestURL, |
|
604 pageURL: aPageURL }); |
|
605 |
|
606 let targets = this._listeners[aManifestURL]; |
|
607 if (targets) { |
|
608 for (let index = 0; index < targets.length; ++index) { |
|
609 let target = targets[index]; |
|
610 // We only need to send the system message to the targets (processes) |
|
611 // which contain the window page that matches the manifest/page URL of |
|
612 // the destination of system message. |
|
613 if (target.winCounts[aPageURL] === undefined) { |
|
614 continue; |
|
615 } |
|
616 |
|
617 appPageIsRunning = true; |
|
618 // We need to acquire a CPU wake lock for that page and expect that |
|
619 // we'll receive a "SystemMessageManager:HandleMessagesDone" message |
|
620 // when the page finishes handling the system message. At that point, |
|
621 // we'll release the lock we acquired. |
|
622 this._acquireCpuWakeLock(pageKey); |
|
623 |
|
624 // Multiple windows can share the same target (process), the content |
|
625 // window needs to check if the manifest/page URL is matched. Only |
|
626 // *one* window should handle the system message. |
|
627 let manager = target.target; |
|
628 manager.sendAsyncMessage("SystemMessageManager:Message", |
|
629 { type: aType, |
|
630 msg: aMessage, |
|
631 manifestURL: aManifestURL, |
|
632 pageURL: aPageURL, |
|
633 msgID: aMessageID }); |
|
634 } |
|
635 } |
|
636 |
|
637 if (!appPageIsRunning) { |
|
638 // The app page isn't running and relies on the 'open-app' chrome event to |
|
639 // wake it up. We still need to acquire a CPU wake lock for that page and |
|
640 // expect that we will receive a "SystemMessageManager:HandleMessagesDone" |
|
641 // message when the page finishes handling the system message with other |
|
642 // pending messages. At that point, we'll release the lock we acquired. |
|
643 this._acquireCpuWakeLock(pageKey); |
|
644 return MSG_SENT_FAILURE_APP_NOT_RUNNING; |
|
645 } else { |
|
646 return MSG_SENT_SUCCESS; |
|
647 } |
|
648 |
|
649 }, |
|
650 |
|
651 classID: Components.ID("{70589ca5-91ac-4b9e-b839-d6a88167d714}"), |
|
652 |
|
653 QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesInternal, |
|
654 Ci.nsIObserver]) |
|
655 } |
|
656 |
|
657 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageInternal]); |