michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cu = Components.utils; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/SystemMessagePermissionsChecker.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "ppmm", michael@0: "@mozilla.org/parentprocessmessagemanager;1", michael@0: "nsIMessageBroadcaster"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", michael@0: "@mozilla.org/uuid-generator;1", michael@0: "nsIUUIDGenerator"); michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService", michael@0: "@mozilla.org/power/powermanagerservice;1", michael@0: "nsIPowerManagerService"); michael@0: michael@0: // Limit the number of pending messages for a given page. michael@0: let kMaxPendingMessages; michael@0: try { michael@0: kMaxPendingMessages = michael@0: Services.prefs.getIntPref("dom.messages.maxPendingMessages"); michael@0: } catch(e) { michael@0: // getIntPref throws when the pref is not set. michael@0: kMaxPendingMessages = 5; michael@0: } michael@0: michael@0: const kMessages =["SystemMessageManager:GetPendingMessages", michael@0: "SystemMessageManager:HasPendingMessages", michael@0: "SystemMessageManager:Register", michael@0: "SystemMessageManager:Unregister", michael@0: "SystemMessageManager:Message:Return:OK", michael@0: "SystemMessageManager:AskReadyToRegister", michael@0: "SystemMessageManager:HandleMessagesDone", michael@0: "child-process-shutdown"] michael@0: michael@0: function debug(aMsg) { michael@0: // dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n"); michael@0: } michael@0: michael@0: michael@0: let defaultMessageConfigurator = { michael@0: get mustShowRunningApp() { michael@0: return false; michael@0: } michael@0: }; michael@0: michael@0: const MSG_SENT_SUCCESS = 0; michael@0: const MSG_SENT_FAILURE_PERM_DENIED = 1; michael@0: const MSG_SENT_FAILURE_APP_NOT_RUNNING = 2; michael@0: michael@0: // Implementation of the component used by internal users. michael@0: michael@0: function SystemMessageInternal() { michael@0: // The set of pages registered by installed apps. We keep the michael@0: // list of pending messages for each page here also. michael@0: this._pages = []; michael@0: michael@0: // The set of listeners. This is a multi-dimensional object. The _listeners michael@0: // object itself is a map from manifest URL -> an array mapping proccesses to michael@0: // windows. We do this so that we can track both what processes we have to michael@0: // send system messages to as well as supporting the single-process case michael@0: // where we track windows instead. michael@0: this._listeners = {}; michael@0: michael@0: this._webappsRegistryReady = false; michael@0: this._bufferedSysMsgs = []; michael@0: michael@0: this._cpuWakeLocks = {}; michael@0: michael@0: this._configurators = {}; michael@0: michael@0: Services.obs.addObserver(this, "xpcom-shutdown", false); michael@0: Services.obs.addObserver(this, "webapps-registry-start", false); michael@0: Services.obs.addObserver(this, "webapps-registry-ready", false); michael@0: kMessages.forEach(function(aMsg) { michael@0: ppmm.addMessageListener(aMsg, this); michael@0: }, this); michael@0: michael@0: Services.obs.notifyObservers(this, "system-message-internal-ready", null); michael@0: } michael@0: michael@0: SystemMessageInternal.prototype = { michael@0: michael@0: _getMessageConfigurator: function(aType) { michael@0: debug("_getMessageConfigurator for type: " + aType); michael@0: if (this._configurators[aType] === undefined) { michael@0: let contractID = michael@0: "@mozilla.org/dom/system-messages/configurator/" + aType + ";1"; michael@0: if (contractID in Cc) { michael@0: debug(contractID + " is registered, creating an instance"); michael@0: this._configurators[aType] = michael@0: Cc[contractID].createInstance(Ci.nsISystemMessagesConfigurator); michael@0: } else { michael@0: debug(contractID + "is not registered, caching the answer"); michael@0: this._configurators[aType] = null; michael@0: } michael@0: } michael@0: return this._configurators[aType] || defaultMessageConfigurator; michael@0: }, michael@0: michael@0: _cancelCpuWakeLock: function(aPageKey) { michael@0: let cpuWakeLock = this._cpuWakeLocks[aPageKey]; michael@0: if (cpuWakeLock) { michael@0: debug("Releasing the CPU wake lock for page key = " + aPageKey); michael@0: cpuWakeLock.wakeLock.unlock(); michael@0: cpuWakeLock.timer.cancel(); michael@0: delete this._cpuWakeLocks[aPageKey]; michael@0: } michael@0: }, michael@0: michael@0: _acquireCpuWakeLock: function(aPageKey) { michael@0: let cpuWakeLock = this._cpuWakeLocks[aPageKey]; michael@0: if (!cpuWakeLock) { michael@0: // We have to ensure the CPU doesn't sleep during the process of the page michael@0: // handling the system messages, so that they can be handled on time. michael@0: debug("Acquiring a CPU wake lock for page key = " + aPageKey); michael@0: cpuWakeLock = this._cpuWakeLocks[aPageKey] = { michael@0: wakeLock: powerManagerService.newWakeLock("cpu"), michael@0: timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), michael@0: lockCount: 1 michael@0: }; michael@0: } else { michael@0: // We've already acquired the CPU wake lock for this page, michael@0: // so just add to the lock count and extend the timeout. michael@0: cpuWakeLock.lockCount++; michael@0: } michael@0: michael@0: // Set a watchdog to avoid locking the CPU wake lock too long, michael@0: // because it'd exhaust the battery quickly which is very bad. michael@0: // This could probably happen if the app failed to launch or michael@0: // handle the system messages due to any unexpected reasons. michael@0: cpuWakeLock.timer.initWithCallback(function timerCb() { michael@0: debug("Releasing the CPU wake lock because the system messages " + michael@0: "were not handled by its registered page before time out."); michael@0: this._cancelCpuWakeLock(aPageKey); michael@0: }.bind(this), 30000, Ci.nsITimer.TYPE_ONE_SHOT); michael@0: }, michael@0: michael@0: _releaseCpuWakeLock: function _releaseCpuWakeLock(aPageKey, aHandledCount) { michael@0: let cpuWakeLock = this._cpuWakeLocks[aPageKey]; michael@0: if (cpuWakeLock) { michael@0: cpuWakeLock.lockCount -= aHandledCount; michael@0: if (cpuWakeLock.lockCount <= 0) { michael@0: debug("Unlocking the CPU wake lock now that the system messages " + michael@0: "have been successfully handled by its registered page."); michael@0: this._cancelCpuWakeLock(aPageKey); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: _findPage: function(aType, aPageURL, aManifestURL) { michael@0: let page = null; michael@0: this._pages.some(function(aPage) { michael@0: if (this._isPageMatched(aPage, aType, aPageURL, aManifestURL)) { michael@0: page = aPage; michael@0: } michael@0: return page !== null; michael@0: }, this); michael@0: return page; michael@0: }, michael@0: michael@0: sendMessage: function(aType, aMessage, aPageURI, aManifestURI, aExtra) { michael@0: // Buffer system messages until the webapps' registration is ready, michael@0: // so that we can know the correct pages registered to be sent. michael@0: if (!this._webappsRegistryReady) { michael@0: this._bufferedSysMsgs.push({ how: "send", michael@0: type: aType, michael@0: msg: aMessage, michael@0: pageURI: aPageURI, michael@0: manifestURI: aManifestURI, michael@0: extra: aExtra }); michael@0: return; michael@0: } michael@0: michael@0: // Give this message an ID so that we can identify the message and michael@0: // clean it up from the pending message queue when apps receive it. michael@0: let messageID = gUUIDGenerator.generateUUID().toString(); michael@0: michael@0: debug("Sending " + aType + " " + JSON.stringify(aMessage) + michael@0: " for " + aPageURI.spec + " @ " + aManifestURI.spec + michael@0: '; extra: ' + JSON.stringify(aExtra)); michael@0: michael@0: let result = this._sendMessageCommon(aType, michael@0: aMessage, michael@0: messageID, michael@0: aPageURI.spec, michael@0: aManifestURI.spec, michael@0: aExtra); michael@0: debug("Returned status of sending message: " + result); michael@0: michael@0: // Don't need to open the pages and queue the system message michael@0: // which was not allowed to be sent. michael@0: if (result === MSG_SENT_FAILURE_PERM_DENIED) { michael@0: return; michael@0: } michael@0: michael@0: let page = this._findPage(aType, aPageURI.spec, aManifestURI.spec); michael@0: if (page) { michael@0: // Queue this message in the corresponding pages. michael@0: this._queueMessage(page, aMessage, messageID); michael@0: michael@0: this._openAppPage(page, aMessage, aExtra, result); michael@0: } michael@0: }, michael@0: michael@0: broadcastMessage: function(aType, aMessage, aExtra) { michael@0: // Buffer system messages until the webapps' registration is ready, michael@0: // so that we can know the correct pages registered to be broadcasted. michael@0: if (!this._webappsRegistryReady) { michael@0: this._bufferedSysMsgs.push({ how: "broadcast", michael@0: type: aType, michael@0: msg: aMessage, michael@0: extra: aExtra }); michael@0: return; michael@0: } michael@0: michael@0: // Give this message an ID so that we can identify the message and michael@0: // clean it up from the pending message queue when apps receive it. michael@0: let messageID = gUUIDGenerator.generateUUID().toString(); michael@0: michael@0: debug("Broadcasting " + aType + " " + JSON.stringify(aMessage) + michael@0: '; extra = ' + JSON.stringify(aExtra)); michael@0: // Find pages that registered an handler for this type. michael@0: this._pages.forEach(function(aPage) { michael@0: if (aPage.type == aType) { michael@0: let result = this._sendMessageCommon(aType, michael@0: aMessage, michael@0: messageID, michael@0: aPage.pageURL, michael@0: aPage.manifestURL, michael@0: aExtra); michael@0: debug("Returned status of sending message: " + result); michael@0: michael@0: michael@0: // Don't need to open the pages and queue the system message michael@0: // which was not allowed to be sent. michael@0: if (result === MSG_SENT_FAILURE_PERM_DENIED) { michael@0: return; michael@0: } michael@0: michael@0: // Queue this message in the corresponding pages. michael@0: this._queueMessage(aPage, aMessage, messageID); michael@0: michael@0: this._openAppPage(aPage, aMessage, aExtra, result); michael@0: } michael@0: }, this); michael@0: }, michael@0: michael@0: registerPage: function(aType, aPageURI, aManifestURI) { michael@0: if (!aPageURI || !aManifestURI) { michael@0: throw Cr.NS_ERROR_INVALID_ARG; michael@0: } michael@0: michael@0: let pageURL = aPageURI.spec; michael@0: let manifestURL = aManifestURI.spec; michael@0: michael@0: // Don't register duplicates for this tuple. michael@0: let page = this._findPage(aType, pageURL, manifestURL); michael@0: if (page) { michael@0: debug("Ignoring duplicate registration of " + michael@0: [aType, pageURL, manifestURL]); michael@0: return; michael@0: } michael@0: michael@0: this._pages.push({ type: aType, michael@0: pageURL: pageURL, michael@0: manifestURL: manifestURL, michael@0: pendingMessages: [] }); michael@0: }, michael@0: michael@0: _findTargetIndex: function(aTargets, aTarget) { michael@0: if (!aTargets || !aTarget) { michael@0: return -1; michael@0: } michael@0: for (let index = 0; index < aTargets.length; ++index) { michael@0: let target = aTargets[index]; michael@0: if (target.target === aTarget) { michael@0: return index; michael@0: } michael@0: } michael@0: return -1; michael@0: }, michael@0: michael@0: _isEmptyObject: function(aObj) { michael@0: for (let name in aObj) { michael@0: return false; michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: _removeTargetFromListener: function(aTarget, michael@0: aManifestURL, michael@0: aRemoveListener, michael@0: aPageURL) { michael@0: let targets = this._listeners[aManifestURL]; michael@0: if (!targets) { michael@0: return false; michael@0: } michael@0: michael@0: let index = this._findTargetIndex(targets, aTarget); michael@0: if (index === -1) { michael@0: return false; michael@0: } michael@0: michael@0: if (aRemoveListener) { michael@0: debug("remove the listener for " + aManifestURL); michael@0: delete this._listeners[aManifestURL]; michael@0: return true; michael@0: } michael@0: michael@0: let target = targets[index]; michael@0: if (aPageURL && target.winCounts[aPageURL] !== undefined && michael@0: --target.winCounts[aPageURL] === 0) { michael@0: delete target.winCounts[aPageURL]; michael@0: } michael@0: michael@0: if (this._isEmptyObject(target.winCounts)) { michael@0: if (targets.length === 1) { michael@0: // If it's the only one, get rid of the entry of manifest URL entirely. michael@0: debug("remove the listener for " + aManifestURL); michael@0: delete this._listeners[aManifestURL]; michael@0: } else { michael@0: // If more than one left, remove this one and leave the rest. michael@0: targets.splice(index, 1); michael@0: } michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: receiveMessage: function(aMessage) { michael@0: let msg = aMessage.json; michael@0: michael@0: // To prevent the hacked child process from sending commands to parent michael@0: // to manage system messages, we need to check its manifest URL. michael@0: if (["SystemMessageManager:Register", michael@0: // TODO: fix bug 988142 to re-enable. michael@0: // "SystemMessageManager:Unregister", michael@0: "SystemMessageManager:GetPendingMessages", michael@0: "SystemMessageManager:HasPendingMessages", michael@0: "SystemMessageManager:Message:Return:OK", michael@0: "SystemMessageManager:HandleMessagesDone"].indexOf(aMessage.name) != -1) { michael@0: if (!aMessage.target.assertContainApp(msg.manifestURL)) { michael@0: debug("Got message from a child process containing illegal manifest URL."); michael@0: return null; michael@0: } michael@0: } michael@0: michael@0: switch(aMessage.name) { michael@0: case "SystemMessageManager:AskReadyToRegister": michael@0: return true; michael@0: break; michael@0: case "SystemMessageManager:Register": michael@0: { michael@0: debug("Got Register from " + msg.pageURL + " @ " + msg.manifestURL); michael@0: let pageURL = msg.pageURL; michael@0: let targets, index; michael@0: if (!(targets = this._listeners[msg.manifestURL])) { michael@0: let winCounts = {}; michael@0: winCounts[pageURL] = 1; michael@0: this._listeners[msg.manifestURL] = [{ target: aMessage.target, michael@0: winCounts: winCounts }]; michael@0: } else if ((index = this._findTargetIndex(targets, michael@0: aMessage.target)) === -1) { michael@0: let winCounts = {}; michael@0: winCounts[pageURL] = 1; michael@0: targets.push({ target: aMessage.target, michael@0: winCounts: winCounts }); michael@0: } else { michael@0: let winCounts = targets[index].winCounts; michael@0: if (winCounts[pageURL] === undefined) { michael@0: winCounts[pageURL] = 1; michael@0: } else { michael@0: winCounts[pageURL]++; michael@0: } michael@0: } michael@0: michael@0: debug("listeners for " + msg.manifestURL + michael@0: " innerWinID " + msg.innerWindowID); michael@0: break; michael@0: } michael@0: case "child-process-shutdown": michael@0: { michael@0: debug("Got child-process-shutdown from " + aMessage.target); michael@0: for (let manifestURL in this._listeners) { michael@0: // See if any processes in this manifest URL have this target. michael@0: this._removeTargetFromListener(aMessage.target, michael@0: manifestURL, michael@0: true, michael@0: null); michael@0: } michael@0: break; michael@0: } michael@0: case "SystemMessageManager:Unregister": michael@0: { michael@0: debug("Got Unregister from " + aMessage.target + michael@0: " innerWinID " + msg.innerWindowID); michael@0: this._removeTargetFromListener(aMessage.target, michael@0: msg.manifestURL, michael@0: false, michael@0: msg.pageURL); michael@0: break; michael@0: } michael@0: case "SystemMessageManager:GetPendingMessages": michael@0: { michael@0: debug("received SystemMessageManager:GetPendingMessages " + msg.type + michael@0: " for " + msg.pageURL + " @ " + msg.manifestURL); michael@0: michael@0: // This is a sync call used to return the pending messages for a page. michael@0: // Find the right page to get its corresponding pending messages. michael@0: let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); michael@0: if (!page) { michael@0: return; michael@0: } michael@0: michael@0: // Return the |msg| of each pending message (drop the |msgID|). michael@0: let pendingMessages = []; michael@0: page.pendingMessages.forEach(function(aMessage) { michael@0: pendingMessages.push(aMessage.msg); michael@0: }); michael@0: michael@0: // Clear the pending queue for this page. This is OK since we'll store michael@0: // pending messages in the content process (|SystemMessageManager|). michael@0: page.pendingMessages.length = 0; michael@0: michael@0: // Send the array of pending messages. michael@0: aMessage.target michael@0: .sendAsyncMessage("SystemMessageManager:GetPendingMessages:Return", michael@0: { type: msg.type, michael@0: manifestURL: msg.manifestURL, michael@0: pageURL: msg.pageURL, michael@0: msgQueue: pendingMessages }); michael@0: break; michael@0: } michael@0: case "SystemMessageManager:HasPendingMessages": michael@0: { michael@0: debug("received SystemMessageManager:HasPendingMessages " + msg.type + michael@0: " for " + msg.pageURL + " @ " + msg.manifestURL); michael@0: michael@0: // This is a sync call used to return if a page has pending messages. michael@0: // Find the right page to get its corresponding pending messages. michael@0: let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); michael@0: if (!page) { michael@0: return false; michael@0: } michael@0: michael@0: return page.pendingMessages.length != 0; michael@0: break; michael@0: } michael@0: case "SystemMessageManager:Message:Return:OK": michael@0: { michael@0: debug("received SystemMessageManager:Message:Return:OK " + msg.type + michael@0: " for " + msg.pageURL + " @ " + msg.manifestURL); michael@0: michael@0: // We need to clean up the pending message since the app has already michael@0: // received it, thus avoiding the re-lanunched app handling it again. michael@0: let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); michael@0: if (page) { michael@0: let pendingMessages = page.pendingMessages; michael@0: for (let i = 0; i < pendingMessages.length; i++) { michael@0: if (pendingMessages[i].msgID === msg.msgID) { michael@0: pendingMessages.splice(i, 1); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: break; michael@0: } michael@0: case "SystemMessageManager:HandleMessagesDone": michael@0: { michael@0: debug("received SystemMessageManager:HandleMessagesDone " + msg.type + michael@0: " with " + msg.handledCount + " for " + msg.pageURL + michael@0: " @ " + msg.manifestURL); michael@0: michael@0: // A page has finished handling some of its system messages, so we try michael@0: // to release the CPU wake lock we acquired on behalf of that page. michael@0: this._releaseCpuWakeLock(this._createKeyForPage(msg), msg.handledCount); michael@0: break; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: switch (aTopic) { michael@0: case "xpcom-shutdown": michael@0: kMessages.forEach(function(aMsg) { michael@0: ppmm.removeMessageListener(aMsg, this); michael@0: }, this); michael@0: Services.obs.removeObserver(this, "xpcom-shutdown"); michael@0: Services.obs.removeObserver(this, "webapps-registry-start"); michael@0: Services.obs.removeObserver(this, "webapps-registry-ready"); michael@0: ppmm = null; michael@0: this._pages = null; michael@0: this._bufferedSysMsgs = null; michael@0: break; michael@0: case "webapps-registry-start": michael@0: this._webappsRegistryReady = false; michael@0: break; michael@0: case "webapps-registry-ready": michael@0: // After the webapps' registration has been done for sure, michael@0: // re-fire the buffered system messages if there is any. michael@0: this._webappsRegistryReady = true; michael@0: this._bufferedSysMsgs.forEach(function(aSysMsg) { michael@0: switch (aSysMsg.how) { michael@0: case "send": michael@0: this.sendMessage( michael@0: aSysMsg.type, aSysMsg.msg, michael@0: aSysMsg.pageURI, aSysMsg.manifestURI, aSysMsg.extra); michael@0: break; michael@0: case "broadcast": michael@0: this.broadcastMessage(aSysMsg.type, aSysMsg.msg, aSysMsg.extra); michael@0: break; michael@0: } michael@0: }, this); michael@0: this._bufferedSysMsgs.length = 0; michael@0: break; michael@0: } michael@0: }, michael@0: michael@0: _queueMessage: function(aPage, aMessage, aMessageID) { michael@0: // Queue the message for this page because we've never known if an app is michael@0: // opened or not. We'll clean it up when the app has already received it. michael@0: aPage.pendingMessages.push({ msg: aMessage, msgID: aMessageID }); michael@0: if (aPage.pendingMessages.length > kMaxPendingMessages) { michael@0: aPage.pendingMessages.splice(0, 1); michael@0: } michael@0: }, michael@0: michael@0: _openAppPage: function(aPage, aMessage, aExtra, aMsgSentStatus) { michael@0: // This means the app must be brought to the foreground. michael@0: let showApp = this._getMessageConfigurator(aPage.type).mustShowRunningApp; michael@0: michael@0: // We should send the open-app message if the system message was michael@0: // not sent, or if it was sent but we should show the app anyway. michael@0: if ((aMsgSentStatus === MSG_SENT_SUCCESS) && !showApp) { michael@0: return; michael@0: } michael@0: michael@0: // This flag means the app must *only* be brought to the foreground michael@0: // and we don't need to load the app to handle messages. michael@0: let onlyShowApp = (aMsgSentStatus === MSG_SENT_SUCCESS) && showApp; michael@0: michael@0: // We don't need to send the full object to observers. michael@0: let page = { pageURL: aPage.pageURL, michael@0: manifestURL: aPage.manifestURL, michael@0: type: aPage.type, michael@0: extra: aExtra, michael@0: target: aMessage.target, michael@0: onlyShowApp: onlyShowApp, michael@0: showApp: showApp }; michael@0: debug("Asking to open " + JSON.stringify(page)); michael@0: Services.obs.notifyObservers(this, michael@0: "system-messages-open-app", michael@0: JSON.stringify(page)); michael@0: }, michael@0: michael@0: _isPageMatched: function(aPage, aType, aPageURL, aManifestURL) { michael@0: return (aPage.type === aType && michael@0: aPage.manifestURL === aManifestURL && michael@0: aPage.pageURL === aPageURL) michael@0: }, michael@0: michael@0: _createKeyForPage: function _createKeyForPage(aPage) { michael@0: let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: converter.charset = "UTF-8"; michael@0: michael@0: let hasher = Cc["@mozilla.org/security/hash;1"] michael@0: .createInstance(Ci.nsICryptoHash); michael@0: hasher.init(hasher.SHA1); michael@0: michael@0: // add manifest/page URL and action to the hash michael@0: ["type", "manifestURL", "pageURL"].forEach(function(aProp) { michael@0: let data = converter.convertToByteArray(aPage[aProp], {}); michael@0: hasher.update(data, data.length); michael@0: }); michael@0: michael@0: return hasher.finish(true); michael@0: }, michael@0: michael@0: _sendMessageCommon: function(aType, aMessage, aMessageID, michael@0: aPageURL, aManifestURL, aExtra) { michael@0: // Don't send the system message not granted by the app's permissions. michael@0: if (!SystemMessagePermissionsChecker michael@0: .isSystemMessagePermittedToSend(aType, michael@0: aPageURL, michael@0: aManifestURL)) { michael@0: return MSG_SENT_FAILURE_PERM_DENIED; michael@0: } michael@0: michael@0: let appPageIsRunning = false; michael@0: let pageKey = this._createKeyForPage({ type: aType, michael@0: manifestURL: aManifestURL, michael@0: pageURL: aPageURL }); michael@0: michael@0: let targets = this._listeners[aManifestURL]; michael@0: if (targets) { michael@0: for (let index = 0; index < targets.length; ++index) { michael@0: let target = targets[index]; michael@0: // We only need to send the system message to the targets (processes) michael@0: // which contain the window page that matches the manifest/page URL of michael@0: // the destination of system message. michael@0: if (target.winCounts[aPageURL] === undefined) { michael@0: continue; michael@0: } michael@0: michael@0: appPageIsRunning = true; michael@0: // We need to acquire a CPU wake lock for that page and expect that michael@0: // we'll receive a "SystemMessageManager:HandleMessagesDone" message michael@0: // when the page finishes handling the system message. At that point, michael@0: // we'll release the lock we acquired. michael@0: this._acquireCpuWakeLock(pageKey); michael@0: michael@0: // Multiple windows can share the same target (process), the content michael@0: // window needs to check if the manifest/page URL is matched. Only michael@0: // *one* window should handle the system message. michael@0: let manager = target.target; michael@0: manager.sendAsyncMessage("SystemMessageManager:Message", michael@0: { type: aType, michael@0: msg: aMessage, michael@0: manifestURL: aManifestURL, michael@0: pageURL: aPageURL, michael@0: msgID: aMessageID }); michael@0: } michael@0: } michael@0: michael@0: if (!appPageIsRunning) { michael@0: // The app page isn't running and relies on the 'open-app' chrome event to michael@0: // wake it up. We still need to acquire a CPU wake lock for that page and michael@0: // expect that we will receive a "SystemMessageManager:HandleMessagesDone" michael@0: // message when the page finishes handling the system message with other michael@0: // pending messages. At that point, we'll release the lock we acquired. michael@0: this._acquireCpuWakeLock(pageKey); michael@0: return MSG_SENT_FAILURE_APP_NOT_RUNNING; michael@0: } else { michael@0: return MSG_SENT_SUCCESS; michael@0: } michael@0: michael@0: }, michael@0: michael@0: classID: Components.ID("{70589ca5-91ac-4b9e-b839-d6a88167d714}"), michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesInternal, michael@0: Ci.nsIObserver]) michael@0: } michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageInternal]);