Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
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/DOMRequestHelper.jsm");
14 Cu.import("resource://gre/modules/Services.jsm");
16 const kSystemMessageInternalReady = "system-message-internal-ready";
18 XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
19 "@mozilla.org/childprocessmessagemanager;1",
20 "nsISyncMessageSender");
22 function debug(aMsg) {
23 // dump("-- SystemMessageManager " + Date.now() + " : " + aMsg + "\n");
24 }
26 // Implementation of the DOM API for system messages
28 function SystemMessageManager() {
29 // If we have a system message handler registered for messages of type
30 // |type|, this._dispatchers[type] equals {handler, messages, isHandling},
31 // where
32 // - |handler| is the message handler that the page registered,
33 // - |messages| is a list of messages which we've received while
34 // dispatching messages to the handler, but haven't yet sent, and
35 // - |isHandling| indicates whether we're currently dispatching messages
36 // to this handler.
37 this._dispatchers = {};
39 // Pending messages for this page, keyed by message type.
40 this._pendings = {};
42 // Flag to specify if this process has already registered the manifest URL.
43 this._registerManifestURLReady = false;
45 // Flag to determine this process is a parent or child process.
46 let appInfo = Cc["@mozilla.org/xre/app-info;1"];
47 this._isParentProcess =
48 !appInfo || appInfo.getService(Ci.nsIXULRuntime)
49 .processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
51 // An oberver to listen to whether the |SystemMessageInternal| is ready.
52 if (this._isParentProcess) {
53 Services.obs.addObserver(this, kSystemMessageInternalReady, false);
54 }
55 }
57 SystemMessageManager.prototype = {
58 __proto__: DOMRequestIpcHelper.prototype,
60 _dispatchMessage: function(aType, aDispatcher, aMessage) {
61 if (aDispatcher.isHandling) {
62 // Queue up the incomming message if we're currently dispatching a
63 // message; we'll send the message once we finish with the current one.
64 //
65 // _dispatchMethod is reentrant because a page can spin up a nested
66 // event loop from within a system message handler (e.g. via alert()),
67 // and we can then try to send the page another message while it's
68 // inside this nested event loop.
69 aDispatcher.messages.push(aMessage);
70 return;
71 }
73 aDispatcher.isHandling = true;
75 // We get a json blob, but in some cases we want another kind of object
76 // to be dispatched. To do so, we check if we have a valid contract ID of
77 // "@mozilla.org/dom/system-messages/wrapper/TYPE;1" component implementing
78 // nsISystemMessageWrapper.
79 debug("Dispatching " + JSON.stringify(aMessage) + "\n");
80 let contractID = "@mozilla.org/dom/system-messages/wrapper/" + aType + ";1";
81 let wrapped = false;
83 if (contractID in Cc) {
84 debug(contractID + " is registered, creating an instance");
85 let wrapper = Cc[contractID].createInstance(Ci.nsISystemMessagesWrapper);
86 if (wrapper) {
87 aMessage = wrapper.wrapMessage(aMessage, this._window);
88 wrapped = true;
89 debug("wrapped = " + aMessage);
90 }
91 }
93 aDispatcher.handler
94 .handleMessage(wrapped ? aMessage
95 : Cu.cloneInto(aMessage, this._window));
97 // We need to notify the parent one of the system messages has been handled,
98 // so the parent can release the CPU wake lock it took on our behalf.
99 cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone",
100 { type: aType,
101 manifestURL: this._manifestURL,
102 pageURL: this._pageURL,
103 handledCount: 1 });
105 aDispatcher.isHandling = false;
107 if (aDispatcher.messages.length > 0) {
108 this._dispatchMessage(aType, aDispatcher, aDispatcher.messages.shift());
109 } else {
110 // No more messages that need to be handled, we can notify the
111 // ContentChild to release the CPU wake lock grabbed by the ContentParent
112 // (i.e. NewWakeLockOnBehalfOfProcess()) and reset the process's priority.
113 //
114 // TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent
115 Services.obs.notifyObservers(/* aSubject */ null,
116 "handle-system-messages-done",
117 /* aData */ null);
118 }
119 },
121 mozSetMessageHandler: function(aType, aHandler) {
122 debug("set message handler for [" + aType + "] " + aHandler);
124 if (this._isInBrowserElement) {
125 debug("the app loaded in the browser cannot set message handler");
126 // Don't throw there, but ignore the registration.
127 return;
128 }
130 if (!aType) {
131 // Just bail out if we have no type.
132 return;
133 }
135 let dispatchers = this._dispatchers;
136 if (!aHandler) {
137 // Setting the dispatcher to null means we don't want to handle messages
138 // for this type anymore.
139 delete dispatchers[aType];
140 return;
141 }
143 // Last registered handler wins.
144 dispatchers[aType] = { handler: aHandler, messages: [], isHandling: false };
146 // Ask for the list of currently pending messages.
147 cpmm.sendAsyncMessage("SystemMessageManager:GetPendingMessages",
148 { type: aType,
149 pageURL: this._pageURL,
150 manifestURL: this._manifestURL });
151 },
153 mozHasPendingMessage: function(aType) {
154 debug("asking pending message for [" + aType + "]");
156 if (this._isInBrowserElement) {
157 debug("the app loaded in the browser cannot ask pending message");
158 // Don't throw there, but pretend to have no messages available.
159 return false;
160 }
162 // If we have a handler for this type, we can't have any pending message.
163 if (aType in this._dispatchers) {
164 return false;
165 }
167 return cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages",
168 { type: aType,
169 pageURL: this._pageURL,
170 manifestURL: this._manifestURL })[0];
171 },
173 uninit: function() {
174 this._dispatchers = null;
175 this._pendings = null;
177 if (this._isParentProcess) {
178 Services.obs.removeObserver(this, kSystemMessageInternalReady);
179 }
181 if (this._isInBrowserElement) {
182 debug("the app loaded in the browser doesn't need to unregister " +
183 "the manifest URL for listening to the system messages");
184 return;
185 }
187 cpmm.sendAsyncMessage("SystemMessageManager:Unregister",
188 { manifestURL: this._manifestURL,
189 pageURL: this._pageURL,
190 innerWindowID: this.innerWindowID });
191 },
193 // Possible messages:
194 //
195 // - SystemMessageManager:Message
196 // This one will only be received when the child process is alive when
197 // the message is initially sent.
198 //
199 // - SystemMessageManager:GetPendingMessages:Return
200 // This one will be received when the starting child process wants to
201 // retrieve the pending system messages from the parent (i.e. after
202 // sending SystemMessageManager:GetPendingMessages).
203 receiveMessage: function(aMessage) {
204 let msg = aMessage.data;
205 debug("receiveMessage " + aMessage.name + " for [" + msg.type + "] " +
206 "with manifest URL = " + msg.manifestURL +
207 " and page URL = " + msg.pageURL);
209 // Multiple windows can share the same target (process), the content
210 // window needs to check if the manifest/page URL is matched. Only
211 // *one* window should handle the system message.
212 if (msg.manifestURL !== this._manifestURL ||
213 msg.pageURL !== this._pageURL) {
214 debug("This page shouldn't handle the messages because its " +
215 "manifest URL = " + this._manifestURL +
216 " and page URL = " + this._pageURL);
217 return;
218 }
220 let messages = (aMessage.name == "SystemMessageManager:Message")
221 ? [msg.msg]
222 : msg.msgQueue;
224 // We only dispatch messages when a handler is registered.
225 let dispatcher = this._dispatchers[msg.type];
226 if (dispatcher) {
227 if (aMessage.name == "SystemMessageManager:Message") {
228 // Send an acknowledgement to parent to clean up the pending message
229 // before we dispatch the message to apps, so a re-launched app won't
230 // handle it again, which is redundant.
231 cpmm.sendAsyncMessage("SystemMessageManager:Message:Return:OK",
232 { type: msg.type,
233 manifestURL: this._manifestURL,
234 pageURL: this._pageURL,
235 msgID: msg.msgID });
236 }
238 messages.forEach(function(aMsg) {
239 this._dispatchMessage(msg.type, dispatcher, aMsg);
240 }, this);
241 } else {
242 // Since no handlers are registered, we need to notify the parent as if
243 // all the queued system messages have been handled (notice |handledCount:
244 // messages.length|), so the parent can release the CPU wake lock it took
245 // on our behalf.
246 cpmm.sendAsyncMessage("SystemMessageManager:HandleMessagesDone",
247 { type: msg.type,
248 manifestURL: this._manifestURL,
249 pageURL: this._pageURL,
250 handledCount: messages.length });
252 // We also need to notify the ContentChild to release the CPU wake lock
253 // grabbed by the ContentParent (i.e. NewWakeLockOnBehalfOfProcess()) and
254 // reset the process's priority.
255 //
256 // TODO: Bug 874353 - Remove SystemMessageHandledListener in ContentParent
257 Services.obs.notifyObservers(/* aSubject */ null,
258 "handle-system-messages-done",
259 /* aData */ null);
260 }
261 },
263 // nsIDOMGlobalPropertyInitializer implementation.
264 init: function(aWindow) {
265 debug("init");
266 this.initDOMRequestHelper(aWindow,
267 ["SystemMessageManager:Message",
268 "SystemMessageManager:GetPendingMessages:Return"]);
270 let principal = aWindow.document.nodePrincipal;
271 this._isInBrowserElement = principal.isInBrowserElement;
272 this._pageURL = principal.URI.spec;
274 let appsService = Cc["@mozilla.org/AppsService;1"]
275 .getService(Ci.nsIAppsService);
276 this._manifestURL = appsService.getManifestURLByLocalId(principal.appId);
278 // Two cases are valid to register the manifest URL for the current process:
279 // 1. This is asked by a child process (parent process must be ready).
280 // 2. Parent process has already constructed the |SystemMessageInternal|.
281 // Otherwise, delay to do it when the |SystemMessageInternal| is ready.
282 let readyToRegister = true;
283 if (this._isParentProcess) {
284 let ready = cpmm.sendSyncMessage(
285 "SystemMessageManager:AskReadyToRegister", null);
286 if (ready.length == 0 || !ready[0]) {
287 readyToRegister = false;
288 }
289 }
290 if (readyToRegister) {
291 this._registerManifestURL();
292 }
294 debug("done");
295 },
297 observe: function(aSubject, aTopic, aData) {
298 if (aTopic === kSystemMessageInternalReady) {
299 this._registerManifestURL();
300 }
302 // Call the DOMRequestIpcHelper.observe method.
303 this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData);
304 },
306 _registerManifestURL: function() {
307 if (this._isInBrowserElement) {
308 debug("the app loaded in the browser doesn't need to register " +
309 "the manifest URL for listening to the system messages");
310 return;
311 }
313 if (!this._registerManifestURLReady) {
314 cpmm.sendAsyncMessage("SystemMessageManager:Register",
315 { manifestURL: this._manifestURL,
316 pageURL: this._pageURL,
317 innerWindowID: this.innerWindowID });
319 this._registerManifestURLReady = true;
320 }
321 },
323 classID: Components.ID("{bc076ea0-609b-4d8f-83d7-5af7cbdc3bb2}"),
325 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMNavigatorSystemMessages,
326 Ci.nsIDOMGlobalPropertyInitializer,
327 Ci.nsIObserver,
328 Ci.nsISupportsWeakReference])
329 }
331 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageManager]);