merge b2g-inbound to mozilla-central

This commit is contained in:
Carsten "Tomcat" Book 2014-04-24 13:12:50 +02:00
commit fed6f5b097
20 changed files with 1384 additions and 871 deletions

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="0292e64ef8451df104dcf9ac3b2c6749b81684dd"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>
@ -128,7 +128,7 @@
<project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="3a9a17613cc685aa232432566ad6cc607eab4ec1"/>
<project name="device_generic_goldfish" path="device/generic/goldfish" remote="b2g" revision="09485b73629856b21b2ed6073e327ab0e69a1189"/>
<project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="7d33aaf740bbf6c7c6e9c34a92b371eda311b66b"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="9a00b3db898a83ef0766baa93e935e525572899e"/>
<project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="bb11a0417efa7e6a08ed1cb2d5d6a13a3100abd3"/>
<project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="0e56e450367cd802241b27164a2979188242b95f"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="72e3a520e3c700839f07ba0113fd527b923c3330"/>
<project name="platform_system_nfcd" path="system/nfcd" remote="b2g" revision="baaf899afb158b9530690002f3656e958e3eb047"/>

View File

@ -15,7 +15,7 @@
<project name="platform_build" path="build" remote="b2g" revision="65fba428f8d76336b33ddd9e15900357953600ba">
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="0292e64ef8451df104dcf9ac3b2c6749b81684dd"/>

View File

@ -18,7 +18,7 @@
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "9f480d1c52a46cac8e9a0be04bdb0d73810bc596",
"revision": "2eae294604eb70e0e6eaee76b17e155ffd031107",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -15,7 +15,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -19,7 +19,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -17,7 +17,7 @@
</project>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
<project name="apitrace" path="external/apitrace" remote="apitrace" revision="8d6c36d74ba9aefbc8c3618fc93dd4907a0dbf5e"/>

View File

@ -17,7 +17,7 @@
<copyfile dest="Makefile" src="core/root.mk"/>
</project>
<project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="d25852a189c9707b144eb5f82d08384eb066c0fd"/>
<project name="gaia.git" path="gaia" remote="mozillaorg" revision="6a2eb4b3664fb3e6f5c87db2af8485b7424f9ecb"/>
<project name="gonk-misc" path="gonk-misc" remote="b2g" revision="266bca6e60dad43e395f38b66edabe8bdc882334"/>
<project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
<project name="librecovery" path="librecovery" remote="b2g" revision="1f6a1fe07f81c5bc5e1d079c9b60f7f78ca2bf4f"/>

View File

