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/DOMRequestHelper.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: const kSystemMessageInternalReady = "system-message-internal-ready"; michael@0: michael@0: XPCOMUtils.defineLazyServiceGetter(this, "cpmm", michael@0: "@mozilla.org/childprocessmessagemanager;1", michael@0: "nsISyncMessageSender"); michael@0: michael@0: function debug(aMsg) { michael@0: // dump("-- SystemMessageManager " + Date.now() + " : " + aMsg + "\n"); michael@0: } michael@0: michael@0: // Implementation of the DOM API for system messages michael@0: michael@0: function SystemMessageManager() { michael@0: // If we have a system message handler registered for messages of type michael@0: // |type|, this._dispatchers[type] equals {handler, messages, isHandling}, michael@0: // where michael@0: // - |handler| is the message handler that the page registered, michael@0: // - |messages| is a list of messages which we've received while michael@0: // dispatching messages to the handler, but haven't yet sent, and michael@0: // - |isHandling| indicates whether we're currently dispatching messages michael@0: // to this handler. michael@0: this._dispatchers = {}; michael@0: michael@0: // Pending messages for this page, keyed by message type. michael@0: this._pendings = {}; michael@0: michael@0: // Flag to specify if this process has already registered the manifest URL. michael@0: this._registerManifestURLReady = false; michael@0: michael@0: // Flag to determine this process is a parent or child process. michael@0: let appInfo = Cc["@mozilla.org/xre/app-info;1"]; michael@0: this._isParentProcess = michael@0: !appInfo || appInfo.getService(Ci.nsIXULRuntime) michael@0: .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT; michael@0: michael@0: // An oberver to listen to whether the |SystemMessageInternal| is ready. michael@0: if (this._isParentProcess) { michael@0: Services.obs.addObserver(this, kSystemMessageInternalReady, false); michael@0: } michael@0: } michael@0: michael@0: SystemMessageManager.prototype = { michael@0: __proto__: DOMRequestIpcHelper.prototype, michael@0: michael@0: _dispatchMessage: function(aType, aDispatcher, aMessage) { michael@0: if (aDispatcher.isHandling) { michael@0: // Queue up the incomming message if we're currently dispatching a michael@0: // message; we'll send the message once we finish with the current one. michael@0: // michael@0: // _dispatchMethod is reentrant because a page can spin up a nested michael@0: // event loop from within a system message handler (e.g. via alert()), michael@0: // and we can then try to send the page another message while it's michael@0: // inside this nested event loop. michael@0: aDispatcher.messages.push(aMessage); michael@0: return; michael@0: } michael@0: michael@0: aDispatcher.isHandling = true; michael@0: michael@0: // We get a json blob, but in some cases we want another kind of object michael@0: // to be dispatched. To do so, we check if we have a valid contract ID of michael@0: // "@mozilla.org/dom/system-messages/wrapper/TYPE;1" component implementing michael@0: // nsISystemMessageWrapper. michael@0: debug("Dispatching " + JSON.stringify(aMessage) + "\n"); michael@0: let contractID = "@mozilla.org/dom/system-messages/wrapper/" + aType + ";1"; michael@0: let wrapped = false; michael@0: michael@0: if (contractID in Cc) { michael@0: debug(contractID + " is registered, creating an instance"); michael@0: let wrapper = Cc[contractID].createInstance(Ci.nsISystemMessagesWrapper); michael@0: if (wrapper) { michael@0: aMessage = wrapper.wrapMessage(aMessage, this._window); michael@0: wrapped = true; michael@0: debug("wrapped = " + aMessage); michael@0: } michael@0: } michael@0: michael@0: aDispatcher.handler michael@0: .handleMessage(wrapped ? aMessage michael@0: : Cu.cloneInto(aMessage, this._window)); michael@0: michael@0: // We need to notify the parent one of the system messages has been handled, michael@0: // so the parent can release the CPU wake lock it took on our behalf. michael@0: cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone", michael@0: { type: aType, michael@0: manifestURL: this._manifestURL, michael@0: pageURL: this._pageURL, michael@0: handledCount: 1 }); michael@0: michael@0: aDispatcher.isHandling = false; michael@0: michael@0: if (aDispatcher.messages.length > 0) { michael@0: this._dispatchMessage(aType, aDispatcher, aDispatcher.messages.shift()); michael@0: } else { michael@0: // No more messages that need to be handled, we can notify the michael@0: // ContentChild to release the CPU wake lock grabbed by the ContentParent michael@0: // (i.e. NewWakeLockOnBehalfOfProcess()) and reset the process's priority. michael@0: // michael@0: // TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent michael@0: Services.obs.notifyObservers(/* aSubject */ null, michael@0: "handle-system-messages-done", michael@0: /* aData */ null); michael@0: } michael@0: }, michael@0: michael@0: mozSetMessageHandler: function(aType, aHandler) { michael@0: debug("set message handler for [" + aType + "] " + aHandler); michael@0: michael@0: if (this._isInBrowserElement) { michael@0: debug("the app loaded in the browser cannot set message handler"); michael@0: // Don't throw there, but ignore the registration. michael@0: return; michael@0: } michael@0: michael@0: if (!aType) { michael@0: // Just bail out if we have no type. michael@0: return; michael@0: } michael@0: michael@0: let dispatchers = this._dispatchers; michael@0: if (!aHandler) { michael@0: // Setting the dispatcher to null means we don't want to handle messages michael@0: // for this type anymore. michael@0: delete dispatchers[aType]; michael@0: return; michael@0: } michael@0: michael@0: // Last registered handler wins. michael@0: dispatchers[aType] = { handler: aHandler, messages: [], isHandling: false }; michael@0: michael@0: // Ask for the list of currently pending messages. michael@0: cpmm.sendAsyncMessage("SystemMessageManager:GetPendingMessages", michael@0: { type: aType, michael@0: pageURL: this._pageURL, michael@0: manifestURL: this._manifestURL }); michael@0: }, michael@0: michael@0: mozHasPendingMessage: function(aType) { michael@0: debug("asking pending message for [" + aType + "]"); michael@0: michael@0: if (this._isInBrowserElement) { michael@0: debug("the app loaded in the browser cannot ask pending message"); michael@0: // Don't throw there, but pretend to have no messages available. michael@0: return false; michael@0: } michael@0: michael@0: // If we have a handler for this type, we can't have any pending message. michael@0: if (aType in this._dispatchers) { michael@0: return false; michael@0: } michael@0: michael@0: return cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages", michael@0: { type: aType, michael@0: pageURL: this._pageURL, michael@0: manifestURL: this._manifestURL })[0]; michael@0: }, michael@0: michael@0: uninit: function() { michael@0: this._dispatchers = null; michael@0: this._pendings = null; michael@0: michael@0: if (this._isParentProcess) { michael@0: Services.obs.removeObserver(this, kSystemMessageInternalReady); michael@0: } michael@0: michael@0: if (this._isInBrowserElement) { michael@0: debug("the app loaded in the browser doesn't need to unregister " + michael@0: "the manifest URL for listening to the system messages"); michael@0: return; michael@0: } michael@0: michael@0: cpmm.sendAsyncMessage("SystemMessageManager:Unregister", michael@0: { manifestURL: this._manifestURL, michael@0: pageURL: this._pageURL, michael@0: innerWindowID: this.innerWindowID }); michael@0: }, michael@0: michael@0: // Possible messages: michael@0: // michael@0: // - SystemMessageManager:Message michael@0: // This one will only be received when the child process is alive when michael@0: // the message is initially sent. michael@0: // michael@0: // - SystemMessageManager:GetPendingMessages:Return michael@0: // This one will be received when the starting child process wants to michael@0: // retrieve the pending system messages from the parent (i.e. after michael@0: // sending SystemMessageManager:GetPendingMessages). michael@0: receiveMessage: function(aMessage) { michael@0: let msg = aMessage.data; michael@0: debug("receiveMessage " + aMessage.name + " for [" + msg.type + "] " + michael@0: "with manifest URL = " + msg.manifestURL + michael@0: " and page URL = " + msg.pageURL); 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: if (msg.manifestURL !== this._manifestURL || michael@0: msg.pageURL !== this._pageURL) { michael@0: debug("This page shouldn't handle the messages because its " + michael@0: "manifest URL = " + this._manifestURL + michael@0: " and page URL = " + this._pageURL); michael@0: return; michael@0: } michael@0: michael@0: let messages = (aMessage.name == "SystemMessageManager:Message") michael@0: ? [msg.msg] michael@0: : msg.msgQueue; michael@0: michael@0: // We only dispatch messages when a handler is registered. michael@0: let dispatcher = this._dispatchers[msg.type]; michael@0: if (dispatcher) { michael@0: if (aMessage.name == "SystemMessageManager:Message") { michael@0: // Send an acknowledgement to parent to clean up the pending message michael@0: // before we dispatch the message to apps, so a re-launched app won't michael@0: // handle it again, which is redundant. michael@0: cpmm.sendAsyncMessage("SystemMessageManager:Message:Return:OK", michael@0: { type: msg.type, michael@0: manifestURL: this._manifestURL, michael@0: pageURL: this._pageURL, michael@0: msgID: msg.msgID }); michael@0: } michael@0: michael@0: messages.forEach(function(aMsg) { michael@0: this._dispatchMessage(msg.type, dispatcher, aMsg); michael@0: }, this); michael@0: } else { michael@0: // Since no handlers are registered, we need to notify the parent as if michael@0: // all the queued system messages have been handled (notice |handledCount: michael@0: // messages.length|), so the parent can release the CPU wake lock it took michael@0: // on our behalf. michael@0: cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone", michael@0: { type: msg.type, michael@0: manifestURL: this._manifestURL, michael@0: pageURL: this._pageURL, michael@0: handledCount: messages.length }); michael@0: michael@0: // We also need to notify the ContentChild to release the CPU wake lock michael@0: // grabbed by the ContentParent (i.e. NewWakeLockOnBehalfOfProcess()) and michael@0: // reset the process's priority. michael@0: // michael@0: // TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent michael@0: Services.obs.notifyObservers(/* aSubject */ null, michael@0: "handle-system-messages-done", michael@0: /* aData */ null); michael@0: } michael@0: }, michael@0: michael@0: // nsIDOMGlobalPropertyInitializer implementation. michael@0: init: function(aWindow) { michael@0: debug("init"); michael@0: this.initDOMRequestHelper(aWindow, michael@0: ["SystemMessageManager:Message", michael@0: "SystemMessageManager:GetPendingMessages:Return"]); michael@0: michael@0: let principal = aWindow.document.nodePrincipal; michael@0: this._isInBrowserElement = principal.isInBrowserElement; michael@0: this._pageURL = principal.URI.spec; michael@0: michael@0: let appsService = Cc["@mozilla.org/AppsService;1"] michael@0: .getService(Ci.nsIAppsService); michael@0: this._manifestURL = appsService.getManifestURLByLocalId(principal.appId); michael@0: michael@0: // Two cases are valid to register the manifest URL for the current process: michael@0: // 1. This is asked by a child process (parent process must be ready). michael@0: // 2. Parent process has already constructed the |SystemMessageInternal|. michael@0: // Otherwise, delay to do it when the |SystemMessageInternal| is ready. michael@0: let readyToRegister = true; michael@0: if (this._isParentProcess) { michael@0: let ready = cpmm.sendSyncMessage( michael@0: "SystemMessageManager:AskReadyToRegister", null); michael@0: if (ready.length == 0 || !ready[0]) { michael@0: readyToRegister = false; michael@0: } michael@0: } michael@0: if (readyToRegister) { michael@0: this._registerManifestURL(); michael@0: } michael@0: michael@0: debug("done"); michael@0: }, michael@0: michael@0: observe: function(aSubject, aTopic, aData) { michael@0: if (aTopic === kSystemMessageInternalReady) { michael@0: this._registerManifestURL(); michael@0: } michael@0: michael@0: // Call the DOMRequestIpcHelper.observe method. michael@0: this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData); michael@0: }, michael@0: michael@0: _registerManifestURL: function() { michael@0: if (this._isInBrowserElement) { michael@0: debug("the app loaded in the browser doesn't need to register " + michael@0: "the manifest URL for listening to the system messages"); michael@0: return; michael@0: } michael@0: michael@0: if (!this._registerManifestURLReady) { michael@0: cpmm.sendAsyncMessage("SystemMessageManager:Register", michael@0: { manifestURL: this._manifestURL, michael@0: pageURL: this._pageURL, michael@0: innerWindowID: this.innerWindowID }); michael@0: michael@0: this._registerManifestURLReady = true; michael@0: } michael@0: }, michael@0: michael@0: classID: Components.ID("{bc076ea0-609b-4d8f-83d7-5af7cbdc3bb2}"), michael@0: michael@0: QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMNavigatorSystemMessages, michael@0: Ci.nsIDOMGlobalPropertyInitializer, michael@0: Ci.nsIObserver, michael@0: Ci.nsISupportsWeakReference]) michael@0: } michael@0: michael@0: this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageManager]);