dom/apps/src/AppsUtils.jsm

Wed, 31 Dec 2014 06:55:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:55:50 +0100
changeset 2
7e26c7da4463
permissions
-rw-r--r--

Added tag UPSTREAM_283F7C6 for changeset ca08bd8f51b2

     1 /* This Source Code Form is subject to the terms of the Mozilla Public
     2  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
     3  * You can obtain one at http://mozilla.org/MPL/2.0/. */
     5 "use strict";
     7 const Cu = Components.utils;
     8 const Cc = Components.classes;
     9 const Ci = Components.interfaces;
    10 const Cr = Components.results;
    12 Cu.import("resource://gre/modules/osfile.jsm");
    13 Cu.import("resource://gre/modules/Services.jsm");
    14 Cu.import("resource://gre/modules/Task.jsm");
    15 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
    16 Cu.import("resource://gre/modules/Promise.jsm");
    18 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
    19   "resource://gre/modules/FileUtils.jsm");
    21 XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
    22   "resource://gre/modules/WebappOSUtils.jsm");
    24 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
    25   "resource://gre/modules/NetUtil.jsm");
    27 // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
    29 this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
    31 function debug(s) {
    32   //dump("-*- AppsUtils.jsm: " + s + "\n");
    33 }
    35 this.isAbsoluteURI = function(aURI) {
    36   let foo = Services.io.newURI("http://foo", null, null);
    37   let bar = Services.io.newURI("http://bar", null, null);
    38   return Services.io.newURI(aURI, null, foo).prePath != foo.prePath ||
    39          Services.io.newURI(aURI, null, bar).prePath != bar.prePath;
    40 }
    42 this.mozIApplication = function(aApp) {
    43   _setAppProperties(this, aApp);
    44 }
    46 mozIApplication.prototype = {
    47   hasPermission: function(aPermission) {
    48     let uri = Services.io.newURI(this.origin, null, null);
    49     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
    50                    .getService(Ci.nsIScriptSecurityManager);
    51     // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
    52     // specific permission. It is not checking if browsers inside |aApp| have such
    53     // permission.
    54     let principal = secMan.getAppCodebasePrincipal(uri, this.localId,
    55                                                    /*mozbrowser*/false);
    56     let perm = Services.perms.testExactPermissionFromPrincipal(principal,
    57                                                                aPermission);
    58     return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
    59   },
    61   QueryInterface: function(aIID) {
    62     if (aIID.equals(Ci.mozIApplication) ||
    63         aIID.equals(Ci.nsISupports))
    64       return this;
    65     throw Cr.NS_ERROR_NO_INTERFACE;
    66   }
    67 }
    69 function _setAppProperties(aObj, aApp) {
    70   aObj.name = aApp.name;
    71   aObj.csp = aApp.csp;
    72   aObj.installOrigin = aApp.installOrigin;
    73   aObj.origin = aApp.origin;
    74 #ifdef MOZ_ANDROID_SYNTHAPKS
    75   aObj.apkPackageName = aApp.apkPackageName;
    76 #endif
    77   aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null;
    78   aObj.installTime = aApp.installTime;
    79   aObj.manifestURL = aApp.manifestURL;
    80   aObj.appStatus = aApp.appStatus;
    81   aObj.removable = aApp.removable;
    82   aObj.id = aApp.id;
    83   aObj.localId = aApp.localId;
    84   aObj.basePath = aApp.basePath;
    85   aObj.progress = aApp.progress || 0.0;
    86   aObj.installState = aApp.installState || "installed";
    87   aObj.downloadAvailable = aApp.downloadAvailable;
    88   aObj.downloading = aApp.downloading;
    89   aObj.readyToApplyDownload = aApp.readyToApplyDownload;
    90   aObj.downloadSize = aApp.downloadSize || 0;
    91   aObj.lastUpdateCheck = aApp.lastUpdateCheck;
    92   aObj.updateTime = aApp.updateTime;
    93   aObj.etag = aApp.etag;
    94   aObj.packageEtag = aApp.packageEtag;
    95   aObj.manifestHash = aApp.manifestHash;
    96   aObj.packageHash = aApp.packageHash;
    97   aObj.staged = aApp.staged;
    98   aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID;
    99   aObj.installerIsBrowser = !!aApp.installerIsBrowser;
   100   aObj.storeId = aApp.storeId || "";
   101   aObj.storeVersion = aApp.storeVersion || 0;
   102   aObj.role = aApp.role || "";
   103   aObj.redirects = aApp.redirects;
   104 }
   106 this.AppsUtils = {
   107   // Clones a app, without the manifest.
   108   cloneAppObject: function(aApp) {
   109     let obj = {};
   110     _setAppProperties(obj, aApp);
   111     return obj;
   112   },
   114   getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
   115     debug("getAppByManifestURL " + aManifestURL);
   116     // This could be O(1) if |webapps| was a dictionary indexed on manifestURL
   117     // which should be the unique app identifier.
   118     // It's currently O(n).
   119     for (let id in aApps) {
   120       let app = aApps[id];
   121       if (app.manifestURL == aManifestURL) {
   122         return new mozIApplication(app);
   123       }
   124     }
   126     return null;
   127   },
   129   getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) {
   130     debug("getAppLocalIdByManifestURL " + aManifestURL);
   131     for (let id in aApps) {
   132       if (aApps[id].manifestURL == aManifestURL) {
   133         return aApps[id].localId;
   134       }
   135     }
   137     return Ci.nsIScriptSecurityManager.NO_APP_ID;
   138   },
   140   getAppLocalIdByStoreId: function(aApps, aStoreId) {
   141     debug("getAppLocalIdByStoreId:" + aStoreId);
   142     for (let id in aApps) {
   143       if (aApps[id].storeId == aStoreId) {
   144         return aApps[id].localId;
   145       }
   146     }
   148     return Ci.nsIScriptSecurityManager.NO_APP_ID;
   149   },
   151   getCSPByLocalId: function getCSPByLocalId(aApps, aLocalId) {
   152     debug("getCSPByLocalId " + aLocalId);
   153     for (let id in aApps) {
   154       let app = aApps[id];
   155       if (app.localId == aLocalId) {
   156 	  return ( app.csp || "" );
   157       }
   158     }
   160     return "";
   161   },
   163   getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
   164     debug("getAppByLocalId " + aLocalId);
   165     for (let id in aApps) {
   166       let app = aApps[id];
   167       if (app.localId == aLocalId) {
   168         return new mozIApplication(app);
   169       }
   170     }
   172     return null;
   173   },
   175   getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) {
   176     debug("getManifestURLByLocalId " + aLocalId);
   177     for (let id in aApps) {
   178       let app = aApps[id];
   179       if (app.localId == aLocalId) {
   180         return app.manifestURL;
   181       }
   182     }
   184     return "";
   185   },
   187   getCoreAppsBasePath: function getCoreAppsBasePath() {
   188     debug("getCoreAppsBasePath()");
   189     try {
   190       return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
   191     } catch(e) {
   192       return null;
   193     }
   194   },
   196   getAppInfo: function getAppInfo(aApps, aAppId) {
   197     let app = aApps[aAppId];
   199     if (!app) {
   200       debug("No webapp for " + aAppId);
   201       return null;
   202     }
   204     // We can have 3rd party apps that are non-removable,
   205     // so we can't use the 'removable' property for isCoreApp
   206     // Instead, we check if the app is installed under /system/b2g
   207     let isCoreApp = false;
   209 #ifdef MOZ_WIDGET_GONK
   210     isCoreApp = app.basePath == this.getCoreAppsBasePath();
   211 #endif
   212     debug(app.basePath + " isCoreApp: " + isCoreApp);
   213     return { "path": WebappOSUtils.getPackagePath(app),
   214              "isCoreApp": isCoreApp };
   215   },
   217   /**
   218     * Remove potential HTML tags from displayable fields in the manifest.
   219     * We check name, description, developer name, and permission description
   220     */
   221   sanitizeManifest: function(aManifest) {
   222     let sanitizer = Cc["@mozilla.org/parserutils;1"]
   223                       .getService(Ci.nsIParserUtils);
   224     if (!sanitizer) {
   225       return;
   226     }
   228     function sanitize(aStr) {
   229       return sanitizer.convertToPlainText(aStr,
   230                Ci.nsIDocumentEncoder.OutputRaw, 0);
   231     }
   233     function sanitizeEntryPoint(aRoot) {
   234       aRoot.name = sanitize(aRoot.name);
   236       if (aRoot.description) {
   237         aRoot.description = sanitize(aRoot.description);
   238       }
   240       if (aRoot.developer && aRoot.developer.name) {
   241         aRoot.developer.name = sanitize(aRoot.developer.name);
   242       }
   244       if (aRoot.permissions) {
   245         for (let permission in aRoot.permissions) {
   246           if (aRoot.permissions[permission].description) {
   247             aRoot.permissions[permission].description =
   248              sanitize(aRoot.permissions[permission].description);
   249           }
   250         }
   251       }
   252     }
   254     // First process the main section, then the entry points.
   255     sanitizeEntryPoint(aManifest);
   257     if (aManifest.entry_points) {
   258       for (let entry in aManifest.entry_points) {
   259         sanitizeEntryPoint(aManifest.entry_points[entry]);
   260       }
   261     }
   262   },
   264   /**
   265    * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest
   266    * Only the name property is mandatory.
   267    */
   268   checkManifest: function(aManifest, app) {
   269     if (aManifest.name == undefined)
   270       return false;
   272     this.sanitizeManifest(aManifest);
   274     // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute
   275     if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path))
   276       return false;
   278     function checkAbsoluteEntryPoints(entryPoints) {
   279       for (let name in entryPoints) {
   280         if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) {
   281           return true;
   282         }
   283       }
   284       return false;
   285     }
   287     if (checkAbsoluteEntryPoints(aManifest.entry_points))
   288       return false;
   290     for (let localeName in aManifest.locales) {
   291       if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) {
   292         return false;
   293       }
   294     }
   296     if (aManifest.activities) {
   297       for (let activityName in aManifest.activities) {
   298         let activity = aManifest.activities[activityName];
   299         if (activity.href && isAbsoluteURI(activity.href)) {
   300           return false;
   301         }
   302       }
   303     }
   305     // |messages| is an array of items, where each item is either a string or
   306     // a {name: href} object.
   307     let messages = aManifest.messages;
   308     if (messages) {
   309       if (!Array.isArray(messages)) {
   310         return false;
   311       }
   312       for (let item of aManifest.messages) {
   313         if (typeof item == "object") {
   314           let keys = Object.keys(item);
   315           if (keys.length != 1) {
   316             return false;
   317           }
   318           if (isAbsoluteURI(item[keys[0]])) {
   319             return false;
   320           }
   321         }
   322       }
   323     }
   325     // The 'size' field must be a positive integer.
   326     if (aManifest.size) {
   327       aManifest.size = parseInt(aManifest.size);
   328       if (Number.isNaN(aManifest.size) || aManifest.size < 0) {
   329         return false;
   330       }
   331     }
   333     // The 'role' field must be a string.
   334     if (aManifest.role && (typeof aManifest.role !== "string")) {
   335       return false;
   336     }
   337     return true;
   338   },
   340   checkManifestContentType: function
   341      checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
   342     let hadCharset = { };
   343     let charset = { };
   344     let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
   345     let contentType = netutil.parseContentType(aContentType, charset, hadCharset);
   346     if (aInstallOrigin != aWebappOrigin &&
   347         contentType != "application/x-web-app-manifest+json") {
   348       return false;
   349     }
   350     return true;
   351   },
   353   /**
   354    * Method to apply modifications to webapp manifests file saved internally.
   355    * For now, only ensure app can't rename itself.
   356    */
   357   ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) {
   358     // Ensure that app name can't be updated
   359     aNewManifest.name = aApp.name;
   361     // Nor through localized names
   362     if ('locales' in aNewManifest) {
   363       let defaultName = new ManifestHelper(aOldManifest, aApp.origin).name;
   364       for (let locale in aNewManifest.locales) {
   365         let entry = aNewManifest.locales[locale];
   366         if (!entry.name) {
   367           continue;
   368         }
   369         // In case previous manifest didn't had a name,
   370         // we use the default app name
   371         let localizedName = defaultName;
   372         if (aOldManifest && 'locales' in aOldManifest &&
   373             locale in aOldManifest.locales) {
   374           localizedName = aOldManifest.locales[locale].name;
   375         }
   376         entry.name = localizedName;
   377       }
   378     }
   379   },
   381   /**
   382    * Determines whether the manifest allows installs for the given origin.
   383    * @param object aManifest
   384    * @param string aInstallOrigin
   385    * @return boolean
   386    **/
   387   checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) {
   388     if (!aManifest.installs_allowed_from) {
   389       return true;
   390     }
   392     function cbCheckAllowedOrigin(aOrigin) {
   393       return aOrigin == "*" || aOrigin == aInstallOrigin;
   394     }
   396     return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin);
   397   },
   399   /**
   400    * Determine the type of app (app, privileged, certified)
   401    * that is installed by the manifest
   402    * @param object aManifest
   403    * @returns integer
   404    **/
   405   getAppManifestStatus: function getAppManifestStatus(aManifest) {
   406     let type = aManifest.type || "web";
   408     switch(type) {
   409     case "web":
   410       return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
   411     case "privileged":
   412       return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
   413     case "certified":
   414       return Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
   415     default:
   416       throw new Error("Webapps.jsm: Undetermined app manifest type");
   417     }
   418   },
   420   /**
   421    * Determines if an update or a factory reset occured.
   422    */
   423   isFirstRun: function isFirstRun(aPrefBranch) {
   424     let savedmstone = null;
   425     try {
   426       savedmstone = aPrefBranch.getCharPref("gecko.mstone");
   427     } catch (e) {}
   429     let mstone = Services.appinfo.platformVersion;
   431     let savedBuildID = null;
   432     try {
   433       savedBuildID = aPrefBranch.getCharPref("gecko.buildID");
   434     } catch (e) {}
   436     let buildID = Services.appinfo.platformBuildID;
   438     aPrefBranch.setCharPref("gecko.mstone", mstone);
   439     aPrefBranch.setCharPref("gecko.buildID", buildID);
   441     return ((mstone != savedmstone) || (buildID != savedBuildID));
   442   },
   444   /**
   445    * Check if two manifests have the same set of properties and that the
   446    * values of these properties are the same, in each locale.
   447    * Manifests here are raw json ones.
   448    */
   449   compareManifests: function compareManifests(aManifest1, aManifest2) {
   450     // 1. check if we have the same locales in both manifests.
   451     let locales1 = [];
   452     let locales2 = [];
   453     if (aManifest1.locales) {
   454       for (let locale in aManifest1.locales) {
   455         locales1.push(locale);
   456       }
   457     }
   458     if (aManifest2.locales) {
   459       for (let locale in aManifest2.locales) {
   460         locales2.push(locale);
   461       }
   462     }
   463     if (locales1.sort().join() !== locales2.sort().join()) {
   464       return false;
   465     }
   467     // Helper function to check the app name and developer information for
   468     // two given roots.
   469     let checkNameAndDev = function(aRoot1, aRoot2) {
   470       let name1 = aRoot1.name;
   471       let name2 = aRoot2.name;
   472       if (name1 !== name2) {
   473         return false;
   474       }
   476       let dev1 = aRoot1.developer;
   477       let dev2 = aRoot2.developer;
   478       if ((dev1 && !dev2) || (dev2 && !dev1)) {
   479         return false;
   480       }
   482       return (!dev1 && !dev2) ||
   483              (dev1.name === dev2.name && dev1.url === dev2.url);
   484     }
   486     // 2. For each locale, check if the name and dev info are the same.
   487     if (!checkNameAndDev(aManifest1, aManifest2)) {
   488       return false;
   489     }
   491     for (let locale in aManifest1.locales) {
   492       if (!checkNameAndDev(aManifest1.locales[locale],
   493                            aManifest2.locales[locale])) {
   494         return false;
   495       }
   496     }
   498     // Nothing failed.
   499     return true;
   500   },
   502   // Asynchronously loads a JSON file. aPath is a string representing the path
   503   // of the file to be read.
   504   loadJSONAsync: function(aPath) {
   505     let deferred = Promise.defer();
   507     try {
   508       let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
   509       file.initWithPath(aPath);
   511       let channel = NetUtil.newChannel(file);
   512       channel.contentType = "application/json";
   514       NetUtil.asyncFetch(channel, function(aStream, aResult) {
   515         if (!Components.isSuccessCode(aResult)) {
   516           deferred.resolve(null);
   518           if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
   519             // We expect this under certain circumstances, like for webapps.json
   520             // on firstrun, so we return early without reporting an error.
   521             return;
   522           }
   524           Cu.reportError("AppsUtils: Could not read from json file " + aPath);
   525           return;
   526         }
   528         try {
   529           // Obtain a converter to read from a UTF-8 encoded input stream.
   530           let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   531                             .createInstance(Ci.nsIScriptableUnicodeConverter);
   532           converter.charset = "UTF-8";
   534           // Read json file into a string
   535           let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
   536                                                             aStream.available()) || ""));
   537           aStream.close();
   539           deferred.resolve(data);
   540         } catch (ex) {
   541           Cu.reportError("AppsUtils: Could not parse JSON: " +
   542                          aPath + " " + ex + "\n" + ex.stack);
   543           deferred.resolve(null);
   544         }
   545       });
   546     } catch (ex) {
   547       Cu.reportError("AppsUtils: Could not read from " +
   548                      aPath + " : " + ex + "\n" + ex.stack);
   549       deferred.resolve(null);
   550     }
   552     return deferred.promise;
   553   },
   555   // Returns the MD5 hash of a string.
   556   computeHash: function(aString) {
   557     let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
   558                       .createInstance(Ci.nsIScriptableUnicodeConverter);
   559     converter.charset = "UTF-8";
   560     let result = {};
   561     // Data is an array of bytes.
   562     let data = converter.convertToByteArray(aString, result);
   564     let hasher = Cc["@mozilla.org/security/hash;1"]
   565                    .createInstance(Ci.nsICryptoHash);
   566     hasher.init(hasher.MD5);
   567     hasher.update(data, data.length);
   568     // We're passing false to get the binary hash and not base64.
   569     let hash = hasher.finish(false);
   571     function toHexString(charCode) {
   572       return ("0" + charCode.toString(16)).slice(-2);
   573     }
   575     // Convert the binary hash data to a hex string.
   576     return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
   577   }
   578 }
   580 /**
   581  * Helper object to access manifest information with locale support
   582  */
   583 this.ManifestHelper = function(aManifest, aOrigin) {
   584   this._origin = Services.io.newURI(aOrigin, null, null);
   585   this._manifest = aManifest;
   586   let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry)
   587                                                           .QueryInterface(Ci.nsIToolkitChromeRegistry);
   588   let locale = chrome.getSelectedLocale("global").toLowerCase();
   589   this._localeRoot = this._manifest;
   591   if (this._manifest.locales && this._manifest.locales[locale]) {
   592     this._localeRoot = this._manifest.locales[locale];
   593   }
   594   else if (this._manifest.locales) {
   595     // try with the language part of the locale ("en" for en-GB) only
   596     let lang = locale.split('-')[0];
   597     if (lang != locale && this._manifest.locales[lang])
   598       this._localeRoot = this._manifest.locales[lang];
   599   }
   600 };
   602 ManifestHelper.prototype = {
   603   _localeProp: function(aProp) {
   604     if (this._localeRoot[aProp] != undefined)
   605       return this._localeRoot[aProp];
   606     return this._manifest[aProp];
   607   },
   609   get name() {
   610     return this._localeProp("name");
   611   },
   613   get description() {
   614     return this._localeProp("description");
   615   },
   617   get type() {
   618     return this._localeProp("type");
   619   },
   621   get version() {
   622     return this._localeProp("version");
   623   },
   625   get launch_path() {
   626     return this._localeProp("launch_path");
   627   },
   629   get developer() {
   630     // Default to {} in order to avoid exception in code
   631     // that doesn't check for null `developer`
   632     return this._localeProp("developer") || {};
   633   },
   635   get icons() {
   636     return this._localeProp("icons");
   637   },
   639   get appcache_path() {
   640     return this._localeProp("appcache_path");
   641   },
   643   get orientation() {
   644     return this._localeProp("orientation");
   645   },
   647   get package_path() {
   648     return this._localeProp("package_path");
   649   },
   651   get size() {
   652     return this._manifest["size"] || 0;
   653   },
   655   get permissions() {
   656     if (this._manifest.permissions) {
   657       return this._manifest.permissions;
   658     }
   659     return {};
   660   },
   662   get biggestIconURL() {
   663     let icons = this._localeProp("icons");
   664     if (!icons) {
   665       return null;
   666     }
   668     let iconSizes = Object.keys(icons);
   669     if (iconSizes.length == 0) {
   670       return null;
   671     }
   673     iconSizes.sort((a, b) => a - b);
   674     let biggestIconSize = iconSizes.pop();
   675     let biggestIcon = icons[biggestIconSize];
   676     let biggestIconURL = this._origin.resolve(biggestIcon);
   678     return biggestIconURL;
   679   },
   681   iconURLForSize: function(aSize) {
   682     let icons = this._localeProp("icons");
   683     if (!icons)
   684       return null;
   685     let dist = 100000;
   686     let icon = null;
   687     for (let size in icons) {
   688       let iSize = parseInt(size);
   689       if (Math.abs(iSize - aSize) < dist) {
   690         icon = this._origin.resolve(icons[size]);
   691         dist = Math.abs(iSize - aSize);
   692       }
   693     }
   694     return icon;
   695   },
   697   fullLaunchPath: function(aStartPoint) {
   698     // If no start point is specified, we use the root launch path.
   699     // In all error cases, we just return null.
   700     if ((aStartPoint || "") === "") {
   701       return this._origin.resolve(this._localeProp("launch_path") || "");
   702     }
   704     // Search for the l10n entry_points property.
   705     let entryPoints = this._localeProp("entry_points");
   706     if (!entryPoints) {
   707       return null;
   708     }
   710     if (entryPoints[aStartPoint]) {
   711       return this._origin.resolve(entryPoints[aStartPoint].launch_path || "");
   712     }
   714     return null;
   715   },
   717   resolveFromOrigin: function(aURI) {
   718     // This should be enforced higher up, but check it here just in case.
   719     if (isAbsoluteURI(aURI)) {
   720       throw new Error("Webapps.jsm: non-relative URI passed to resolveFromOrigin");
   721     }
   722     return this._origin.resolve(aURI);
   723   },
   725   fullAppcachePath: function() {
   726     let appcachePath = this._localeProp("appcache_path");
   727     return this._origin.resolve(appcachePath ? appcachePath : "");
   728   },
   730   fullPackagePath: function() {
   731     let packagePath = this._localeProp("package_path");
   732     return this._origin.resolve(packagePath ? packagePath : "");
   733   },
   735   get role() {
   736     return this._manifest.role || "";
   737   }
   738 }

mercurial