@ -6,869 +6,23 @@
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
Cu.import("resource://gre/modules/InterAppCommService.jsm");
const DEBUG = false;
function debug(aMsg) {
dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
function InterAppCommServiceProxy() {
}
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "messenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
const kMessages =["Webapps:Connect",
"Webapps:GetConnections",
"InterAppConnection:Cancel",
"InterAppMessagePort:PostMessage",
"InterAppMessagePort:Register",
"InterAppMessagePort:Unregister",
"child-process-shutdown"];
function InterAppCommService() {
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
kMessages.forEach(function(aMsg) {
ppmm.addMessageListener(aMsg, this);
}, this);
// This matrix is used for saving the inter-app connection info registered in
// the app manifest. The object literal is defined as below:
//
// {
// "keyword1": {
// "subAppManifestURL1": {
// /* subscribed info */
// },
// "subAppManifestURL2": {
// /* subscribed info */
// },
// ...
// },
// "keyword2": {
// "subAppManifestURL3": {
// /* subscribed info */
// },
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://subApp1.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp1.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// },
// "app://subApp2.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp2.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// },
// "bar": {
// "app://subApp3.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp3.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// }
// }
//
// TODO Bug 908999 - Update registered connections when app gets uninstalled.
this._registeredConnections = {};
// This matrix is used for saving the permitted connections, which allows
// the messaging between publishers and subscribers. The object literal is
// defined as below:
//
// {
// "keyword1": {
// "pubAppManifestURL1": [
// "subAppManifestURL1",
// "subAppManifestURL2",
// ...
// ],
// "pubAppManifestURL2": [
// "subAppManifestURL3",
// "subAppManifestURL4",
// ...
// ],
// ...
// },
// "keyword2": {
// "pubAppManifestURL3": [
// "subAppManifestURL5",
// ...
// ],
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://pubApp1.gaiamobile.org/manifest.webapp": [
// "app://subApp1.gaiamobile.org/manifest.webapp",
// "app://subApp2.gaiamobile.org/manifest.webapp"
// ],
// "app://pubApp2.gaiamobile.org/manifest.webapp": [
// "app://subApp3.gaiamobile.org/manifest.webapp",
// "app://subApp4.gaiamobile.org/manifest.webapp"
// ]
// },
// "bar": {
// "app://pubApp3.gaiamobile.org/manifest.webapp": [
// "app://subApp5.gaiamobile.org/manifest.webapp",
// ]
// }
// }
//
// TODO Bug 908999 - Update allowed connections when app gets uninstalled.
this._allowedConnections = {};
// This matrix is used for saving the caller info from the content process,
// which is indexed by a random UUID, to know where to return the promise
// resolvser's callback when the prompt UI for allowing connections returns.
// An example of the object literal is shown as below:
//
// {
// "fooID": {
// outerWindowID: 12,
// requestID: 34,
// target: pubAppTarget1
// },
// "barID": {
// outerWindowID: 56,
// requestID: 78,
// target: pubAppTarget2
// }
// }
//
// where |outerWindowID| is the ID of the window requesting the connection,
// |requestID| is the ID specifying the promise resolver to return,
// |target| is the target of the process requesting the connection.
this._promptUICallers = {};
// This matrix is used for saving the pair of message ports, which is indexed
// by a random UUID, so that each port can know whom it should talk to.
// An example of the object literal is shown as below:
//
// {
// "UUID1": {
// keyword: "keyword1",
// publisher: {
// manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
// target: pubAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
// target: subAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// },
// "UUID2": {
// keyword: "keyword2",
// publisher: {
// manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
// target: pubAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
// target: subAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// }
// }
this._messagePortPairs = {};
}
InterAppCommService.prototype = {
InterAppCommServiceProxy.prototype = {
registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
aDescription, aRules) {
let manifestURL = aManifestURI.spec;
let pageURL = aHandlerPageURI.spec;
if (DEBUG) {
debug("registerConnection: aKeyword: " + aKeyword +
" manifestURL: " + manifestURL + " pageURL: " + pageURL +
" aDescription: " + aDescription +
" aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
" aRules.manifestURLs: " + aRules.manifestURLs +
" aRules.installOrigins: " + aRules.installOrigins);
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
subAppManifestURLs = this._registeredConnections[aKeyword] = {};
}
subAppManifestURLs[manifestURL] = {
pageURL: pageURL,
description: aDescription,
rules: aRules,
manifestURL: manifestURL
};
},
_matchMinimumAccessLevel: function(aRules, aAppStatus) {
if (!aRules || !aRules.minimumAccessLevel) {
if (DEBUG) {
debug("rules.minimumAccessLevel is not available. No need to match.");
}
return true;
}
let minAccessLevel = aRules.minimumAccessLevel;
switch (minAccessLevel) {
case "web":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "privileged":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "certified":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
}
if (DEBUG) {
debug("rules.minimumAccessLevel is not matched!" +
" minAccessLevel: " + minAccessLevel +
" aAppStatus : " + aAppStatus);
}
return false;
},
_matchManifestURLs: function(aRules, aManifestURL) {
if (!aRules || !Array.isArray(aRules.manifestURLs)) {
if (DEBUG) {
debug("rules.manifestURLs is not available. No need to match.");
}
return true;
}
let manifestURLs = aRules.manifestURLs;
if (manifestURLs.indexOf(aManifestURL) != -1) {
return true;
}
if (DEBUG) {
debug("rules.manifestURLs is not matched!" +
" manifestURLs: " + manifestURLs +
" aManifestURL : " + aManifestURL);
}
return false;
},
_matchInstallOrigins: function(aRules, aInstallOrigin) {
if (!aRules || !Array.isArray(aRules.installOrigins)) {
if (DEBUG) {
debug("rules.installOrigins is not available. No need to match.");
}
return true;
}
let installOrigins = aRules.installOrigins;
if (installOrigins.indexOf(aInstallOrigin) != -1) {
return true;
}
if (DEBUG) {
debug("rules.installOrigins is not matched!" +
" installOrigins: " + installOrigins +
" installOrigin : " + aInstallOrigin);
}
return false;
},
_matchRules: function(aPubAppManifestURL, aPubRules,
aSubAppManifestURL, aSubRules) {
let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL);
let subApp = appsService.getAppByManifestURL(aSubAppManifestURL);
// TODO Bug 907068 In the initiative step, we only expose this API to
// certified apps to meet the time line. Eventually, we need to make
// it available for the non-certified apps as well. For now, only the
// certified apps can match the rules.
if (pubApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
subApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
if (DEBUG) {
debug("Only certified apps are allowed to do connections.");
}
return false;
}
if (!aPubRules && !aSubRules) {
if (DEBUG) {
debug("No rules for publisher and subscriber. No need to match.");
}
return true;
}
// Check minimumAccessLevel.
if (!this._matchMinimumAccessLevel(aPubRules, subApp.appStatus) ||
!this._matchMinimumAccessLevel(aSubRules, pubApp.appStatus)) {
return false;
}
// Check manifestURLs.
if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
!this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
return false;
}
// Check installOrigins.
if (!this._matchInstallOrigins(aPubRules, subApp.installOrigin) ||
!this._matchInstallOrigins(aSubRules, pubApp.installOrigin)) {
return false;
}
// Check developers.
// TODO Do we really want to check this? This one seems naive.
if (DEBUG) debug("All rules are matched.");
return true;
},
_dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
aAllowedSubAppManifestURLs,
aTarget, aOuterWindowID, aRequestID) {
if (DEBUG) {
debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
" aPubAppManifestURL: " + aPubAppManifestURL +
" aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
}
if (aAllowedSubAppManifestURLs.length == 0) {
if (DEBUG) debug("No apps are allowed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let messagePortIDs = [];
aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) {
let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL];
if (!subscribedInfo) {
if (DEBUG) {
debug("The sunscribed info is not available. Skipping: " +
aAllowedSubAppManifestURL);
}
return;
}
// The message port ID is aimed for identifying the coupling targets
// to deliver messages with each other. This ID is centrally generated
// by the parent and dispatched to both the sender and receiver ends
// for creating their own message ports respectively.
let messagePortID = UUIDGenerator.generateUUID().toString();
this._messagePortPairs[messagePortID] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubAppManifestURL
},
subscriber: {
manifestURL: aAllowedSubAppManifestURL
}
};
// Fire system message to deliver the message port to the subscriber.
messenger.sendMessage("connection",
{ keyword: aKeyword,
messagePortID: messagePortID },
Services.io.newURI(subscribedInfo.pageURL, null, null),
Services.io.newURI(subscribedInfo.manifestURL, null, null));
messagePortIDs.push(messagePortID);
}, this);
if (messagePortIDs.length == 0) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
// Return the message port IDs to open the message ports for the publisher.
if (DEBUG) debug("messagePortIDs: " + messagePortIDs);
aTarget.sendAsyncMessage("Webapps:Connect:Return:OK",
{ keyword: aKeyword,
messagePortIDs: messagePortIDs,
oid: aOuterWindowID, requestID: aRequestID });
},
_connect: function(aMessage, aTarget) {
let keyword = aMessage.keyword;
let pubRules = aMessage.rules;
let pubAppManifestURL = aMessage.manifestURL;
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let subAppManifestURLs = this._registeredConnections[keyword];
if (!subAppManifestURLs) {
if (DEBUG) {
debug("No apps are subscribed for this connection. Returning.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
aTarget, outerWindowID, requestID);
return;
}
// Fetch the apps that used to be allowed to connect before, so that
// users don't need to select/allow them again. That is, we only pop up
// the prompt UI for the *new* connections.
let allowedSubAppManifestURLs = [];
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (allowedPubAppManifestURLs &&
allowedPubAppManifestURLs[pubAppManifestURL]) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL];
}
// Check rules to see if a subscribed app is allowed to connect.
let appsToSelect = [];
for (let subAppManifestURL in subAppManifestURLs) {
if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) {
if (DEBUG) {
debug("Don't need to select again. Skipping: " + subAppManifestURL);
}
continue;
}
// Only rule-matched publishers/subscribers are allowed to connect.
let subscribedInfo = subAppManifestURLs[subAppManifestURL];
let subRules = subscribedInfo.rules;
let matched =
this._matchRules(pubAppManifestURL, pubRules,
subAppManifestURL, subRules);
if (!matched) {
if (DEBUG) {
debug("Rules are not matched. Skipping: " + subAppManifestURL);
}
continue;
}
appsToSelect.push({
manifestURL: subAppManifestURL,
description: subscribedInfo.description
});
}
if (appsToSelect.length == 0) {
if (DEBUG) {
debug("No additional apps need to be selected for this connection. " +
"Just dispatch message ports for the existing connections.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL,
allowedSubAppManifestURLs,
aTarget, outerWindowID, requestID);
return;
}
// Remember the caller info with an UUID so that we can know where to
// return the promise resolver's callback when the prompt UI returns.
let callerID = UUIDGenerator.generateUUID().toString();
this._promptUICallers[callerID] = {
outerWindowID: outerWindowID,
requestID: requestID,
target: aTarget
};
// TODO Bug 897169 Temporarily disable the notification for popping up
// the prompt until the UX/UI for the prompt is confirmed.
//
// TODO Bug 908191 We need to change the way of interaction between API and
// run-time prompt from observer notification to xpcom-interface caller.
//
/*
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, "inter-app-comm-select-app",
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
appsToSelect: appsToSelect }));
*/
// TODO Bug 897169 Simulate the return of the app-selected result by
// the prompt, which always allows the connection. This dummy codes
// will be removed when the UX/UI for the prompt is ready.
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
selectedApps: appsToSelect }));
},
_getConnections: function(aMessage, aTarget) {
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let connections = [];
for (let keyword in this._allowedConnections) {
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) {
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[allowedPubAppManifestURL];
allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) {
connections.push({ keyword: keyword,
pubAppManifestURL: allowedPubAppManifestURL,
subAppManifestURL: allowedSubAppManifestURL });
});
}
}
aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK",
{ connections: connections,
oid: outerWindowID, requestID: requestID });
},
_cancelConnection: function(aMessage) {
let keyword = aMessage.keyword;
let pubAppManifestURL = aMessage.pubAppManifestURL;
let subAppManifestURL = aMessage.subAppManifestURL;
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
if (DEBUG) debug("keyword is not found: " + keyword);
return;
}
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[pubAppManifestURL];
if (!allowedSubAppManifestURLs) {
if (DEBUG) debug("publisher is not found: " + pubAppManifestURL);
return;
}
let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL);
if (index == -1) {
if (DEBUG) debug("subscriber is not found: " + subAppManifestURL);
return;
}
if (DEBUG) debug("Cancelling the connection.");
allowedSubAppManifestURLs.splice(index, 1);
// Clean up the parent entries if needed.
if (allowedSubAppManifestURLs.length == 0) {
delete allowedPubAppManifestURLs[pubAppManifestURL];
if (Object.keys(allowedPubAppManifestURLs).length == 0) {
delete this._allowedConnections[keyword];
}
}
if (DEBUG) debug("Unregistering message ports based on this connection.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.keyword == keyword &&
pair.publisher.manifestURL == pubAppManifestURL &&
pair.subscriber.manifestURL == subAppManifestURL) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_identifyMessagePort: function(aMessagePortID, aManifestURL) {
let pair = this._messagePortPairs[aMessagePortID];
if (!pair) {
if (DEBUG) {
debug("Error! The message port ID is invalid: " + aMessagePortID +
", which should have been generated by parent.");
}
return null;
}
// Check it the message port is for publisher.
if (pair.publisher.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: true };
}
// Check it the message port is for subscriber.
if (pair.subscriber.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: false };
}
if (DEBUG) {
debug("Error! The manifest URL is invalid: " + aManifestURL +
", which might be a hacked app.");
}
return null;
},
_registerMessagePort: function(aMessage, aTarget) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let pageURL = aMessage.pageURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to register.");
}
return;
}
if (DEBUG) debug("Registering message port for " + manifestURL);
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.target = aTarget;
sender.pageURL = pageURL;
sender.messageQueue = [];
// Check if the other port has queued messages. Deliver them if needed.
if (DEBUG) {
debug("Checking if the other port used to send messages but queued.");
}
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (receiver.messageQueue) {
while (receiver.messageQueue.length) {
let message = receiver.messageQueue.shift();
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ message: message,
manifestURL: sender.manifestURL,
pageURL: sender.pageURL,
messagePortID: messagePortID });
}
}
},
_unregisterMessagePort: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to unregister.");
}
return;
}
if (DEBUG) {
debug("Unregistering message port for " + manifestURL);
}
delete this._messagePortPairs[messagePortID];
},
_removeTarget: function(aTarget) {
if (!aTarget) {
if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way.");
return
}
if (DEBUG) debug("Unregistering message ports based on this target.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.publisher.target === aTarget ||
pair.subscriber.target === aTarget) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_postMessage: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let message = aMessage.message;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) debug("Cannot identify the message port. Failed to post.");
return;
}
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (!receiver.target) {
if (DEBUG) {
debug("The receiver's target is not ready yet. Queuing the message.");
}
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.messageQueue.push(message);
return;
}
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ manifestURL: receiver.manifestURL,
pageURL: receiver.pageURL,
messagePortID: messagePortID,
message: message });
},
_handleSelectcedApps: function(aData) {
let callerID = aData.callerID;
let caller = this._promptUICallers[callerID];
if (!caller) {
if (DEBUG) debug("Error! Cannot find the caller.");
return;
}
delete this._promptUICallers[callerID];
let outerWindowID = caller.outerWindowID;
let requestID = caller.requestID;
let target = caller.target;
let manifestURL = aData.manifestURL;
let keyword = aData.keyword;
let selectedApps = aData.selectedApps;
if (selectedApps.length == 0) {
if (DEBUG) debug("No apps are selected to connect.")
this._dispatchMessagePorts(keyword, manifestURL, [],
target, outerWindowID, requestID);
return;
}
// Find the entry of allowed connections to add the selected apps.
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
allowedPubAppManifestURLs = this._allowedConnections[keyword] = {};
}
let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL];
if (!allowedSubAppManifestURLs) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = [];
}
// Add the selected app into the existing set of allowed connections.
selectedApps.forEach(function(aSelectedApp) {
let allowedSubAppManifestURL = aSelectedApp.manifestURL;
if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) {
allowedSubAppManifestURLs.push(allowedSubAppManifestURL);
}
});
// Finally, dispatch the message ports for the allowed connections,
// including the old connections and the newly selected connection.
this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
target, outerWindowID, requestID);
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
let message = aMessage.json;
let target = aMessage.target;
// To prevent the hacked child process from sending commands to parent
// to do illegal connections, we need to check its manifest URL.
if (aMessage.name !== "child-process-shutdown" &&
// TODO: fix bug 988142 to re-enable "InterAppMessagePort:Unregister".
aMessage.name !== "InterAppMessagePort:Unregister" &&
kMessages.indexOf(aMessage.name) != -1) {
if (!target.assertContainApp(message.manifestURL)) {
if (DEBUG) {
debug("Got message from a process carrying illegal manifest URL.");
}
return null;
}
}
switch (aMessage.name) {
case "Webapps:Connect":
this._connect(message, target);
break;
case "Webapps:GetConnections":
this._getConnections(message, target);
break;
case "InterAppConnection:Cancel":
this._cancelConnection(message);
break;
case "InterAppMessagePort:PostMessage":
this._postMessage(message);
break;
case "InterAppMessagePort:Register":
this._registerMessagePort(message, target);
break;
case "InterAppMessagePort:Unregister":
this._unregisterMessagePort(message);
break;
case "child-process-shutdown":
this._removeTarget(target);
break;
}
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "xpcom-shutdown":
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.obs.removeObserver(this, "inter-app-comm-select-app-result");
kMessages.forEach(function(aMsg) {
ppmm.removeMessageListener(aMsg, this);
}, this);
ppmm = null;
break;
case "inter-app-comm-select-app-result":
if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
this._handleSelectcedApps(JSON.parse(aData));
break;
}
InterAppCommService.
registerConnection(aKeyword, aHandlerPageURI, aManifestURI,
aDescription, aRules);
},
classID: Components.ID("{3dd15ce6-e7be-11e2-82bc-77967e7a63e6}"),
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService,
Ci.nsIObserver])
QueryInterface: XPCOMUtils.generateQI([Ci.nsIInterAppCommService])
}
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommService]);
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([InterAppCommServiceProxy]);

