mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-05 08:35:26 +00:00
396 lines
14 KiB
JavaScript
396 lines
14 KiB
JavaScript
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
|
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
"use strict";
|
|
|
|
const Cc = Components.classes;
|
|
const Ci = Components.interfaces;
|
|
const Cu = Components.utils;
|
|
const Cr = Components.results;
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
|
|
|
const kSystemMessageInternalReady = "system-message-internal-ready";
|
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
|
"@mozilla.org/childprocessmessagemanager;1",
|
|
"nsISyncMessageSender");
|
|
|
|
function debug(aMsg) {
|
|
// dump("-- SystemMessageManager " + Date.now() + " : " + aMsg + "\n");
|
|
}
|
|
|
|
// Implementation of the DOM API for system messages
|
|
|
|
function SystemMessageManager() {
|
|
// If we have a system message handler registered for messages of type
|
|
// |type|, this._dispatchers[type] equals {handler, messages, isHandling},
|
|
// where
|
|
// - |handler| is the message handler that the page registered,
|
|
// - |messages| is a list of messages which we've received while
|
|
// dispatching messages to the handler, but haven't yet sent, and
|
|
// - |isHandling| indicates whether we're currently dispatching messages
|
|
// to this handler.
|
|
this._dispatchers = {};
|
|
|
|
// Pending messages for this page, keyed by message type.
|
|
this._pendings = {};
|
|
|
|
// Flag to specify if this process has already registered the manifest URL.
|
|
this._registerManifestURLReady = false;
|
|
|
|
// Used to know if the promise has to be accepted or not.
|
|
this._isHandling = false;
|
|
this._promise = null;
|
|
|
|
// Flag to determine this process is a parent or child process.
|
|
let appInfo = Cc["@mozilla.org/xre/app-info;1"];
|
|
this._isParentProcess =
|
|
!appInfo || appInfo.getService(Ci.nsIXULRuntime)
|
|
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
|
|
|
// An oberver to listen to whether the |SystemMessageInternal| is ready.
|
|
if (this._isParentProcess) {
|
|
Services.obs.addObserver(this, kSystemMessageInternalReady, false);
|
|
}
|
|
}
|
|
|
|
SystemMessageManager.prototype = {
|
|
__proto__: DOMRequestIpcHelper.prototype,
|
|
|
|
_dispatchMessage: function(aType, aDispatcher, aMessage, aMessageID) {
|
|
if (aDispatcher.isHandling) {
|
|
// Queue up the incomming message if we're currently dispatching a
|
|
// message; we'll send the message once we finish with the current one.
|
|
//
|
|
// _dispatchMethod is reentrant because a page can spin up a nested
|
|
// event loop from within a system message handler (e.g. via alert()),
|
|
// and we can then try to send the page another message while it's
|
|
// inside this nested event loop.
|
|
aDispatcher.messages.push({ message: aMessage, messageID: aMessageID });
|
|
return;
|
|
}
|
|
|
|
aDispatcher.isHandling = true;
|
|
this._isHandling = true;
|
|
|
|
// We get a json blob, but in some cases we want another kind of object
|
|
// to be dispatched. To do so, we check if we have a valid contract ID of
|
|
// "@mozilla.org/dom/system-messages/wrapper/TYPE;1" component implementing
|
|
// nsISystemMessageWrapper.
|
|
debug("Dispatching " + JSON.stringify(aMessage) + "\n");
|
|
let contractID = "@mozilla.org/dom/system-messages/wrapper/" + aType + ";1";
|
|
let wrapped = false;
|
|
|
|
if (contractID in Cc) {
|
|
debug(contractID + " is registered, creating an instance");
|
|
let wrapper = Cc[contractID].createInstance(Ci.nsISystemMessagesWrapper);
|
|
if (wrapper) {
|
|
aMessage = wrapper.wrapMessage(aMessage, this._window);
|
|
wrapped = true;
|
|
debug("wrapped = " + aMessage);
|
|
}
|
|
}
|
|
|
|
let message = wrapped ? aMessage : Cu.cloneInto(aMessage, this._window);
|
|
|
|
let rejectPromise = false;
|
|
try {
|
|
aDispatcher.handler.handleMessage(message);
|
|
} catch(e) {
|
|
rejectPromise = true;
|
|
}
|
|
|
|
this._isHandling = false;
|
|
aDispatcher.isHandling = false;
|
|
|
|
let self = this;
|
|
function sendResponse() {
|
|
// We need to notify the parent one of the system messages has been
|
|
// handled, so the parent can release the CPU wake lock it took on our
|
|
// behalf.
|
|
cpmm.sendAsyncMessage("SystemMessageManager:HandleMessageDone",
|
|
{ type: aType,
|
|
manifestURL: self._manifestURL,
|
|
pageURL: self._pageURL,
|
|
msgID: aMessageID,
|
|
rejected: rejectPromise });
|
|
}
|
|
|
|
if (!this._promise) {
|
|
debug("No promise set, sending the response immediately.");
|
|
sendResponse();
|
|
} else {
|
|
debug("Using the promise to postpone the response.");
|
|
this._promise.then(sendResponse, function() {
|
|
rejectPromise = true;
|
|
sendResponse();
|
|
});
|
|
this._promise = null;
|
|
}
|
|
|
|
if (aDispatcher.messages.length > 0) {
|
|
let msg = aDispatcher.messages.shift();
|
|
this._dispatchMessage(aType, aDispatcher, msg.message, msg.messageID);
|
|
} else {
|
|
// No more messages that need to be handled, we can notify the
|
|
// ContentChild to release the CPU wake lock grabbed by the ContentParent
|
|
// (i.e. NewWakeLockOnBehalfOfProcess()) and reset the process's priority.
|
|
//
|
|
// TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent
|
|
Services.obs.notifyObservers(/* aSubject */ null,
|
|
"handle-system-messages-done",
|
|
/* aData */ null);
|
|
}
|
|
},
|
|
|
|
mozSetMessageHandler: function(aType, aHandler) {
|
|
debug("set message handler for [" + aType + "] " + aHandler);
|
|
|
|
if (this._isInBrowserElement) {
|
|
debug("the app loaded in the browser cannot set message handler");
|
|
// Don't throw there, but ignore the registration.
|
|
return;
|
|
}
|
|
|
|
if (!aType) {
|
|
// Just bail out if we have no type.
|
|
return;
|
|
}
|
|
|
|
let dispatchers = this._dispatchers;
|
|
if (!aHandler) {
|
|
// Setting the dispatcher to null means we don't want to handle messages
|
|
// for this type anymore.
|
|
delete dispatchers[aType];
|
|
return;
|
|
}
|
|
|
|
// Last registered handler wins.
|
|
dispatchers[aType] = { handler: aHandler, messages: [], isHandling: false };
|
|
|
|
// Ask for the list of currently pending messages.
|
|
cpmm.sendAsyncMessage("SystemMessageManager:GetPendingMessages",
|
|
{ type: aType,
|
|
pageURL: this._pageURL,
|
|
manifestURL: this._manifestURL });
|
|
},
|
|
|
|
mozHasPendingMessage: function(aType) {
|
|
debug("asking pending message for [" + aType + "]");
|
|
|
|
if (this._isInBrowserElement) {
|
|
debug("the app loaded in the browser cannot ask pending message");
|
|
// Don't throw there, but pretend to have no messages available.
|
|
return false;
|
|
}
|
|
|
|
// If we have a handler for this type, we can't have any pending message.
|
|
if (aType in this._dispatchers) {
|
|
return false;
|
|
}
|
|
|
|
|
|
// Use SystemMessageManager directly when we are in the same process.
|
|
if (Services.appinfo.processType === Services.appinfo.PROCESS_TYPE_DEFAULT) {
|
|
return cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages",
|
|
{ type: aType,
|
|
pageURL: this._pageURL,
|
|
manifestURL: this._manifestURL })[0];
|
|
}
|
|
|
|
/*
|
|
* NB: If the system message is fired after we received the cache
|
|
* and before we registered the pageURL we will get false
|
|
* negative however this is unlikely and will do no harm.
|
|
*/
|
|
let cache = Cc["@mozilla.org/system-message-cache;1"]
|
|
.getService(Ci.nsISystemMessageCache);
|
|
|
|
return cache.hasPendingMessage(aType, this._pageURL, this._manifestURL);
|
|
},
|
|
|
|
mozIsHandlingMessage: function() {
|
|
debug("is handling message: " + this._isHandling);
|
|
return this._isHandling;
|
|
},
|
|
|
|
mozSetMessageHandlerPromise: function(aPromise) {
|
|
debug("setting a promise");
|
|
|
|
if (!this._isHandling) {
|
|
throw "Not in a handleMessage method";
|
|
}
|
|
|
|
if (this._promise) {
|
|
throw "Promise already set";
|
|
}
|
|
|
|
this._promise = aPromise;
|
|
},
|
|
|
|
uninit: function() {
|
|
this._dispatchers = null;
|
|
this._pendings = null;
|
|
this._promise = null;
|
|
|
|
if (this._isParentProcess) {
|
|
Services.obs.removeObserver(this, kSystemMessageInternalReady);
|
|
}
|
|
|
|
if (this._isInBrowserElement) {
|
|
debug("the app loaded in the browser doesn't need to unregister " +
|
|
"the manifest URL for listening to the system messages");
|
|
return;
|
|
}
|
|
|
|
cpmm.sendAsyncMessage("SystemMessageManager:Unregister",
|
|
{ manifestURL: this._manifestURL,
|
|
pageURL: this._pageURL,
|
|
innerWindowID: this.innerWindowID });
|
|
},
|
|
|
|
// Possible messages:
|
|
//
|
|
// - SystemMessageManager:Message
|
|
// This one will only be received when the child process is alive when
|
|
// the message is initially sent.
|
|
//
|
|
// - SystemMessageManager:GetPendingMessages:Return
|
|
// This one will be received when the starting child process wants to
|
|
// retrieve the pending system messages from the parent (i.e. after
|
|
// sending SystemMessageManager:GetPendingMessages).
|
|
receiveMessage: function(aMessage) {
|
|
let msg = aMessage.data;
|
|
debug("receiveMessage " + aMessage.name + " for [" + msg.type + "] " +
|
|
"with manifest URL = " + msg.manifestURL +
|
|
" and page URL = " + msg.pageURL);
|
|
|
|
// Multiple windows can share the same target (process), the content
|
|
// window needs to check if the manifest/page URL is matched. Only
|
|
// *one* window should handle the system message.
|
|
if (msg.manifestURL !== this._manifestURL ||
|
|
msg.pageURL !== this._pageURL) {
|
|
debug("This page shouldn't handle the messages because its " +
|
|
"manifest URL = " + this._manifestURL +
|
|
" and page URL = " + this._pageURL);
|
|
return;
|
|
}
|
|
|
|
let messages = (aMessage.name == "SystemMessageManager:Message")
|
|
? [{ msg: msg.msg, msgID: msg.msgID }]
|
|
: msg.msgQueue;
|
|
|
|
// We only dispatch messages when a handler is registered.
|
|
let dispatcher = this._dispatchers[msg.type];
|
|
if (dispatcher) {
|
|
if (aMessage.name == "SystemMessageManager:Message") {
|
|
// Send an acknowledgement to parent to clean up the pending message
|
|
// before we dispatch the message to apps, so a re-launched app won't
|
|
// handle it again, which is redundant.
|
|
cpmm.sendAsyncMessage("SystemMessageManager:Message:Return:OK",
|
|
{ type: msg.type,
|
|
manifestURL: this._manifestURL,
|
|
pageURL: this._pageURL,
|
|
msgID: msg.msgID });
|
|
}
|
|
|
|
messages.forEach(function(aMsg) {
|
|
this._dispatchMessage(msg.type, dispatcher, aMsg.msg, aMsg.msgID);
|
|
}, this);
|
|
|
|
} else {
|
|
// Since no handlers are registered, we need to notify the parent as if
|
|
// all the queued system messages have been handled (notice |handledCount:
|
|
// messages.length|), so the parent can release the CPU wake lock it took
|
|
// on our behalf.
|
|
cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone",
|
|
{ type: msg.type,
|
|
manifestURL: this._manifestURL,
|
|
pageURL: this._pageURL,
|
|
handledCount: messages.length });
|
|
|
|
// We also need to notify the ContentChild to release the CPU wake lock
|
|
// grabbed by the ContentParent (i.e. NewWakeLockOnBehalfOfProcess()) and
|
|
// reset the process's priority.
|
|
//
|
|
// TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent
|
|
Services.obs.notifyObservers(/* aSubject */ null,
|
|
"handle-system-messages-done",
|
|
/* aData */ null);
|
|
}
|
|
},
|
|
|
|
// nsIDOMGlobalPropertyInitializer implementation.
|
|
init: function(aWindow) {
|
|
debug("init");
|
|
this.initDOMRequestHelper(aWindow,
|
|
["SystemMessageManager:Message",
|
|
"SystemMessageManager:GetPendingMessages:Return"]);
|
|
|
|
let principal = aWindow.document.nodePrincipal;
|
|
this._isInBrowserElement = principal.isInBrowserElement;
|
|
this._pageURL = principal.URI.spec;
|
|
|
|
let appsService = Cc["@mozilla.org/AppsService;1"]
|
|
.getService(Ci.nsIAppsService);
|
|
this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
|
|
|
|
// Two cases are valid to register the manifest URL for the current process:
|
|
// 1. This is asked by a child process (parent process must be ready).
|
|
// 2. Parent process has already constructed the |SystemMessageInternal|.
|
|
// Otherwise, delay to do it when the |SystemMessageInternal| is ready.
|
|
let readyToRegister = true;
|
|
if (this._isParentProcess) {
|
|
let ready = cpmm.sendSyncMessage(
|
|
"SystemMessageManager:AskReadyToRegister", null);
|
|
if (ready.length == 0 || !ready[0]) {
|
|
readyToRegister = false;
|
|
}
|
|
}
|
|
if (readyToRegister) {
|
|
this._registerManifestURL();
|
|
}
|
|
|
|
debug("done");
|
|
},
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
if (aTopic === kSystemMessageInternalReady) {
|
|
this._registerManifestURL();
|
|
}
|
|
|
|
// Call the DOMRequestIpcHelper.observe method.
|
|
this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData);
|
|
},
|
|
|
|
_registerManifestURL: function() {
|
|
if (this._isInBrowserElement) {
|
|
debug("the app loaded in the browser doesn't need to register " +
|
|
"the manifest URL for listening to the system messages");
|
|
return;
|
|
}
|
|
|
|
if (!this._registerManifestURLReady) {
|
|
cpmm.sendAsyncMessage("SystemMessageManager:Register",
|
|
{ manifestURL: this._manifestURL,
|
|
pageURL: this._pageURL,
|
|
innerWindowID: this.innerWindowID });
|
|
|
|
this._registerManifestURLReady = true;
|
|
}
|
|
},
|
|
|
|
classID: Components.ID("{bc076ea0-609b-4d8f-83d7-5af7cbdc3bb2}"),
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMNavigatorSystemMessages,
|
|
Ci.nsIDOMGlobalPropertyInitializer,
|
|
Ci.nsIObserver,
|
|
Ci.nsISupportsWeakReference])
|
|
}
|
|
|
|
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageManager]);
|