1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/apps/src/AppsUtils.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,738 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const Cu = Components.utils; 1.11 +const Cc = Components.classes; 1.12 +const Ci = Components.interfaces; 1.13 +const Cr = Components.results; 1.14 + 1.15 +Cu.import("resource://gre/modules/osfile.jsm"); 1.16 +Cu.import("resource://gre/modules/Services.jsm"); 1.17 +Cu.import("resource://gre/modules/Task.jsm"); 1.18 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.19 +Cu.import("resource://gre/modules/Promise.jsm"); 1.20 + 1.21 +XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", 1.22 + "resource://gre/modules/FileUtils.jsm"); 1.23 + 1.24 +XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils", 1.25 + "resource://gre/modules/WebappOSUtils.jsm"); 1.26 + 1.27 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", 1.28 + "resource://gre/modules/NetUtil.jsm"); 1.29 + 1.30 +// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js 1.31 + 1.32 +this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"]; 1.33 + 1.34 +function debug(s) { 1.35 + //dump("-*- AppsUtils.jsm: " + s + "\n"); 1.36 +} 1.37 + 1.38 +this.isAbsoluteURI = function(aURI) { 1.39 + let foo = Services.io.newURI("http://foo", null, null); 1.40 + let bar = Services.io.newURI("http://bar", null, null); 1.41 + return Services.io.newURI(aURI, null, foo).prePath != foo.prePath || 1.42 + Services.io.newURI(aURI, null, bar).prePath != bar.prePath; 1.43 +} 1.44 + 1.45 +this.mozIApplication = function(aApp) { 1.46 + _setAppProperties(this, aApp); 1.47 +} 1.48 + 1.49 +mozIApplication.prototype = { 1.50 + hasPermission: function(aPermission) { 1.51 + let uri = Services.io.newURI(this.origin, null, null); 1.52 + let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"] 1.53 + .getService(Ci.nsIScriptSecurityManager); 1.54 + // This helper checks an URI inside |aApp|'s origin and part of |aApp| has a 1.55 + // specific permission. It is not checking if browsers inside |aApp| have such 1.56 + // permission. 1.57 + let principal = secMan.getAppCodebasePrincipal(uri, this.localId, 1.58 + /*mozbrowser*/false); 1.59 + let perm = Services.perms.testExactPermissionFromPrincipal(principal, 1.60 + aPermission); 1.61 + return (perm === Ci.nsIPermissionManager.ALLOW_ACTION); 1.62 + }, 1.63 + 1.64 + QueryInterface: function(aIID) { 1.65 + if (aIID.equals(Ci.mozIApplication) || 1.66 + aIID.equals(Ci.nsISupports)) 1.67 + return this; 1.68 + throw Cr.NS_ERROR_NO_INTERFACE; 1.69 + } 1.70 +} 1.71 + 1.72 +function _setAppProperties(aObj, aApp) { 1.73 + aObj.name = aApp.name; 1.74 + aObj.csp = aApp.csp; 1.75 + aObj.installOrigin = aApp.installOrigin; 1.76 + aObj.origin = aApp.origin; 1.77 +#ifdef MOZ_ANDROID_SYNTHAPKS 1.78 + aObj.apkPackageName = aApp.apkPackageName; 1.79 +#endif 1.80 + aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null; 1.81 + aObj.installTime = aApp.installTime; 1.82 + aObj.manifestURL = aApp.manifestURL; 1.83 + aObj.appStatus = aApp.appStatus; 1.84 + aObj.removable = aApp.removable; 1.85 + aObj.id = aApp.id; 1.86 + aObj.localId = aApp.localId; 1.87 + aObj.basePath = aApp.basePath; 1.88 + aObj.progress = aApp.progress || 0.0; 1.89 + aObj.installState = aApp.installState || "installed"; 1.90 + aObj.downloadAvailable = aApp.downloadAvailable; 1.91 + aObj.downloading = aApp.downloading; 1.92 + aObj.readyToApplyDownload = aApp.readyToApplyDownload; 1.93 + aObj.downloadSize = aApp.downloadSize || 0; 1.94 + aObj.lastUpdateCheck = aApp.lastUpdateCheck; 1.95 + aObj.updateTime = aApp.updateTime; 1.96 + aObj.etag = aApp.etag; 1.97 + aObj.packageEtag = aApp.packageEtag; 1.98 + aObj.manifestHash = aApp.manifestHash; 1.99 + aObj.packageHash = aApp.packageHash; 1.100 + aObj.staged = aApp.staged; 1.101 + aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID; 1.102 + aObj.installerIsBrowser = !!aApp.installerIsBrowser; 1.103 + aObj.storeId = aApp.storeId || ""; 1.104 + aObj.storeVersion = aApp.storeVersion || 0; 1.105 + aObj.role = aApp.role || ""; 1.106 + aObj.redirects = aApp.redirects; 1.107 +} 1.108 + 1.109 +this.AppsUtils = { 1.110 + // Clones a app, without the manifest. 1.111 + cloneAppObject: function(aApp) { 1.112 + let obj = {}; 1.113 + _setAppProperties(obj, aApp); 1.114 + return obj; 1.115 + }, 1.116 + 1.117 + getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) { 1.118 + debug("getAppByManifestURL " + aManifestURL); 1.119 + // This could be O(1) if |webapps| was a dictionary indexed on manifestURL 1.120 + // which should be the unique app identifier. 1.121 + // It's currently O(n). 1.122 + for (let id in aApps) { 1.123 + let app = aApps[id]; 1.124 + if (app.manifestURL == aManifestURL) { 1.125 + return new mozIApplication(app); 1.126 + } 1.127 + } 1.128 + 1.129 + return null; 1.130 + }, 1.131 + 1.132 + getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) { 1.133 + debug("getAppLocalIdByManifestURL " + aManifestURL); 1.134 + for (let id in aApps) { 1.135 + if (aApps[id].manifestURL == aManifestURL) { 1.136 + return aApps[id].localId; 1.137 + } 1.138 + } 1.139 + 1.140 + return Ci.nsIScriptSecurityManager.NO_APP_ID; 1.141 + }, 1.142 + 1.143 + getAppLocalIdByStoreId: function(aApps, aStoreId) { 1.144 + debug("getAppLocalIdByStoreId:" + aStoreId); 1.145 + for (let id in aApps) { 1.146 + if (aApps[id].storeId == aStoreId) { 1.147 + return aApps[id].localId; 1.148 + } 1.149 + } 1.150 + 1.151 + return Ci.nsIScriptSecurityManager.NO_APP_ID; 1.152 + }, 1.153 + 1.154 + getCSPByLocalId: function getCSPByLocalId(aApps, aLocalId) { 1.155 + debug("getCSPByLocalId " + aLocalId); 1.156 + for (let id in aApps) { 1.157 + let app = aApps[id]; 1.158 + if (app.localId == aLocalId) { 1.159 + return ( app.csp || "" ); 1.160 + } 1.161 + } 1.162 + 1.163 + return ""; 1.164 + }, 1.165 + 1.166 + getAppByLocalId: function getAppByLocalId(aApps, aLocalId) { 1.167 + debug("getAppByLocalId " + aLocalId); 1.168 + for (let id in aApps) { 1.169 + let app = aApps[id]; 1.170 + if (app.localId == aLocalId) { 1.171 + return new mozIApplication(app); 1.172 + } 1.173 + } 1.174 + 1.175 + return null; 1.176 + }, 1.177 + 1.178 + getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) { 1.179 + debug("getManifestURLByLocalId " + aLocalId); 1.180 + for (let id in aApps) { 1.181 + let app = aApps[id]; 1.182 + if (app.localId == aLocalId) { 1.183 + return app.manifestURL; 1.184 + } 1.185 + } 1.186 + 1.187 + return ""; 1.188 + }, 1.189 + 1.190 + getCoreAppsBasePath: function getCoreAppsBasePath() { 1.191 + debug("getCoreAppsBasePath()"); 1.192 + try { 1.193 + return FileUtils.getDir("coreAppsDir", ["webapps"], false).path; 1.194 + } catch(e) { 1.195 + return null; 1.196 + } 1.197 + }, 1.198 + 1.199 + getAppInfo: function getAppInfo(aApps, aAppId) { 1.200 + let app = aApps[aAppId]; 1.201 + 1.202 + if (!app) { 1.203 + debug("No webapp for " + aAppId); 1.204 + return null; 1.205 + } 1.206 + 1.207 + // We can have 3rd party apps that are non-removable, 1.208 + // so we can't use the 'removable' property for isCoreApp 1.209 + // Instead, we check if the app is installed under /system/b2g 1.210 + let isCoreApp = false; 1.211 + 1.212 +#ifdef MOZ_WIDGET_GONK 1.213 + isCoreApp = app.basePath == this.getCoreAppsBasePath(); 1.214 +#endif 1.215 + debug(app.basePath + " isCoreApp: " + isCoreApp); 1.216 + return { "path": WebappOSUtils.getPackagePath(app), 1.217 + "isCoreApp": isCoreApp }; 1.218 + }, 1.219 + 1.220 + /** 1.221 + * Remove potential HTML tags from displayable fields in the manifest. 1.222 + * We check name, description, developer name, and permission description 1.223 + */ 1.224 + sanitizeManifest: function(aManifest) { 1.225 + let sanitizer = Cc["@mozilla.org/parserutils;1"] 1.226 + .getService(Ci.nsIParserUtils); 1.227 + if (!sanitizer) { 1.228 + return; 1.229 + } 1.230 + 1.231 + function sanitize(aStr) { 1.232 + return sanitizer.convertToPlainText(aStr, 1.233 + Ci.nsIDocumentEncoder.OutputRaw, 0); 1.234 + } 1.235 + 1.236 + function sanitizeEntryPoint(aRoot) { 1.237 + aRoot.name = sanitize(aRoot.name); 1.238 + 1.239 + if (aRoot.description) { 1.240 + aRoot.description = sanitize(aRoot.description); 1.241 + } 1.242 + 1.243 + if (aRoot.developer && aRoot.developer.name) { 1.244 + aRoot.developer.name = sanitize(aRoot.developer.name); 1.245 + } 1.246 + 1.247 + if (aRoot.permissions) { 1.248 + for (let permission in aRoot.permissions) { 1.249 + if (aRoot.permissions[permission].description) { 1.250 + aRoot.permissions[permission].description = 1.251 + sanitize(aRoot.permissions[permission].description); 1.252 + } 1.253 + } 1.254 + } 1.255 + } 1.256 + 1.257 + // First process the main section, then the entry points. 1.258 + sanitizeEntryPoint(aManifest); 1.259 + 1.260 + if (aManifest.entry_points) { 1.261 + for (let entry in aManifest.entry_points) { 1.262 + sanitizeEntryPoint(aManifest.entry_points[entry]); 1.263 + } 1.264 + } 1.265 + }, 1.266 + 1.267 + /** 1.268 + * From https://developer.mozilla.org/en/OpenWebApps/The_Manifest 1.269 + * Only the name property is mandatory. 1.270 + */ 1.271 + checkManifest: function(aManifest, app) { 1.272 + if (aManifest.name == undefined) 1.273 + return false; 1.274 + 1.275 + this.sanitizeManifest(aManifest); 1.276 + 1.277 + // launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute 1.278 + if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path)) 1.279 + return false; 1.280 + 1.281 + function checkAbsoluteEntryPoints(entryPoints) { 1.282 + for (let name in entryPoints) { 1.283 + if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) { 1.284 + return true; 1.285 + } 1.286 + } 1.287 + return false; 1.288 + } 1.289 + 1.290 + if (checkAbsoluteEntryPoints(aManifest.entry_points)) 1.291 + return false; 1.292 + 1.293 + for (let localeName in aManifest.locales) { 1.294 + if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) { 1.295 + return false; 1.296 + } 1.297 + } 1.298 + 1.299 + if (aManifest.activities) { 1.300 + for (let activityName in aManifest.activities) { 1.301 + let activity = aManifest.activities[activityName]; 1.302 + if (activity.href && isAbsoluteURI(activity.href)) { 1.303 + return false; 1.304 + } 1.305 + } 1.306 + } 1.307 + 1.308 + // |messages| is an array of items, where each item is either a string or 1.309 + // a {name: href} object. 1.310 + let messages = aManifest.messages; 1.311 + if (messages) { 1.312 + if (!Array.isArray(messages)) { 1.313 + return false; 1.314 + } 1.315 + for (let item of aManifest.messages) { 1.316 + if (typeof item == "object") { 1.317 + let keys = Object.keys(item); 1.318 + if (keys.length != 1) { 1.319 + return false; 1.320 + } 1.321 + if (isAbsoluteURI(item[keys[0]])) { 1.322 + return false; 1.323 + } 1.324 + } 1.325 + } 1.326 + } 1.327 + 1.328 + // The 'size' field must be a positive integer. 1.329 + if (aManifest.size) { 1.330 + aManifest.size = parseInt(aManifest.size); 1.331 + if (Number.isNaN(aManifest.size) || aManifest.size < 0) { 1.332 + return false; 1.333 + } 1.334 + } 1.335 + 1.336 + // The 'role' field must be a string. 1.337 + if (aManifest.role && (typeof aManifest.role !== "string")) { 1.338 + return false; 1.339 + } 1.340 + return true; 1.341 + }, 1.342 + 1.343 + checkManifestContentType: function 1.344 + checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) { 1.345 + let hadCharset = { }; 1.346 + let charset = { }; 1.347 + let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil); 1.348 + let contentType = netutil.parseContentType(aContentType, charset, hadCharset); 1.349 + if (aInstallOrigin != aWebappOrigin && 1.350 + contentType != "application/x-web-app-manifest+json") { 1.351 + return false; 1.352 + } 1.353 + return true; 1.354 + }, 1.355 + 1.356 + /** 1.357 + * Method to apply modifications to webapp manifests file saved internally. 1.358 + * For now, only ensure app can't rename itself. 1.359 + */ 1.360 + ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) { 1.361 + // Ensure that app name can't be updated 1.362 + aNewManifest.name = aApp.name; 1.363 + 1.364 + // Nor through localized names 1.365 + if ('locales' in aNewManifest) { 1.366 + let defaultName = new ManifestHelper(aOldManifest, aApp.origin).name; 1.367 + for (let locale in aNewManifest.locales) { 1.368 + let entry = aNewManifest.locales[locale]; 1.369 + if (!entry.name) { 1.370 + continue; 1.371 + } 1.372 + // In case previous manifest didn't had a name, 1.373 + // we use the default app name 1.374 + let localizedName = defaultName; 1.375 + if (aOldManifest && 'locales' in aOldManifest && 1.376 + locale in aOldManifest.locales) { 1.377 + localizedName = aOldManifest.locales[locale].name; 1.378 + } 1.379 + entry.name = localizedName; 1.380 + } 1.381 + } 1.382 + }, 1.383 + 1.384 + /** 1.385 + * Determines whether the manifest allows installs for the given origin. 1.386 + * @param object aManifest 1.387 + * @param string aInstallOrigin 1.388 + * @return boolean 1.389 + **/ 1.390 + checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) { 1.391 + if (!aManifest.installs_allowed_from) { 1.392 + return true; 1.393 + } 1.394 + 1.395 + function cbCheckAllowedOrigin(aOrigin) { 1.396 + return aOrigin == "*" || aOrigin == aInstallOrigin; 1.397 + } 1.398 + 1.399 + return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin); 1.400 + }, 1.401 + 1.402 + /** 1.403 + * Determine the type of app (app, privileged, certified) 1.404 + * that is installed by the manifest 1.405 + * @param object aManifest 1.406 + * @returns integer 1.407 + **/ 1.408 + getAppManifestStatus: function getAppManifestStatus(aManifest) { 1.409 + let type = aManifest.type || "web"; 1.410 + 1.411 + switch(type) { 1.412 + case "web": 1.413 + return Ci.nsIPrincipal.APP_STATUS_INSTALLED; 1.414 + case "privileged": 1.415 + return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED; 1.416 + case "certified": 1.417 + return Ci.nsIPrincipal.APP_STATUS_CERTIFIED; 1.418 + default: 1.419 + throw new Error("Webapps.jsm: Undetermined app manifest type"); 1.420 + } 1.421 + }, 1.422 + 1.423 + /** 1.424 + * Determines if an update or a factory reset occured. 1.425 + */ 1.426 + isFirstRun: function isFirstRun(aPrefBranch) { 1.427 + let savedmstone = null; 1.428 + try { 1.429 + savedmstone = aPrefBranch.getCharPref("gecko.mstone"); 1.430 + } catch (e) {} 1.431 + 1.432 + let mstone = Services.appinfo.platformVersion; 1.433 + 1.434 + let savedBuildID = null; 1.435 + try { 1.436 + savedBuildID = aPrefBranch.getCharPref("gecko.buildID"); 1.437 + } catch (e) {} 1.438 + 1.439 + let buildID = Services.appinfo.platformBuildID; 1.440 + 1.441 + aPrefBranch.setCharPref("gecko.mstone", mstone); 1.442 + aPrefBranch.setCharPref("gecko.buildID", buildID); 1.443 + 1.444 + return ((mstone != savedmstone) || (buildID != savedBuildID)); 1.445 + }, 1.446 + 1.447 + /** 1.448 + * Check if two manifests have the same set of properties and that the 1.449 + * values of these properties are the same, in each locale. 1.450 + * Manifests here are raw json ones. 1.451 + */ 1.452 + compareManifests: function compareManifests(aManifest1, aManifest2) { 1.453 + // 1. check if we have the same locales in both manifests. 1.454 + let locales1 = []; 1.455 + let locales2 = []; 1.456 + if (aManifest1.locales) { 1.457 + for (let locale in aManifest1.locales) { 1.458 + locales1.push(locale); 1.459 + } 1.460 + } 1.461 + if (aManifest2.locales) { 1.462 + for (let locale in aManifest2.locales) { 1.463 + locales2.push(locale); 1.464 + } 1.465 + } 1.466 + if (locales1.sort().join() !== locales2.sort().join()) { 1.467 + return false; 1.468 + } 1.469 + 1.470 + // Helper function to check the app name and developer information for 1.471 + // two given roots. 1.472 + let checkNameAndDev = function(aRoot1, aRoot2) { 1.473 + let name1 = aRoot1.name; 1.474 + let name2 = aRoot2.name; 1.475 + if (name1 !== name2) { 1.476 + return false; 1.477 + } 1.478 + 1.479 + let dev1 = aRoot1.developer; 1.480 + let dev2 = aRoot2.developer; 1.481 + if ((dev1 && !dev2) || (dev2 && !dev1)) { 1.482 + return false; 1.483 + } 1.484 + 1.485 + return (!dev1 && !dev2) || 1.486 + (dev1.name === dev2.name && dev1.url === dev2.url); 1.487 + } 1.488 + 1.489 + // 2. For each locale, check if the name and dev info are the same. 1.490 + if (!checkNameAndDev(aManifest1, aManifest2)) { 1.491 + return false; 1.492 + } 1.493 + 1.494 + for (let locale in aManifest1.locales) { 1.495 + if (!checkNameAndDev(aManifest1.locales[locale], 1.496 + aManifest2.locales[locale])) { 1.497 + return false; 1.498 + } 1.499 + } 1.500 + 1.501 + // Nothing failed. 1.502 + return true; 1.503 + }, 1.504 + 1.505 + // Asynchronously loads a JSON file. aPath is a string representing the path 1.506 + // of the file to be read. 1.507 + loadJSONAsync: function(aPath) { 1.508 + let deferred = Promise.defer(); 1.509 + 1.510 + try { 1.511 + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); 1.512 + file.initWithPath(aPath); 1.513 + 1.514 + let channel = NetUtil.newChannel(file); 1.515 + channel.contentType = "application/json"; 1.516 + 1.517 + NetUtil.asyncFetch(channel, function(aStream, aResult) { 1.518 + if (!Components.isSuccessCode(aResult)) { 1.519 + deferred.resolve(null); 1.520 + 1.521 + if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) { 1.522 + // We expect this under certain circumstances, like for webapps.json 1.523 + // on firstrun, so we return early without reporting an error. 1.524 + return; 1.525 + } 1.526 + 1.527 + Cu.reportError("AppsUtils: Could not read from json file " + aPath); 1.528 + return; 1.529 + } 1.530 + 1.531 + try { 1.532 + // Obtain a converter to read from a UTF-8 encoded input stream. 1.533 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.534 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.535 + converter.charset = "UTF-8"; 1.536 + 1.537 + // Read json file into a string 1.538 + let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream, 1.539 + aStream.available()) || "")); 1.540 + aStream.close(); 1.541 + 1.542 + deferred.resolve(data); 1.543 + } catch (ex) { 1.544 + Cu.reportError("AppsUtils: Could not parse JSON: " + 1.545 + aPath + " " + ex + "\n" + ex.stack); 1.546 + deferred.resolve(null); 1.547 + } 1.548 + }); 1.549 + } catch (ex) { 1.550 + Cu.reportError("AppsUtils: Could not read from " + 1.551 + aPath + " : " + ex + "\n" + ex.stack); 1.552 + deferred.resolve(null); 1.553 + } 1.554 + 1.555 + return deferred.promise; 1.556 + }, 1.557 + 1.558 + // Returns the MD5 hash of a string. 1.559 + computeHash: function(aString) { 1.560 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.561 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.562 + converter.charset = "UTF-8"; 1.563 + let result = {}; 1.564 + // Data is an array of bytes. 1.565 + let data = converter.convertToByteArray(aString, result); 1.566 + 1.567 + let hasher = Cc["@mozilla.org/security/hash;1"] 1.568 + .createInstance(Ci.nsICryptoHash); 1.569 + hasher.init(hasher.MD5); 1.570 + hasher.update(data, data.length); 1.571 + // We're passing false to get the binary hash and not base64. 1.572 + let hash = hasher.finish(false); 1.573 + 1.574 + function toHexString(charCode) { 1.575 + return ("0" + charCode.toString(16)).slice(-2); 1.576 + } 1.577 + 1.578 + // Convert the binary hash data to a hex string. 1.579 + return [toHexString(hash.charCodeAt(i)) for (i in hash)].join(""); 1.580 + } 1.581 +} 1.582 + 1.583 +/** 1.584 + * Helper object to access manifest information with locale support 1.585 + */ 1.586 +this.ManifestHelper = function(aManifest, aOrigin) { 1.587 + this._origin = Services.io.newURI(aOrigin, null, null); 1.588 + this._manifest = aManifest; 1.589 + let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"].getService(Ci.nsIXULChromeRegistry) 1.590 + .QueryInterface(Ci.nsIToolkitChromeRegistry); 1.591 + let locale = chrome.getSelectedLocale("global").toLowerCase(); 1.592 + this._localeRoot = this._manifest; 1.593 + 1.594 + if (this._manifest.locales && this._manifest.locales[locale]) { 1.595 + this._localeRoot = this._manifest.locales[locale]; 1.596 + } 1.597 + else if (this._manifest.locales) { 1.598 + // try with the language part of the locale ("en" for en-GB) only 1.599 + let lang = locale.split('-')[0]; 1.600 + if (lang != locale && this._manifest.locales[lang]) 1.601 + this._localeRoot = this._manifest.locales[lang]; 1.602 + } 1.603 +}; 1.604 + 1.605 +ManifestHelper.prototype = { 1.606 + _localeProp: function(aProp) { 1.607 + if (this._localeRoot[aProp] != undefined) 1.608 + return this._localeRoot[aProp]; 1.609 + return this._manifest[aProp]; 1.610 + }, 1.611 + 1.612 + get name() { 1.613 + return this._localeProp("name"); 1.614 + }, 1.615 + 1.616 + get description() { 1.617 + return this._localeProp("description"); 1.618 + }, 1.619 + 1.620 + get type() { 1.621 + return this._localeProp("type"); 1.622 + }, 1.623 + 1.624 + get version() { 1.625 + return this._localeProp("version"); 1.626 + }, 1.627 + 1.628 + get launch_path() { 1.629 + return this._localeProp("launch_path"); 1.630 + }, 1.631 + 1.632 + get developer() { 1.633 + // Default to {} in order to avoid exception in code 1.634 + // that doesn't check for null `developer` 1.635 + return this._localeProp("developer") || {}; 1.636 + }, 1.637 + 1.638 + get icons() { 1.639 + return this._localeProp("icons"); 1.640 + }, 1.641 + 1.642 + get appcache_path() { 1.643 + return this._localeProp("appcache_path"); 1.644 + }, 1.645 + 1.646 + get orientation() { 1.647 + return this._localeProp("orientation"); 1.648 + }, 1.649 + 1.650 + get package_path() { 1.651 + return this._localeProp("package_path"); 1.652 + }, 1.653 + 1.654 + get size() { 1.655 + return this._manifest["size"] || 0; 1.656 + }, 1.657 + 1.658 + get permissions() { 1.659 + if (this._manifest.permissions) { 1.660 + return this._manifest.permissions; 1.661 + } 1.662 + return {}; 1.663 + }, 1.664 + 1.665 + get biggestIconURL() { 1.666 + let icons = this._localeProp("icons"); 1.667 + if (!icons) { 1.668 + return null; 1.669 + } 1.670 + 1.671 + let iconSizes = Object.keys(icons); 1.672 + if (iconSizes.length == 0) { 1.673 + return null; 1.674 + } 1.675 + 1.676 + iconSizes.sort((a, b) => a - b); 1.677 + let biggestIconSize = iconSizes.pop(); 1.678 + let biggestIcon = icons[biggestIconSize]; 1.679 + let biggestIconURL = this._origin.resolve(biggestIcon); 1.680 + 1.681 + return biggestIconURL; 1.682 + }, 1.683 + 1.684 + iconURLForSize: function(aSize) { 1.685 + let icons = this._localeProp("icons"); 1.686 + if (!icons) 1.687 + return null; 1.688 + let dist = 100000; 1.689 + let icon = null; 1.690 + for (let size in icons) { 1.691 + let iSize = parseInt(size); 1.692 + if (Math.abs(iSize - aSize) < dist) { 1.693 + icon = this._origin.resolve(icons[size]); 1.694 + dist = Math.abs(iSize - aSize); 1.695 + } 1.696 + } 1.697 + return icon; 1.698 + }, 1.699 + 1.700 + fullLaunchPath: function(aStartPoint) { 1.701 + // If no start point is specified, we use the root launch path. 1.702 + // In all error cases, we just return null. 1.703 + if ((aStartPoint || "") === "") { 1.704 + return this._origin.resolve(this._localeProp("launch_path") || ""); 1.705 + } 1.706 + 1.707 + // Search for the l10n entry_points property. 1.708 + let entryPoints = this._localeProp("entry_points"); 1.709 + if (!entryPoints) { 1.710 + return null; 1.711 + } 1.712 + 1.713 + if (entryPoints[aStartPoint]) { 1.714 + return this._origin.resolve(entryPoints[aStartPoint].launch_path || ""); 1.715 + } 1.716 + 1.717 + return null; 1.718 + }, 1.719 + 1.720 + resolveFromOrigin: function(aURI) { 1.721 + // This should be enforced higher up, but check it here just in case. 1.722 + if (isAbsoluteURI(aURI)) { 1.723 + throw new Error("Webapps.jsm: non-relative URI passed to resolveFromOrigin"); 1.724 + } 1.725 + return this._origin.resolve(aURI); 1.726 + }, 1.727 + 1.728 + fullAppcachePath: function() { 1.729 + let appcachePath = this._localeProp("appcache_path"); 1.730 + return this._origin.resolve(appcachePath ? appcachePath : ""); 1.731 + }, 1.732 + 1.733 + fullPackagePath: function() { 1.734 + let packagePath = this._localeProp("package_path"); 1.735 + return this._origin.resolve(packagePath ? packagePath : ""); 1.736 + }, 1.737 + 1.738 + get role() { 1.739 + return this._manifest.role || ""; 1.740 + } 1.741 +}