View File

@ -0,0 +1,889 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
const { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
this.EXPORTED_SYMBOLS = ["InterAppCommService"];
Cu.import("resource://gre/modules/Services.jsm");
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/AppsUtils.jsm");
const DEBUG = false;
function debug(aMsg) {
dump("-- InterAppCommService: " + Date.now() + ": " + aMsg + "\n");
}
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
"@mozilla.org/AppsService;1",
"nsIAppsService");
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
"@mozilla.org/parentprocessmessagemanager;1",
"nsIMessageBroadcaster");
XPCOMUtils.defineLazyServiceGetter(this, "UUIDGenerator",
"@mozilla.org/uuid-generator;1",
"nsIUUIDGenerator");
XPCOMUtils.defineLazyServiceGetter(this, "messenger",
"@mozilla.org/system-message-internal;1",
"nsISystemMessagesInternal");
const kMessages =["Webapps:Connect",
"Webapps:GetConnections",
"InterAppConnection:Cancel",
"InterAppMessagePort:PostMessage",
"InterAppMessagePort:Register",
"InterAppMessagePort:Unregister",
"child-process-shutdown"];
/**
* This module contains helpers for Inter-App Communication API [1] related
* purposes, which plays the role of the central service receiving messages
* from and interacting with the content processes.
*
* [1] https://wiki.mozilla.org/WebAPI/Inter_App_Communication_Alt_proposal
*/
this.InterAppCommService = {
init: function() {
Services.obs.addObserver(this, "xpcom-shutdown", false);
Services.obs.addObserver(this, "inter-app-comm-select-app-result", false);
kMessages.forEach(function(aMsg) {
ppmm.addMessageListener(aMsg, this);
}, this);
// This matrix is used for saving the inter-app connection info registered in
// the app manifest. The object literal is defined as below:
//
// {
// "keyword1": {
// "subAppManifestURL1": {
// /* subscribed info */
// },
// "subAppManifestURL2": {
// /* subscribed info */
// },
// ...
// },
// "keyword2": {
// "subAppManifestURL3": {
// /* subscribed info */
// },
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://subApp1.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp1.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// },
// "app://subApp2.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp2.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// },
// "bar": {
// "app://subApp3.gaiamobile.org/manifest.webapp": {
// pageURL: "app://subApp3.gaiamobile.org/handler.html",
// description: "blah blah",
// rules: { ... }
// }
// }
// }
//
// TODO Bug 908999 - Update registered connections when app gets uninstalled.
this._registeredConnections = {};
// This matrix is used for saving the permitted connections, which allows
// the messaging between publishers and subscribers. The object literal is
// defined as below:
//
// {
// "keyword1": {
// "pubAppManifestURL1": [
// "subAppManifestURL1",
// "subAppManifestURL2",
// ...
// ],
// "pubAppManifestURL2": [
// "subAppManifestURL3",
// "subAppManifestURL4",
// ...
// ],
// ...
// },
// "keyword2": {
// "pubAppManifestURL3": [
// "subAppManifestURL5",
// ...
// ],
// ...
// },
// ...
// }
//
// For example:
//
// {
// "foo": {
// "app://pubApp1.gaiamobile.org/manifest.webapp": [
// "app://subApp1.gaiamobile.org/manifest.webapp",
// "app://subApp2.gaiamobile.org/manifest.webapp"
// ],
// "app://pubApp2.gaiamobile.org/manifest.webapp": [
// "app://subApp3.gaiamobile.org/manifest.webapp",
// "app://subApp4.gaiamobile.org/manifest.webapp"
// ]
// },
// "bar": {
// "app://pubApp3.gaiamobile.org/manifest.webapp": [
// "app://subApp5.gaiamobile.org/manifest.webapp",
// ]
// }
// }
//
// TODO Bug 908999 - Update allowed connections when app gets uninstalled.
this._allowedConnections = {};
// This matrix is used for saving the caller info from the content process,
// which is indexed by a random UUID, to know where to return the promise
// resolvser's callback when the prompt UI for allowing connections returns.
// An example of the object literal is shown as below:
//
// {
// "fooID": {
// outerWindowID: 12,
// requestID: 34,
// target: pubAppTarget1
// },
// "barID": {
// outerWindowID: 56,
// requestID: 78,
// target: pubAppTarget2
// }
// }
//
// where |outerWindowID| is the ID of the window requesting the connection,
// |requestID| is the ID specifying the promise resolver to return,
// |target| is the target of the process requesting the connection.
this._promptUICallers = {};
// This matrix is used for saving the pair of message ports, which is indexed
// by a random UUID, so that each port can know whom it should talk to.
// An example of the object literal is shown as below:
//
// {
// "UUID1": {
// keyword: "keyword1",
// publisher: {
// manifestURL: "app://pubApp1.gaiamobile.org/manifest.webapp",
// target: pubAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp1.gaiamobile.org/manifest.webapp",
// target: subAppTarget1,
// pageURL: "app://pubApp1.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// },
// "UUID2": {
// keyword: "keyword2",
// publisher: {
// manifestURL: "app://pubApp2.gaiamobile.org/manifest.webapp",
// target: pubAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/caller.html",
// messageQueue: [...]
// },
// subscriber: {
// manifestURL: "app://subApp2.gaiamobile.org/manifest.webapp",
// target: subAppTarget2,
// pageURL: "app://pubApp2.gaiamobile.org/handler.html",
// messageQueue: [...]
// }
// }
// }
this._messagePortPairs = {};
},
/**
* Registration of a page that wants to be connected to other apps through
* the Inter-App Communication API.
*
* @param aKeyword The connection's keyword.
* @param aHandlerPageURI The URI of the handler's page.
* @param aManifestURI The webapp's manifest URI.
* @param aDescription The connection's description.
* @param aRules The connection's rules.
*/
registerConnection: function(aKeyword, aHandlerPageURI, aManifestURI,
aDescription, aRules) {
let manifestURL = aManifestURI.spec;
let pageURL = aHandlerPageURI.spec;
if (DEBUG) {
debug("registerConnection: aKeyword: " + aKeyword +
" manifestURL: " + manifestURL + " pageURL: " + pageURL +
" aDescription: " + aDescription +
" aRules.minimumAccessLevel: " + aRules.minimumAccessLevel +
" aRules.manifestURLs: " + aRules.manifestURLs +
" aRules.installOrigins: " + aRules.installOrigins);
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
subAppManifestURLs = this._registeredConnections[aKeyword] = {};
}
subAppManifestURLs[manifestURL] = {
pageURL: pageURL,
description: aDescription,
rules: aRules,
manifestURL: manifestURL
};
},
_matchMinimumAccessLevel: function(aRules, aAppStatus) {
if (!aRules || !aRules.minimumAccessLevel) {
if (DEBUG) {
debug("rules.minimumAccessLevel is not available. No need to match.");
}
return true;
}
let minAccessLevel = aRules.minimumAccessLevel;
switch (minAccessLevel) {
case "web":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_INSTALLED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "privileged":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_PRIVILEGED ||
aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
case "certified":
if (aAppStatus == Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
return true;
}
break;
}
if (DEBUG) {
debug("rules.minimumAccessLevel is not matched!" +
" minAccessLevel: " + minAccessLevel +
" aAppStatus : " + aAppStatus);
}
return false;
},
_matchManifestURLs: function(aRules, aManifestURL) {
if (!aRules || !Array.isArray(aRules.manifestURLs)) {
if (DEBUG) {
debug("rules.manifestURLs is not available. No need to match.");
}
return true;
}
let manifestURLs = aRules.manifestURLs;
if (manifestURLs.indexOf(aManifestURL) != -1) {
return true;
}
if (DEBUG) {
debug("rules.manifestURLs is not matched!" +
" manifestURLs: " + manifestURLs +
" aManifestURL : " + aManifestURL);
}
return false;
},
_matchInstallOrigins: function(aRules, aInstallOrigin) {
if (!aRules || !Array.isArray(aRules.installOrigins)) {
if (DEBUG) {
debug("rules.installOrigins is not available. No need to match.");
}
return true;
}
let installOrigins = aRules.installOrigins;
if (installOrigins.indexOf(aInstallOrigin) != -1) {
return true;
}
if (DEBUG) {
debug("rules.installOrigins is not matched!" +
" installOrigins: " + installOrigins +
" installOrigin : " + aInstallOrigin);
}
return false;
},
_matchRules: function(aPubAppManifestURL, aPubRules,
aSubAppManifestURL, aSubRules) {
let pubApp = appsService.getAppByManifestURL(aPubAppManifestURL);
let subApp = appsService.getAppByManifestURL(aSubAppManifestURL);
// TODO Bug 907068 In the initiative step, we only expose this API to
// certified apps to meet the time line. Eventually, we need to make
// it available for the non-certified apps as well. For now, only the
// certified apps can match the rules.
if (pubApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED ||
subApp.appStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
if (DEBUG) {
debug("Only certified apps are allowed to do connections.");
}
return false;
}
if (!aPubRules && !aSubRules) {
if (DEBUG) {
debug("No rules for publisher and subscriber. No need to match.");
}
return true;
}
// Check minimumAccessLevel.
if (!this._matchMinimumAccessLevel(aPubRules, subApp.appStatus) ||
!this._matchMinimumAccessLevel(aSubRules, pubApp.appStatus)) {
return false;
}
// Check manifestURLs.
if (!this._matchManifestURLs(aPubRules, aSubAppManifestURL) ||
!this._matchManifestURLs(aSubRules, aPubAppManifestURL)) {
return false;
}
// Check installOrigins.
if (!this._matchInstallOrigins(aPubRules, subApp.installOrigin) ||
!this._matchInstallOrigins(aSubRules, pubApp.installOrigin)) {
return false;
}
// Check developers.
// TODO Do we really want to check this? This one seems naive.
if (DEBUG) debug("All rules are matched.");
return true;
},
_dispatchMessagePorts: function(aKeyword, aPubAppManifestURL,
aAllowedSubAppManifestURLs,
aTarget, aOuterWindowID, aRequestID) {
if (DEBUG) {
debug("_dispatchMessagePorts: aKeyword: " + aKeyword +
" aPubAppManifestURL: " + aPubAppManifestURL +
" aAllowedSubAppManifestURLs: " + aAllowedSubAppManifestURLs);
}
if (aAllowedSubAppManifestURLs.length == 0) {
if (DEBUG) debug("No apps are allowed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let subAppManifestURLs = this._registeredConnections[aKeyword];
if (!subAppManifestURLs) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
let messagePortIDs = [];
aAllowedSubAppManifestURLs.forEach(function(aAllowedSubAppManifestURL) {
let subscribedInfo = subAppManifestURLs[aAllowedSubAppManifestURL];
if (!subscribedInfo) {
if (DEBUG) {
debug("The sunscribed info is not available. Skipping: " +
aAllowedSubAppManifestURL);
}
return;
}
// The message port ID is aimed for identifying the coupling targets
// to deliver messages with each other. This ID is centrally generated
// by the parent and dispatched to both the sender and receiver ends
// for creating their own message ports respectively.
let messagePortID = UUIDGenerator.generateUUID().toString();
this._messagePortPairs[messagePortID] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubAppManifestURL
},
subscriber: {
manifestURL: aAllowedSubAppManifestURL
}
};
// Fire system message to deliver the message port to the subscriber.
messenger.sendMessage("connection",
{ keyword: aKeyword,
messagePortID: messagePortID },
Services.io.newURI(subscribedInfo.pageURL, null, null),
Services.io.newURI(subscribedInfo.manifestURL, null, null));
messagePortIDs.push(messagePortID);
}, this);
if (messagePortIDs.length == 0) {
if (DEBUG) debug("No apps are subscribed to connect. Returning.");
aTarget.sendAsyncMessage("Webapps:Connect:Return:KO",
{ oid: aOuterWindowID, requestID: aRequestID });
return;
}
// Return the message port IDs to open the message ports for the publisher.
if (DEBUG) debug("messagePortIDs: " + messagePortIDs);
aTarget.sendAsyncMessage("Webapps:Connect:Return:OK",
{ keyword: aKeyword,
messagePortIDs: messagePortIDs,
oid: aOuterWindowID, requestID: aRequestID });
},
_connect: function(aMessage, aTarget) {
let keyword = aMessage.keyword;
let pubRules = aMessage.rules;
let pubAppManifestURL = aMessage.manifestURL;
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let subAppManifestURLs = this._registeredConnections[keyword];
if (!subAppManifestURLs) {
if (DEBUG) {
debug("No apps are subscribed for this connection. Returning.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL, [],
aTarget, outerWindowID, requestID);
return;
}
// Fetch the apps that used to be allowed to connect before, so that
// users don't need to select/allow them again. That is, we only pop up
// the prompt UI for the *new* connections.
let allowedSubAppManifestURLs = [];
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (allowedPubAppManifestURLs &&
allowedPubAppManifestURLs[pubAppManifestURL]) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[pubAppManifestURL];
}
// Check rules to see if a subscribed app is allowed to connect.
let appsToSelect = [];
for (let subAppManifestURL in subAppManifestURLs) {
if (allowedSubAppManifestURLs.indexOf(subAppManifestURL) != -1) {
if (DEBUG) {
debug("Don't need to select again. Skipping: " + subAppManifestURL);
}
continue;
}
// Only rule-matched publishers/subscribers are allowed to connect.
let subscribedInfo = subAppManifestURLs[subAppManifestURL];
let subRules = subscribedInfo.rules;
let matched =
this._matchRules(pubAppManifestURL, pubRules,
subAppManifestURL, subRules);
if (!matched) {
if (DEBUG) {
debug("Rules are not matched. Skipping: " + subAppManifestURL);
}
continue;
}
appsToSelect.push({
manifestURL: subAppManifestURL,
description: subscribedInfo.description
});
}
if (appsToSelect.length == 0) {
if (DEBUG) {
debug("No additional apps need to be selected for this connection. " +
"Just dispatch message ports for the existing connections.");
}
this._dispatchMessagePorts(keyword, pubAppManifestURL,
allowedSubAppManifestURLs,
aTarget, outerWindowID, requestID);
return;
}
// Remember the caller info with an UUID so that we can know where to
// return the promise resolver's callback when the prompt UI returns.
let callerID = UUIDGenerator.generateUUID().toString();
this._promptUICallers[callerID] = {
outerWindowID: outerWindowID,
requestID: requestID,
target: aTarget
};
// TODO Bug 897169 Temporarily disable the notification for popping up
// the prompt until the UX/UI for the prompt is confirmed.
//
// TODO Bug 908191 We need to change the way of interaction between API and
// run-time prompt from observer notification to xpcom-interface caller.
//
/*
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, "inter-app-comm-select-app",
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
appsToSelect: appsToSelect }));
*/
// TODO Bug 897169 Simulate the return of the app-selected result by
// the prompt, which always allows the connection. This dummy codes
// will be removed when the UX/UI for the prompt is ready.
if (DEBUG) debug("appsToSelect: " + appsToSelect);
Services.obs.notifyObservers(null, 'inter-app-comm-select-app-result',
JSON.stringify({ callerID: callerID,
manifestURL: pubAppManifestURL,
keyword: keyword,
selectedApps: appsToSelect }));
},
_getConnections: function(aMessage, aTarget) {
let outerWindowID = aMessage.outerWindowID;
let requestID = aMessage.requestID;
let connections = [];
for (let keyword in this._allowedConnections) {
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
for (let allowedPubAppManifestURL in allowedPubAppManifestURLs) {
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[allowedPubAppManifestURL];
allowedSubAppManifestURLs.forEach(function(allowedSubAppManifestURL) {
connections.push({ keyword: keyword,
pubAppManifestURL: allowedPubAppManifestURL,
subAppManifestURL: allowedSubAppManifestURL });
});
}
}
aTarget.sendAsyncMessage("Webapps:GetConnections:Return:OK",
{ connections: connections,
oid: outerWindowID, requestID: requestID });
},
_cancelConnection: function(aMessage) {
let keyword = aMessage.keyword;
let pubAppManifestURL = aMessage.pubAppManifestURL;
let subAppManifestURL = aMessage.subAppManifestURL;
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
if (DEBUG) debug("keyword is not found: " + keyword);
return;
}
let allowedSubAppManifestURLs =
allowedPubAppManifestURLs[pubAppManifestURL];
if (!allowedSubAppManifestURLs) {
if (DEBUG) debug("publisher is not found: " + pubAppManifestURL);
return;
}
let index = allowedSubAppManifestURLs.indexOf(subAppManifestURL);
if (index == -1) {
if (DEBUG) debug("subscriber is not found: " + subAppManifestURL);
return;
}
if (DEBUG) debug("Cancelling the connection.");
allowedSubAppManifestURLs.splice(index, 1);
// Clean up the parent entries if needed.
if (allowedSubAppManifestURLs.length == 0) {
delete allowedPubAppManifestURLs[pubAppManifestURL];
if (Object.keys(allowedPubAppManifestURLs).length == 0) {
delete this._allowedConnections[keyword];
}
}
if (DEBUG) debug("Unregistering message ports based on this connection.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.keyword == keyword &&
pair.publisher.manifestURL == pubAppManifestURL &&
pair.subscriber.manifestURL == subAppManifestURL) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_identifyMessagePort: function(aMessagePortID, aManifestURL) {
let pair = this._messagePortPairs[aMessagePortID];
if (!pair) {
if (DEBUG) {
debug("Error! The message port ID is invalid: " + aMessagePortID +
", which should have been generated by parent.");
}
return null;
}
// Check it the message port is for publisher.
if (pair.publisher.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: true };
}
// Check it the message port is for subscriber.
if (pair.subscriber.manifestURL == aManifestURL) {
return { pair: pair, isPublisher: false };
}
if (DEBUG) {
debug("Error! The manifest URL is invalid: " + aManifestURL +
", which might be a hacked app.");
}
return null;
},
_registerMessagePort: function(aMessage, aTarget) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let pageURL = aMessage.pageURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to register.");
}
return;
}
if (DEBUG) debug("Registering message port for " + manifestURL);
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.target = aTarget;
sender.pageURL = pageURL;
sender.messageQueue = [];
// Check if the other port has queued messages. Deliver them if needed.
if (DEBUG) {
debug("Checking if the other port used to send messages but queued.");
}
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (receiver.messageQueue) {
while (receiver.messageQueue.length) {
let message = receiver.messageQueue.shift();
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
sender.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ message: message,
manifestURL: sender.manifestURL,
pageURL: sender.pageURL,
messagePortID: messagePortID });
}
}
},
_unregisterMessagePort: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) {
debug("Cannot identify the message port. Failed to unregister.");
}
return;
}
if (DEBUG) {
debug("Unregistering message port for " + manifestURL);
}
delete this._messagePortPairs[messagePortID];
},
_removeTarget: function(aTarget) {
if (!aTarget) {
if (DEBUG) debug("Error! aTarget cannot be null/undefined in any way.");
return
}
if (DEBUG) debug("Unregistering message ports based on this target.");
let messagePortIDs = [];
for (let messagePortID in this._messagePortPairs) {
let pair = this._messagePortPairs[messagePortID];
if (pair.publisher.target === aTarget ||
pair.subscriber.target === aTarget) {
messagePortIDs.push(messagePortID);
}
}
messagePortIDs.forEach(function(aMessagePortID) {
delete this._messagePortPairs[aMessagePortID];
}, this);
},
_postMessage: function(aMessage) {
let messagePortID = aMessage.messagePortID;
let manifestURL = aMessage.manifestURL;
let message = aMessage.message;
let identity = this._identifyMessagePort(messagePortID, manifestURL);
if (!identity) {
if (DEBUG) debug("Cannot identify the message port. Failed to post.");
return;
}
let pair = identity.pair;
let isPublisher = identity.isPublisher;
let receiver = isPublisher ? pair.subscriber : pair.publisher;
if (!receiver.target) {
if (DEBUG) {
debug("The receiver's target is not ready yet. Queuing the message.");
}
let sender = isPublisher ? pair.publisher : pair.subscriber;
sender.messageQueue.push(message);
return;
}
if (DEBUG) debug("Delivering message: " + JSON.stringify(message));
receiver.target.sendAsyncMessage("InterAppMessagePort:OnMessage",
{ manifestURL: receiver.manifestURL,
pageURL: receiver.pageURL,
messagePortID: messagePortID,
message: message });
},
_handleSelectcedApps: function(aData) {
let callerID = aData.callerID;
let caller = this._promptUICallers[callerID];
if (!caller) {
if (DEBUG) debug("Error! Cannot find the caller.");
return;
}
delete this._promptUICallers[callerID];
let outerWindowID = caller.outerWindowID;
let requestID = caller.requestID;
let target = caller.target;
let manifestURL = aData.manifestURL;
let keyword = aData.keyword;
let selectedApps = aData.selectedApps;
if (selectedApps.length == 0) {
if (DEBUG) debug("No apps are selected to connect.")
this._dispatchMessagePorts(keyword, manifestURL, [],
target, outerWindowID, requestID);
return;
}
// Find the entry of allowed connections to add the selected apps.
let allowedPubAppManifestURLs = this._allowedConnections[keyword];
if (!allowedPubAppManifestURLs) {
allowedPubAppManifestURLs = this._allowedConnections[keyword] = {};
}
let allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL];
if (!allowedSubAppManifestURLs) {
allowedSubAppManifestURLs = allowedPubAppManifestURLs[manifestURL] = [];
}
// Add the selected app into the existing set of allowed connections.
selectedApps.forEach(function(aSelectedApp) {
let allowedSubAppManifestURL = aSelectedApp.manifestURL;
if (allowedSubAppManifestURLs.indexOf(allowedSubAppManifestURL) == -1) {
allowedSubAppManifestURLs.push(allowedSubAppManifestURL);
}
});
// Finally, dispatch the message ports for the allowed connections,
// including the old connections and the newly selected connection.
this._dispatchMessagePorts(keyword, manifestURL, allowedSubAppManifestURLs,
target, outerWindowID, requestID);
},
receiveMessage: function(aMessage) {
if (DEBUG) debug("receiveMessage: name: " + aMessage.name);
let message = aMessage.json;
let target = aMessage.target;
// To prevent the hacked child process from sending commands to parent
// to do illegal connections, we need to check its manifest URL.
if (aMessage.name !== "child-process-shutdown" &&
// TODO: fix bug 988142 to re-enable "InterAppMessagePort:Unregister".
aMessage.name !== "InterAppMessagePort:Unregister" &&
kMessages.indexOf(aMessage.name) != -1) {
if (!target.assertContainApp(message.manifestURL)) {
if (DEBUG) {
debug("Got message from a process carrying illegal manifest URL.");
}
return null;
}
}
switch (aMessage.name) {
case "Webapps:Connect":
this._connect(message, target);
break;
case "Webapps:GetConnections":
this._getConnections(message, target);
break;
case "InterAppConnection:Cancel":
this._cancelConnection(message);
break;
case "InterAppMessagePort:PostMessage":
this._postMessage(message);
break;
case "InterAppMessagePort:Register":
this._registerMessagePort(message, target);
break;
case "InterAppMessagePort:Unregister":
this._unregisterMessagePort(message);
break;
case "child-process-shutdown":
this._removeTarget(target);
break;
}
},
observe: function(aSubject, aTopic, aData) {
switch (aTopic) {
case "xpcom-shutdown":
Services.obs.removeObserver(this, "xpcom-shutdown");
Services.obs.removeObserver(this, "inter-app-comm-select-app-result");
kMessages.forEach(function(aMsg) {
ppmm.removeMessageListener(aMsg, this);
}, this);
ppmm = null;
break;
case "inter-app-comm-select-app-result":
if (DEBUG) debug("inter-app-comm-select-app-result: " + aData);
this._handleSelectcedApps(JSON.parse(aData));
break;
}
}
};
InterAppCommService.init();

