|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const Cc = Components.classes; |
|
8 const Ci = Components.interfaces; |
|
9 const Cu = Components.utils; |
|
10 const Cr = Components.results; |
|
11 |
|
12 Cu.import("resource://gre/modules/XPCOMUtils.jsm"); |
|
13 Cu.import("resource://gre/modules/DOMRequestHelper.jsm"); |
|
14 Cu.import("resource://gre/modules/Services.jsm"); |
|
15 |
|
16 const kSystemMessageInternalReady = "system-message-internal-ready"; |
|
17 |
|
18 XPCOMUtils.defineLazyServiceGetter(this, "cpmm", |
|
19 "@mozilla.org/childprocessmessagemanager;1", |
|
20 "nsISyncMessageSender"); |
|
21 |
|
22 function debug(aMsg) { |
|
23 // dump("-- SystemMessageManager " + Date.now() + " : " + aMsg + "\n"); |
|
24 } |
|
25 |
|
26 // Implementation of the DOM API for system messages |
|
27 |
|
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 = {}; |
|
38 |
|
39 // Pending messages for this page, keyed by message type. |
|
40 this._pendings = {}; |
|
41 |
|
42 // Flag to specify if this process has already registered the manifest URL. |
|
43 this._registerManifestURLReady = false; |
|
44 |
|
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; |
|
50 |
|
51 // An oberver to listen to whether the |SystemMessageInternal| is ready. |
|
52 if (this._isParentProcess) { |
|
53 Services.obs.addObserver(this, kSystemMessageInternalReady, false); |
|
54 } |
|
55 } |
|
56 |
|
57 SystemMessageManager.prototype = { |
|
58 __proto__: DOMRequestIpcHelper.prototype, |
|
59 |
|
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 } |
|
72 |
|
73 aDispatcher.isHandling = true; |
|
74 |
|
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; |
|
82 |
|
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 } |
|
92 |
|
93 aDispatcher.handler |
|
94 .handleMessage(wrapped ? aMessage |
|
95 : Cu.cloneInto(aMessage, this._window)); |
|
96 |
|
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 }); |
|
104 |
|
105 aDispatcher.isHandling = false; |
|
106 |
|
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 }, |
|
120 |
|
121 mozSetMessageHandler: function(aType, aHandler) { |
|
122 debug("set message handler for [" + aType + "] " + aHandler); |
|
123 |
|
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 } |
|
129 |
|
130 if (!aType) { |
|
131 // Just bail out if we have no type. |
|
132 return; |
|
133 } |
|
134 |
|
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 } |
|
142 |
|
143 // Last registered handler wins. |
|
144 dispatchers[aType] = { handler: aHandler, messages: [], isHandling: false }; |
|
145 |
|
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 }, |
|
152 |
|
153 mozHasPendingMessage: function(aType) { |
|
154 debug("asking pending message for [" + aType + "]"); |
|
155 |
|
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 } |
|
161 |
|
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 } |
|
166 |
|
167 return cpmm.sendSyncMessage("SystemMessageManager:HasPendingMessages", |
|
168 { type: aType, |
|
169 pageURL: this._pageURL, |
|
170 manifestURL: this._manifestURL })[0]; |
|
171 }, |
|
172 |
|
173 uninit: function() { |
|
174 this._dispatchers = null; |
|
175 this._pendings = null; |
|
176 |
|
177 if (this._isParentProcess) { |
|
178 Services.obs.removeObserver(this, kSystemMessageInternalReady); |
|
179 } |
|
180 |
|
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 } |
|
186 |
|
187 cpmm.sendAsyncMessage("SystemMessageManager:Unregister", |
|
188 { manifestURL: this._manifestURL, |
|
189 pageURL: this._pageURL, |
|
190 innerWindowID: this.innerWindowID }); |
|
191 }, |
|
192 |
|
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); |
|
208 |
|
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 } |
|
219 |
|
220 let messages = (aMessage.name == "SystemMessageManager:Message") |
|
221 ? [msg.msg] |
|
222 : msg.msgQueue; |
|
223 |
|
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 } |
|
237 |
|
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 }); |
|
251 |
|
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 }, |
|
262 |
|
263 // nsIDOMGlobalPropertyInitializer implementation. |
|
264 init: function(aWindow) { |
|
265 debug("init"); |
|
266 this.initDOMRequestHelper(aWindow, |
|
267 ["SystemMessageManager:Message", |
|
268 "SystemMessageManager:GetPendingMessages:Return"]); |
|
269 |
|
270 let principal = aWindow.document.nodePrincipal; |
|
271 this._isInBrowserElement = principal.isInBrowserElement; |
|
272 this._pageURL = principal.URI.spec; |
|
273 |
|
274 let appsService = Cc["@mozilla.org/AppsService;1"] |
|
275 .getService(Ci.nsIAppsService); |
|
276 this._manifestURL = appsService.getManifestURLByLocalId(principal.appId); |
|
277 |
|
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 } |
|
293 |
|
294 debug("done"); |
|
295 }, |
|
296 |
|
297 observe: function(aSubject, aTopic, aData) { |
|
298 if (aTopic === kSystemMessageInternalReady) { |
|
299 this._registerManifestURL(); |
|
300 } |
|
301 |
|
302 // Call the DOMRequestIpcHelper.observe method. |
|
303 this.__proto__.__proto__.observe.call(this, aSubject, aTopic, aData); |
|
304 }, |
|
305 |
|
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 } |
|
312 |
|
313 if (!this._registerManifestURLReady) { |
|
314 cpmm.sendAsyncMessage("SystemMessageManager:Register", |
|
315 { manifestURL: this._manifestURL, |
|
316 pageURL: this._pageURL, |
|
317 innerWindowID: this.innerWindowID }); |
|
318 |
|
319 this._registerManifestURLReady = true; |
|
320 } |
|
321 }, |
|
322 |
|
323 classID: Components.ID("{bc076ea0-609b-4d8f-83d7-5af7cbdc3bb2}"), |
|
324 |
|
325 QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMNavigatorSystemMessages, |
|
326 Ci.nsIDOMGlobalPropertyInitializer, |
|
327 Ci.nsIObserver, |
|
328 Ci.nsISupportsWeakReference]) |
|
329 } |
|
330 |
|
331 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageManager]); |