Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/. */
5 "use strict";
7 const Cc = Components.classes;
8 const Ci = Components.interfaces;
9 const Cu = Components.utils;
10 const Cr = Components.results;
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
13 Cu.import("resource://gre/modules/Services.jsm");
14 Cu.import("resource://gre/modules/SystemMessagePermissionsChecker.jsm");
16 XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
17 "@mozilla.org/parentprocessmessagemanager;1",
18 "nsIMessageBroadcaster");
20 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
21 "@mozilla.org/uuid-generator;1",
22 "nsIUUIDGenerator");
24 XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService",
25 "@mozilla.org/power/powermanagerservice;1",
26 "nsIPowerManagerService");
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 }
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"]
47 function debug(aMsg) {
48 // dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n");
49 }
52 let defaultMessageConfigurator = {
53 get mustShowRunningApp() {
54 return false;
55 }
56 };
58 const MSG_SENT_SUCCESS = 0;
59 const MSG_SENT_FAILURE_PERM_DENIED = 1;
60 const MSG_SENT_FAILURE_APP_NOT_RUNNING = 2;
62 // Implementation of the component used by internal users.
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 = [];
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 = {};
76 this._webappsRegistryReady = false;
77 this._bufferedSysMsgs = [];
79 this._cpuWakeLocks = {};
81 this._configurators = {};
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);
90 Services.obs.notifyObservers(this, "system-message-internal-ready", null);
91 }
93 SystemMessageInternal.prototype = {
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 },
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 },
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 }
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 },
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 },
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 },
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 }
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();
190 debug("Sending " + aType + " " + JSON.stringify(aMessage) +
191 " for " + aPageURI.spec + " @ " + aManifestURI.spec +
192 '; extra: ' + JSON.stringify(aExtra));
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);
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 }
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);
213 this._openAppPage(page, aMessage, aExtra, result);
214 }
215 },
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 }
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();
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);
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 }
252 // Queue this message in the corresponding pages.
253 this._queueMessage(aPage, aMessage, messageID);
255 this._openAppPage(aPage, aMessage, aExtra, result);
256 }
257 }, this);
258 },
260 registerPage: function(aType, aPageURI, aManifestURI) {
261 if (!aPageURI || !aManifestURI) {
262 throw Cr.NS_ERROR_INVALID_ARG;
263 }
265 let pageURL = aPageURI.spec;
266 let manifestURL = aManifestURI.spec;
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 }
276 this._pages.push({ type: aType,
277 pageURL: pageURL,
278 manifestURL: manifestURL,
279 pendingMessages: [] });
280 },
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 },
295 _isEmptyObject: function(aObj) {
296 for (let name in aObj) {
297 return false;
298 }
299 return true;
300 },
302 _removeTargetFromListener: function(aTarget,
303 aManifestURL,
304 aRemoveListener,
305 aPageURL) {
306 let targets = this._listeners[aManifestURL];
307 if (!targets) {
308 return false;
309 }
311 let index = this._findTargetIndex(targets, aTarget);
312 if (index === -1) {
313 return false;
314 }
316 if (aRemoveListener) {
317 debug("remove the listener for " + aManifestURL);
318 delete this._listeners[aManifestURL];
319 return true;
320 }
322 let target = targets[index];
323 if (aPageURL && target.winCounts[aPageURL] !== undefined &&
324 --target.winCounts[aPageURL] === 0) {
325 delete target.winCounts[aPageURL];
326 }
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 },
341 receiveMessage: function(aMessage) {
342 let msg = aMessage.json;
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 }
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 }
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);
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 }
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 });
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;
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);
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 }
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);
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);
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 },
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 },
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 },
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;
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 }
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;
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 },
567 _isPageMatched: function(aPage, aType, aPageURL, aManifestURL) {
568 return (aPage.type === aType &&
569 aPage.manifestURL === aManifestURL &&
570 aPage.pageURL === aPageURL)
571 },
573 _createKeyForPage: function _createKeyForPage(aPage) {
574 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
575 .createInstance(Ci.nsIScriptableUnicodeConverter);
576 converter.charset = "UTF-8";
578 let hasher = Cc["@mozilla.org/security/hash;1"]
579 .createInstance(Ci.nsICryptoHash);
580 hasher.init(hasher.SHA1);
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 });
588 return hasher.finish(true);
589 },
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 }
601 let appPageIsRunning = false;
602 let pageKey = this._createKeyForPage({ type: aType,
603 manifestURL: aManifestURL,
604 pageURL: aPageURL });
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 }
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);
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 }
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 }
649 },
651 classID: Components.ID("{70589ca5-91ac-4b9e-b839-d6a88167d714}"),
653 QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesInternal,
654 Ci.nsIObserver])
655 }
657 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageInternal]);