View File

@ -27,6 +27,7 @@ EXTRA_JS_MODULES += [
'AppDownloadManager.jsm',
'AppsServiceChild.jsm',
'FreeSpaceWatcher.jsm',
'InterAppCommService.jsm',
'OfflineCacheInstaller.jsm',
'PermissionsInstaller.jsm',
'PermissionsTable.jsm',

View File

@ -0,0 +1,457 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
Cu.import("resource://gre/modules/InterAppCommService.jsm");
let UUIDGenerator = Cc["@mozilla.org/uuid-generator;1"]
.getService(Ci.nsIUUIDGenerator);
const MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
const FAKE_MESSAGE_PORT_ID = UUIDGenerator.generateUUID().toString();
const OUTER_WINDOW_ID = UUIDGenerator.generateUUID().toString();
const REQUEST_ID = UUIDGenerator.generateUUID().toString();
const PUB_APP_MANIFEST_URL = "app://pubApp.gaiamobile.org/manifest.webapp";
const SUB_APP_MANIFEST_URL = "app://subApp.gaiamobile.org/manifest.webapp";
const PUB_APP_PAGE_URL = "app://pubApp.gaiamobile.org/handler.html";
const SUB_APP_PAGE_URL = "app://subApp.gaiamobile.org/handler.html";
const KEYWORD = "test";
function create_message_port_pair(aMessagePortId,
aKeyword,
aPubManifestURL,
aSubManifestURL) {
InterAppCommService._messagePortPairs[aMessagePortId] = {
keyword: aKeyword,
publisher: {
manifestURL: aPubManifestURL
},
subscriber: {
manifestURL: aSubManifestURL
}
};
}
function clear_message_port_pairs() {
InterAppCommService._messagePortPairs = {};
}
function register_message_port(aMessagePortId,
aManifestURL,
aPageURL,
aTargetSendAsyncMessage) {
let message = {
name: "InterAppMessagePort:Register",
json: {
messagePortID: aMessagePortId,
manifestURL: aManifestURL,
pageURL: aPageURL
},
target: {
sendAsyncMessage: function(aName, aData) {
if (aTargetSendAsyncMessage) {
aTargetSendAsyncMessage(aName, aData);
}
},
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
return message.target;
}
function register_message_ports(aMessagePortId,
aPubTargetSendAsyncMessage,
aSubTargetSendAsyncMessage) {
let pubTarget = register_message_port(aMessagePortId,
PUB_APP_MANIFEST_URL,
PUB_APP_PAGE_URL,
aPubTargetSendAsyncMessage);
let subTarget = register_message_port(aMessagePortId,
SUB_APP_MANIFEST_URL,
SUB_APP_PAGE_URL,
aSubTargetSendAsyncMessage);
return { pubTarget: pubTarget, subTarget: subTarget };
}
function unregister_message_port(aMessagePortId,
aManifestURL) {
let message = {
name: "InterAppMessagePort:Unregister",
json: {
messagePortID: aMessagePortId,
manifestURL: aManifestURL
},
target: {
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
function remove_target(aTarget) {
let message = {
name: "child-process-shutdown",
target: aTarget
};
InterAppCommService.receiveMessage(message);
}
function post_message(aMessagePortId,
aManifestURL,
aMessage) {
let message = {
name: "InterAppMessagePort:PostMessage",
json: {
messagePortID: aMessagePortId,
manifestURL: aManifestURL,
message: aMessage
},
target: {
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
function create_allowed_connections(aKeyword,
aPubManifestURL,
aSubManifestURL) {
let allowedPubAppManifestURLs =
InterAppCommService._allowedConnections[aKeyword] = {};
allowedPubAppManifestURLs[aPubManifestURL] = [aSubManifestURL];
}
function clear_allowed_connections() {
InterAppCommService._allowedConnections = {};
}
function get_connections(aManifestURL,
aOuterWindowID,
aRequestID,
aTargetSendAsyncMessage) {
let message = {
name: "Webapps:GetConnections",
json: {
manifestURL: aManifestURL,
outerWindowID: aOuterWindowID,
requestID: aRequestID
},
target: {
sendAsyncMessage: function(aName, aData) {
if (aTargetSendAsyncMessage) {
aTargetSendAsyncMessage(aName, aData);
}
},
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
function cancel_connections(aManifestURL,
aKeyword,
aPubManifestURL,
aSubManifestURL) {
let message = {
name: "InterAppConnection:Cancel",
json: {
manifestURL: aManifestURL,
keyword: aKeyword,
pubAppManifestURL: aPubManifestURL,
subAppManifestURL: aSubManifestURL
},
target: {
assertContainApp: function(_manifestURL) {
return (aManifestURL == _manifestURL);
}
}
};
InterAppCommService.receiveMessage(message);
}
add_test(function test_registerMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let targets = register_message_ports(MESSAGE_PORT_ID);
let messagePortPair = InterAppCommService._messagePortPairs[MESSAGE_PORT_ID];
do_check_eq(PUB_APP_PAGE_URL, messagePortPair.publisher.pageURL);
do_check_eq(SUB_APP_PAGE_URL, messagePortPair.subscriber.pageURL);
do_check_true(targets.pubTarget === messagePortPair.publisher.target);
do_check_true(targets.subTarget === messagePortPair.subscriber.target);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_failToRegisterMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let targets = register_message_ports(FAKE_MESSAGE_PORT_ID);
let messagePortPair = InterAppCommService._messagePortPairs[MESSAGE_PORT_ID];
// Because it failed to register, the page URLs and targets don't exist.
do_check_true(messagePortPair.publisher.pageURL === undefined);
do_check_true(messagePortPair.subscriber.pageURL === undefined);
do_check_true(messagePortPair.publisher.target === undefined);
do_check_true(messagePortPair.subscriber.target === undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_unregisterMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_ports(MESSAGE_PORT_ID);
unregister_message_port(MESSAGE_PORT_ID, PUB_APP_MANIFEST_URL);
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
=== undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_failToUnregisterMessagePort() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_ports(MESSAGE_PORT_ID);
unregister_message_port(FAKE_MESSAGE_PORT_ID, PUB_APP_MANIFEST_URL);
// Because it failed to unregister, the entry still exists.
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
!== undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_removeTarget() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let targets = register_message_ports(MESSAGE_PORT_ID);
remove_target(targets.pubTarget);
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
=== undefined);
clear_message_port_pairs();
run_next_test();
});
add_test(function test_postMessage() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
let countPubAppOnMessage = 0;
function pubAppOnMessage(aName, aData) {
countPubAppOnMessage++;
do_check_eq(aName, "InterAppMessagePort:OnMessage");
do_check_eq(aData.manifestURL, PUB_APP_MANIFEST_URL);
do_check_eq(aData.pageURL, PUB_APP_PAGE_URL);
do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
if (countPubAppOnMessage == 1) {
do_check_eq(aData.message.text, "sub app says world");
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello again" });
} else if (countPubAppOnMessage == 2) {
do_check_eq(aData.message.text, "sub app says world again");
clear_message_port_pairs();
run_next_test();
} else {
do_throw("pub app receives an unexpected message")
}
};
let countSubAppOnMessage = 0;
function subAppOnMessage(aName, aData) {
countSubAppOnMessage++;
do_check_eq(aName, "InterAppMessagePort:OnMessage");
do_check_eq(aData.manifestURL, SUB_APP_MANIFEST_URL);
do_check_eq(aData.pageURL, SUB_APP_PAGE_URL);
do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
if (countSubAppOnMessage == 1) {
do_check_eq(aData.message.text, "pub app says hello");
post_message(MESSAGE_PORT_ID,
SUB_APP_MANIFEST_URL,
{ text: "sub app says world" });
} else if (countSubAppOnMessage == 2) {
do_check_eq(aData.message.text, "pub app says hello again");
post_message(MESSAGE_PORT_ID,
SUB_APP_MANIFEST_URL,
{ text: "sub app says world again" });
} else {
do_throw("sub app receives an unexpected message")
}
};
register_message_ports(MESSAGE_PORT_ID, pubAppOnMessage, subAppOnMessage);
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello" });
});
add_test(function test_registerMessagePort_with_queued_messages() {
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_port(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
PUB_APP_PAGE_URL);
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello" });
post_message(MESSAGE_PORT_ID,
PUB_APP_MANIFEST_URL,
{ text: "pub app says hello again" });
let countSubAppOnMessage = 0;
function subAppOnMessage(aName, aData) {
countSubAppOnMessage++;
do_check_eq(aName, "InterAppMessagePort:OnMessage");
do_check_eq(aData.manifestURL, SUB_APP_MANIFEST_URL);
do_check_eq(aData.pageURL, SUB_APP_PAGE_URL);
do_check_eq(aData.messagePortID, MESSAGE_PORT_ID);
if (countSubAppOnMessage == 1) {
do_check_eq(aData.message.text, "pub app says hello");
} else if (countSubAppOnMessage == 2) {
do_check_eq(aData.message.text, "pub app says hello again");
clear_message_port_pairs();
run_next_test();
} else {
do_throw("sub app receives an unexpected message")
}
};
register_message_port(MESSAGE_PORT_ID,
SUB_APP_MANIFEST_URL,
SUB_APP_PAGE_URL,
subAppOnMessage);
});
add_test(function test_getConnections() {
create_allowed_connections(KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
function onGetConnections(aName, aData) {
do_check_eq(aName, "Webapps:GetConnections:Return:OK");
do_check_eq(aData.oid, OUTER_WINDOW_ID);
do_check_eq(aData.requestID, REQUEST_ID);
let connections = aData.connections;
do_check_eq(connections.length, 1);
do_check_eq(connections[0].keyword, KEYWORD);
do_check_eq(connections[0].pubAppManifestURL, PUB_APP_MANIFEST_URL);
do_check_eq(connections[0].subAppManifestURL, SUB_APP_MANIFEST_URL);
clear_allowed_connections();
run_next_test();
};
get_connections(PUB_APP_MANIFEST_URL,
OUTER_WINDOW_ID,
REQUEST_ID,
onGetConnections);
});
add_test(function test_cancelConnection() {
create_allowed_connections(KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
create_message_port_pair(MESSAGE_PORT_ID,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
register_message_ports(MESSAGE_PORT_ID);
cancel_connections(PUB_APP_MANIFEST_URL,
KEYWORD,
PUB_APP_MANIFEST_URL,
SUB_APP_MANIFEST_URL);
do_check_true(InterAppCommService._allowedConnections[KEYWORD]
=== undefined);
do_check_true(InterAppCommService._messagePortPairs[MESSAGE_PORT_ID]
=== undefined);
clear_allowed_connections();
clear_message_port_pairs();
run_next_test();
});
function run_test() {
do_get_profile();
run_next_test();
}

View File

@ -2,4 +2,5 @@
head =
tail =
[test_inter_app_comm_service.js]
[test_manifestSanitizer.js]

View File

@ -64,7 +64,8 @@ class B2GDesktopReftest(RefTest):
log.info("%s | Running tests: start.", os.path.basename(__file__))
cmd, args = self.build_command_line(options.app,
ignore_window_size=options.ignoreWindowSize)
ignore_window_size=options.ignoreWindowSize,
browser_arg=options.browser_arg)
self.runner = FirefoxRunner(profile=self.profile,
binary=cmd,
cmdargs=args,
@ -123,10 +124,14 @@ class B2GDesktopReftest(RefTest):
profile.set_preferences(prefs)
return profile
def build_command_line(self, app, ignore_window_size=False):
def build_command_line(self, app, ignore_window_size=False,
browser_arg=None):
cmd = os.path.abspath(app)
args = ['-marionette']
if browser_arg:
args += [browser_arg]
if not ignore_window_size:
args.extend(['--screen', '800x1000'])
return cmd, args

View File

@ -31,6 +31,11 @@ class B2GOptions(ReftestOptions):
ReftestOptions.__init__(self, automation)
self.add_option("--browser-arg", action="store",
type = "string", dest = "browser_arg",
help = "Optional command-line arg to pass to the browser")
defaults["browser_arg"] = None
self.add_option("--b2gpath", action="store",
type = "string", dest = "b2gPath",
help = "path to B2G repo or qemu dir")

View File

@ -8,12 +8,13 @@ config = {
"--total-chunks=%(total_chunks)s", "--this-chunk=%(this_chunk)s",
"--profile=%(gaia_profile)s", "--app=%(application)s", "--desktop",
"--utility-path=%(utility_path)s", "--certificate-path=%(cert_path)s",
"--symbols-path=%(symbols_path)s",
"--symbols-path=%(symbols_path)s", "--browser-arg=%(browser_arg)s",
"--quiet"
],
"reftest_options": [
"--desktop", "--profile=%(gaia_profile)s", "--appname=%(application)s",
"--symbols-path=%(symbols_path)s", "%(test_manifest)s",
"--browser-arg=%(browser_arg)s", "--symbols-path=%(symbols_path)s",
"%(test_manifest)s"
]
}