dom/messages/SystemMessageManager.js

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

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

mercurial