gecko-dev/dom/apps/src/AppsUtils.jsm

454 lines
13 KiB
JavaScript
Raw Normal View History

/* 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 Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cr = Components.results;
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
Cu.import("resource://gre/modules/Services.jsm");
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
return Cc["@mozilla.org/network/util;1"]
.getService(Ci.nsINetUtil);
});
// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper"];
function debug(s) {
//dump("-*- AppsUtils.jsm: " + s + "\n");
}
this.AppsUtils = {
// Clones a app, without the manifest.
cloneAppObject: function cloneAppObject(aApp) {
return {
name: aApp.name,
2012-10-19 10:43:17 +00:00
csp: aApp.csp,
installOrigin: aApp.installOrigin,
origin: aApp.origin,
receipts: aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null,
installTime: aApp.installTime,
manifestURL: aApp.manifestURL,
appStatus: aApp.appStatus,
removable: aApp.removable,
localId: aApp.localId,
progress: aApp.progress || 0.0,
installState: aApp.installState || "installed",
downloadAvailable: aApp.downloadAvailable,
downloading: aApp.downloading,
readyToApplyDownload: aApp.readyToApplyDownload,
downloadSize: aApp.downloadSize || 0,
lastUpdateCheck: aApp.lastUpdateCheck,
updateTime: aApp.updateTime,
etag: aApp.etag,
packageEtag: aApp.packageEtag,
installerAppId: aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID,
installerIsBrowser: !!aApp.installerIsBrowser
};
},
cloneAsMozIApplication: function cloneAsMozIApplication(aApp) {
let res = this.cloneAppObject(aApp);
res.hasPermission = function(aPermission) {
let uri = Services.io.newURI(this.origin, null, null);
let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
.getService(Ci.nsIScriptSecurityManager);
// This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
// specific permission. It is not checking if browsers inside |aApp| have such
// permission.
let principal = secMan.getAppCodebasePrincipal(uri, aApp.localId,
/*mozbrowser*/false);
let perm = Services.perms.testExactPermissionFromPrincipal(principal,
aPermission);
return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
};
res.QueryInterface = XPCOMUtils.generateQI([Ci.mozIDOMApplication,
Ci.mozIApplication]);
return res;
},
getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
debug("getAppByManifestURL " + aManifestURL);
// This could be O(1) if |webapps| was a dictionary indexed on manifestURL
// which should be the unique app identifier.
// It's currently O(n).
for (let id in aApps) {
let app = aApps[id];
if (app.manifestURL == aManifestURL) {
return this.cloneAsMozIApplication(app);
}
}
return null;
},
getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) {
debug("getAppLocalIdByManifestURL " + aManifestURL);
for (let id in aApps) {
if (aApps[id].manifestURL == aManifestURL) {
return aApps[id].localId;
}
}
return Ci.nsIScriptSecurityManager.NO_APP_ID;
},
2012-10-19 10:43:17 +00:00
getCSPByLocalId: function getCSPByLocalId(aApps, aLocalId) {
debug("getCSPByLocalId " + aLocalId);
for (let id in aApps) {
let app = aApps[id];
if (app.localId == aLocalId) {
return ( app.csp || "" );
}
}
return "";
},
getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
debug("getAppByLocalId " + aLocalId);
for (let id in aApps) {
let app = aApps[id];
if (app.localId == aLocalId) {
return this.cloneAsMozIApplication(app);
}
}
return null;
},
getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) {
debug("getManifestURLByLocalId " + aLocalId);
for (let id in aApps) {
let app = aApps[id];
if (app.localId == aLocalId) {
return app.manifestURL;
}
}
return "";
},
getAppFromObserverMessage: function(aApps, aMessage) {
let data = JSON.parse(aMessage);
for (let id in aApps) {
let app = aApps[id];
if (app.origin != data.origin) {
continue;
}
return this.cloneAsMozIApplication(app);
}
return null;
},
/**
* from https://developer.mozilla.org/en/OpenWebApps/The_Manifest
* only the name property is mandatory
*/
checkManifest: function(aManifest) {
if (aManifest.name == undefined)
return false;
function isAbsolute(uri) {
2012-11-16 00:31:06 +00:00
// See bug 810551
let foo = Services.io.newURI("http://foo", null, null);
let bar = Services.io.newURI("http://bar", null, null);
return Services.io.newURI(uri, null, foo).prePath != foo.prePath ||
Services.io.newURI(uri, null, bar).prePath != bar.prePath;
}
// launch_path and entry_points launch paths can't be absolute
if (aManifest.launch_path && isAbsolute(aManifest.launch_path))
return false;
function checkAbsoluteEntryPoints(entryPoints) {
for (let name in entryPoints) {
if (entryPoints[name].launch_path && isAbsolute(entryPoints[name].launch_path)) {
return true;
}
}
return false;
}
if (checkAbsoluteEntryPoints(aManifest.entry_points))
return false;
for (let localeName in aManifest.locales) {
if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) {
return false;
}
}
return true;
},
checkManifestContentType: function
checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
let hadCharset = { };
let charset = { };
let contentType = NetUtil.parseContentType(aContentType, charset, hadCharset);
if (aInstallOrigin != aWebappOrigin &&
contentType != "application/x-web-app-manifest+json") {
return false;
}
return true;
},
/**
* Determines whether the manifest allows installs for the given origin.
* @param object aManifest
* @param string aInstallOrigin
* @return boolean
**/
checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) {
if (!aManifest.installs_allowed_from) {
return true;
}
function cbCheckAllowedOrigin(aOrigin) {
return aOrigin == "*" || aOrigin == aInstallOrigin;
}
return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin);
},
/**
* Determine the type of app (app, privileged, certified)
* that is installed by the manifest
* @param object aManifest
* @returns integer
**/
getAppManifestStatus: function getAppManifestStatus(aManifest) {
let type = aManifest.type || "web";
switch(type) {
case "web":
return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
case "privileged":
return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
case "certified":
return Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
default:
throw new Error("Webapps.jsm: Undetermined app manifest type");
}
},
/**
* Determines if an update or a factory reset occured.
*/
isFirstRun: function isFirstRun(aPrefBranch) {
let savedmstone = null;
try {
savedmstone = aPrefBranch.getCharPref("gecko.mstone");
} catch (e) {}
let mstone = Services.appinfo.platformVersion;
let savedBuildID = null;
try {
savedBuildID = aPrefBranch.getCharPref("gecko.buildID");
} catch (e) {}
let buildID = Services.appinfo.platformBuildID;
aPrefBranch.setCharPref("gecko.mstone", mstone);
aPrefBranch.setCharPref("gecko.buildID", buildID);
return ((mstone != savedmstone) || (buildID != savedBuildID));
},
/**
* Check if two manifests have the same set of properties and that the
* values of these properties are the same, in each locale.
* Manifests here are raw json ones.
*/
compareManifests: function compareManifests(aManifest1, aManifest2) {
// 1. check if we have the same locales in both manifests.
let locales1 = [];
let locales2 = [];
if (aManifest1.locales) {
for (let locale in aManifest1.locales) {
locales1.push(locale);
}
}
if (aManifest2.locales) {
for (let locale in aManifest2.locales) {
locales2.push(locale);
}
}
if (locales1.sort().join() !== locales2.sort().join()) {
return false;
}
// Helper function to check the app name and developer information for
// two given roots.
let checkNameAndDev = function(aRoot1, aRoot2) {
let name1 = aRoot1.name;
let name2 = aRoot2.name;
if (name1 !== name2) {
return false;
}
let dev1 = aRoot1.developer;
let dev2 = aRoot2.developer;
if ((dev1 && !dev2) || (dev2 && !dev1)) {
return false;
}
return (dev1.name === dev2.name && dev1.url === dev2.url);
}
// 2. For each locale, check if the name and dev info are the same.
if (!checkNameAndDev(aManifest1, aManifest2)) {
return false;
}
for (let locale in aManifest1.locales) {
if (!checkNameAndDev(aManifest1.locales[locale],
aManifest2.locales[locale])) {
return false;
}
}
// Nothing failed.
return true;
}
}
/**
* Helper object to access manifest information with locale support
*/
this.ManifestHelper = function(aManifest, aOrigin) {
this._origin = Services.io.newURI(aOrigin, null, null);
this._manifest = aManifest;
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
.QueryInterface(Ci.nsIToolkitChromeRegistry);
let locale = chrome.getSelectedLocale("global").toLowerCase();
this._localeRoot = this._manifest;
if (this._manifest.locales && this._manifest.locales[locale]) {
this._localeRoot = this._manifest.locales[locale];
}
else if (this._manifest.locales) {
// try with the language part of the locale ("en" for en-GB) only
let lang = locale.split('-')[0];
if (lang != locale && this._manifest.locales[lang])
this._localeRoot = this._manifest.locales[lang];
}
};
ManifestHelper.prototype = {
_localeProp: function(aProp) {
if (this._localeRoot[aProp] != undefined)
return this._localeRoot[aProp];
return this._manifest[aProp];
},
get name() {
return this._localeProp("name");
},
get description() {
return this._localeProp("description");
},
get version() {
return this._localeProp("version");
},
get launch_path() {
return this._localeProp("launch_path");
},
get developer() {
return this._localeProp("developer");
},
get icons() {
return this._localeProp("icons");
},
get appcache_path() {
return this._localeProp("appcache_path");
},
get orientation() {
return this._localeProp("orientation");
},
get package_path() {
return this._localeProp("package_path");
},
get size() {
return this._manifest["size"] || 0;
},
get permissions() {
if (this._manifest.permissions) {
return this._manifest.permissions;
}
return {};
},
iconURLForSize: function(aSize) {
let icons = this._localeProp("icons");
if (!icons)
return null;
let dist = 100000;
let icon = null;
for (let size in icons) {
let iSize = parseInt(size);
if (Math.abs(iSize - aSize) < dist) {
icon = this._origin.resolve(icons[size]);
dist = Math.abs(iSize - aSize);
}
}
return icon;
},
fullLaunchPath: function(aStartPoint) {
// If no start point is specified, we use the root launch path.
// In all error cases, we just return null.
if ((aStartPoint || "") === "") {
return this._origin.resolve(this._localeProp("launch_path") || "");
}
// Search for the l10n entry_points property.
let entryPoints = this._localeProp("entry_points");
if (!entryPoints) {
return null;
}
if (entryPoints[aStartPoint]) {
return this._origin.resolve(entryPoints[aStartPoint].launch_path || "");
}
return null;
},
resolveFromOrigin: function(aURI) {
return this._origin.resolve(aURI);
},
fullAppcachePath: function() {
let appcachePath = this._localeProp("appcache_path");
return this._origin.resolve(appcachePath ? appcachePath : "");
},
fullPackagePath: function() {
let packagePath = this._localeProp("package_path");
return this._origin.resolve(packagePath ? packagePath : "");
}
}