1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/messages/SystemMessageInternal.js Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,657 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const Cc = Components.classes; 1.11 +const Ci = Components.interfaces; 1.12 +const Cu = Components.utils; 1.13 +const Cr = Components.results; 1.14 + 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource://gre/modules/SystemMessagePermissionsChecker.jsm"); 1.18 + 1.19 +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", 1.20 + "@mozilla.org/parentprocessmessagemanager;1", 1.21 + "nsIMessageBroadcaster"); 1.22 + 1.23 +XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", 1.24 + "@mozilla.org/uuid-generator;1", 1.25 + "nsIUUIDGenerator"); 1.26 + 1.27 +XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService", 1.28 + "@mozilla.org/power/powermanagerservice;1", 1.29 + "nsIPowerManagerService"); 1.30 + 1.31 +// Limit the number of pending messages for a given page. 1.32 +let kMaxPendingMessages; 1.33 +try { 1.34 + kMaxPendingMessages = 1.35 + Services.prefs.getIntPref("dom.messages.maxPendingMessages"); 1.36 +} catch(e) { 1.37 + // getIntPref throws when the pref is not set. 1.38 + kMaxPendingMessages = 5; 1.39 +} 1.40 + 1.41 +const kMessages =["SystemMessageManager:GetPendingMessages", 1.42 + "SystemMessageManager:HasPendingMessages", 1.43 + "SystemMessageManager:Register", 1.44 + "SystemMessageManager:Unregister", 1.45 + "SystemMessageManager:Message:Return:OK", 1.46 + "SystemMessageManager:AskReadyToRegister", 1.47 + "SystemMessageManager:HandleMessagesDone", 1.48 + "child-process-shutdown"] 1.49 + 1.50 +function debug(aMsg) { 1.51 + // dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n"); 1.52 +} 1.53 + 1.54 + 1.55 +let defaultMessageConfigurator = { 1.56 + get mustShowRunningApp() { 1.57 + return false; 1.58 + } 1.59 +}; 1.60 + 1.61 +const MSG_SENT_SUCCESS = 0; 1.62 +const MSG_SENT_FAILURE_PERM_DENIED = 1; 1.63 +const MSG_SENT_FAILURE_APP_NOT_RUNNING = 2; 1.64 + 1.65 +// Implementation of the component used by internal users. 1.66 + 1.67 +function SystemMessageInternal() { 1.68 + // The set of pages registered by installed apps. We keep the 1.69 + // list of pending messages for each page here also. 1.70 + this._pages = []; 1.71 + 1.72 + // The set of listeners. This is a multi-dimensional object. The _listeners 1.73 + // object itself is a map from manifest URL -> an array mapping proccesses to 1.74 + // windows. We do this so that we can track both what processes we have to 1.75 + // send system messages to as well as supporting the single-process case 1.76 + // where we track windows instead. 1.77 + this._listeners = {}; 1.78 + 1.79 + this._webappsRegistryReady = false; 1.80 + this._bufferedSysMsgs = []; 1.81 + 1.82 + this._cpuWakeLocks = {}; 1.83 + 1.84 + this._configurators = {}; 1.85 + 1.86 + Services.obs.addObserver(this, "xpcom-shutdown", false); 1.87 + Services.obs.addObserver(this, "webapps-registry-start", false); 1.88 + Services.obs.addObserver(this, "webapps-registry-ready", false); 1.89 + kMessages.forEach(function(aMsg) { 1.90 + ppmm.addMessageListener(aMsg, this); 1.91 + }, this); 1.92 + 1.93 + Services.obs.notifyObservers(this, "system-message-internal-ready", null); 1.94 +} 1.95 + 1.96 +SystemMessageInternal.prototype = { 1.97 + 1.98 + _getMessageConfigurator: function(aType) { 1.99 + debug("_getMessageConfigurator for type: " + aType); 1.100 + if (this._configurators[aType] === undefined) { 1.101 + let contractID = 1.102 + "@mozilla.org/dom/system-messages/configurator/" + aType + ";1"; 1.103 + if (contractID in Cc) { 1.104 + debug(contractID + " is registered, creating an instance"); 1.105 + this._configurators[aType] = 1.106 + Cc[contractID].createInstance(Ci.nsISystemMessagesConfigurator); 1.107 + } else { 1.108 + debug(contractID + "is not registered, caching the answer"); 1.109 + this._configurators[aType] = null; 1.110 + } 1.111 + } 1.112 + return this._configurators[aType] || defaultMessageConfigurator; 1.113 + }, 1.114 + 1.115 + _cancelCpuWakeLock: function(aPageKey) { 1.116 + let cpuWakeLock = this._cpuWakeLocks[aPageKey]; 1.117 + if (cpuWakeLock) { 1.118 + debug("Releasing the CPU wake lock for page key = " + aPageKey); 1.119 + cpuWakeLock.wakeLock.unlock(); 1.120 + cpuWakeLock.timer.cancel(); 1.121 + delete this._cpuWakeLocks[aPageKey]; 1.122 + } 1.123 + }, 1.124 + 1.125 + _acquireCpuWakeLock: function(aPageKey) { 1.126 + let cpuWakeLock = this._cpuWakeLocks[aPageKey]; 1.127 + if (!cpuWakeLock) { 1.128 + // We have to ensure the CPU doesn't sleep during the process of the page 1.129 + // handling the system messages, so that they can be handled on time. 1.130 + debug("Acquiring a CPU wake lock for page key = " + aPageKey); 1.131 + cpuWakeLock = this._cpuWakeLocks[aPageKey] = { 1.132 + wakeLock: powerManagerService.newWakeLock("cpu"), 1.133 + timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), 1.134 + lockCount: 1 1.135 + }; 1.136 + } else { 1.137 + // We've already acquired the CPU wake lock for this page, 1.138 + // so just add to the lock count and extend the timeout. 1.139 + cpuWakeLock.lockCount++; 1.140 + } 1.141 + 1.142 + // Set a watchdog to avoid locking the CPU wake lock too long, 1.143 + // because it'd exhaust the battery quickly which is very bad. 1.144 + // This could probably happen if the app failed to launch or 1.145 + // handle the system messages due to any unexpected reasons. 1.146 + cpuWakeLock.timer.initWithCallback(function timerCb() { 1.147 + debug("Releasing the CPU wake lock because the system messages " + 1.148 + "were not handled by its registered page before time out."); 1.149 + this._cancelCpuWakeLock(aPageKey); 1.150 + }.bind(this), 30000, Ci.nsITimer.TYPE_ONE_SHOT); 1.151 + }, 1.152 + 1.153 + _releaseCpuWakeLock: function _releaseCpuWakeLock(aPageKey, aHandledCount) { 1.154 + let cpuWakeLock = this._cpuWakeLocks[aPageKey]; 1.155 + if (cpuWakeLock) { 1.156 + cpuWakeLock.lockCount -= aHandledCount; 1.157 + if (cpuWakeLock.lockCount <= 0) { 1.158 + debug("Unlocking the CPU wake lock now that the system messages " + 1.159 + "have been successfully handled by its registered page."); 1.160 + this._cancelCpuWakeLock(aPageKey); 1.161 + } 1.162 + } 1.163 + }, 1.164 + 1.165 + _findPage: function(aType, aPageURL, aManifestURL) { 1.166 + let page = null; 1.167 + this._pages.some(function(aPage) { 1.168 + if (this._isPageMatched(aPage, aType, aPageURL, aManifestURL)) { 1.169 + page = aPage; 1.170 + } 1.171 + return page !== null; 1.172 + }, this); 1.173 + return page; 1.174 + }, 1.175 + 1.176 + sendMessage: function(aType, aMessage, aPageURI, aManifestURI, aExtra) { 1.177 + // Buffer system messages until the webapps' registration is ready, 1.178 + // so that we can know the correct pages registered to be sent. 1.179 + if (!this._webappsRegistryReady) { 1.180 + this._bufferedSysMsgs.push({ how: "send", 1.181 + type: aType, 1.182 + msg: aMessage, 1.183 + pageURI: aPageURI, 1.184 + manifestURI: aManifestURI, 1.185 + extra: aExtra }); 1.186 + return; 1.187 + } 1.188 + 1.189 + // Give this message an ID so that we can identify the message and 1.190 + // clean it up from the pending message queue when apps receive it. 1.191 + let messageID = gUUIDGenerator.generateUUID().toString(); 1.192 + 1.193 + debug("Sending " + aType + " " + JSON.stringify(aMessage) + 1.194 + " for " + aPageURI.spec + " @ " + aManifestURI.spec + 1.195 + '; extra: ' + JSON.stringify(aExtra)); 1.196 + 1.197 + let result = this._sendMessageCommon(aType, 1.198 + aMessage, 1.199 + messageID, 1.200 + aPageURI.spec, 1.201 + aManifestURI.spec, 1.202 + aExtra); 1.203 + debug("Returned status of sending message: " + result); 1.204 + 1.205 + // Don't need to open the pages and queue the system message 1.206 + // which was not allowed to be sent. 1.207 + if (result === MSG_SENT_FAILURE_PERM_DENIED) { 1.208 + return; 1.209 + } 1.210 + 1.211 + let page = this._findPage(aType, aPageURI.spec, aManifestURI.spec); 1.212 + if (page) { 1.213 + // Queue this message in the corresponding pages. 1.214 + this._queueMessage(page, aMessage, messageID); 1.215 + 1.216 + this._openAppPage(page, aMessage, aExtra, result); 1.217 + } 1.218 + }, 1.219 + 1.220 + broadcastMessage: function(aType, aMessage, aExtra) { 1.221 + // Buffer system messages until the webapps' registration is ready, 1.222 + // so that we can know the correct pages registered to be broadcasted. 1.223 + if (!this._webappsRegistryReady) { 1.224 + this._bufferedSysMsgs.push({ how: "broadcast", 1.225 + type: aType, 1.226 + msg: aMessage, 1.227 + extra: aExtra }); 1.228 + return; 1.229 + } 1.230 + 1.231 + // Give this message an ID so that we can identify the message and 1.232 + // clean it up from the pending message queue when apps receive it. 1.233 + let messageID = gUUIDGenerator.generateUUID().toString(); 1.234 + 1.235 + debug("Broadcasting " + aType + " " + JSON.stringify(aMessage) + 1.236 + '; extra = ' + JSON.stringify(aExtra)); 1.237 + // Find pages that registered an handler for this type. 1.238 + this._pages.forEach(function(aPage) { 1.239 + if (aPage.type == aType) { 1.240 + let result = this._sendMessageCommon(aType, 1.241 + aMessage, 1.242 + messageID, 1.243 + aPage.pageURL, 1.244 + aPage.manifestURL, 1.245 + aExtra); 1.246 + debug("Returned status of sending message: " + result); 1.247 + 1.248 + 1.249 + // Don't need to open the pages and queue the system message 1.250 + // which was not allowed to be sent. 1.251 + if (result === MSG_SENT_FAILURE_PERM_DENIED) { 1.252 + return; 1.253 + } 1.254 + 1.255 + // Queue this message in the corresponding pages. 1.256 + this._queueMessage(aPage, aMessage, messageID); 1.257 + 1.258 + this._openAppPage(aPage, aMessage, aExtra, result); 1.259 + } 1.260 + }, this); 1.261 + }, 1.262 + 1.263 + registerPage: function(aType, aPageURI, aManifestURI) { 1.264 + if (!aPageURI || !aManifestURI) { 1.265 + throw Cr.NS_ERROR_INVALID_ARG; 1.266 + } 1.267 + 1.268 + let pageURL = aPageURI.spec; 1.269 + let manifestURL = aManifestURI.spec; 1.270 + 1.271 + // Don't register duplicates for this tuple. 1.272 + let page = this._findPage(aType, pageURL, manifestURL); 1.273 + if (page) { 1.274 + debug("Ignoring duplicate registration of " + 1.275 + [aType, pageURL, manifestURL]); 1.276 + return; 1.277 + } 1.278 + 1.279 + this._pages.push({ type: aType, 1.280 + pageURL: pageURL, 1.281 + manifestURL: manifestURL, 1.282 + pendingMessages: [] }); 1.283 + }, 1.284 + 1.285 + _findTargetIndex: function(aTargets, aTarget) { 1.286 + if (!aTargets || !aTarget) { 1.287 + return -1; 1.288 + } 1.289 + for (let index = 0; index < aTargets.length; ++index) { 1.290 + let target = aTargets[index]; 1.291 + if (target.target === aTarget) { 1.292 + return index; 1.293 + } 1.294 + } 1.295 + return -1; 1.296 + }, 1.297 + 1.298 + _isEmptyObject: function(aObj) { 1.299 + for (let name in aObj) { 1.300 + return false; 1.301 + } 1.302 + return true; 1.303 + }, 1.304 + 1.305 + _removeTargetFromListener: function(aTarget, 1.306 + aManifestURL, 1.307 + aRemoveListener, 1.308 + aPageURL) { 1.309 + let targets = this._listeners[aManifestURL]; 1.310 + if (!targets) { 1.311 + return false; 1.312 + } 1.313 + 1.314 + let index = this._findTargetIndex(targets, aTarget); 1.315 + if (index === -1) { 1.316 + return false; 1.317 + } 1.318 + 1.319 + if (aRemoveListener) { 1.320 + debug("remove the listener for " + aManifestURL); 1.321 + delete this._listeners[aManifestURL]; 1.322 + return true; 1.323 + } 1.324 + 1.325 + let target = targets[index]; 1.326 + if (aPageURL && target.winCounts[aPageURL] !== undefined && 1.327 + --target.winCounts[aPageURL] === 0) { 1.328 + delete target.winCounts[aPageURL]; 1.329 + } 1.330 + 1.331 + if (this._isEmptyObject(target.winCounts)) { 1.332 + if (targets.length === 1) { 1.333 + // If it's the only one, get rid of the entry of manifest URL entirely. 1.334 + debug("remove the listener for " + aManifestURL); 1.335 + delete this._listeners[aManifestURL]; 1.336 + } else { 1.337 + // If more than one left, remove this one and leave the rest. 1.338 + targets.splice(index, 1); 1.339 + } 1.340 + } 1.341 + return true; 1.342 + }, 1.343 + 1.344 + receiveMessage: function(aMessage) { 1.345 + let msg = aMessage.json; 1.346 + 1.347 + // To prevent the hacked child process from sending commands to parent 1.348 + // to manage system messages, we need to check its manifest URL. 1.349 + if (["SystemMessageManager:Register", 1.350 + // TODO: fix bug 988142 to re-enable. 1.351 + // "SystemMessageManager:Unregister", 1.352 + "SystemMessageManager:GetPendingMessages", 1.353 + "SystemMessageManager:HasPendingMessages", 1.354 + "SystemMessageManager:Message:Return:OK", 1.355 + "SystemMessageManager:HandleMessagesDone"].indexOf(aMessage.name) != -1) { 1.356 + if (!aMessage.target.assertContainApp(msg.manifestURL)) { 1.357 + debug("Got message from a child process containing illegal manifest URL."); 1.358 + return null; 1.359 + } 1.360 + } 1.361 + 1.362 + switch(aMessage.name) { 1.363 + case "SystemMessageManager:AskReadyToRegister": 1.364 + return true; 1.365 + break; 1.366 + case "SystemMessageManager:Register": 1.367 + { 1.368 + debug("Got Register from " + msg.pageURL + " @ " + msg.manifestURL); 1.369 + let pageURL = msg.pageURL; 1.370 + let targets, index; 1.371 + if (!(targets = this._listeners[msg.manifestURL])) { 1.372 + let winCounts = {}; 1.373 + winCounts[pageURL] = 1; 1.374 + this._listeners[msg.manifestURL] = [{ target: aMessage.target, 1.375 + winCounts: winCounts }]; 1.376 + } else if ((index = this._findTargetIndex(targets, 1.377 + aMessage.target)) === -1) { 1.378 + let winCounts = {}; 1.379 + winCounts[pageURL] = 1; 1.380 + targets.push({ target: aMessage.target, 1.381 + winCounts: winCounts }); 1.382 + } else { 1.383 + let winCounts = targets[index].winCounts; 1.384 + if (winCounts[pageURL] === undefined) { 1.385 + winCounts[pageURL] = 1; 1.386 + } else { 1.387 + winCounts[pageURL]++; 1.388 + } 1.389 + } 1.390 + 1.391 + debug("listeners for " + msg.manifestURL + 1.392 + " innerWinID " + msg.innerWindowID); 1.393 + break; 1.394 + } 1.395 + case "child-process-shutdown": 1.396 + { 1.397 + debug("Got child-process-shutdown from " + aMessage.target); 1.398 + for (let manifestURL in this._listeners) { 1.399 + // See if any processes in this manifest URL have this target. 1.400 + this._removeTargetFromListener(aMessage.target, 1.401 + manifestURL, 1.402 + true, 1.403 + null); 1.404 + } 1.405 + break; 1.406 + } 1.407 + case "SystemMessageManager:Unregister": 1.408 + { 1.409 + debug("Got Unregister from " + aMessage.target + 1.410 + " innerWinID " + msg.innerWindowID); 1.411 + this._removeTargetFromListener(aMessage.target, 1.412 + msg.manifestURL, 1.413 + false, 1.414 + msg.pageURL); 1.415 + break; 1.416 + } 1.417 + case "SystemMessageManager:GetPendingMessages": 1.418 + { 1.419 + debug("received SystemMessageManager:GetPendingMessages " + msg.type + 1.420 + " for " + msg.pageURL + " @ " + msg.manifestURL); 1.421 + 1.422 + // This is a sync call used to return the pending messages for a page. 1.423 + // Find the right page to get its corresponding pending messages. 1.424 + let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); 1.425 + if (!page) { 1.426 + return; 1.427 + } 1.428 + 1.429 + // Return the |msg| of each pending message (drop the |msgID|). 1.430 + let pendingMessages = []; 1.431 + page.pendingMessages.forEach(function(aMessage) { 1.432 + pendingMessages.push(aMessage.msg); 1.433 + }); 1.434 + 1.435 + // Clear the pending queue for this page. This is OK since we'll store 1.436 + // pending messages in the content process (|SystemMessageManager|). 1.437 + page.pendingMessages.length = 0; 1.438 + 1.439 + // Send the array of pending messages. 1.440 + aMessage.target 1.441 + .sendAsyncMessage("SystemMessageManager:GetPendingMessages:Return", 1.442 + { type: msg.type, 1.443 + manifestURL: msg.manifestURL, 1.444 + pageURL: msg.pageURL, 1.445 + msgQueue: pendingMessages }); 1.446 + break; 1.447 + } 1.448 + case "SystemMessageManager:HasPendingMessages": 1.449 + { 1.450 + debug("received SystemMessageManager:HasPendingMessages " + msg.type + 1.451 + " for " + msg.pageURL + " @ " + msg.manifestURL); 1.452 + 1.453 + // This is a sync call used to return if a page has pending messages. 1.454 + // Find the right page to get its corresponding pending messages. 1.455 + let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); 1.456 + if (!page) { 1.457 + return false; 1.458 + } 1.459 + 1.460 + return page.pendingMessages.length != 0; 1.461 + break; 1.462 + } 1.463 + case "SystemMessageManager:Message:Return:OK": 1.464 + { 1.465 + debug("received SystemMessageManager:Message:Return:OK " + msg.type + 1.466 + " for " + msg.pageURL + " @ " + msg.manifestURL); 1.467 + 1.468 + // We need to clean up the pending message since the app has already 1.469 + // received it, thus avoiding the re-lanunched app handling it again. 1.470 + let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); 1.471 + if (page) { 1.472 + let pendingMessages = page.pendingMessages; 1.473 + for (let i = 0; i < pendingMessages.length; i++) { 1.474 + if (pendingMessages[i].msgID === msg.msgID) { 1.475 + pendingMessages.splice(i, 1); 1.476 + break; 1.477 + } 1.478 + } 1.479 + } 1.480 + break; 1.481 + } 1.482 + case "SystemMessageManager:HandleMessagesDone": 1.483 + { 1.484 + debug("received SystemMessageManager:HandleMessagesDone " + msg.type + 1.485 + " with " + msg.handledCount + " for " + msg.pageURL + 1.486 + " @ " + msg.manifestURL); 1.487 + 1.488 + // A page has finished handling some of its system messages, so we try 1.489 + // to release the CPU wake lock we acquired on behalf of that page. 1.490 + this._releaseCpuWakeLock(this._createKeyForPage(msg), msg.handledCount); 1.491 + break; 1.492 + } 1.493 + } 1.494 + }, 1.495 + 1.496 + observe: function(aSubject, aTopic, aData) { 1.497 + switch (aTopic) { 1.498 + case "xpcom-shutdown": 1.499 + kMessages.forEach(function(aMsg) { 1.500 + ppmm.removeMessageListener(aMsg, this); 1.501 + }, this); 1.502 + Services.obs.removeObserver(this, "xpcom-shutdown"); 1.503 + Services.obs.removeObserver(this, "webapps-registry-start"); 1.504 + Services.obs.removeObserver(this, "webapps-registry-ready"); 1.505 + ppmm = null; 1.506 + this._pages = null; 1.507 + this._bufferedSysMsgs = null; 1.508 + break; 1.509 + case "webapps-registry-start": 1.510 + this._webappsRegistryReady = false; 1.511 + break; 1.512 + case "webapps-registry-ready": 1.513 + // After the webapps' registration has been done for sure, 1.514 + // re-fire the buffered system messages if there is any. 1.515 + this._webappsRegistryReady = true; 1.516 + this._bufferedSysMsgs.forEach(function(aSysMsg) { 1.517 + switch (aSysMsg.how) { 1.518 + case "send": 1.519 + this.sendMessage( 1.520 + aSysMsg.type, aSysMsg.msg, 1.521 + aSysMsg.pageURI, aSysMsg.manifestURI, aSysMsg.extra); 1.522 + break; 1.523 + case "broadcast": 1.524 + this.broadcastMessage(aSysMsg.type, aSysMsg.msg, aSysMsg.extra); 1.525 + break; 1.526 + } 1.527 + }, this); 1.528 + this._bufferedSysMsgs.length = 0; 1.529 + break; 1.530 + } 1.531 + }, 1.532 + 1.533 + _queueMessage: function(aPage, aMessage, aMessageID) { 1.534 + // Queue the message for this page because we've never known if an app is 1.535 + // opened or not. We'll clean it up when the app has already received it. 1.536 + aPage.pendingMessages.push({ msg: aMessage, msgID: aMessageID }); 1.537 + if (aPage.pendingMessages.length > kMaxPendingMessages) { 1.538 + aPage.pendingMessages.splice(0, 1); 1.539 + } 1.540 + }, 1.541 + 1.542 + _openAppPage: function(aPage, aMessage, aExtra, aMsgSentStatus) { 1.543 + // This means the app must be brought to the foreground. 1.544 + let showApp = this._getMessageConfigurator(aPage.type).mustShowRunningApp; 1.545 + 1.546 + // We should send the open-app message if the system message was 1.547 + // not sent, or if it was sent but we should show the app anyway. 1.548 + if ((aMsgSentStatus === MSG_SENT_SUCCESS) && !showApp) { 1.549 + return; 1.550 + } 1.551 + 1.552 + // This flag means the app must *only* be brought to the foreground 1.553 + // and we don't need to load the app to handle messages. 1.554 + let onlyShowApp = (aMsgSentStatus === MSG_SENT_SUCCESS) && showApp; 1.555 + 1.556 + // We don't need to send the full object to observers. 1.557 + let page = { pageURL: aPage.pageURL, 1.558 + manifestURL: aPage.manifestURL, 1.559 + type: aPage.type, 1.560 + extra: aExtra, 1.561 + target: aMessage.target, 1.562 + onlyShowApp: onlyShowApp, 1.563 + showApp: showApp }; 1.564 + debug("Asking to open " + JSON.stringify(page)); 1.565 + Services.obs.notifyObservers(this, 1.566 + "system-messages-open-app", 1.567 + JSON.stringify(page)); 1.568 + }, 1.569 + 1.570 + _isPageMatched: function(aPage, aType, aPageURL, aManifestURL) { 1.571 + return (aPage.type === aType && 1.572 + aPage.manifestURL === aManifestURL && 1.573 + aPage.pageURL === aPageURL) 1.574 + }, 1.575 + 1.576 + _createKeyForPage: function _createKeyForPage(aPage) { 1.577 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.578 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.579 + converter.charset = "UTF-8"; 1.580 + 1.581 + let hasher = Cc["@mozilla.org/security/hash;1"] 1.582 + .createInstance(Ci.nsICryptoHash); 1.583 + hasher.init(hasher.SHA1); 1.584 + 1.585 + // add manifest/page URL and action to the hash 1.586 + ["type", "manifestURL", "pageURL"].forEach(function(aProp) { 1.587 + let data = converter.convertToByteArray(aPage[aProp], {}); 1.588 + hasher.update(data, data.length); 1.589 + }); 1.590 + 1.591 + return hasher.finish(true); 1.592 + }, 1.593 + 1.594 + _sendMessageCommon: function(aType, aMessage, aMessageID, 1.595 + aPageURL, aManifestURL, aExtra) { 1.596 + // Don't send the system message not granted by the app's permissions. 1.597 + if (!SystemMessagePermissionsChecker 1.598 + .isSystemMessagePermittedToSend(aType, 1.599 + aPageURL, 1.600 + aManifestURL)) { 1.601 + return MSG_SENT_FAILURE_PERM_DENIED; 1.602 + } 1.603 + 1.604 + let appPageIsRunning = false; 1.605 + let pageKey = this._createKeyForPage({ type: aType, 1.606 + manifestURL: aManifestURL, 1.607 + pageURL: aPageURL }); 1.608 + 1.609 + let targets = this._listeners[aManifestURL]; 1.610 + if (targets) { 1.611 + for (let index = 0; index < targets.length; ++index) { 1.612 + let target = targets[index]; 1.613 + // We only need to send the system message to the targets (processes) 1.614 + // which contain the window page that matches the manifest/page URL of 1.615 + // the destination of system message. 1.616 + if (target.winCounts[aPageURL] === undefined) { 1.617 + continue; 1.618 + } 1.619 + 1.620 + appPageIsRunning = true; 1.621 + // We need to acquire a CPU wake lock for that page and expect that 1.622 + // we'll receive a "SystemMessageManager:HandleMessagesDone" message 1.623 + // when the page finishes handling the system message. At that point, 1.624 + // we'll release the lock we acquired. 1.625 + this._acquireCpuWakeLock(pageKey); 1.626 + 1.627 + // Multiple windows can share the same target (process), the content 1.628 + // window needs to check if the manifest/page URL is matched. Only 1.629 + // *one* window should handle the system message. 1.630 + let manager = target.target; 1.631 + manager.sendAsyncMessage("SystemMessageManager:Message", 1.632 + { type: aType, 1.633 + msg: aMessage, 1.634 + manifestURL: aManifestURL, 1.635 + pageURL: aPageURL, 1.636 + msgID: aMessageID }); 1.637 + } 1.638 + } 1.639 + 1.640 + if (!appPageIsRunning) { 1.641 + // The app page isn't running and relies on the 'open-app' chrome event to 1.642 + // wake it up. We still need to acquire a CPU wake lock for that page and 1.643 + // expect that we will receive a "SystemMessageManager:HandleMessagesDone" 1.644 + // message when the page finishes handling the system message with other 1.645 + // pending messages. At that point, we'll release the lock we acquired. 1.646 + this._acquireCpuWakeLock(pageKey); 1.647 + return MSG_SENT_FAILURE_APP_NOT_RUNNING; 1.648 + } else { 1.649 + return MSG_SENT_SUCCESS; 1.650 + } 1.651 + 1.652 + }, 1.653 + 1.654 + classID: Components.ID("{70589ca5-91ac-4b9e-b839-d6a88167d714}"), 1.655 + 1.656 + QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesInternal, 1.657 + Ci.nsIObserver]) 1.658 +} 1.659 + 1.660 +this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageInternal]);