1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/apps/src/InterAppCommService.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,889 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components; 1.11 + 1.12 +this.EXPORTED_SYMBOLS = ["InterAppCommService"]; 1.13 + 1.14 +Cu.import("resource://gre/modules/Services.jsm"); 1.15 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.16 +Cu.import("resource://gre/modules/AppsUtils.jsm"); 1.17 + 1.18 +const DEBUG = false; 1.19 +function debug(aMsg) { 1.20 + dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n"); 1.21 +} 1.22 + 1.23 +XPCOMUtils.defineLazyServiceGetter(this, "appsService", 1.24 + "@mozilla.org/AppsService;1", 1.25 + "nsIAppsService"); 1.26 + 1.27 +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", 1.28 + "@mozilla.org/parentprocessmessagemanager;1", 1.29 + "nsIMessageBroadcaster"); 1.30 + 1.31 +XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator", 1.32 + "@mozilla.org/uuid-generator;1", 1.33 + "nsIUUIDGenerator"); 1.34 + 1.35 +XPCOMUtils.defineLazyServiceGetter(this, "messenger", 1.36 + "@mozilla.org/system-message-internal;1", 1.37 + "nsISystemMessagesInternal"); 1.38 + 1.39 +const kMessages =["Webapps:Connect", 1.40 + "Webapps:GetConnections", 1.41 + "InterAppConnection:Cancel", 1.42 + "InterAppMessagePort:PostMessage", 1.43 + "InterAppMessagePort:Register", 1.44 + "InterAppMessagePort:Unregister", 1.45 + "child-process-shutdown"]; 1.46 + 1.47 +/** 1.48 + * This module contains helpers for Inter-App Communication API [1] related 1.49 + * purposes, which plays the role of the central service receiving messages 1.50 + * from and interacting with the content processes. 1.51 + * 1.52 + * [1] https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal 1.53 + */ 1.54 + 1.55 +this.InterAppCommService = { 1.56 + init: function() { 1.57 + Services.obs.addObserver(this, "xpcom-shutdown", false); 1.58 + Services.obs.addObserver(this, "inter-app-comm-select-app-result", false); 1.59 + 1.60 + kMessages.forEach(function(aMsg) { 1.61 + ppmm.addMessageListener(aMsg, this); 1.62 + }, this); 1.63 + 1.64 + // This matrix is used for saving the inter-app connection info registered in 1.65 + // the app manifest. The object literal is defined as below: 1.66 + // 1.67 + // { 1.68 + // "keyword1": { 1.69 + // "subAppManifestURL1": { 1.70 + // /* subscribed info */ 1.71 + // }, 1.72 + // "subAppManifestURL2": { 1.73 + // /* subscribed info */ 1.74 + // }, 1.75 + // ... 1.76 + // }, 1.77 + // "keyword2": { 1.78 + // "subAppManifestURL3": { 1.79 + // /* subscribed info */ 1.80 + // }, 1.81 + // ... 1.82 + // }, 1.83 + // ... 1.84 + // } 1.85 + // 1.86 + // For example: 1.87 + // 1.88 + // { 1.89 + // "foo": { 1.90 + // "app://subApp1.gaiamobile.org/manifest.webapp": { 1.91 + // pageURL: "app://subApp1.gaiamobile.org/handler.html", 1.92 + // description: "blah blah", 1.93 + // rules: { ... } 1.94 + // }, 1.95 + // "app://subApp2.gaiamobile.org/manifest.webapp": { 1.96 + // pageURL: "app://subApp2.gaiamobile.org/handler.html", 1.97 + // description: "blah blah", 1.98 + // rules: { ... } 1.99 + // } 1.100 + // }, 1.101 + // "bar": { 1.102 + // "app://subApp3.gaiamobile.org/manifest.webapp": { 1.103 + // pageURL: "app://subApp3.gaiamobile.org/handler.html", 1.104 + // description: "blah blah", 1.105 + // rules: { ... } 1.106 + // } 1.107 + // } 1.108 + // } 1.109 + // 1.110 + // TODO Bug 908999 - Update registered connections when app gets uninstalled. 1.111 + this._registeredConnections = {}; 1.112 + 1.113 + // This matrix is used for saving the permitted connections, which allows 1.114 + // the messaging between publishers and subscribers. The object literal is 1.115 + // defined as below: 1.116 + // 1.117 + // { 1.118 + // "keyword1": { 1.119 + // "pubAppManifestURL1": [ 1.120 + // "subAppManifestURL1", 1.121 + // "subAppManifestURL2", 1.122 + // ... 1.123 + // ], 1.124 + // "pubAppManifestURL2": [ 1.125 + // "subAppManifestURL3", 1.126 + // "subAppManifestURL4", 1.127 + // ... 1.128 + // ], 1.129 + // ... 1.130 + // }, 1.131 + // "keyword2": { 1.132 + // "pubAppManifestURL3": [ 1.133 + // "subAppManifestURL5", 1.134 + // ... 1.135 + // ], 1.136 + // ... 1.137 + // }, 1.138 + // ... 1.139 + // } 1.140 + // 1.141 + // For example: 1.142 + // 1.143 + // { 1.144 + // "foo": { 1.145 + // "app://pubApp1.gaiamobile.org/manifest.webapp": [ 1.146 + // "app://subApp1.gaiamobile.org/manifest.webapp", 1.147 + // "app://subApp2.gaiamobile.org/manifest.webapp" 1.148 + // ], 1.149 + // "app://pubApp2.gaiamobile.org/manifest.webapp": [ 1.150 + // "app://subApp3.gaiamobile.org/manifest.webapp", 1.151 + // "app://subApp4.gaiamobile.org/manifest.webapp" 1.152 + // ] 1.153 + // }, 1.154 + // "bar": { 1.155 + // "app://pubApp3.gaiamobile.org/manifest.webapp": [ 1.156 + // "app://subApp5.gaiamobile.org/manifest.webapp", 1.157 + // ] 1.158 + // } 1.159 + // } 1.160 + // 1.161 + // TODO Bug 908999 - Update allowed connections when app gets uninstalled. 1.162 + this._allowedConnections = {}; 1.163 + 1.164 + // This matrix is used for saving the caller info from the content process, 1.165 + // which is indexed by a random UUID, to know where to return the promise 1.166 + // resolvser's callback when the prompt UI for allowing connections returns. 1.167 + // An example of the object literal is shown as below: 1.168 + // 1.169 + // { 1.170 + // "fooID": { 1.171 + // outerWindowID: 12, 1.172 + // requestID: 34, 1.173 + // target: pubAppTarget1 1.174 + // }, 1.175 + // "barID": { 1.176 + // outerWindowID: 56, 1.177 + // requestID: 78, 1.178 + // target: pubAppTarget2 1.179 + // } 1.180 + // } 1.181 + // 1.182 + // where |outerWindowID| is the ID of the window requesting the connection, 1.183 + // |requestID| is the ID specifying the promise resolver to return, 1.184 + // |target| is the target of the process requesting the connection. 1.185 + this._promptUICallers = {}; 1.186 + 1.187 + // This matrix is used for saving the pair of message ports, which is indexed 1.188 + // by a random UUID, so that each port can know whom it should talk to. 1.189 + // An example of the object literal is shown as below: 1.190 + // 1.191 + // { 1.192 + // "UUID1": { 1.193 + // keyword: "keyword1", 1.194 + // publisher: { 1.195 + // manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp", 1.196 + // target: pubAppTarget1, 1.197 + // pageURL: "app://pubApp1.gaiamobile.org/caller.html", 1.198 + // messageQueue: [...] 1.199 + // }, 1.200 + // subscriber: { 1.201 + // manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp", 1.202 + // target: subAppTarget1, 1.203 + // pageURL: "app://pubApp1.gaiamobile.org/handler.html", 1.204 + // messageQueue: [...] 1.205 + // } 1.206 + // }, 1.207 + // "UUID2": { 1.208 + // keyword: "keyword2", 1.209 + // publisher: { 1.210 + // manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp", 1.211 + // target: pubAppTarget2, 1.212 + // pageURL: "app://pubApp2.gaiamobile.org/caller.html", 1.213 + // messageQueue: [...] 1.214 + // }, 1.215 + // subscriber: { 1.216 + // manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp", 1.217 + // target: subAppTarget2, 1.218 + // pageURL: "app://pubApp2.gaiamobile.org/handler.html", 1.219 + // messageQueue: [...] 1.220 + // } 1.221 + // } 1.222 + // } 1.223 + this._messagePortPairs = {}; 1.224 + }, 1.225 + 1.226 + /** 1.227 + * Registration of a page that wants to be connected to other apps through 1.228 + * the Inter-App Communication API. 1.229 + * 1.230 + * @param aKeyword The connection's keyword. 1.231 + * @param aHandlerPageURI The URI of the handler's page. 1.232 + * @param aManifestURI The webapp's manifest URI. 1.233 + * @param aDescription The connection's description. 1.234 + * @param aRules The connection's rules. 1.235 + */ 1.236 + registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI, 1.237 + aDescription, aRules) { 1.238 + let manifestURL = aManifestURI.spec; 1.239 + let pageURL = aHandlerPageURI.spec; 1.240 + 1.241 + if (DEBUG) { 1.242 + debug("registerConnection: aKeyword: " + aKeyword + 1.243 + " manifestURL: " + manifestURL + " pageURL: " + pageURL + 1.244 + " aDescription: " + aDescription + 1.245 + " aRules.minimumAccessLevel: " + aRules.minimumAccessLevel + 1.246 + " aRules.manifestURLs: " + aRules.manifestURLs + 1.247 + " aRules.installOrigins: " + aRules.installOrigins); 1.248 + } 1.249 + 1.250 + let subAppManifestURLs = this._registeredConnections[aKeyword]; 1.251 + if (!subAppManifestURLs) { 1.252 + subAppManifestURLs = this._registeredConnections[aKeyword] = {}; 1.253 + } 1.254 + 1.255 + subAppManifestURLs[manifestURL] = { 1.256 + pageURL: pageURL, 1.257 + description: aDescription, 1.258 + rules: aRules, 1.259 + manifestURL: manifestURL 1.260 + }; 1.261 + }, 1.262 + 1.263 + _matchMinimumAccessLevel: function(aRules, aAppStatus) { 1.264 + if (!aRules || !aRules.minimumAccessLevel) { 1.265 + if (DEBUG) { 1.266 + debug("rules.minimumAccessLevel is not available. No need to match."); 1.267 + } 1.268 + return true; 1.269 + } 1.270 + 1.271 + let minAccessLevel = aRules.minimumAccessLevel; 1.272 + switch (minAccessLevel) { 1.273 + case "web": 1.274 + if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED || 1.275 + aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED || 1.276 + aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) { 1.277 + return true; 1.278 + } 1.279 + break; 1.280 + case "privileged": 1.281 + if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED || 1.282 + aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) { 1.283 + return true; 1.284 + } 1.285 + break; 1.286 + case "certified": 1.287 + if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) { 1.288 + return true; 1.289 + } 1.290 + break; 1.291 + } 1.292 + 1.293 + if (DEBUG) { 1.294 + debug("rules.minimumAccessLevel is not matched!" + 1.295 + " minAccessLevel: " + minAccessLevel + 1.296 + " aAppStatus : " + aAppStatus); 1.297 + } 1.298 + return false; 1.299 + }, 1.300 + 1.301 + _matchManifestURLs: function(aRules, aManifestURL) { 1.302 + if (!aRules || !Array.isArray(aRules.manifestURLs)) { 1.303 + if (DEBUG) { 1.304 + debug("rules.manifestURLs is not available. No need to match."); 1.305 + } 1.306 + return true; 1.307 + } 1.308 + 1.309 + let manifestURLs = aRules.manifestURLs; 1.310 + if (manifestURLs.indexOf(aManifestURL) != -1) { 1.311 + return true; 1.312 + } 1.313 + 1.314 + if (DEBUG) { 1.315 + debug("rules.manifestURLs is not matched!" + 1.316 + " manifestURLs: " + manifestURLs + 1.317 + " aManifestURL : " + aManifestURL); 1.318 + } 1.319 + return false; 1.320 + }, 1.321 + 1.322 + _matchInstallOrigins: function(aRules, aInstallOrigin) { 1.323 + if (!aRules || !Array.isArray(aRules.installOrigins)) { 1.324 + if (DEBUG) { 1.325 + debug("rules.installOrigins is not available. No need to match."); 1.326 + } 1.327 + return true; 1.328 + } 1.329 + 1.330 + let installOrigins = aRules.installOrigins; 1.331 + if (installOrigins.indexOf(aInstallOrigin) != -1) { 1.332 + return true; 1.333 + } 1.334 + 1.335 + if (DEBUG) { 1.336 + debug("rules.installOrigins is not matched!" + 1.337 + " installOrigins: " + installOrigins + 1.338 + " installOrigin : " + aInstallOrigin); 1.339 + } 1.340 + return false; 1.341 + }, 1.342 + 1.343 + _matchRules: function(aPubAppManifestURL, aPubRules, 1.344 + aSubAppManifestURL, aSubRules) { 1.345 + let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL); 1.346 + let subApp = appsService.getAppByManifestURL(aSubAppManifestURL); 1.347 + 1.348 + // TODO Bug 907068 In the initiative step, we only expose this API to 1.349 + // certified apps to meet the time line. Eventually, we need to make 1.350 + // it available for the non-certified apps as well. For now, only the 1.351 + // certified apps can match the rules. 1.352 + if (pubApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED || 1.353 + subApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) { 1.354 + if (DEBUG) { 1.355 + debug("Only certified apps are allowed to do connections."); 1.356 + } 1.357 + return false; 1.358 + } 1.359 + 1.360 + if (!aPubRules && !aSubRules) { 1.361 + if (DEBUG) { 1.362 + debug("No rules for publisher and subscriber. No need to match."); 1.363 + } 1.364 + return true; 1.365 + } 1.366 + 1.367 + // Check minimumAccessLevel. 1.368 + if (!this._matchMinimumAccessLevel(aPubRules, subApp.appStatus) || 1.369 + !this._matchMinimumAccessLevel(aSubRules, pubApp.appStatus)) { 1.370 + return false; 1.371 + } 1.372 + 1.373 + // Check manifestURLs. 1.374 + if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) || 1.375 + !this._matchManifestURLs(aSubRules, aPubAppManifestURL)) { 1.376 + return false; 1.377 + } 1.378 + 1.379 + // Check installOrigins. 1.380 + if (!this._matchInstallOrigins(aPubRules, subApp.installOrigin) || 1.381 + !this._matchInstallOrigins(aSubRules, pubApp.installOrigin)) { 1.382 + return false; 1.383 + } 1.384 + 1.385 + // Check developers. 1.386 + // TODO Do we really want to check this? This one seems naive. 1.387 + 1.388 + if (DEBUG) debug("All rules are matched."); 1.389 + return true; 1.390 + }, 1.391 + 1.392 + _dispatchMessagePorts: function(aKeyword, aPubAppManifestURL, 1.393 + aAllowedSubAppManifestURLs, 1.394 + aTarget, aOuterWindowID, aRequestID) { 1.395 + if (DEBUG) { 1.396 + debug("_dispatchMessagePorts: aKeyword: " + aKeyword + 1.397 + " aPubAppManifestURL: " + aPubAppManifestURL + 1.398 + " aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs); 1.399 + } 1.400 + 1.401 + if (aAllowedSubAppManifestURLs.length == 0) { 1.402 + if (DEBUG) debug("No apps are allowed to connect. Returning."); 1.403 + aTarget.sendAsyncMessage("Webapps:Connect:Return:KO", 1.404 + { oid: aOuterWindowID, requestID: aRequestID }); 1.405 + return; 1.406 + } 1.407 + 1.408 + let subAppManifestURLs = this._registeredConnections[aKeyword]; 1.409 + if (!subAppManifestURLs) { 1.410 + if (DEBUG) debug("No apps are subscribed to connect. Returning."); 1.411 + aTarget.sendAsyncMessage("Webapps:Connect:Return:KO", 1.412 + { oid: aOuterWindowID, requestID: aRequestID }); 1.413 + return; 1.414 + } 1.415 + 1.416 + let messagePortIDs = []; 1.417 + aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) { 1.418 + let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL]; 1.419 + if (!subscribedInfo) { 1.420 + if (DEBUG) { 1.421 + debug("The sunscribed info is not available. Skipping: " + 1.422 + aAllowedSubAppManifestURL); 1.423 + } 1.424 + return; 1.425 + } 1.426 + 1.427 + // The message port ID is aimed for identifying the coupling targets 1.428 + // to deliver messages with each other. This ID is centrally generated 1.429 + // by the parent and dispatched to both the sender and receiver ends 1.430 + // for creating their own message ports respectively. 1.431 + let messagePortID = UUIDGenerator.generateUUID().toString(); 1.432 + this._messagePortPairs[messagePortID] = { 1.433 + keyword: aKeyword, 1.434 + publisher: { 1.435 + manifestURL: aPubAppManifestURL 1.436 + }, 1.437 + subscriber: { 1.438 + manifestURL: aAllowedSubAppManifestURL 1.439 + } 1.440 + }; 1.441 + 1.442 + // Fire system message to deliver the message port to the subscriber. 1.443 + messenger.sendMessage("connection", 1.444 + { keyword: aKeyword, 1.445 + messagePortID: messagePortID }, 1.446 + Services.io.newURI(subscribedInfo.pageURL, null, null), 1.447 + Services.io.newURI(subscribedInfo.manifestURL, null, null)); 1.448 + 1.449 + messagePortIDs.push(messagePortID); 1.450 + }, this); 1.451 + 1.452 + if (messagePortIDs.length == 0) { 1.453 + if (DEBUG) debug("No apps are subscribed to connect. Returning."); 1.454 + aTarget.sendAsyncMessage("Webapps:Connect:Return:KO", 1.455 + { oid: aOuterWindowID, requestID: aRequestID }); 1.456 + return; 1.457 + } 1.458 + 1.459 + // Return the message port IDs to open the message ports for the publisher. 1.460 + if (DEBUG) debug("messagePortIDs: " + messagePortIDs); 1.461 + aTarget.sendAsyncMessage("Webapps:Connect:Return:OK", 1.462 + { keyword: aKeyword, 1.463 + messagePortIDs: messagePortIDs, 1.464 + oid: aOuterWindowID, requestID: aRequestID }); 1.465 + }, 1.466 + 1.467 + _connect: function(aMessage, aTarget) { 1.468 + let keyword = aMessage.keyword; 1.469 + let pubRules = aMessage.rules; 1.470 + let pubAppManifestURL = aMessage.manifestURL; 1.471 + let outerWindowID = aMessage.outerWindowID; 1.472 + let requestID = aMessage.requestID; 1.473 + 1.474 + let subAppManifestURLs = this._registeredConnections[keyword]; 1.475 + if (!subAppManifestURLs) { 1.476 + if (DEBUG) { 1.477 + debug("No apps are subscribed for this connection. Returning."); 1.478 + } 1.479 + this._dispatchMessagePorts(keyword, pubAppManifestURL, [], 1.480 + aTarget, outerWindowID, requestID); 1.481 + return; 1.482 + } 1.483 + 1.484 + // Fetch the apps that used to be allowed to connect before, so that 1.485 + // users don't need to select/allow them again. That is, we only pop up 1.486 + // the prompt UI for the *new* connections. 1.487 + let allowedSubAppManifestURLs = []; 1.488 + let allowedPubAppManifestURLs = this._allowedConnections[keyword]; 1.489 + if (allowedPubAppManifestURLs && 1.490 + allowedPubAppManifestURLs[pubAppManifestURL]) { 1.491 + allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL]; 1.492 + } 1.493 + 1.494 + // Check rules to see if a subscribed app is allowed to connect. 1.495 + let appsToSelect = []; 1.496 + for (let subAppManifestURL in subAppManifestURLs) { 1.497 + if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) { 1.498 + if (DEBUG) { 1.499 + debug("Don't need to select again. Skipping: " + subAppManifestURL); 1.500 + } 1.501 + continue; 1.502 + } 1.503 + 1.504 + // Only rule-matched publishers/subscribers are allowed to connect. 1.505 + let subscribedInfo = subAppManifestURLs[subAppManifestURL]; 1.506 + let subRules = subscribedInfo.rules; 1.507 + 1.508 + let matched = 1.509 + this._matchRules(pubAppManifestURL, pubRules, 1.510 + subAppManifestURL, subRules); 1.511 + if (!matched) { 1.512 + if (DEBUG) { 1.513 + debug("Rules are not matched. Skipping: " + subAppManifestURL); 1.514 + } 1.515 + continue; 1.516 + } 1.517 + 1.518 + appsToSelect.push({ 1.519 + manifestURL: subAppManifestURL, 1.520 + description: subscribedInfo.description 1.521 + }); 1.522 + } 1.523 + 1.524 + if (appsToSelect.length == 0) { 1.525 + if (DEBUG) { 1.526 + debug("No additional apps need to be selected for this connection. " + 1.527 + "Just dispatch message ports for the existing connections."); 1.528 + } 1.529 + 1.530 + this._dispatchMessagePorts(keyword, pubAppManifestURL, 1.531 + allowedSubAppManifestURLs, 1.532 + aTarget, outerWindowID, requestID); 1.533 + return; 1.534 + } 1.535 + 1.536 + // Remember the caller info with an UUID so that we can know where to 1.537 + // return the promise resolver's callback when the prompt UI returns. 1.538 + let callerID = UUIDGenerator.generateUUID().toString(); 1.539 + this._promptUICallers[callerID] = { 1.540 + outerWindowID: outerWindowID, 1.541 + requestID: requestID, 1.542 + target: aTarget 1.543 + }; 1.544 + 1.545 + // TODO Bug 897169 Temporarily disable the notification for popping up 1.546 + // the prompt until the UX/UI for the prompt is confirmed. 1.547 + // 1.548 + // TODO Bug 908191 We need to change the way of interaction between API and 1.549 + // run-time prompt from observer notification to xpcom-interface caller. 1.550 + // 1.551 + /* 1.552 + if (DEBUG) debug("appsToSelect: " + appsToSelect); 1.553 + Services.obs.notifyObservers(null, "inter-app-comm-select-app", 1.554 + JSON.stringify({ callerID: callerID, 1.555 + manifestURL: pubAppManifestURL, 1.556 + keyword: keyword, 1.557 + appsToSelect: appsToSelect })); 1.558 + */ 1.559 + 1.560 + // TODO Bug 897169 Simulate the return of the app-selected result by 1.561 + // the prompt, which always allows the connection. This dummy codes 1.562 + // will be removed when the UX/UI for the prompt is ready. 1.563 + if (DEBUG) debug("appsToSelect: " + appsToSelect); 1.564 + Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result', 1.565 + JSON.stringify({ callerID: callerID, 1.566 + manifestURL: pubAppManifestURL, 1.567 + keyword: keyword, 1.568 + selectedApps: appsToSelect })); 1.569 + }, 1.570 + 1.571 + _getConnections: function(aMessage, aTarget) { 1.572 + let outerWindowID = aMessage.outerWindowID; 1.573 + let requestID = aMessage.requestID; 1.574 + 1.575 + let connections = []; 1.576 + for (let keyword in this._allowedConnections) { 1.577 + let allowedPubAppManifestURLs = this._allowedConnections[keyword]; 1.578 + for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) { 1.579 + let allowedSubAppManifestURLs = 1.580 + allowedPubAppManifestURLs[allowedPubAppManifestURL]; 1.581 + allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) { 1.582 + connections.push({ keyword: keyword, 1.583 + pubAppManifestURL: allowedPubAppManifestURL, 1.584 + subAppManifestURL: allowedSubAppManifestURL }); 1.585 + }); 1.586 + } 1.587 + } 1.588 + 1.589 + aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK", 1.590 + { connections: connections, 1.591 + oid: outerWindowID, requestID: requestID }); 1.592 + }, 1.593 + 1.594 + _cancelConnection: function(aMessage) { 1.595 + let keyword = aMessage.keyword; 1.596 + let pubAppManifestURL = aMessage.pubAppManifestURL; 1.597 + let subAppManifestURL = aMessage.subAppManifestURL; 1.598 + 1.599 + let allowedPubAppManifestURLs = this._allowedConnections[keyword]; 1.600 + if (!allowedPubAppManifestURLs) { 1.601 + if (DEBUG) debug("keyword is not found: " + keyword); 1.602 + return; 1.603 + } 1.604 + 1.605 + let allowedSubAppManifestURLs = 1.606 + allowedPubAppManifestURLs[pubAppManifestURL]; 1.607 + if (!allowedSubAppManifestURLs) { 1.608 + if (DEBUG) debug("publisher is not found: " + pubAppManifestURL); 1.609 + return; 1.610 + } 1.611 + 1.612 + let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL); 1.613 + if (index == -1) { 1.614 + if (DEBUG) debug("subscriber is not found: " + subAppManifestURL); 1.615 + return; 1.616 + } 1.617 + 1.618 + if (DEBUG) debug("Cancelling the connection."); 1.619 + allowedSubAppManifestURLs.splice(index, 1); 1.620 + 1.621 + // Clean up the parent entries if needed. 1.622 + if (allowedSubAppManifestURLs.length == 0) { 1.623 + delete allowedPubAppManifestURLs[pubAppManifestURL]; 1.624 + if (Object.keys(allowedPubAppManifestURLs).length == 0) { 1.625 + delete this._allowedConnections[keyword]; 1.626 + } 1.627 + } 1.628 + 1.629 + if (DEBUG) debug("Unregistering message ports based on this connection."); 1.630 + let messagePortIDs = []; 1.631 + for (let messagePortID in this._messagePortPairs) { 1.632 + let pair = this._messagePortPairs[messagePortID]; 1.633 + if (pair.keyword == keyword && 1.634 + pair.publisher.manifestURL == pubAppManifestURL && 1.635 + pair.subscriber.manifestURL == subAppManifestURL) { 1.636 + messagePortIDs.push(messagePortID); 1.637 + } 1.638 + } 1.639 + messagePortIDs.forEach(function(aMessagePortID) { 1.640 + delete this._messagePortPairs[aMessagePortID]; 1.641 + }, this); 1.642 + }, 1.643 + 1.644 + _identifyMessagePort: function(aMessagePortID, aManifestURL) { 1.645 + let pair = this._messagePortPairs[aMessagePortID]; 1.646 + if (!pair) { 1.647 + if (DEBUG) { 1.648 + debug("Error! The message port ID is invalid: " + aMessagePortID + 1.649 + ", which should have been generated by parent."); 1.650 + } 1.651 + return null; 1.652 + } 1.653 + 1.654 + // Check it the message port is for publisher. 1.655 + if (pair.publisher.manifestURL == aManifestURL) { 1.656 + return { pair: pair, isPublisher: true }; 1.657 + } 1.658 + 1.659 + // Check it the message port is for subscriber. 1.660 + if (pair.subscriber.manifestURL == aManifestURL) { 1.661 + return { pair: pair, isPublisher: false }; 1.662 + } 1.663 + 1.664 + if (DEBUG) { 1.665 + debug("Error! The manifest URL is invalid: " + aManifestURL + 1.666 + ", which might be a hacked app."); 1.667 + } 1.668 + return null; 1.669 + }, 1.670 + 1.671 + _registerMessagePort: function(aMessage, aTarget) { 1.672 + let messagePortID = aMessage.messagePortID; 1.673 + let manifestURL = aMessage.manifestURL; 1.674 + let pageURL = aMessage.pageURL; 1.675 + 1.676 + let identity = this._identifyMessagePort(messagePortID, manifestURL); 1.677 + if (!identity) { 1.678 + if (DEBUG) { 1.679 + debug("Cannot identify the message port. Failed to register."); 1.680 + } 1.681 + return; 1.682 + } 1.683 + 1.684 + if (DEBUG) debug("Registering message port for " + manifestURL); 1.685 + let pair = identity.pair; 1.686 + let isPublisher = identity.isPublisher; 1.687 + 1.688 + let sender = isPublisher ? pair.publisher : pair.subscriber; 1.689 + sender.target = aTarget; 1.690 + sender.pageURL = pageURL; 1.691 + sender.messageQueue = []; 1.692 + 1.693 + // Check if the other port has queued messages. Deliver them if needed. 1.694 + if (DEBUG) { 1.695 + debug("Checking if the other port used to send messages but queued."); 1.696 + } 1.697 + let receiver = isPublisher ? pair.subscriber : pair.publisher; 1.698 + if (receiver.messageQueue) { 1.699 + while (receiver.messageQueue.length) { 1.700 + let message = receiver.messageQueue.shift(); 1.701 + if (DEBUG) debug("Delivering message: " + JSON.stringify(message)); 1.702 + sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage", 1.703 + { message: message, 1.704 + manifestURL: sender.manifestURL, 1.705 + pageURL: sender.pageURL, 1.706 + messagePortID: messagePortID }); 1.707 + } 1.708 + } 1.709 + }, 1.710 + 1.711 + _unregisterMessagePort: function(aMessage) { 1.712 + let messagePortID = aMessage.messagePortID; 1.713 + let manifestURL = aMessage.manifestURL; 1.714 + 1.715 + let identity = this._identifyMessagePort(messagePortID, manifestURL); 1.716 + if (!identity) { 1.717 + if (DEBUG) { 1.718 + debug("Cannot identify the message port. Failed to unregister."); 1.719 + } 1.720 + return; 1.721 + } 1.722 + 1.723 + if (DEBUG) { 1.724 + debug("Unregistering message port for " + manifestURL); 1.725 + } 1.726 + delete this._messagePortPairs[messagePortID]; 1.727 + }, 1.728 + 1.729 + _removeTarget: function(aTarget) { 1.730 + if (!aTarget) { 1.731 + if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way."); 1.732 + return 1.733 + } 1.734 + 1.735 + if (DEBUG) debug("Unregistering message ports based on this target."); 1.736 + let messagePortIDs = []; 1.737 + for (let messagePortID in this._messagePortPairs) { 1.738 + let pair = this._messagePortPairs[messagePortID]; 1.739 + if (pair.publisher.target === aTarget || 1.740 + pair.subscriber.target === aTarget) { 1.741 + messagePortIDs.push(messagePortID); 1.742 + } 1.743 + } 1.744 + messagePortIDs.forEach(function(aMessagePortID) { 1.745 + delete this._messagePortPairs[aMessagePortID]; 1.746 + }, this); 1.747 + }, 1.748 + 1.749 + _postMessage: function(aMessage) { 1.750 + let messagePortID = aMessage.messagePortID; 1.751 + let manifestURL = aMessage.manifestURL; 1.752 + let message = aMessage.message; 1.753 + 1.754 + let identity = this._identifyMessagePort(messagePortID, manifestURL); 1.755 + if (!identity) { 1.756 + if (DEBUG) debug("Cannot identify the message port. Failed to post."); 1.757 + return; 1.758 + } 1.759 + 1.760 + let pair = identity.pair; 1.761 + let isPublisher = identity.isPublisher; 1.762 + 1.763 + let receiver = isPublisher ? pair.subscriber : pair.publisher; 1.764 + if (!receiver.target) { 1.765 + if (DEBUG) { 1.766 + debug("The receiver's target is not ready yet. Queuing the message."); 1.767 + } 1.768 + let sender = isPublisher ? pair.publisher : pair.subscriber; 1.769 + sender.messageQueue.push(message); 1.770 + return; 1.771 + } 1.772 + 1.773 + if (DEBUG) debug("Delivering message: " + JSON.stringify(message)); 1.774 + receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage", 1.775 + { manifestURL: receiver.manifestURL, 1.776 + pageURL: receiver.pageURL, 1.777 + messagePortID: messagePortID, 1.778 + message: message }); 1.779 + }, 1.780 + 1.781 + _handleSelectcedApps: function(aData) { 1.782 + let callerID = aData.callerID; 1.783 + let caller = this._promptUICallers[callerID]; 1.784 + if (!caller) { 1.785 + if (DEBUG) debug("Error! Cannot find the caller."); 1.786 + return; 1.787 + } 1.788 + 1.789 + delete this._promptUICallers[callerID]; 1.790 + 1.791 + let outerWindowID = caller.outerWindowID; 1.792 + let requestID = caller.requestID; 1.793 + let target = caller.target; 1.794 + 1.795 + let manifestURL = aData.manifestURL; 1.796 + let keyword = aData.keyword; 1.797 + let selectedApps = aData.selectedApps; 1.798 + 1.799 + if (selectedApps.length == 0) { 1.800 + if (DEBUG) debug("No apps are selected to connect.") 1.801 + this._dispatchMessagePorts(keyword, manifestURL, [], 1.802 + target, outerWindowID, requestID); 1.803 + return; 1.804 + } 1.805 + 1.806 + // Find the entry of allowed connections to add the selected apps. 1.807 + let allowedPubAppManifestURLs = this._allowedConnections[keyword]; 1.808 + if (!allowedPubAppManifestURLs) { 1.809 + allowedPubAppManifestURLs = this._allowedConnections[keyword] = {}; 1.810 + } 1.811 + let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL]; 1.812 + if (!allowedSubAppManifestURLs) { 1.813 + allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = []; 1.814 + } 1.815 + 1.816 + // Add the selected app into the existing set of allowed connections. 1.817 + selectedApps.forEach(function(aSelectedApp) { 1.818 + let allowedSubAppManifestURL = aSelectedApp.manifestURL; 1.819 + if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) { 1.820 + allowedSubAppManifestURLs.push(allowedSubAppManifestURL); 1.821 + } 1.822 + }); 1.823 + 1.824 + // Finally, dispatch the message ports for the allowed connections, 1.825 + // including the old connections and the newly selected connection. 1.826 + this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs, 1.827 + target, outerWindowID, requestID); 1.828 + }, 1.829 + 1.830 + receiveMessage: function(aMessage) { 1.831 + if (DEBUG) debug("receiveMessage: name: " + aMessage.name); 1.832 + let message = aMessage.json; 1.833 + let target = aMessage.target; 1.834 + 1.835 + // To prevent the hacked child process from sending commands to parent 1.836 + // to do illegal connections, we need to check its manifest URL. 1.837 + if (aMessage.name !== "child-process-shutdown" && 1.838 + // TODO: fix bug 988142 to re-enable "InterAppMessagePort:Unregister". 1.839 + aMessage.name !== "InterAppMessagePort:Unregister" && 1.840 + kMessages.indexOf(aMessage.name) != -1) { 1.841 + if (!target.assertContainApp(message.manifestURL)) { 1.842 + if (DEBUG) { 1.843 + debug("Got message from a process carrying illegal manifest URL."); 1.844 + } 1.845 + return null; 1.846 + } 1.847 + } 1.848 + 1.849 + switch (aMessage.name) { 1.850 + case "Webapps:Connect": 1.851 + this._connect(message, target); 1.852 + break; 1.853 + case "Webapps:GetConnections": 1.854 + this._getConnections(message, target); 1.855 + break; 1.856 + case "InterAppConnection:Cancel": 1.857 + this._cancelConnection(message); 1.858 + break; 1.859 + case "InterAppMessagePort:PostMessage": 1.860 + this._postMessage(message); 1.861 + break; 1.862 + case "InterAppMessagePort:Register": 1.863 + this._registerMessagePort(message, target); 1.864 + break; 1.865 + case "InterAppMessagePort:Unregister": 1.866 + this._unregisterMessagePort(message); 1.867 + break; 1.868 + case "child-process-shutdown": 1.869 + this._removeTarget(target); 1.870 + break; 1.871 + } 1.872 + }, 1.873 + 1.874 + observe: function(aSubject, aTopic, aData) { 1.875 + switch (aTopic) { 1.876 + case "xpcom-shutdown": 1.877 + Services.obs.removeObserver(this, "xpcom-shutdown"); 1.878 + Services.obs.removeObserver(this, "inter-app-comm-select-app-result"); 1.879 + kMessages.forEach(function(aMsg) { 1.880 + ppmm.removeMessageListener(aMsg, this); 1.881 + }, this); 1.882 + ppmm = null; 1.883 + break; 1.884 + case "inter-app-comm-select-app-result": 1.885 + if (DEBUG) debug("inter-app-comm-select-app-result: " + aData); 1.886 + this._handleSelectcedApps(JSON.parse(aData)); 1.887 + break; 1.888 + } 1.889 + } 1.890 +}; 1.891 + 1.892 +InterAppCommService.init();