Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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/Services.jsm"); |
michael@0 | 14 | Cu.import("resource://gre/modules/SystemMessagePermissionsChecker.jsm"); |
michael@0 | 15 | |
michael@0 | 16 | XPCOMUtils.defineLazyServiceGetter(this, "ppmm", |
michael@0 | 17 | "@mozilla.org/parentprocessmessagemanager;1", |
michael@0 | 18 | "nsIMessageBroadcaster"); |
michael@0 | 19 | |
michael@0 | 20 | XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator", |
michael@0 | 21 | "@mozilla.org/uuid-generator;1", |
michael@0 | 22 | "nsIUUIDGenerator"); |
michael@0 | 23 | |
michael@0 | 24 | XPCOMUtils.defineLazyServiceGetter(this, "powerManagerService", |
michael@0 | 25 | "@mozilla.org/power/powermanagerservice;1", |
michael@0 | 26 | "nsIPowerManagerService"); |
michael@0 | 27 | |
michael@0 | 28 | // Limit the number of pending messages for a given page. |
michael@0 | 29 | let kMaxPendingMessages; |
michael@0 | 30 | try { |
michael@0 | 31 | kMaxPendingMessages = |
michael@0 | 32 | Services.prefs.getIntPref("dom.messages.maxPendingMessages"); |
michael@0 | 33 | } catch(e) { |
michael@0 | 34 | // getIntPref throws when the pref is not set. |
michael@0 | 35 | kMaxPendingMessages = 5; |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | const kMessages =["SystemMessageManager:GetPendingMessages", |
michael@0 | 39 | "SystemMessageManager:HasPendingMessages", |
michael@0 | 40 | "SystemMessageManager:Register", |
michael@0 | 41 | "SystemMessageManager:Unregister", |
michael@0 | 42 | "SystemMessageManager:Message:Return:OK", |
michael@0 | 43 | "SystemMessageManager:AskReadyToRegister", |
michael@0 | 44 | "SystemMessageManager:HandleMessagesDone", |
michael@0 | 45 | "child-process-shutdown"] |
michael@0 | 46 | |
michael@0 | 47 | function debug(aMsg) { |
michael@0 | 48 | // dump("-- SystemMessageInternal " + Date.now() + " : " + aMsg + "\n"); |
michael@0 | 49 | } |
michael@0 | 50 | |
michael@0 | 51 | |
michael@0 | 52 | let defaultMessageConfigurator = { |
michael@0 | 53 | get mustShowRunningApp() { |
michael@0 | 54 | return false; |
michael@0 | 55 | } |
michael@0 | 56 | }; |
michael@0 | 57 | |
michael@0 | 58 | const MSG_SENT_SUCCESS = 0; |
michael@0 | 59 | const MSG_SENT_FAILURE_PERM_DENIED = 1; |
michael@0 | 60 | const MSG_SENT_FAILURE_APP_NOT_RUNNING = 2; |
michael@0 | 61 | |
michael@0 | 62 | // Implementation of the component used by internal users. |
michael@0 | 63 | |
michael@0 | 64 | function SystemMessageInternal() { |
michael@0 | 65 | // The set of pages registered by installed apps. We keep the |
michael@0 | 66 | // list of pending messages for each page here also. |
michael@0 | 67 | this._pages = []; |
michael@0 | 68 | |
michael@0 | 69 | // The set of listeners. This is a multi-dimensional object. The _listeners |
michael@0 | 70 | // object itself is a map from manifest URL -> an array mapping proccesses to |
michael@0 | 71 | // windows. We do this so that we can track both what processes we have to |
michael@0 | 72 | // send system messages to as well as supporting the single-process case |
michael@0 | 73 | // where we track windows instead. |
michael@0 | 74 | this._listeners = {}; |
michael@0 | 75 | |
michael@0 | 76 | this._webappsRegistryReady = false; |
michael@0 | 77 | this._bufferedSysMsgs = []; |
michael@0 | 78 | |
michael@0 | 79 | this._cpuWakeLocks = {}; |
michael@0 | 80 | |
michael@0 | 81 | this._configurators = {}; |
michael@0 | 82 | |
michael@0 | 83 | Services.obs.addObserver(this, "xpcom-shutdown", false); |
michael@0 | 84 | Services.obs.addObserver(this, "webapps-registry-start", false); |
michael@0 | 85 | Services.obs.addObserver(this, "webapps-registry-ready", false); |
michael@0 | 86 | kMessages.forEach(function(aMsg) { |
michael@0 | 87 | ppmm.addMessageListener(aMsg, this); |
michael@0 | 88 | }, this); |
michael@0 | 89 | |
michael@0 | 90 | Services.obs.notifyObservers(this, "system-message-internal-ready", null); |
michael@0 | 91 | } |
michael@0 | 92 | |
michael@0 | 93 | SystemMessageInternal.prototype = { |
michael@0 | 94 | |
michael@0 | 95 | _getMessageConfigurator: function(aType) { |
michael@0 | 96 | debug("_getMessageConfigurator for type: " + aType); |
michael@0 | 97 | if (this._configurators[aType] === undefined) { |
michael@0 | 98 | let contractID = |
michael@0 | 99 | "@mozilla.org/dom/system-messages/configurator/" + aType + ";1"; |
michael@0 | 100 | if (contractID in Cc) { |
michael@0 | 101 | debug(contractID + " is registered, creating an instance"); |
michael@0 | 102 | this._configurators[aType] = |
michael@0 | 103 | Cc[contractID].createInstance(Ci.nsISystemMessagesConfigurator); |
michael@0 | 104 | } else { |
michael@0 | 105 | debug(contractID + "is not registered, caching the answer"); |
michael@0 | 106 | this._configurators[aType] = null; |
michael@0 | 107 | } |
michael@0 | 108 | } |
michael@0 | 109 | return this._configurators[aType] || defaultMessageConfigurator; |
michael@0 | 110 | }, |
michael@0 | 111 | |
michael@0 | 112 | _cancelCpuWakeLock: function(aPageKey) { |
michael@0 | 113 | let cpuWakeLock = this._cpuWakeLocks[aPageKey]; |
michael@0 | 114 | if (cpuWakeLock) { |
michael@0 | 115 | debug("Releasing the CPU wake lock for page key = " + aPageKey); |
michael@0 | 116 | cpuWakeLock.wakeLock.unlock(); |
michael@0 | 117 | cpuWakeLock.timer.cancel(); |
michael@0 | 118 | delete this._cpuWakeLocks[aPageKey]; |
michael@0 | 119 | } |
michael@0 | 120 | }, |
michael@0 | 121 | |
michael@0 | 122 | _acquireCpuWakeLock: function(aPageKey) { |
michael@0 | 123 | let cpuWakeLock = this._cpuWakeLocks[aPageKey]; |
michael@0 | 124 | if (!cpuWakeLock) { |
michael@0 | 125 | // We have to ensure the CPU doesn't sleep during the process of the page |
michael@0 | 126 | // handling the system messages, so that they can be handled on time. |
michael@0 | 127 | debug("Acquiring a CPU wake lock for page key = " + aPageKey); |
michael@0 | 128 | cpuWakeLock = this._cpuWakeLocks[aPageKey] = { |
michael@0 | 129 | wakeLock: powerManagerService.newWakeLock("cpu"), |
michael@0 | 130 | timer: Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer), |
michael@0 | 131 | lockCount: 1 |
michael@0 | 132 | }; |
michael@0 | 133 | } else { |
michael@0 | 134 | // We've already acquired the CPU wake lock for this page, |
michael@0 | 135 | // so just add to the lock count and extend the timeout. |
michael@0 | 136 | cpuWakeLock.lockCount++; |
michael@0 | 137 | } |
michael@0 | 138 | |
michael@0 | 139 | // Set a watchdog to avoid locking the CPU wake lock too long, |
michael@0 | 140 | // because it'd exhaust the battery quickly which is very bad. |
michael@0 | 141 | // This could probably happen if the app failed to launch or |
michael@0 | 142 | // handle the system messages due to any unexpected reasons. |
michael@0 | 143 | cpuWakeLock.timer.initWithCallback(function timerCb() { |
michael@0 | 144 | debug("Releasing the CPU wake lock because the system messages " + |
michael@0 | 145 | "were not handled by its registered page before time out."); |
michael@0 | 146 | this._cancelCpuWakeLock(aPageKey); |
michael@0 | 147 | }.bind(this), 30000, Ci.nsITimer.TYPE_ONE_SHOT); |
michael@0 | 148 | }, |
michael@0 | 149 | |
michael@0 | 150 | _releaseCpuWakeLock: function _releaseCpuWakeLock(aPageKey, aHandledCount) { |
michael@0 | 151 | let cpuWakeLock = this._cpuWakeLocks[aPageKey]; |
michael@0 | 152 | if (cpuWakeLock) { |
michael@0 | 153 | cpuWakeLock.lockCount -= aHandledCount; |
michael@0 | 154 | if (cpuWakeLock.lockCount <= 0) { |
michael@0 | 155 | debug("Unlocking the CPU wake lock now that the system messages " + |
michael@0 | 156 | "have been successfully handled by its registered page."); |
michael@0 | 157 | this._cancelCpuWakeLock(aPageKey); |
michael@0 | 158 | } |
michael@0 | 159 | } |
michael@0 | 160 | }, |
michael@0 | 161 | |
michael@0 | 162 | _findPage: function(aType, aPageURL, aManifestURL) { |
michael@0 | 163 | let page = null; |
michael@0 | 164 | this._pages.some(function(aPage) { |
michael@0 | 165 | if (this._isPageMatched(aPage, aType, aPageURL, aManifestURL)) { |
michael@0 | 166 | page = aPage; |
michael@0 | 167 | } |
michael@0 | 168 | return page !== null; |
michael@0 | 169 | }, this); |
michael@0 | 170 | return page; |
michael@0 | 171 | }, |
michael@0 | 172 | |
michael@0 | 173 | sendMessage: function(aType, aMessage, aPageURI, aManifestURI, aExtra) { |
michael@0 | 174 | // Buffer system messages until the webapps' registration is ready, |
michael@0 | 175 | // so that we can know the correct pages registered to be sent. |
michael@0 | 176 | if (!this._webappsRegistryReady) { |
michael@0 | 177 | this._bufferedSysMsgs.push({ how: "send", |
michael@0 | 178 | type: aType, |
michael@0 | 179 | msg: aMessage, |
michael@0 | 180 | pageURI: aPageURI, |
michael@0 | 181 | manifestURI: aManifestURI, |
michael@0 | 182 | extra: aExtra }); |
michael@0 | 183 | return; |
michael@0 | 184 | } |
michael@0 | 185 | |
michael@0 | 186 | // Give this message an ID so that we can identify the message and |
michael@0 | 187 | // clean it up from the pending message queue when apps receive it. |
michael@0 | 188 | let messageID = gUUIDGenerator.generateUUID().toString(); |
michael@0 | 189 | |
michael@0 | 190 | debug("Sending " + aType + " " + JSON.stringify(aMessage) + |
michael@0 | 191 | " for " + aPageURI.spec + " @ " + aManifestURI.spec + |
michael@0 | 192 | '; extra: ' + JSON.stringify(aExtra)); |
michael@0 | 193 | |
michael@0 | 194 | let result = this._sendMessageCommon(aType, |
michael@0 | 195 | aMessage, |
michael@0 | 196 | messageID, |
michael@0 | 197 | aPageURI.spec, |
michael@0 | 198 | aManifestURI.spec, |
michael@0 | 199 | aExtra); |
michael@0 | 200 | debug("Returned status of sending message: " + result); |
michael@0 | 201 | |
michael@0 | 202 | // Don't need to open the pages and queue the system message |
michael@0 | 203 | // which was not allowed to be sent. |
michael@0 | 204 | if (result === MSG_SENT_FAILURE_PERM_DENIED) { |
michael@0 | 205 | return; |
michael@0 | 206 | } |
michael@0 | 207 | |
michael@0 | 208 | let page = this._findPage(aType, aPageURI.spec, aManifestURI.spec); |
michael@0 | 209 | if (page) { |
michael@0 | 210 | // Queue this message in the corresponding pages. |
michael@0 | 211 | this._queueMessage(page, aMessage, messageID); |
michael@0 | 212 | |
michael@0 | 213 | this._openAppPage(page, aMessage, aExtra, result); |
michael@0 | 214 | } |
michael@0 | 215 | }, |
michael@0 | 216 | |
michael@0 | 217 | broadcastMessage: function(aType, aMessage, aExtra) { |
michael@0 | 218 | // Buffer system messages until the webapps' registration is ready, |
michael@0 | 219 | // so that we can know the correct pages registered to be broadcasted. |
michael@0 | 220 | if (!this._webappsRegistryReady) { |
michael@0 | 221 | this._bufferedSysMsgs.push({ how: "broadcast", |
michael@0 | 222 | type: aType, |
michael@0 | 223 | msg: aMessage, |
michael@0 | 224 | extra: aExtra }); |
michael@0 | 225 | return; |
michael@0 | 226 | } |
michael@0 | 227 | |
michael@0 | 228 | // Give this message an ID so that we can identify the message and |
michael@0 | 229 | // clean it up from the pending message queue when apps receive it. |
michael@0 | 230 | let messageID = gUUIDGenerator.generateUUID().toString(); |
michael@0 | 231 | |
michael@0 | 232 | debug("Broadcasting " + aType + " " + JSON.stringify(aMessage) + |
michael@0 | 233 | '; extra = ' + JSON.stringify(aExtra)); |
michael@0 | 234 | // Find pages that registered an handler for this type. |
michael@0 | 235 | this._pages.forEach(function(aPage) { |
michael@0 | 236 | if (aPage.type == aType) { |
michael@0 | 237 | let result = this._sendMessageCommon(aType, |
michael@0 | 238 | aMessage, |
michael@0 | 239 | messageID, |
michael@0 | 240 | aPage.pageURL, |
michael@0 | 241 | aPage.manifestURL, |
michael@0 | 242 | aExtra); |
michael@0 | 243 | debug("Returned status of sending message: " + result); |
michael@0 | 244 | |
michael@0 | 245 | |
michael@0 | 246 | // Don't need to open the pages and queue the system message |
michael@0 | 247 | // which was not allowed to be sent. |
michael@0 | 248 | if (result === MSG_SENT_FAILURE_PERM_DENIED) { |
michael@0 | 249 | return; |
michael@0 | 250 | } |
michael@0 | 251 | |
michael@0 | 252 | // Queue this message in the corresponding pages. |
michael@0 | 253 | this._queueMessage(aPage, aMessage, messageID); |
michael@0 | 254 | |
michael@0 | 255 | this._openAppPage(aPage, aMessage, aExtra, result); |
michael@0 | 256 | } |
michael@0 | 257 | }, this); |
michael@0 | 258 | }, |
michael@0 | 259 | |
michael@0 | 260 | registerPage: function(aType, aPageURI, aManifestURI) { |
michael@0 | 261 | if (!aPageURI || !aManifestURI) { |
michael@0 | 262 | throw Cr.NS_ERROR_INVALID_ARG; |
michael@0 | 263 | } |
michael@0 | 264 | |
michael@0 | 265 | let pageURL = aPageURI.spec; |
michael@0 | 266 | let manifestURL = aManifestURI.spec; |
michael@0 | 267 | |
michael@0 | 268 | // Don't register duplicates for this tuple. |
michael@0 | 269 | let page = this._findPage(aType, pageURL, manifestURL); |
michael@0 | 270 | if (page) { |
michael@0 | 271 | debug("Ignoring duplicate registration of " + |
michael@0 | 272 | [aType, pageURL, manifestURL]); |
michael@0 | 273 | return; |
michael@0 | 274 | } |
michael@0 | 275 | |
michael@0 | 276 | this._pages.push({ type: aType, |
michael@0 | 277 | pageURL: pageURL, |
michael@0 | 278 | manifestURL: manifestURL, |
michael@0 | 279 | pendingMessages: [] }); |
michael@0 | 280 | }, |
michael@0 | 281 | |
michael@0 | 282 | _findTargetIndex: function(aTargets, aTarget) { |
michael@0 | 283 | if (!aTargets || !aTarget) { |
michael@0 | 284 | return -1; |
michael@0 | 285 | } |
michael@0 | 286 | for (let index = 0; index < aTargets.length; ++index) { |
michael@0 | 287 | let target = aTargets[index]; |
michael@0 | 288 | if (target.target === aTarget) { |
michael@0 | 289 | return index; |
michael@0 | 290 | } |
michael@0 | 291 | } |
michael@0 | 292 | return -1; |
michael@0 | 293 | }, |
michael@0 | 294 | |
michael@0 | 295 | _isEmptyObject: function(aObj) { |
michael@0 | 296 | for (let name in aObj) { |
michael@0 | 297 | return false; |
michael@0 | 298 | } |
michael@0 | 299 | return true; |
michael@0 | 300 | }, |
michael@0 | 301 | |
michael@0 | 302 | _removeTargetFromListener: function(aTarget, |
michael@0 | 303 | aManifestURL, |
michael@0 | 304 | aRemoveListener, |
michael@0 | 305 | aPageURL) { |
michael@0 | 306 | let targets = this._listeners[aManifestURL]; |
michael@0 | 307 | if (!targets) { |
michael@0 | 308 | return false; |
michael@0 | 309 | } |
michael@0 | 310 | |
michael@0 | 311 | let index = this._findTargetIndex(targets, aTarget); |
michael@0 | 312 | if (index === -1) { |
michael@0 | 313 | return false; |
michael@0 | 314 | } |
michael@0 | 315 | |
michael@0 | 316 | if (aRemoveListener) { |
michael@0 | 317 | debug("remove the listener for " + aManifestURL); |
michael@0 | 318 | delete this._listeners[aManifestURL]; |
michael@0 | 319 | return true; |
michael@0 | 320 | } |
michael@0 | 321 | |
michael@0 | 322 | let target = targets[index]; |
michael@0 | 323 | if (aPageURL && target.winCounts[aPageURL] !== undefined && |
michael@0 | 324 | --target.winCounts[aPageURL] === 0) { |
michael@0 | 325 | delete target.winCounts[aPageURL]; |
michael@0 | 326 | } |
michael@0 | 327 | |
michael@0 | 328 | if (this._isEmptyObject(target.winCounts)) { |
michael@0 | 329 | if (targets.length === 1) { |
michael@0 | 330 | // If it's the only one, get rid of the entry of manifest URL entirely. |
michael@0 | 331 | debug("remove the listener for " + aManifestURL); |
michael@0 | 332 | delete this._listeners[aManifestURL]; |
michael@0 | 333 | } else { |
michael@0 | 334 | // If more than one left, remove this one and leave the rest. |
michael@0 | 335 | targets.splice(index, 1); |
michael@0 | 336 | } |
michael@0 | 337 | } |
michael@0 | 338 | return true; |
michael@0 | 339 | }, |
michael@0 | 340 | |
michael@0 | 341 | receiveMessage: function(aMessage) { |
michael@0 | 342 | let msg = aMessage.json; |
michael@0 | 343 | |
michael@0 | 344 | // To prevent the hacked child process from sending commands to parent |
michael@0 | 345 | // to manage system messages, we need to check its manifest URL. |
michael@0 | 346 | if (["SystemMessageManager:Register", |
michael@0 | 347 | // TODO: fix bug 988142 to re-enable. |
michael@0 | 348 | // "SystemMessageManager:Unregister", |
michael@0 | 349 | "SystemMessageManager:GetPendingMessages", |
michael@0 | 350 | "SystemMessageManager:HasPendingMessages", |
michael@0 | 351 | "SystemMessageManager:Message:Return:OK", |
michael@0 | 352 | "SystemMessageManager:HandleMessagesDone"].indexOf(aMessage.name) != -1) { |
michael@0 | 353 | if (!aMessage.target.assertContainApp(msg.manifestURL)) { |
michael@0 | 354 | debug("Got message from a child process containing illegal manifest URL."); |
michael@0 | 355 | return null; |
michael@0 | 356 | } |
michael@0 | 357 | } |
michael@0 | 358 | |
michael@0 | 359 | switch(aMessage.name) { |
michael@0 | 360 | case "SystemMessageManager:AskReadyToRegister": |
michael@0 | 361 | return true; |
michael@0 | 362 | break; |
michael@0 | 363 | case "SystemMessageManager:Register": |
michael@0 | 364 | { |
michael@0 | 365 | debug("Got Register from " + msg.pageURL + " @ " + msg.manifestURL); |
michael@0 | 366 | let pageURL = msg.pageURL; |
michael@0 | 367 | let targets, index; |
michael@0 | 368 | if (!(targets = this._listeners[msg.manifestURL])) { |
michael@0 | 369 | let winCounts = {}; |
michael@0 | 370 | winCounts[pageURL] = 1; |
michael@0 | 371 | this._listeners[msg.manifestURL] = [{ target: aMessage.target, |
michael@0 | 372 | winCounts: winCounts }]; |
michael@0 | 373 | } else if ((index = this._findTargetIndex(targets, |
michael@0 | 374 | aMessage.target)) === -1) { |
michael@0 | 375 | let winCounts = {}; |
michael@0 | 376 | winCounts[pageURL] = 1; |
michael@0 | 377 | targets.push({ target: aMessage.target, |
michael@0 | 378 | winCounts: winCounts }); |
michael@0 | 379 | } else { |
michael@0 | 380 | let winCounts = targets[index].winCounts; |
michael@0 | 381 | if (winCounts[pageURL] === undefined) { |
michael@0 | 382 | winCounts[pageURL] = 1; |
michael@0 | 383 | } else { |
michael@0 | 384 | winCounts[pageURL]++; |
michael@0 | 385 | } |
michael@0 | 386 | } |
michael@0 | 387 | |
michael@0 | 388 | debug("listeners for " + msg.manifestURL + |
michael@0 | 389 | " innerWinID " + msg.innerWindowID); |
michael@0 | 390 | break; |
michael@0 | 391 | } |
michael@0 | 392 | case "child-process-shutdown": |
michael@0 | 393 | { |
michael@0 | 394 | debug("Got child-process-shutdown from " + aMessage.target); |
michael@0 | 395 | for (let manifestURL in this._listeners) { |
michael@0 | 396 | // See if any processes in this manifest URL have this target. |
michael@0 | 397 | this._removeTargetFromListener(aMessage.target, |
michael@0 | 398 | manifestURL, |
michael@0 | 399 | true, |
michael@0 | 400 | null); |
michael@0 | 401 | } |
michael@0 | 402 | break; |
michael@0 | 403 | } |
michael@0 | 404 | case "SystemMessageManager:Unregister": |
michael@0 | 405 | { |
michael@0 | 406 | debug("Got Unregister from " + aMessage.target + |
michael@0 | 407 | " innerWinID " + msg.innerWindowID); |
michael@0 | 408 | this._removeTargetFromListener(aMessage.target, |
michael@0 | 409 | msg.manifestURL, |
michael@0 | 410 | false, |
michael@0 | 411 | msg.pageURL); |
michael@0 | 412 | break; |
michael@0 | 413 | } |
michael@0 | 414 | case "SystemMessageManager:GetPendingMessages": |
michael@0 | 415 | { |
michael@0 | 416 | debug("received SystemMessageManager:GetPendingMessages " + msg.type + |
michael@0 | 417 | " for " + msg.pageURL + " @ " + msg.manifestURL); |
michael@0 | 418 | |
michael@0 | 419 | // This is a sync call used to return the pending messages for a page. |
michael@0 | 420 | // Find the right page to get its corresponding pending messages. |
michael@0 | 421 | let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); |
michael@0 | 422 | if (!page) { |
michael@0 | 423 | return; |
michael@0 | 424 | } |
michael@0 | 425 | |
michael@0 | 426 | // Return the |msg| of each pending message (drop the |msgID|). |
michael@0 | 427 | let pendingMessages = []; |
michael@0 | 428 | page.pendingMessages.forEach(function(aMessage) { |
michael@0 | 429 | pendingMessages.push(aMessage.msg); |
michael@0 | 430 | }); |
michael@0 | 431 | |
michael@0 | 432 | // Clear the pending queue for this page. This is OK since we'll store |
michael@0 | 433 | // pending messages in the content process (|SystemMessageManager|). |
michael@0 | 434 | page.pendingMessages.length = 0; |
michael@0 | 435 | |
michael@0 | 436 | // Send the array of pending messages. |
michael@0 | 437 | aMessage.target |
michael@0 | 438 | .sendAsyncMessage("SystemMessageManager:GetPendingMessages:Return", |
michael@0 | 439 | { type: msg.type, |
michael@0 | 440 | manifestURL: msg.manifestURL, |
michael@0 | 441 | pageURL: msg.pageURL, |
michael@0 | 442 | msgQueue: pendingMessages }); |
michael@0 | 443 | break; |
michael@0 | 444 | } |
michael@0 | 445 | case "SystemMessageManager:HasPendingMessages": |
michael@0 | 446 | { |
michael@0 | 447 | debug("received SystemMessageManager:HasPendingMessages " + msg.type + |
michael@0 | 448 | " for " + msg.pageURL + " @ " + msg.manifestURL); |
michael@0 | 449 | |
michael@0 | 450 | // This is a sync call used to return if a page has pending messages. |
michael@0 | 451 | // Find the right page to get its corresponding pending messages. |
michael@0 | 452 | let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); |
michael@0 | 453 | if (!page) { |
michael@0 | 454 | return false; |
michael@0 | 455 | } |
michael@0 | 456 | |
michael@0 | 457 | return page.pendingMessages.length != 0; |
michael@0 | 458 | break; |
michael@0 | 459 | } |
michael@0 | 460 | case "SystemMessageManager:Message:Return:OK": |
michael@0 | 461 | { |
michael@0 | 462 | debug("received SystemMessageManager:Message:Return:OK " + msg.type + |
michael@0 | 463 | " for " + msg.pageURL + " @ " + msg.manifestURL); |
michael@0 | 464 | |
michael@0 | 465 | // We need to clean up the pending message since the app has already |
michael@0 | 466 | // received it, thus avoiding the re-lanunched app handling it again. |
michael@0 | 467 | let page = this._findPage(msg.type, msg.pageURL, msg.manifestURL); |
michael@0 | 468 | if (page) { |
michael@0 | 469 | let pendingMessages = page.pendingMessages; |
michael@0 | 470 | for (let i = 0; i < pendingMessages.length; i++) { |
michael@0 | 471 | if (pendingMessages[i].msgID === msg.msgID) { |
michael@0 | 472 | pendingMessages.splice(i, 1); |
michael@0 | 473 | break; |
michael@0 | 474 | } |
michael@0 | 475 | } |
michael@0 | 476 | } |
michael@0 | 477 | break; |
michael@0 | 478 | } |
michael@0 | 479 | case "SystemMessageManager:HandleMessagesDone": |
michael@0 | 480 | { |
michael@0 | 481 | debug("received SystemMessageManager:HandleMessagesDone " + msg.type + |
michael@0 | 482 | " with " + msg.handledCount + " for " + msg.pageURL + |
michael@0 | 483 | " @ " + msg.manifestURL); |
michael@0 | 484 | |
michael@0 | 485 | // A page has finished handling some of its system messages, so we try |
michael@0 | 486 | // to release the CPU wake lock we acquired on behalf of that page. |
michael@0 | 487 | this._releaseCpuWakeLock(this._createKeyForPage(msg), msg.handledCount); |
michael@0 | 488 | break; |
michael@0 | 489 | } |
michael@0 | 490 | } |
michael@0 | 491 | }, |
michael@0 | 492 | |
michael@0 | 493 | observe: function(aSubject, aTopic, aData) { |
michael@0 | 494 | switch (aTopic) { |
michael@0 | 495 | case "xpcom-shutdown": |
michael@0 | 496 | kMessages.forEach(function(aMsg) { |
michael@0 | 497 | ppmm.removeMessageListener(aMsg, this); |
michael@0 | 498 | }, this); |
michael@0 | 499 | Services.obs.removeObserver(this, "xpcom-shutdown"); |
michael@0 | 500 | Services.obs.removeObserver(this, "webapps-registry-start"); |
michael@0 | 501 | Services.obs.removeObserver(this, "webapps-registry-ready"); |
michael@0 | 502 | ppmm = null; |
michael@0 | 503 | this._pages = null; |
michael@0 | 504 | this._bufferedSysMsgs = null; |
michael@0 | 505 | break; |
michael@0 | 506 | case "webapps-registry-start": |
michael@0 | 507 | this._webappsRegistryReady = false; |
michael@0 | 508 | break; |
michael@0 | 509 | case "webapps-registry-ready": |
michael@0 | 510 | // After the webapps' registration has been done for sure, |
michael@0 | 511 | // re-fire the buffered system messages if there is any. |
michael@0 | 512 | this._webappsRegistryReady = true; |
michael@0 | 513 | this._bufferedSysMsgs.forEach(function(aSysMsg) { |
michael@0 | 514 | switch (aSysMsg.how) { |
michael@0 | 515 | case "send": |
michael@0 | 516 | this.sendMessage( |
michael@0 | 517 | aSysMsg.type, aSysMsg.msg, |
michael@0 | 518 | aSysMsg.pageURI, aSysMsg.manifestURI, aSysMsg.extra); |
michael@0 | 519 | break; |
michael@0 | 520 | case "broadcast": |
michael@0 | 521 | this.broadcastMessage(aSysMsg.type, aSysMsg.msg, aSysMsg.extra); |
michael@0 | 522 | break; |
michael@0 | 523 | } |
michael@0 | 524 | }, this); |
michael@0 | 525 | this._bufferedSysMsgs.length = 0; |
michael@0 | 526 | break; |
michael@0 | 527 | } |
michael@0 | 528 | }, |
michael@0 | 529 | |
michael@0 | 530 | _queueMessage: function(aPage, aMessage, aMessageID) { |
michael@0 | 531 | // Queue the message for this page because we've never known if an app is |
michael@0 | 532 | // opened or not. We'll clean it up when the app has already received it. |
michael@0 | 533 | aPage.pendingMessages.push({ msg: aMessage, msgID: aMessageID }); |
michael@0 | 534 | if (aPage.pendingMessages.length > kMaxPendingMessages) { |
michael@0 | 535 | aPage.pendingMessages.splice(0, 1); |
michael@0 | 536 | } |
michael@0 | 537 | }, |
michael@0 | 538 | |
michael@0 | 539 | _openAppPage: function(aPage, aMessage, aExtra, aMsgSentStatus) { |
michael@0 | 540 | // This means the app must be brought to the foreground. |
michael@0 | 541 | let showApp = this._getMessageConfigurator(aPage.type).mustShowRunningApp; |
michael@0 | 542 | |
michael@0 | 543 | // We should send the open-app message if the system message was |
michael@0 | 544 | // not sent, or if it was sent but we should show the app anyway. |
michael@0 | 545 | if ((aMsgSentStatus === MSG_SENT_SUCCESS) && !showApp) { |
michael@0 | 546 | return; |
michael@0 | 547 | } |
michael@0 | 548 | |
michael@0 | 549 | // This flag means the app must *only* be brought to the foreground |
michael@0 | 550 | // and we don't need to load the app to handle messages. |
michael@0 | 551 | let onlyShowApp = (aMsgSentStatus === MSG_SENT_SUCCESS) && showApp; |
michael@0 | 552 | |
michael@0 | 553 | // We don't need to send the full object to observers. |
michael@0 | 554 | let page = { pageURL: aPage.pageURL, |
michael@0 | 555 | manifestURL: aPage.manifestURL, |
michael@0 | 556 | type: aPage.type, |
michael@0 | 557 | extra: aExtra, |
michael@0 | 558 | target: aMessage.target, |
michael@0 | 559 | onlyShowApp: onlyShowApp, |
michael@0 | 560 | showApp: showApp }; |
michael@0 | 561 | debug("Asking to open " + JSON.stringify(page)); |
michael@0 | 562 | Services.obs.notifyObservers(this, |
michael@0 | 563 | "system-messages-open-app", |
michael@0 | 564 | JSON.stringify(page)); |
michael@0 | 565 | }, |
michael@0 | 566 | |
michael@0 | 567 | _isPageMatched: function(aPage, aType, aPageURL, aManifestURL) { |
michael@0 | 568 | return (aPage.type === aType && |
michael@0 | 569 | aPage.manifestURL === aManifestURL && |
michael@0 | 570 | aPage.pageURL === aPageURL) |
michael@0 | 571 | }, |
michael@0 | 572 | |
michael@0 | 573 | _createKeyForPage: function _createKeyForPage(aPage) { |
michael@0 | 574 | let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] |
michael@0 | 575 | .createInstance(Ci.nsIScriptableUnicodeConverter); |
michael@0 | 576 | converter.charset = "UTF-8"; |
michael@0 | 577 | |
michael@0 | 578 | let hasher = Cc["@mozilla.org/security/hash;1"] |
michael@0 | 579 | .createInstance(Ci.nsICryptoHash); |
michael@0 | 580 | hasher.init(hasher.SHA1); |
michael@0 | 581 | |
michael@0 | 582 | // add manifest/page URL and action to the hash |
michael@0 | 583 | ["type", "manifestURL", "pageURL"].forEach(function(aProp) { |
michael@0 | 584 | let data = converter.convertToByteArray(aPage[aProp], {}); |
michael@0 | 585 | hasher.update(data, data.length); |
michael@0 | 586 | }); |
michael@0 | 587 | |
michael@0 | 588 | return hasher.finish(true); |
michael@0 | 589 | }, |
michael@0 | 590 | |
michael@0 | 591 | _sendMessageCommon: function(aType, aMessage, aMessageID, |
michael@0 | 592 | aPageURL, aManifestURL, aExtra) { |
michael@0 | 593 | // Don't send the system message not granted by the app's permissions. |
michael@0 | 594 | if (!SystemMessagePermissionsChecker |
michael@0 | 595 | .isSystemMessagePermittedToSend(aType, |
michael@0 | 596 | aPageURL, |
michael@0 | 597 | aManifestURL)) { |
michael@0 | 598 | return MSG_SENT_FAILURE_PERM_DENIED; |
michael@0 | 599 | } |
michael@0 | 600 | |
michael@0 | 601 | let appPageIsRunning = false; |
michael@0 | 602 | let pageKey = this._createKeyForPage({ type: aType, |
michael@0 | 603 | manifestURL: aManifestURL, |
michael@0 | 604 | pageURL: aPageURL }); |
michael@0 | 605 | |
michael@0 | 606 | let targets = this._listeners[aManifestURL]; |
michael@0 | 607 | if (targets) { |
michael@0 | 608 | for (let index = 0; index < targets.length; ++index) { |
michael@0 | 609 | let target = targets[index]; |
michael@0 | 610 | // We only need to send the system message to the targets (processes) |
michael@0 | 611 | // which contain the window page that matches the manifest/page URL of |
michael@0 | 612 | // the destination of system message. |
michael@0 | 613 | if (target.winCounts[aPageURL] === undefined) { |
michael@0 | 614 | continue; |
michael@0 | 615 | } |
michael@0 | 616 | |
michael@0 | 617 | appPageIsRunning = true; |
michael@0 | 618 | // We need to acquire a CPU wake lock for that page and expect that |
michael@0 | 619 | // we'll receive a "SystemMessageManager:HandleMessagesDone" message |
michael@0 | 620 | // when the page finishes handling the system message. At that point, |
michael@0 | 621 | // we'll release the lock we acquired. |
michael@0 | 622 | this._acquireCpuWakeLock(pageKey); |
michael@0 | 623 | |
michael@0 | 624 | // Multiple windows can share the same target (process), the content |
michael@0 | 625 | // window needs to check if the manifest/page URL is matched. Only |
michael@0 | 626 | // *one* window should handle the system message. |
michael@0 | 627 | let manager = target.target; |
michael@0 | 628 | manager.sendAsyncMessage("SystemMessageManager:Message", |
michael@0 | 629 | { type: aType, |
michael@0 | 630 | msg: aMessage, |
michael@0 | 631 | manifestURL: aManifestURL, |
michael@0 | 632 | pageURL: aPageURL, |
michael@0 | 633 | msgID: aMessageID }); |
michael@0 | 634 | } |
michael@0 | 635 | } |
michael@0 | 636 | |
michael@0 | 637 | if (!appPageIsRunning) { |
michael@0 | 638 | // The app page isn't running and relies on the 'open-app' chrome event to |
michael@0 | 639 | // wake it up. We still need to acquire a CPU wake lock for that page and |
michael@0 | 640 | // expect that we will receive a "SystemMessageManager:HandleMessagesDone" |
michael@0 | 641 | // message when the page finishes handling the system message with other |
michael@0 | 642 | // pending messages. At that point, we'll release the lock we acquired. |
michael@0 | 643 | this._acquireCpuWakeLock(pageKey); |
michael@0 | 644 | return MSG_SENT_FAILURE_APP_NOT_RUNNING; |
michael@0 | 645 | } else { |
michael@0 | 646 | return MSG_SENT_SUCCESS; |
michael@0 | 647 | } |
michael@0 | 648 | |
michael@0 | 649 | }, |
michael@0 | 650 | |
michael@0 | 651 | classID: Components.ID("{70589ca5-91ac-4b9e-b839-d6a88167d714}"), |
michael@0 | 652 | |
michael@0 | 653 | QueryInterface: XPCOMUtils.generateQI([Ci.nsISystemMessagesInternal, |
michael@0 | 654 | Ci.nsIObserver]) |
michael@0 | 655 | } |
michael@0 | 656 | |
michael@0 | 657 | this.NSGetFactory = XPCOMUtils.generateNSGetFactory([SystemMessageInternal]); |