123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899 |
- /* 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/Services.jsm");
- Cu.import("resource://gre/modules/XPCOMUtils.jsm");
- Cu.import("resource://gre/modules/Promise.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
- "resource://gre/modules/FileUtils.jsm");
- XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
- "resource://gre/modules/NetUtil.jsm");
- XPCOMUtils.defineLazyServiceGetter(this, "appsService",
- "@mozilla.org/AppsService;1",
- "nsIAppsService");
- // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
- this.EXPORTED_SYMBOLS =
- ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
- function debug(s) {
- //dump("-*- AppsUtils.jsm: " + s + "\n");
- }
- this.isAbsoluteURI = function(aURI) {
- let foo = Services.io.newURI("http://foo", null, null);
- let bar = Services.io.newURI("http://bar", null, null);
- return Services.io.newURI(aURI, null, foo).prePath != foo.prePath ||
- Services.io.newURI(aURI, null, bar).prePath != bar.prePath;
- }
- this.mozIApplication = function(aApp) {
- _setAppProperties(this, aApp);
- }
- mozIApplication.prototype = {
- hasPermission: function(aPermission) {
- // 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 perm = Services.perms.testExactPermissionFromPrincipal(this.principal,
- aPermission);
- return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
- },
- get principal() {
- if (this._principal) {
- return this._principal;
- }
- this._principal = null;
- try {
- this._principal = Services.scriptSecurityManager.createCodebasePrincipal(
- Services.io.newURI(this.origin, null, null),
- {appId: this.localId});
- } catch(e) {
- dump("Could not create app principal " + e + "\n");
- }
- return this._principal;
- },
- QueryInterface: function(aIID) {
- if (aIID.equals(Ci.mozIApplication) ||
- aIID.equals(Ci.nsISupports))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
- function _setAppProperties(aObj, aApp) {
- aObj.name = aApp.name;
- aObj.csp = aApp.csp;
- aObj.installOrigin = aApp.installOrigin;
- aObj.origin = aApp.origin;
- aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null;
- aObj.installTime = aApp.installTime;
- aObj.manifestURL = aApp.manifestURL;
- aObj.appStatus = aApp.appStatus;
- aObj.removable = aApp.removable;
- aObj.id = aApp.id;
- aObj.localId = aApp.localId;
- aObj.basePath = aApp.basePath;
- aObj.progress = aApp.progress || 0.0;
- aObj.installState = aApp.installState || "installed";
- aObj.downloadAvailable = aApp.downloadAvailable;
- aObj.downloading = aApp.downloading;
- aObj.readyToApplyDownload = aApp.readyToApplyDownload;
- aObj.downloadSize = aApp.downloadSize || 0;
- aObj.lastUpdateCheck = aApp.lastUpdateCheck;
- aObj.updateTime = aApp.updateTime;
- aObj.etag = aApp.etag;
- aObj.packageEtag = aApp.packageEtag;
- aObj.manifestHash = aApp.manifestHash;
- aObj.packageHash = aApp.packageHash;
- aObj.staged = aApp.staged;
- aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID;
- aObj.installerIsBrowser = !!aApp.installerIsBrowser;
- aObj.storeId = aApp.storeId || "";
- aObj.storeVersion = aApp.storeVersion || 0;
- aObj.role = aApp.role || "";
- aObj.kind = aApp.kind;
- aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
- aObj.sideloaded = aApp.sideloaded;
- aObj.extensionVersion = aApp.extensionVersion;
- aObj.blockedStatus =
- aApp.blockedStatus !== undefined ? aApp.blockedStatus
- : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
- aObj.blocklistId = aApp.blocklistId;
- }
- this.AppsUtils = {
- // Clones a app, without the manifest.
- cloneAppObject: function(aApp) {
- let obj = {};
- _setAppProperties(obj, aApp);
- return obj;
- },
- // Creates a nsILoadContext object with a given appId and inIsolatedMozBrowser
- // flag.
- createLoadContext: function createLoadContext(aAppId, aInIsolatedMozBrowser) {
- return {
- associatedWindow: null,
- topWindow : null,
- appId: aAppId,
- isInIsolatedMozBrowserElement: aInIsolatedMozBrowser,
- originAttributes: {
- appId: aAppId,
- inIsolatedMozBrowser: aInIsolatedMozBrowser
- },
- usePrivateBrowsing: false,
- isContent: false,
- QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext,
- Ci.nsIInterfaceRequestor,
- Ci.nsISupports]),
- getInterface: function(iid) {
- if (iid.equals(Ci.nsILoadContext))
- return this;
- throw Cr.NS_ERROR_NO_INTERFACE;
- }
- }
- },
- // Sends data downloaded from aRequestChannel to a file
- // identified by aId and aFileName.
- getFile: function(aRequestChannel, aId, aFileName) {
- let deferred = Promise.defer();
- // Staging the file in TmpD until all the checks are done.
- let file = FileUtils.getFile("TmpD", ["webapps", aId, aFileName], true);
- // We need an output stream to write the channel content to the out file.
- let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
- .createInstance(Ci.nsIFileOutputStream);
- // write, create, truncate
- outputStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
- let bufferedOutputStream =
- Cc['@mozilla.org/network/buffered-output-stream;1']
- .createInstance(Ci.nsIBufferedOutputStream);
- bufferedOutputStream.init(outputStream, 1024);
- // Create a listener that will give data to the file output stream.
- let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
- .createInstance(Ci.nsISimpleStreamListener);
- listener.init(bufferedOutputStream, {
- onStartRequest: function(aRequest, aContext) {
- // Nothing to do there anymore.
- },
- onStopRequest: function(aRequest, aContext, aStatusCode) {
- bufferedOutputStream.close();
- outputStream.close();
- if (!Components.isSuccessCode(aStatusCode)) {
- deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: true});
- return;
- }
- // If we get a 4XX or a 5XX http status, bail out like if we had a
- // network error.
- let responseStatus = aRequestChannel.responseStatus;
- if (responseStatus >= 400 && responseStatus <= 599) {
- // unrecoverable error, don't bug the user
- deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: false});
- return;
- }
- deferred.resolve(file);
- }
- });
- aRequestChannel.asyncOpen2(listener);
- return deferred.promise;
- },
- // Eliminate query and hash string.
- getFilePath: function(aPagePath) {
- let urlParser = Cc["@mozilla.org/network/url-parser;1?auth=no"]
- .getService(Ci.nsIURLParser);
- let uriData = [aPagePath, aPagePath.length, {}, {}, {}, {}, {}, {}];
- urlParser.parsePath.apply(urlParser, uriData);
- let [{value: pathPos}, {value: pathLen}] = uriData.slice(2, 4);
- return aPagePath.substr(pathPos, pathLen);
- },
- 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 new mozIApplication(app);
- }
- }
- return null;
- },
- getManifestFor: function getManifestFor(aManifestURL) {
- debug("getManifestFor(" + aManifestURL + ")");
- return DOMApplicationRegistry.getManifestFor(aManifestURL);
- },
- 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;
- },
- getAppLocalIdByStoreId: function(aApps, aStoreId) {
- debug("getAppLocalIdByStoreId:" + aStoreId);
- for (let id in aApps) {
- if (aApps[id].storeId == aStoreId) {
- return aApps[id].localId;
- }
- }
- return Ci.nsIScriptSecurityManager.NO_APP_ID;
- },
- getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
- debug("getAppByLocalId " + aLocalId);
- for (let id in aApps) {
- let app = aApps[id];
- if (app.localId == aLocalId) {
- return new mozIApplication(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 "";
- },
- areAnyAppsInstalled: function(aApps) {
- return Object.getOwnPropertyNames(aApps).length > 0;
- },
- getCoreAppsBasePath: function getCoreAppsBasePath() {
- debug("getCoreAppsBasePath()");
- try {
- return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
- } catch(e) {
- return null;
- }
- },
- getAppInfo: function getAppInfo(aApps, aAppId) {
- let app = aApps[aAppId];
- if (!app) {
- debug("No webapp for " + aAppId);
- return null;
- }
- // We can have 3rd party apps that are non-removable,
- // so we can't use the 'removable' property for isCoreApp
- // Instead, we check if the app is installed under /system/b2g
- let isCoreApp = false;
- debug(app.basePath + " isCoreApp: " + isCoreApp);
- return { "path": app.basePath + "/" + app.id,
- "isCoreApp": isCoreApp };
- },
- /**
- * Remove potential HTML tags from displayable fields in the manifest.
- * We check name, description, developer name, and permission description
- */
- sanitizeManifest: function(aManifest) {
- let sanitizer = Cc["@mozilla.org/parserutils;1"]
- .getService(Ci.nsIParserUtils);
- if (!sanitizer) {
- return;
- }
- function sanitize(aStr) {
- return sanitizer.convertToPlainText(aStr,
- Ci.nsIDocumentEncoder.OutputRaw, 0);
- }
- function sanitizeEntryPoint(aRoot) {
- aRoot.name = sanitize(aRoot.name);
- if (aRoot.description) {
- aRoot.description = sanitize(aRoot.description);
- }
- if (aRoot.developer && aRoot.developer.name) {
- aRoot.developer.name = sanitize(aRoot.developer.name);
- }
- if (aRoot.permissions) {
- for (let permission in aRoot.permissions) {
- if (aRoot.permissions[permission].description) {
- aRoot.permissions[permission].description =
- sanitize(aRoot.permissions[permission].description);
- }
- }
- }
- }
- // First process the main section, then the entry points.
- sanitizeEntryPoint(aManifest);
- if (aManifest.entry_points) {
- for (let entry in aManifest.entry_points) {
- sanitizeEntryPoint(aManifest.entry_points[entry]);
- }
- }
- },
- /**
- * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest
- * Only the name property is mandatory.
- */
- checkManifest: function(aManifest, app) {
- if (aManifest.name == undefined)
- return false;
- this.sanitizeManifest(aManifest);
- // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute
- if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path))
- return false;
- function checkAbsoluteEntryPoints(entryPoints) {
- for (let name in entryPoints) {
- if (entryPoints[name].launch_path && isAbsoluteURI(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;
- }
- }
- if (aManifest.activities) {
- for (let activityName in aManifest.activities) {
- let activity = aManifest.activities[activityName];
- if (activity.href && isAbsoluteURI(activity.href)) {
- return false;
- }
- }
- }
- // |messages| is an array of items, where each item is either a string or
- // a {name: href} object.
- let messages = aManifest.messages;
- if (messages) {
- if (!Array.isArray(messages)) {
- return false;
- }
- for (let item of aManifest.messages) {
- if (typeof item == "object") {
- let keys = Object.keys(item);
- if (keys.length != 1) {
- return false;
- }
- if (isAbsoluteURI(item[keys[0]])) {
- return false;
- }
- }
- }
- }
- // The 'size' field must be a positive integer.
- if (aManifest.size) {
- aManifest.size = parseInt(aManifest.size);
- if (Number.isNaN(aManifest.size) || aManifest.size < 0) {
- return false;
- }
- }
- // The 'role' field must be a string.
- if (aManifest.role && (typeof aManifest.role !== "string")) {
- return false;
- }
- return true;
- },
- checkManifestContentType: function
- checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
- let hadCharset = { };
- let charset = { };
- let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
- let contentType = netutil.parseResponseContentType(aContentType, charset, hadCharset);
- if (aInstallOrigin != aWebappOrigin &&
- !(contentType == "application/x-web-app-manifest+json" ||
- contentType == "application/manifest+json")) {
- return false;
- }
- return true;
- },
- allowUnsignedAddons: false, // for testing purposes.
- /**
- * Checks if the app role is allowed:
- * Only certified apps can be themes.
- * Only privileged or certified apps can be addons.
- * @param aRole : the role assigned to this app.
- * @param aStatus : the APP_STATUS_* for this app.
- */
- checkAppRole: function(aRole, aStatus) {
- try {
- // Anything is possible in developer mode.
- if (Services.prefs.getBoolPref("dom.apps.developer_mode")) {
- return true;
- }
- } catch(e) {}
- if (aRole == "theme" && aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
- return false;
- }
- if (!this.allowUnsignedAddons &&
- (aRole == "addon" &&
- aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
- aStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED)) {
- return false;
- }
- return true;
- },
- /**
- * Method to apply modifications to webapp manifests file saved internally.
- * For now, only ensure app can't rename itself.
- */
- ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) {
- // Ensure that app name can't be updated
- aNewManifest.name = aApp.name;
- let defaultShortName =
- new ManifestHelper(aOldManifest, aApp.origin, aApp.manifestURL).short_name;
- aNewManifest.short_name = defaultShortName;
- // Nor through localized names
- if ("locales" in aNewManifest) {
- for (let locale in aNewManifest.locales) {
- let newLocaleEntry = aNewManifest.locales[locale];
- let oldLocaleEntry = aOldManifest && "locales" in aOldManifest &&
- locale in aOldManifest.locales && aOldManifest.locales[locale];
- if (newLocaleEntry.name) {
- // In case previous manifest didn't had a name,
- // we use the default app name
- newLocaleEntry.name =
- (oldLocaleEntry && oldLocaleEntry.name) || aApp.name;
- }
- if (newLocaleEntry.short_name) {
- newLocaleEntry.short_name =
- (oldLocaleEntry && oldLocaleEntry.short_name) || defaultShortName;
- }
- }
- }
- },
- /**
- * 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");
- }
- },
- /**
- * 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 && !dev2) ||
- (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;
- },
- // Asynchronously loads a JSON file. aPath is a string representing the path
- // of the file to be read.
- loadJSONAsync: function(aPath) {
- let deferred = Promise.defer();
- try {
- let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
- file.initWithPath(aPath);
- let channel = NetUtil.newChannel({
- uri: NetUtil.newURI(file),
- loadUsingSystemPrincipal: true});
- channel.contentType = "application/json";
- NetUtil.asyncFetch(channel, function(aStream, aResult) {
- if (!Components.isSuccessCode(aResult)) {
- deferred.resolve(null);
- if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
- // We expect this under certain circumstances, like for webapps.json
- // on firstrun, so we return early without reporting an error.
- return;
- }
- Cu.reportError("AppsUtils: Could not read from json file " + aPath);
- return;
- }
- try {
- // Obtain a converter to read from a UTF-8 encoded input stream.
- let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = "UTF-8";
- // Read json file into a string
- let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
- aStream.available()) || ""));
- aStream.close();
- deferred.resolve(data);
- } catch (ex) {
- Cu.reportError("AppsUtils: Could not parse JSON: " +
- aPath + " " + ex + "\n" + ex.stack);
- deferred.resolve(null);
- }
- });
- } catch (ex) {
- Cu.reportError("AppsUtils: Could not read from " +
- aPath + " : " + ex + "\n" + ex.stack);
- deferred.resolve(null);
- }
- return deferred.promise;
- },
- // Returns the hash of a string, with MD5 as a default hashing function.
- computeHash: function(aString, aAlgorithm = "MD5") {
- let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = "UTF-8";
- let result = {};
- // Data is an array of bytes.
- let data = converter.convertToByteArray(aString, result);
- let hasher = Cc["@mozilla.org/security/hash;1"]
- .createInstance(Ci.nsICryptoHash);
- hasher.initWithString(aAlgorithm);
- hasher.update(data, data.length);
- // We're passing false to get the binary hash and not base64.
- let hash = hasher.finish(false);
- function toHexString(charCode) {
- return ("0" + charCode.toString(16)).slice(-2);
- }
- // Convert the binary hash data to a hex string.
- return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
- },
- // Returns the hash for a JS object.
- computeObjectHash: function(aObject) {
- return this.computeHash(JSON.stringify(aObject));
- },
- getAppManifestURLFromWindow: function(aWindow) {
- let appId = aWindow.document.nodePrincipal.appId;
- if (appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
- return null;
- }
- return appsService.getManifestURLByLocalId(appId);
- },
- }
- /**
- * Helper object to access manifest information with locale support
- */
- this.ManifestHelper = function(aManifest, aOrigin, aManifestURL, aLang) {
- // If the app is packaged, we resolve uris against the origin.
- // If it's not, against the manifest url.
- if (!aOrigin || !aManifestURL) {
- throw Error("ManifestHelper needs both origin and manifestURL");
- }
- this._baseURI = Services.io.newURI(
- aOrigin.startsWith("app://") ? aOrigin : aManifestURL, null, null);
- // We keep the manifest url in all cases since we need it to
- // resolve the package path for packaged apps.
- this._manifestURL = Services.io.newURI(aManifestURL, null, null);
- this._manifest = aManifest;
- let locale = aLang;
- if (!locale) {
- let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"]
- .getService(Ci.nsIXULChromeRegistry)
- .QueryInterface(Ci.nsIToolkitChromeRegistry);
- 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 (aProp in this._manifest) ? this._manifest[aProp] : undefined;
- },
- get name() {
- return this._localeProp("name");
- },
- get short_name() {
- return this._localeProp("short_name");
- },
- get description() {
- return this._localeProp("description");
- },
- get type() {
- return this._localeProp("type");
- },
- get version() {
- return this._localeProp("version");
- },
- get launch_path() {
- return this._localeProp("launch_path");
- },
- get developer() {
- // Default to {} in order to avoid exception in code
- // that doesn't check for null `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 {};
- },
- biggestIconURL: function(predicate) {
- let icons = this._localeProp("icons");
- if (!icons) {
- return null;
- }
- let iconSizes = Object.keys(icons).sort((a, b) => a - b)
- .filter(predicate || (() => true));
- if (iconSizes.length == 0) {
- return null;
- }
- let biggestIconSize = iconSizes.pop();
- let biggestIcon = icons[biggestIconSize];
- let biggestIconURL = this._baseURI.resolve(biggestIcon);
- return biggestIconURL;
- },
- 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._baseURI.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 || "") === "") {
- // W3C start_url takes precedence over mozApps launch_path
- if (this._localeProp("start_url")) {
- return this._baseURI.resolve(this._localeProp("start_url") || "/");
- }
- return this._baseURI.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._baseURI.resolve(entryPoints[aStartPoint].launch_path || "/");
- }
- return null;
- },
- resolveURL: function(aURI) {
- // This should be enforced higher up, but check it here just in case.
- if (isAbsoluteURI(aURI)) {
- throw new Error("Webapps.jsm: non-relative URI passed to resolve");
- }
- return this._baseURI.resolve(aURI);
- },
- fullAppcachePath: function() {
- let appcachePath = this._localeProp("appcache_path");
- return this._baseURI.resolve(appcachePath ? appcachePath : "/");
- },
- fullPackagePath: function() {
- let packagePath = this._localeProp("package_path");
- return this._manifestURL.resolve(packagePath ? packagePath : "/");
- },
- get role() {
- return this._manifest.role || "";
- },
- get csp() {
- return this._manifest.csp || "";
- }
- }
|