michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: "use strict"; michael@0: michael@0: const Cu = Components.utils; michael@0: const Cc = Components.classes; michael@0: const Ci = Components.interfaces; michael@0: const Cr = Components.results; michael@0: michael@0: Cu.import("resource://gre/modules/osfile.jsm"); michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: Cu.import("resource://gre/modules/Task.jsm"); michael@0: Cu.import("resource://gre/modules/XPCOMUtils.jsm"); michael@0: Cu.import("resource://gre/modules/Promise.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", michael@0: "resource://gre/modules/FileUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils", michael@0: "resource://gre/modules/WebappOSUtils.jsm"); michael@0: michael@0: XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", michael@0: "resource://gre/modules/NetUtil.jsm"); michael@0: michael@0: // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js michael@0: michael@0: this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"]; michael@0: michael@0: function debug(s) { michael@0: //dump("-*- AppsUtils.jsm: " + s + "\n"); michael@0: } michael@0: michael@0: this.isAbsoluteURI = function(aURI) { michael@0: let foo = Services.io.newURI("http://foo", null, null); michael@0: let bar = Services.io.newURI("http://bar", null, null); michael@0: return Services.io.newURI(aURI, null, foo).prePath != foo.prePath || michael@0: Services.io.newURI(aURI, null, bar).prePath != bar.prePath; michael@0: } michael@0: michael@0: this.mozIApplication = function(aApp) { michael@0: _setAppProperties(this, aApp); michael@0: } michael@0: michael@0: mozIApplication.prototype = { michael@0: hasPermission: function(aPermission) { michael@0: let uri = Services.io.newURI(this.origin, null, null); michael@0: let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] michael@0: .getService(Ci.nsIScriptSecurityManager); michael@0: // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a michael@0: // specific permission. It is not checking if browsers inside |aApp| have such michael@0: // permission. michael@0: let principal = secMan.getAppCodebasePrincipal(uri, this.localId, michael@0: /*mozbrowser*/false); michael@0: let perm = Services.perms.testExactPermissionFromPrincipal(principal, michael@0: aPermission); michael@0: return (perm === Ci.nsIPermissionManager.ALLOW_ACTION); michael@0: }, michael@0: michael@0: QueryInterface: function(aIID) { michael@0: if (aIID.equals(Ci.mozIApplication) || michael@0: aIID.equals(Ci.nsISupports)) michael@0: return this; michael@0: throw Cr.NS_ERROR_NO_INTERFACE; michael@0: } michael@0: } michael@0: michael@0: function _setAppProperties(aObj, aApp) { michael@0: aObj.name = aApp.name; michael@0: aObj.csp = aApp.csp; michael@0: aObj.installOrigin = aApp.installOrigin; michael@0: aObj.origin = aApp.origin; michael@0: #ifdef MOZ_ANDROID_SYNTHAPKS michael@0: aObj.apkPackageName = aApp.apkPackageName; michael@0: #endif michael@0: aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null; michael@0: aObj.installTime = aApp.installTime; michael@0: aObj.manifestURL = aApp.manifestURL; michael@0: aObj.appStatus = aApp.appStatus; michael@0: aObj.removable = aApp.removable; michael@0: aObj.id = aApp.id; michael@0: aObj.localId = aApp.localId; michael@0: aObj.basePath = aApp.basePath; michael@0: aObj.progress = aApp.progress || 0.0; michael@0: aObj.installState = aApp.installState || "installed"; michael@0: aObj.downloadAvailable = aApp.downloadAvailable; michael@0: aObj.downloading = aApp.downloading; michael@0: aObj.readyToApplyDownload = aApp.readyToApplyDownload; michael@0: aObj.downloadSize = aApp.downloadSize || 0; michael@0: aObj.lastUpdateCheck = aApp.lastUpdateCheck; michael@0: aObj.updateTime = aApp.updateTime; michael@0: aObj.etag = aApp.etag; michael@0: aObj.packageEtag = aApp.packageEtag; michael@0: aObj.manifestHash = aApp.manifestHash; michael@0: aObj.packageHash = aApp.packageHash; michael@0: aObj.staged = aApp.staged; michael@0: aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID; michael@0: aObj.installerIsBrowser = !!aApp.installerIsBrowser; michael@0: aObj.storeId = aApp.storeId || ""; michael@0: aObj.storeVersion = aApp.storeVersion || 0; michael@0: aObj.role = aApp.role || ""; michael@0: aObj.redirects = aApp.redirects; michael@0: } michael@0: michael@0: this.AppsUtils = { michael@0: // Clones a app, without the manifest. michael@0: cloneAppObject: function(aApp) { michael@0: let obj = {}; michael@0: _setAppProperties(obj, aApp); michael@0: return obj; michael@0: }, michael@0: michael@0: getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) { michael@0: debug("getAppByManifestURL " + aManifestURL); michael@0: // This could be O(1) if |webapps| was a dictionary indexed on manifestURL michael@0: // which should be the unique app identifier. michael@0: // It's currently O(n). michael@0: for (let id in aApps) { michael@0: let app = aApps[id]; michael@0: if (app.manifestURL == aManifestURL) { michael@0: return new mozIApplication(app); michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) { michael@0: debug("getAppLocalIdByManifestURL " + aManifestURL); michael@0: for (let id in aApps) { michael@0: if (aApps[id].manifestURL == aManifestURL) { michael@0: return aApps[id].localId; michael@0: } michael@0: } michael@0: michael@0: return Ci.nsIScriptSecurityManager.NO_APP_ID; michael@0: }, michael@0: michael@0: getAppLocalIdByStoreId: function(aApps, aStoreId) { michael@0: debug("getAppLocalIdByStoreId:" + aStoreId); michael@0: for (let id in aApps) { michael@0: if (aApps[id].storeId == aStoreId) { michael@0: return aApps[id].localId; michael@0: } michael@0: } michael@0: michael@0: return Ci.nsIScriptSecurityManager.NO_APP_ID; michael@0: }, michael@0: michael@0: getCSPByLocalId: function getCSPByLocalId(aApps, aLocalId) { michael@0: debug("getCSPByLocalId " + aLocalId); michael@0: for (let id in aApps) { michael@0: let app = aApps[id]; michael@0: if (app.localId == aLocalId) { michael@0: return ( app.csp || "" ); michael@0: } michael@0: } michael@0: michael@0: return ""; michael@0: }, michael@0: michael@0: getAppByLocalId: function getAppByLocalId(aApps, aLocalId) { michael@0: debug("getAppByLocalId " + aLocalId); michael@0: for (let id in aApps) { michael@0: let app = aApps[id]; michael@0: if (app.localId == aLocalId) { michael@0: return new mozIApplication(app); michael@0: } michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) { michael@0: debug("getManifestURLByLocalId " + aLocalId); michael@0: for (let id in aApps) { michael@0: let app = aApps[id]; michael@0: if (app.localId == aLocalId) { michael@0: return app.manifestURL; michael@0: } michael@0: } michael@0: michael@0: return ""; michael@0: }, michael@0: michael@0: getCoreAppsBasePath: function getCoreAppsBasePath() { michael@0: debug("getCoreAppsBasePath()"); michael@0: try { michael@0: return FileUtils.getDir("coreAppsDir", ["webapps"], false).path; michael@0: } catch(e) { michael@0: return null; michael@0: } michael@0: }, michael@0: michael@0: getAppInfo: function getAppInfo(aApps, aAppId) { michael@0: let app = aApps[aAppId]; michael@0: michael@0: if (!app) { michael@0: debug("No webapp for " + aAppId); michael@0: return null; michael@0: } michael@0: michael@0: // We can have 3rd party apps that are non-removable, michael@0: // so we can't use the 'removable' property for isCoreApp michael@0: // Instead, we check if the app is installed under /system/b2g michael@0: let isCoreApp = false; michael@0: michael@0: #ifdef MOZ_WIDGET_GONK michael@0: isCoreApp = app.basePath == this.getCoreAppsBasePath(); michael@0: #endif michael@0: debug(app.basePath + " isCoreApp: " + isCoreApp); michael@0: return { "path": WebappOSUtils.getPackagePath(app), michael@0: "isCoreApp": isCoreApp }; michael@0: }, michael@0: michael@0: /** michael@0: * Remove potential HTML tags from displayable fields in the manifest. michael@0: * We check name, description, developer name, and permission description michael@0: */ michael@0: sanitizeManifest: function(aManifest) { michael@0: let sanitizer = Cc["@mozilla.org/parserutils;1"] michael@0: .getService(Ci.nsIParserUtils); michael@0: if (!sanitizer) { michael@0: return; michael@0: } michael@0: michael@0: function sanitize(aStr) { michael@0: return sanitizer.convertToPlainText(aStr, michael@0: Ci.nsIDocumentEncoder.OutputRaw, 0); michael@0: } michael@0: michael@0: function sanitizeEntryPoint(aRoot) { michael@0: aRoot.name = sanitize(aRoot.name); michael@0: michael@0: if (aRoot.description) { michael@0: aRoot.description = sanitize(aRoot.description); michael@0: } michael@0: michael@0: if (aRoot.developer && aRoot.developer.name) { michael@0: aRoot.developer.name = sanitize(aRoot.developer.name); michael@0: } michael@0: michael@0: if (aRoot.permissions) { michael@0: for (let permission in aRoot.permissions) { michael@0: if (aRoot.permissions[permission].description) { michael@0: aRoot.permissions[permission].description = michael@0: sanitize(aRoot.permissions[permission].description); michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // First process the main section, then the entry points. michael@0: sanitizeEntryPoint(aManifest); michael@0: michael@0: if (aManifest.entry_points) { michael@0: for (let entry in aManifest.entry_points) { michael@0: sanitizeEntryPoint(aManifest.entry_points[entry]); michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest michael@0: * Only the name property is mandatory. michael@0: */ michael@0: checkManifest: function(aManifest, app) { michael@0: if (aManifest.name == undefined) michael@0: return false; michael@0: michael@0: this.sanitizeManifest(aManifest); michael@0: michael@0: // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute michael@0: if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path)) michael@0: return false; michael@0: michael@0: function checkAbsoluteEntryPoints(entryPoints) { michael@0: for (let name in entryPoints) { michael@0: if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) { michael@0: return true; michael@0: } michael@0: } michael@0: return false; michael@0: } michael@0: michael@0: if (checkAbsoluteEntryPoints(aManifest.entry_points)) michael@0: return false; michael@0: michael@0: for (let localeName in aManifest.locales) { michael@0: if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: if (aManifest.activities) { michael@0: for (let activityName in aManifest.activities) { michael@0: let activity = aManifest.activities[activityName]; michael@0: if (activity.href && isAbsoluteURI(activity.href)) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: michael@0: // |messages| is an array of items, where each item is either a string or michael@0: // a {name: href} object. michael@0: let messages = aManifest.messages; michael@0: if (messages) { michael@0: if (!Array.isArray(messages)) { michael@0: return false; michael@0: } michael@0: for (let item of aManifest.messages) { michael@0: if (typeof item == "object") { michael@0: let keys = Object.keys(item); michael@0: if (keys.length != 1) { michael@0: return false; michael@0: } michael@0: if (isAbsoluteURI(item[keys[0]])) { michael@0: return false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: // The 'size' field must be a positive integer. michael@0: if (aManifest.size) { michael@0: aManifest.size = parseInt(aManifest.size); michael@0: if (Number.isNaN(aManifest.size) || aManifest.size < 0) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // The 'role' field must be a string. michael@0: if (aManifest.role && (typeof aManifest.role !== "string")) { michael@0: return false; michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: checkManifestContentType: function michael@0: checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) { michael@0: let hadCharset = { }; michael@0: let charset = { }; michael@0: let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil); michael@0: let contentType = netutil.parseContentType(aContentType, charset, hadCharset); michael@0: if (aInstallOrigin != aWebappOrigin && michael@0: contentType != "application/x-web-app-manifest+json") { michael@0: return false; michael@0: } michael@0: return true; michael@0: }, michael@0: michael@0: /** michael@0: * Method to apply modifications to webapp manifests file saved internally. michael@0: * For now, only ensure app can't rename itself. michael@0: */ michael@0: ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) { michael@0: // Ensure that app name can't be updated michael@0: aNewManifest.name = aApp.name; michael@0: michael@0: // Nor through localized names michael@0: if ('locales' in aNewManifest) { michael@0: let defaultName = new ManifestHelper(aOldManifest, aApp.origin).name; michael@0: for (let locale in aNewManifest.locales) { michael@0: let entry = aNewManifest.locales[locale]; michael@0: if (!entry.name) { michael@0: continue; michael@0: } michael@0: // In case previous manifest didn't had a name, michael@0: // we use the default app name michael@0: let localizedName = defaultName; michael@0: if (aOldManifest && 'locales' in aOldManifest && michael@0: locale in aOldManifest.locales) { michael@0: localizedName = aOldManifest.locales[locale].name; michael@0: } michael@0: entry.name = localizedName; michael@0: } michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Determines whether the manifest allows installs for the given origin. michael@0: * @param object aManifest michael@0: * @param string aInstallOrigin michael@0: * @return boolean michael@0: **/ michael@0: checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) { michael@0: if (!aManifest.installs_allowed_from) { michael@0: return true; michael@0: } michael@0: michael@0: function cbCheckAllowedOrigin(aOrigin) { michael@0: return aOrigin == "*" || aOrigin == aInstallOrigin; michael@0: } michael@0: michael@0: return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin); michael@0: }, michael@0: michael@0: /** michael@0: * Determine the type of app (app, privileged, certified) michael@0: * that is installed by the manifest michael@0: * @param object aManifest michael@0: * @returns integer michael@0: **/ michael@0: getAppManifestStatus: function getAppManifestStatus(aManifest) { michael@0: let type = aManifest.type || "web"; michael@0: michael@0: switch(type) { michael@0: case "web": michael@0: return Ci.nsIPrincipal.APP_STATUS_INSTALLED; michael@0: case "privileged": michael@0: return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED; michael@0: case "certified": michael@0: return Ci.nsIPrincipal.APP_STATUS_CERTIFIED; michael@0: default: michael@0: throw new Error("Webapps.jsm: Undetermined app manifest type"); michael@0: } michael@0: }, michael@0: michael@0: /** michael@0: * Determines if an update or a factory reset occured. michael@0: */ michael@0: isFirstRun: function isFirstRun(aPrefBranch) { michael@0: let savedmstone = null; michael@0: try { michael@0: savedmstone = aPrefBranch.getCharPref("gecko.mstone"); michael@0: } catch (e) {} michael@0: michael@0: let mstone = Services.appinfo.platformVersion; michael@0: michael@0: let savedBuildID = null; michael@0: try { michael@0: savedBuildID = aPrefBranch.getCharPref("gecko.buildID"); michael@0: } catch (e) {} michael@0: michael@0: let buildID = Services.appinfo.platformBuildID; michael@0: michael@0: aPrefBranch.setCharPref("gecko.mstone", mstone); michael@0: aPrefBranch.setCharPref("gecko.buildID", buildID); michael@0: michael@0: return ((mstone != savedmstone) || (buildID != savedBuildID)); michael@0: }, michael@0: michael@0: /** michael@0: * Check if two manifests have the same set of properties and that the michael@0: * values of these properties are the same, in each locale. michael@0: * Manifests here are raw json ones. michael@0: */ michael@0: compareManifests: function compareManifests(aManifest1, aManifest2) { michael@0: // 1. check if we have the same locales in both manifests. michael@0: let locales1 = []; michael@0: let locales2 = []; michael@0: if (aManifest1.locales) { michael@0: for (let locale in aManifest1.locales) { michael@0: locales1.push(locale); michael@0: } michael@0: } michael@0: if (aManifest2.locales) { michael@0: for (let locale in aManifest2.locales) { michael@0: locales2.push(locale); michael@0: } michael@0: } michael@0: if (locales1.sort().join() !== locales2.sort().join()) { michael@0: return false; michael@0: } michael@0: michael@0: // Helper function to check the app name and developer information for michael@0: // two given roots. michael@0: let checkNameAndDev = function(aRoot1, aRoot2) { michael@0: let name1 = aRoot1.name; michael@0: let name2 = aRoot2.name; michael@0: if (name1 !== name2) { michael@0: return false; michael@0: } michael@0: michael@0: let dev1 = aRoot1.developer; michael@0: let dev2 = aRoot2.developer; michael@0: if ((dev1 && !dev2) || (dev2 && !dev1)) { michael@0: return false; michael@0: } michael@0: michael@0: return (!dev1 && !dev2) || michael@0: (dev1.name === dev2.name && dev1.url === dev2.url); michael@0: } michael@0: michael@0: // 2. For each locale, check if the name and dev info are the same. michael@0: if (!checkNameAndDev(aManifest1, aManifest2)) { michael@0: return false; michael@0: } michael@0: michael@0: for (let locale in aManifest1.locales) { michael@0: if (!checkNameAndDev(aManifest1.locales[locale], michael@0: aManifest2.locales[locale])) { michael@0: return false; michael@0: } michael@0: } michael@0: michael@0: // Nothing failed. michael@0: return true; michael@0: }, michael@0: michael@0: // Asynchronously loads a JSON file. aPath is a string representing the path michael@0: // of the file to be read. michael@0: loadJSONAsync: function(aPath) { michael@0: let deferred = Promise.defer(); michael@0: michael@0: try { michael@0: let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); michael@0: file.initWithPath(aPath); michael@0: michael@0: let channel = NetUtil.newChannel(file); michael@0: channel.contentType = "application/json"; michael@0: michael@0: NetUtil.asyncFetch(channel, function(aStream, aResult) { michael@0: if (!Components.isSuccessCode(aResult)) { michael@0: deferred.resolve(null); michael@0: michael@0: if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) { michael@0: // We expect this under certain circumstances, like for webapps.json michael@0: // on firstrun, so we return early without reporting an error. michael@0: return; michael@0: } michael@0: michael@0: Cu.reportError("AppsUtils: Could not read from json file " + aPath); michael@0: return; michael@0: } michael@0: michael@0: try { michael@0: // Obtain a converter to read from a UTF-8 encoded input stream. michael@0: let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: converter.charset = "UTF-8"; michael@0: michael@0: // Read json file into a string michael@0: let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream, michael@0: aStream.available()) || "")); michael@0: aStream.close(); michael@0: michael@0: deferred.resolve(data); michael@0: } catch (ex) { michael@0: Cu.reportError("AppsUtils: Could not parse JSON: " + michael@0: aPath + " " + ex + "\n" + ex.stack); michael@0: deferred.resolve(null); michael@0: } michael@0: }); michael@0: } catch (ex) { michael@0: Cu.reportError("AppsUtils: Could not read from " + michael@0: aPath + " : " + ex + "\n" + ex.stack); michael@0: deferred.resolve(null); michael@0: } michael@0: michael@0: return deferred.promise; michael@0: }, michael@0: michael@0: // Returns the MD5 hash of a string. michael@0: computeHash: function(aString) { michael@0: let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] michael@0: .createInstance(Ci.nsIScriptableUnicodeConverter); michael@0: converter.charset = "UTF-8"; michael@0: let result = {}; michael@0: // Data is an array of bytes. michael@0: let data = converter.convertToByteArray(aString, result); michael@0: michael@0: let hasher = Cc["@mozilla.org/security/hash;1"] michael@0: .createInstance(Ci.nsICryptoHash); michael@0: hasher.init(hasher.MD5); michael@0: hasher.update(data, data.length); michael@0: // We're passing false to get the binary hash and not base64. michael@0: let hash = hasher.finish(false); michael@0: michael@0: function toHexString(charCode) { michael@0: return ("0" + charCode.toString(16)).slice(-2); michael@0: } michael@0: michael@0: // Convert the binary hash data to a hex string. michael@0: return [toHexString(hash.charCodeAt(i)) for (i in hash)].join(""); michael@0: } michael@0: } michael@0: michael@0: /** michael@0: * Helper object to access manifest information with locale support michael@0: */ michael@0: this.ManifestHelper = function(aManifest, aOrigin) { michael@0: this._origin = Services.io.newURI(aOrigin, null, null); michael@0: this._manifest = aManifest; michael@0: let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry) michael@0: .QueryInterface(Ci.nsIToolkitChromeRegistry); michael@0: let locale = chrome.getSelectedLocale("global").toLowerCase(); michael@0: this._localeRoot = this._manifest; michael@0: michael@0: if (this._manifest.locales && this._manifest.locales[locale]) { michael@0: this._localeRoot = this._manifest.locales[locale]; michael@0: } michael@0: else if (this._manifest.locales) { michael@0: // try with the language part of the locale ("en" for en-GB) only michael@0: let lang = locale.split('-')[0]; michael@0: if (lang != locale && this._manifest.locales[lang]) michael@0: this._localeRoot = this._manifest.locales[lang]; michael@0: } michael@0: }; michael@0: michael@0: ManifestHelper.prototype = { michael@0: _localeProp: function(aProp) { michael@0: if (this._localeRoot[aProp] != undefined) michael@0: return this._localeRoot[aProp]; michael@0: return this._manifest[aProp]; michael@0: }, michael@0: michael@0: get name() { michael@0: return this._localeProp("name"); michael@0: }, michael@0: michael@0: get description() { michael@0: return this._localeProp("description"); michael@0: }, michael@0: michael@0: get type() { michael@0: return this._localeProp("type"); michael@0: }, michael@0: michael@0: get version() { michael@0: return this._localeProp("version"); michael@0: }, michael@0: michael@0: get launch_path() { michael@0: return this._localeProp("launch_path"); michael@0: }, michael@0: michael@0: get developer() { michael@0: // Default to {} in order to avoid exception in code michael@0: // that doesn't check for null `developer` michael@0: return this._localeProp("developer") || {}; michael@0: }, michael@0: michael@0: get icons() { michael@0: return this._localeProp("icons"); michael@0: }, michael@0: michael@0: get appcache_path() { michael@0: return this._localeProp("appcache_path"); michael@0: }, michael@0: michael@0: get orientation() { michael@0: return this._localeProp("orientation"); michael@0: }, michael@0: michael@0: get package_path() { michael@0: return this._localeProp("package_path"); michael@0: }, michael@0: michael@0: get size() { michael@0: return this._manifest["size"] || 0; michael@0: }, michael@0: michael@0: get permissions() { michael@0: if (this._manifest.permissions) { michael@0: return this._manifest.permissions; michael@0: } michael@0: return {}; michael@0: }, michael@0: michael@0: get biggestIconURL() { michael@0: let icons = this._localeProp("icons"); michael@0: if (!icons) { michael@0: return null; michael@0: } michael@0: michael@0: let iconSizes = Object.keys(icons); michael@0: if (iconSizes.length == 0) { michael@0: return null; michael@0: } michael@0: michael@0: iconSizes.sort((a, b) => a - b); michael@0: let biggestIconSize = iconSizes.pop(); michael@0: let biggestIcon = icons[biggestIconSize]; michael@0: let biggestIconURL = this._origin.resolve(biggestIcon); michael@0: michael@0: return biggestIconURL; michael@0: }, michael@0: michael@0: iconURLForSize: function(aSize) { michael@0: let icons = this._localeProp("icons"); michael@0: if (!icons) michael@0: return null; michael@0: let dist = 100000; michael@0: let icon = null; michael@0: for (let size in icons) { michael@0: let iSize = parseInt(size); michael@0: if (Math.abs(iSize - aSize) < dist) { michael@0: icon = this._origin.resolve(icons[size]); michael@0: dist = Math.abs(iSize - aSize); michael@0: } michael@0: } michael@0: return icon; michael@0: }, michael@0: michael@0: fullLaunchPath: function(aStartPoint) { michael@0: // If no start point is specified, we use the root launch path. michael@0: // In all error cases, we just return null. michael@0: if ((aStartPoint || "") === "") { michael@0: return this._origin.resolve(this._localeProp("launch_path") || ""); michael@0: } michael@0: michael@0: // Search for the l10n entry_points property. michael@0: let entryPoints = this._localeProp("entry_points"); michael@0: if (!entryPoints) { michael@0: return null; michael@0: } michael@0: michael@0: if (entryPoints[aStartPoint]) { michael@0: return this._origin.resolve(entryPoints[aStartPoint].launch_path || ""); michael@0: } michael@0: michael@0: return null; michael@0: }, michael@0: michael@0: resolveFromOrigin: function(aURI) { michael@0: // This should be enforced higher up, but check it here just in case. michael@0: if (isAbsoluteURI(aURI)) { michael@0: throw new Error("Webapps.jsm: non-relative URI passed to resolveFromOrigin"); michael@0: } michael@0: return this._origin.resolve(aURI); michael@0: }, michael@0: michael@0: fullAppcachePath: function() { michael@0: let appcachePath = this._localeProp("appcache_path"); michael@0: return this._origin.resolve(appcachePath ? appcachePath : ""); michael@0: }, michael@0: michael@0: fullPackagePath: function() { michael@0: let packagePath = this._localeProp("package_path"); michael@0: return this._origin.resolve(packagePath ? packagePath : ""); michael@0: }, michael@0: michael@0: get role() { michael@0: return this._manifest.role || ""; michael@0: } michael@0: }