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/. */
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/Services.jsm");
13 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
14 Cu.import("resource://gre/modules/Promise.jsm");
16 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
17 "resource://gre/modules/FileUtils.jsm");
19 XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils",
20 "resource://gre/modules/WebappOSUtils.jsm");
22 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
23 "resource://gre/modules/NetUtil.jsm");
25 XPCOMUtils.defineLazyServiceGetter(this, "appsService",
26 "@mozilla.org/AppsService;1",
29 // Shared code for AppsServiceChild.jsm, TrustedHostedAppsUtils.jsm,
30 // Webapps.jsm and Webapps.js
32 this.EXPORTED_SYMBOLS =
33 ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
36 //dump("-*- AppsUtils.jsm: " + s + "\n");
39 this.isAbsoluteURI = function(aURI) {
40 let foo = Services.io.newURI("http://foo", null, null);
41 let bar = Services.io.newURI("http://bar", null, null);
42 return Services.io.newURI(aURI, null, foo).prePath != foo.prePath ||
43 Services.io.newURI(aURI, null, bar).prePath != bar.prePath;
46 this.mozIApplication = function(aApp) {
47 _setAppProperties(this, aApp);
50 mozIApplication.prototype = {
51 hasPermission: function(aPermission) {
52 let uri = Services.io.newURI(this.origin, null, null);
53 let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
54 .getService(Ci.nsIScriptSecurityManager);
55 // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
56 // specific permission. It is not checking if browsers inside |aApp| have such
58 let principal = secMan.getAppCodebasePrincipal(uri, this.localId,
60 let perm = Services.perms.testExactPermissionFromPrincipal(principal,
62 return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
65 hasWidgetPage: function(aPageURL) {
66 return this.widgetPages.indexOf(aPageURL) != -1;
69 QueryInterface: function(aIID) {
70 if (aIID.equals(Ci.mozIApplication) ||
71 aIID.equals(Ci.nsISupports))
73 throw Cr.NS_ERROR_NO_INTERFACE;
77 function _setAppProperties(aObj, aApp) {
78 aObj.name = aApp.name;
80 aObj.installOrigin = aApp.installOrigin;
81 aObj.origin = aApp.origin;
82 #ifdef MOZ_WIDGET_ANDROID
83 aObj.apkPackageName = aApp.apkPackageName;
85 aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null;
86 aObj.installTime = aApp.installTime;
87 aObj.manifestURL = aApp.manifestURL;
88 aObj.appStatus = aApp.appStatus;
89 aObj.removable = aApp.removable;
91 aObj.localId = aApp.localId;
92 aObj.basePath = aApp.basePath;
93 aObj.progress = aApp.progress || 0.0;
94 aObj.installState = aApp.installState || "installed";
95 aObj.downloadAvailable = aApp.downloadAvailable;
96 aObj.downloading = aApp.downloading;
97 aObj.readyToApplyDownload = aApp.readyToApplyDownload;
98 aObj.downloadSize = aApp.downloadSize || 0;
99 aObj.lastUpdateCheck = aApp.lastUpdateCheck;
100 aObj.updateTime = aApp.updateTime;
101 aObj.etag = aApp.etag;
102 aObj.packageEtag = aApp.packageEtag;
103 aObj.manifestHash = aApp.manifestHash;
104 aObj.packageHash = aApp.packageHash;
105 aObj.staged = aApp.staged;
106 aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID;
107 aObj.installerIsBrowser = !!aApp.installerIsBrowser;
108 aObj.storeId = aApp.storeId || "";
109 aObj.storeVersion = aApp.storeVersion || 0;
110 aObj.role = aApp.role || "";
111 aObj.redirects = aApp.redirects;
112 aObj.widgetPages = aApp.widgetPages || [];
113 aObj.kind = aApp.kind;
114 aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
115 aObj.sideloaded = aApp.sideloaded;
119 // Clones a app, without the manifest.
120 cloneAppObject: function(aApp) {
122 _setAppProperties(obj, aApp);
126 // Creates a nsILoadContext object with a given appId and isBrowser flag.
127 createLoadContext: function createLoadContext(aAppId, aIsBrowser) {
129 associatedWindow: null,
132 isInBrowserElement: aIsBrowser,
133 usePrivateBrowsing: false,
136 isAppOfType: function(appType) {
137 throw Cr.NS_ERROR_NOT_IMPLEMENTED;
140 QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext,
141 Ci.nsIInterfaceRequestor,
143 getInterface: function(iid) {
144 if (iid.equals(Ci.nsILoadContext))
146 throw Cr.NS_ERROR_NO_INTERFACE;
151 // Sends data downloaded from aRequestChannel to a file
152 // identified by aId and aFileName.
153 getFile: function(aRequestChannel, aId, aFileName) {
154 let deferred = Promise.defer();
156 // Staging the file in TmpD until all the checks are done.
157 let file = FileUtils.getFile("TmpD", ["webapps", aId, aFileName], true);
159 // We need an output stream to write the channel content to the out file.
160 let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
161 .createInstance(Ci.nsIFileOutputStream);
162 // write, create, truncate
163 outputStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
164 let bufferedOutputStream =
165 Cc['@mozilla.org/network/buffered-output-stream;1']
166 .createInstance(Ci.nsIBufferedOutputStream);
167 bufferedOutputStream.init(outputStream, 1024);
169 // Create a listener that will give data to the file output stream.
170 let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
171 .createInstance(Ci.nsISimpleStreamListener);
173 listener.init(bufferedOutputStream, {
174 onStartRequest: function(aRequest, aContext) {
175 // Nothing to do there anymore.
178 onStopRequest: function(aRequest, aContext, aStatusCode) {
179 bufferedOutputStream.close();
180 outputStream.close();
182 if (!Components.isSuccessCode(aStatusCode)) {
183 deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: true});
187 // If we get a 4XX or a 5XX http status, bail out like if we had a
189 let responseStatus = aRequestChannel.responseStatus;
190 if (responseStatus >= 400 && responseStatus <= 599) {
191 // unrecoverable error, don't bug the user
192 deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: false});
196 deferred.resolve(file);
199 aRequestChannel.asyncOpen(listener, null);
201 return deferred.promise;
204 getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
205 debug("getAppByManifestURL " + aManifestURL);
206 // This could be O(1) if |webapps| was a dictionary indexed on manifestURL
207 // which should be the unique app identifier.
208 // It's currently O(n).
209 for (let id in aApps) {
211 if (app.manifestURL == aManifestURL) {
212 return new mozIApplication(app);
219 getManifestFor: function getManifestFor(aManifestURL) {
220 debug("getManifestFor(" + aManifestURL + ")");
221 return DOMApplicationRegistry.getManifestFor(aManifestURL);
224 getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) {
225 debug("getAppLocalIdByManifestURL " + aManifestURL);
226 for (let id in aApps) {
227 if (aApps[id].manifestURL == aManifestURL) {
228 return aApps[id].localId;
232 return Ci.nsIScriptSecurityManager.NO_APP_ID;
235 getAppLocalIdByStoreId: function(aApps, aStoreId) {
236 debug("getAppLocalIdByStoreId:" + aStoreId);
237 for (let id in aApps) {
238 if (aApps[id].storeId == aStoreId) {
239 return aApps[id].localId;
243 return Ci.nsIScriptSecurityManager.NO_APP_ID;
246 getManifestCSPByLocalId: function getManifestCSPByLocalId(aApps, aLocalId) {
247 debug("getManifestCSPByLocalId " + aLocalId);
248 for (let id in aApps) {
250 if (app.localId == aLocalId) {
251 return ( app.csp || "" );
258 getDefaultCSPByLocalId: function(aApps, aLocalId) {
259 debug("getDefaultCSPByLocalId " + aLocalId);
260 for (let id in aApps) {
262 if (app.localId == aLocalId) {
263 // Use the app kind and the app status to choose the right default CSP.
265 switch (app.appStatus) {
266 case Ci.nsIPrincipal.APP_STATUS_CERTIFIED:
267 return Services.prefs.getCharPref("security.apps.certified.CSP.default");
269 case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED:
270 return Services.prefs.getCharPref("security.apps.privileged.CSP.default");
272 case Ci.nsIPrincipal.APP_STATUS_INSTALLED:
273 return app.kind == "hosted-trusted"
274 ? Services.prefs.getCharPref("security.apps.trusted.CSP.default")
282 return "default-src 'self'; object-src 'none'";
285 getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
286 debug("getAppByLocalId " + aLocalId);
287 for (let id in aApps) {
289 if (app.localId == aLocalId) {
290 return new mozIApplication(app);
297 getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) {
298 debug("getManifestURLByLocalId " + aLocalId);
299 for (let id in aApps) {
301 if (app.localId == aLocalId) {
302 return app.manifestURL;
309 getCoreAppsBasePath: function getCoreAppsBasePath() {
310 debug("getCoreAppsBasePath()");
312 return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
318 getAppInfo: function getAppInfo(aApps, aAppId) {
319 let app = aApps[aAppId];
322 debug("No webapp for " + aAppId);
326 // We can have 3rd party apps that are non-removable,
327 // so we can't use the 'removable' property for isCoreApp
328 // Instead, we check if the app is installed under /system/b2g
329 let isCoreApp = false;
331 #ifdef MOZ_WIDGET_GONK
332 isCoreApp = app.basePath == this.getCoreAppsBasePath();
334 debug(app.basePath + " isCoreApp: " + isCoreApp);
336 // Before bug 910473, this is a temporary workaround to get correct path
337 // from child process in mochitest.
338 let prefName = "dom.mozApps.auto_confirm_install";
339 if (Services.prefs.prefHasUserValue(prefName) &&
340 Services.prefs.getBoolPref(prefName)) {
341 return { "path": app.basePath + "/" + app.id,
342 "isCoreApp": isCoreApp };
345 return { "path": WebappOSUtils.getPackagePath(app),
346 "isCoreApp": isCoreApp };
350 * Remove potential HTML tags from displayable fields in the manifest.
351 * We check name, description, developer name, and permission description
353 sanitizeManifest: function(aManifest) {
354 let sanitizer = Cc["@mozilla.org/parserutils;1"]
355 .getService(Ci.nsIParserUtils);
360 function sanitize(aStr) {
361 return sanitizer.convertToPlainText(aStr,
362 Ci.nsIDocumentEncoder.OutputRaw, 0);
365 function sanitizeEntryPoint(aRoot) {
366 aRoot.name = sanitize(aRoot.name);
368 if (aRoot.description) {
369 aRoot.description = sanitize(aRoot.description);
372 if (aRoot.developer && aRoot.developer.name) {
373 aRoot.developer.name = sanitize(aRoot.developer.name);
376 if (aRoot.permissions) {
377 for (let permission in aRoot.permissions) {
378 if (aRoot.permissions[permission].description) {
379 aRoot.permissions[permission].description =
380 sanitize(aRoot.permissions[permission].description);
386 // First process the main section, then the entry points.
387 sanitizeEntryPoint(aManifest);
389 if (aManifest.entry_points) {
390 for (let entry in aManifest.entry_points) {
391 sanitizeEntryPoint(aManifest.entry_points[entry]);
397 * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest
398 * Only the name property is mandatory.
400 checkManifest: function(aManifest, app) {
401 if (aManifest.name == undefined)
404 this.sanitizeManifest(aManifest);
406 // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute
407 if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path))
410 function checkAbsoluteEntryPoints(entryPoints) {
411 for (let name in entryPoints) {
412 if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) {
419 if (checkAbsoluteEntryPoints(aManifest.entry_points))
422 for (let localeName in aManifest.locales) {
423 if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) {
428 if (aManifest.activities) {
429 for (let activityName in aManifest.activities) {
430 let activity = aManifest.activities[activityName];
431 if (activity.href && isAbsoluteURI(activity.href)) {
437 // |messages| is an array of items, where each item is either a string or
438 // a {name: href} object.
439 let messages = aManifest.messages;
441 if (!Array.isArray(messages)) {
444 for (let item of aManifest.messages) {
445 if (typeof item == "object") {
446 let keys = Object.keys(item);
447 if (keys.length != 1) {
450 if (isAbsoluteURI(item[keys[0]])) {
457 // The 'size' field must be a positive integer.
458 if (aManifest.size) {
459 aManifest.size = parseInt(aManifest.size);
460 if (Number.isNaN(aManifest.size) || aManifest.size < 0) {
465 // The 'role' field must be a string.
466 if (aManifest.role && (typeof aManifest.role !== "string")) {
472 checkManifestContentType: function
473 checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
474 let hadCharset = { };
476 let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
477 let contentType = netutil.parseContentType(aContentType, charset, hadCharset);
478 if (aInstallOrigin != aWebappOrigin &&
479 !(contentType == "application/x-web-app-manifest+json" ||
480 contentType == "application/manifest+json")) {
486 allowUnsignedAddons: false, // for testing purposes.
489 * Checks if the app role is allowed:
490 * Only certified apps can be themes.
491 * Only privileged or certified apps can be addons.
492 * @param aRole : the role assigned to this app.
493 * @param aStatus : the APP_STATUS_* for this app.
495 checkAppRole: function(aRole, aStatus) {
496 if (aRole == "theme" && aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
499 if (!this.allowUnsignedAddons &&
501 aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
502 aStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED)) {
509 * Method to apply modifications to webapp manifests file saved internally.
510 * For now, only ensure app can't rename itself.
512 ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) {
513 // Ensure that app name can't be updated
514 aNewManifest.name = aApp.name;
516 let defaultShortName =
517 new ManifestHelper(aOldManifest, aApp.origin, aApp.manifestURL).short_name;
518 aNewManifest.short_name = defaultShortName;
520 // Nor through localized names
521 if ("locales" in aNewManifest) {
522 for (let locale in aNewManifest.locales) {
523 let newLocaleEntry = aNewManifest.locales[locale];
525 let oldLocaleEntry = aOldManifest && "locales" in aOldManifest &&
526 locale in aOldManifest.locales && aOldManifest.locales[locale];
528 if (newLocaleEntry.name) {
529 // In case previous manifest didn't had a name,
530 // we use the default app name
531 newLocaleEntry.name =
532 (oldLocaleEntry && oldLocaleEntry.name) || aApp.name;
534 if (newLocaleEntry.short_name) {
535 newLocaleEntry.short_name =
536 (oldLocaleEntry && oldLocaleEntry.short_name) || defaultShortName;
543 * Determines whether the manifest allows installs for the given origin.
544 * @param object aManifest
545 * @param string aInstallOrigin
548 checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) {
549 if (!aManifest.installs_allowed_from) {
553 function cbCheckAllowedOrigin(aOrigin) {
554 return aOrigin == "*" || aOrigin == aInstallOrigin;
557 return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin);
561 * Determine the type of app (app, privileged, certified)
562 * that is installed by the manifest
563 * @param object aManifest
566 getAppManifestStatus: function getAppManifestStatus(aManifest) {
567 let type = aManifest.type || "web";
572 return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
574 return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
576 return Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
578 throw new Error("Webapps.jsm: Undetermined app manifest type");
583 * Determines if an update or a factory reset occured.
585 isFirstRun: function isFirstRun(aPrefBranch) {
586 let savedmstone = null;
588 savedmstone = aPrefBranch.getCharPref("gecko.mstone");
591 let mstone = Services.appinfo.platformVersion;
593 let savedBuildID = null;
595 savedBuildID = aPrefBranch.getCharPref("gecko.buildID");
598 let buildID = Services.appinfo.platformBuildID;
600 aPrefBranch.setCharPref("gecko.mstone", mstone);
601 aPrefBranch.setCharPref("gecko.buildID", buildID);
603 if ((mstone != savedmstone) || (buildID != savedBuildID)) {
604 aPrefBranch.setBoolPref("dom.apps.reset-permissions", false);
612 * Check if two manifests have the same set of properties and that the
613 * values of these properties are the same, in each locale.
614 * Manifests here are raw json ones.
616 compareManifests: function compareManifests(aManifest1, aManifest2) {
617 // 1. check if we have the same locales in both manifests.
620 if (aManifest1.locales) {
621 for (let locale in aManifest1.locales) {
622 locales1.push(locale);
625 if (aManifest2.locales) {
626 for (let locale in aManifest2.locales) {
627 locales2.push(locale);
630 if (locales1.sort().join() !== locales2.sort().join()) {
634 // Helper function to check the app name and developer information for
636 let checkNameAndDev = function(aRoot1, aRoot2) {
637 let name1 = aRoot1.name;
638 let name2 = aRoot2.name;
639 if (name1 !== name2) {
643 let dev1 = aRoot1.developer;
644 let dev2 = aRoot2.developer;
645 if ((dev1 && !dev2) || (dev2 && !dev1)) {
649 return (!dev1 && !dev2) ||
650 (dev1.name === dev2.name && dev1.url === dev2.url);
653 // 2. For each locale, check if the name and dev info are the same.
654 if (!checkNameAndDev(aManifest1, aManifest2)) {
658 for (let locale in aManifest1.locales) {
659 if (!checkNameAndDev(aManifest1.locales[locale],
660 aManifest2.locales[locale])) {
669 // Asynchronously loads a JSON file. aPath is a string representing the path
670 // of the file to be read.
671 loadJSONAsync: function(aPath) {
672 let deferred = Promise.defer();
675 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
676 file.initWithPath(aPath);
678 let channel = NetUtil.newChannel(file);
679 channel.contentType = "application/json";
681 NetUtil.asyncFetch(channel, function(aStream, aResult) {
682 if (!Components.isSuccessCode(aResult)) {
683 deferred.resolve(null);
685 if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
686 // We expect this under certain circumstances, like for webapps.json
687 // on firstrun, so we return early without reporting an error.
691 Cu.reportError("AppsUtils: Could not read from json file " + aPath);
696 // Obtain a converter to read from a UTF-8 encoded input stream.
697 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
698 .createInstance(Ci.nsIScriptableUnicodeConverter);
699 converter.charset = "UTF-8";
701 // Read json file into a string
702 let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
703 aStream.available()) || ""));
706 deferred.resolve(data);
708 Cu.reportError("AppsUtils: Could not parse JSON: " +
709 aPath + " " + ex + "\n" + ex.stack);
710 deferred.resolve(null);
714 Cu.reportError("AppsUtils: Could not read from " +
715 aPath + " : " + ex + "\n" + ex.stack);
716 deferred.resolve(null);
719 return deferred.promise;
722 // Returns the MD5 hash of a string.
723 computeHash: function(aString) {
724 let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
725 .createInstance(Ci.nsIScriptableUnicodeConverter);
726 converter.charset = "UTF-8";
728 // Data is an array of bytes.
729 let data = converter.convertToByteArray(aString, result);
731 let hasher = Cc["@mozilla.org/security/hash;1"]
732 .createInstance(Ci.nsICryptoHash);
733 hasher.init(hasher.MD5);
734 hasher.update(data, data.length);
735 // We're passing false to get the binary hash and not base64.
736 let hash = hasher.finish(false);
738 function toHexString(charCode) {
739 return ("0" + charCode.toString(16)).slice(-2);
742 // Convert the binary hash data to a hex string.
743 return [toHexString(hash.charCodeAt(i)) for (i in hash)].join("");
746 // Returns the hash for a JS object.
747 computeObjectHash: function(aObject) {
748 return this.computeHash(JSON.stringify(aObject));
751 getAppManifestURLFromWindow: function(aWindow) {
752 let appId = aWindow.document.nodePrincipal.appId;
753 if (appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
757 return appsService.getManifestURLByLocalId(appId);
762 * Helper object to access manifest information with locale support
764 this.ManifestHelper = function(aManifest, aOrigin, aManifestURL, aLang) {
765 // If the app is packaged, we resolve uris against the origin.
766 // If it's not, against the manifest url.
768 if (!aOrigin || !aManifestURL) {
769 throw Error("ManifestHelper needs both origin and manifestURL");
772 this._baseURI = Services.io.newURI(
773 aOrigin.startsWith("app://") ? aOrigin : aManifestURL, null, null);
775 // We keep the manifest url in all cases since we need it to
776 // resolve the package path for packaged apps.
777 this._manifestURL = Services.io.newURI(aManifestURL, null, null);
779 this._manifest = aManifest;
783 let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"]
784 .getService(Ci.nsIXULChromeRegistry)
785 .QueryInterface(Ci.nsIToolkitChromeRegistry);
786 locale = chrome.getSelectedLocale("global").toLowerCase();
789 this._localeRoot = this._manifest;
791 if (this._manifest.locales && this._manifest.locales[locale]) {
792 this._localeRoot = this._manifest.locales[locale];
794 else if (this._manifest.locales) {
795 // try with the language part of the locale ("en" for en-GB) only
796 let lang = locale.split('-')[0];
797 if (lang != locale && this._manifest.locales[lang])
798 this._localeRoot = this._manifest.locales[lang];
802 ManifestHelper.prototype = {
803 _localeProp: function(aProp) {
804 if (this._localeRoot[aProp] != undefined)
805 return this._localeRoot[aProp];
806 return this._manifest[aProp];
810 return this._localeProp("name");
814 return this._localeProp("short_name");
818 return this._localeProp("description");
822 return this._localeProp("type");
826 return this._localeProp("version");
830 return this._localeProp("launch_path");
834 // Default to {} in order to avoid exception in code
835 // that doesn't check for null `developer`
836 return this._localeProp("developer") || {};
840 return this._localeProp("icons");
843 get appcache_path() {
844 return this._localeProp("appcache_path");
848 return this._localeProp("orientation");
852 return this._localeProp("package_path");
856 return this._localeProp("widgetPages");
860 return this._manifest["size"] || 0;
864 if (this._manifest.permissions) {
865 return this._manifest.permissions;
870 get biggestIconURL() {
871 let icons = this._localeProp("icons");
876 let iconSizes = Object.keys(icons);
877 if (iconSizes.length == 0) {
881 iconSizes.sort((a, b) => a - b);
882 let biggestIconSize = iconSizes.pop();
883 let biggestIcon = icons[biggestIconSize];
884 let biggestIconURL = this._baseURI.resolve(biggestIcon);
886 return biggestIconURL;
889 iconURLForSize: function(aSize) {
890 let icons = this._localeProp("icons");
895 for (let size in icons) {
896 let iSize = parseInt(size);
897 if (Math.abs(iSize - aSize) < dist) {
898 icon = this._baseURI.resolve(icons[size]);
899 dist = Math.abs(iSize - aSize);
905 fullLaunchPath: function(aStartPoint) {
906 // If no start point is specified, we use the root launch path.
907 // In all error cases, we just return null.
908 if ((aStartPoint || "") === "") {
909 return this._baseURI.resolve(this._localeProp("launch_path") || "/");
912 // Search for the l10n entry_points property.
913 let entryPoints = this._localeProp("entry_points");
918 if (entryPoints[aStartPoint]) {
919 return this._baseURI.resolve(entryPoints[aStartPoint].launch_path || "/");
925 resolveURL: function(aURI) {
926 // This should be enforced higher up, but check it here just in case.
927 if (isAbsoluteURI(aURI)) {
928 throw new Error("Webapps.jsm: non-relative URI passed to resolve");
930 return this._baseURI.resolve(aURI);
933 fullAppcachePath: function() {
934 let appcachePath = this._localeProp("appcache_path");
935 return this._baseURI.resolve(appcachePath ? appcachePath : "/");
938 fullPackagePath: function() {
939 let packagePath = this._localeProp("package_path");
940 return this._manifestURL.resolve(packagePath ? packagePath : "/");
944 return this._manifest.role || "";
948 return this._manifest.csp || "";