|
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/. */ |
|
4 |
|
5 "use strict"; |
|
6 |
|
7 const Cu = Components.utils; |
|
8 const Cc = Components.classes; |
|
9 const Ci = Components.interfaces; |
|
10 const Cr = Components.results; |
|
11 |
|
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"); |
|
17 |
|
18 XPCOMUtils.defineLazyModuleGetter(this, "FileUtils", |
|
19 "resource://gre/modules/FileUtils.jsm"); |
|
20 |
|
21 XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils", |
|
22 "resource://gre/modules/WebappOSUtils.jsm"); |
|
23 |
|
24 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", |
|
25 "resource://gre/modules/NetUtil.jsm"); |
|
26 |
|
27 // Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js |
|
28 |
|
29 this.EXPORTED_SYMBOLS = ["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"]; |
|
30 |
|
31 function debug(s) { |
|
32 //dump("-*- AppsUtils.jsm: " + s + "\n"); |
|
33 } |
|
34 |
|
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 } |
|
41 |
|
42 this.mozIApplication = function(aApp) { |
|
43 _setAppProperties(this, aApp); |
|
44 } |
|
45 |
|
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 }, |
|
60 |
|
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 } |
|
68 |
|
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 } |
|
105 |
|
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 }, |
|
113 |
|
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 } |
|
125 |
|
126 return null; |
|
127 }, |
|
128 |
|
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 } |
|
136 |
|
137 return Ci.nsIScriptSecurityManager.NO_APP_ID; |
|
138 }, |
|
139 |
|
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 } |
|
147 |
|
148 return Ci.nsIScriptSecurityManager.NO_APP_ID; |
|
149 }, |
|
150 |
|
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 } |
|
159 |
|
160 return ""; |
|
161 }, |
|
162 |
|
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 } |
|
171 |
|
172 return null; |
|
173 }, |
|
174 |
|
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 } |
|
183 |
|
184 return ""; |
|
185 }, |
|
186 |
|
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 }, |
|
195 |
|
196 getAppInfo: function getAppInfo(aApps, aAppId) { |
|
197 let app = aApps[aAppId]; |
|
198 |
|
199 if (!app) { |
|
200 debug("No webapp for " + aAppId); |
|
201 return null; |
|
202 } |
|
203 |
|
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; |
|
208 |
|
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 }, |
|
216 |
|
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 } |
|
227 |
|
228 function sanitize(aStr) { |
|
229 return sanitizer.convertToPlainText(aStr, |
|
230 Ci.nsIDocumentEncoder.OutputRaw, 0); |
|
231 } |
|
232 |
|
233 function sanitizeEntryPoint(aRoot) { |
|
234 aRoot.name = sanitize(aRoot.name); |
|
235 |
|
236 if (aRoot.description) { |
|
237 aRoot.description = sanitize(aRoot.description); |
|
238 } |
|
239 |
|
240 if (aRoot.developer && aRoot.developer.name) { |
|
241 aRoot.developer.name = sanitize(aRoot.developer.name); |
|
242 } |
|
243 |
|
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 } |
|
253 |
|
254 // First process the main section, then the entry points. |
|
255 sanitizeEntryPoint(aManifest); |
|
256 |
|
257 if (aManifest.entry_points) { |
|
258 for (let entry in aManifest.entry_points) { |
|
259 sanitizeEntryPoint(aManifest.entry_points[entry]); |
|
260 } |
|
261 } |
|
262 }, |
|
263 |
|
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; |
|
271 |
|
272 this.sanitizeManifest(aManifest); |
|
273 |
|
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; |
|
277 |
|
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 } |
|
286 |
|
287 if (checkAbsoluteEntryPoints(aManifest.entry_points)) |
|
288 return false; |
|
289 |
|
290 for (let localeName in aManifest.locales) { |
|
291 if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) { |
|
292 return false; |
|
293 } |
|
294 } |
|
295 |
|
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 } |
|
304 |
|
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 } |
|
324 |
|
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 } |
|
332 |
|
333 // The 'role' field must be a string. |
|
334 if (aManifest.role && (typeof aManifest.role !== "string")) { |
|
335 return false; |
|
336 } |
|
337 return true; |
|
338 }, |
|
339 |
|
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 }, |
|
352 |
|
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; |
|
360 |
|
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 }, |
|
380 |
|
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 } |
|
391 |
|
392 function cbCheckAllowedOrigin(aOrigin) { |
|
393 return aOrigin == "*" || aOrigin == aInstallOrigin; |
|
394 } |
|
395 |
|
396 return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin); |
|
397 }, |
|
398 |
|
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"; |
|
407 |
|
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 }, |
|
419 |
|
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) {} |
|
428 |
|
429 let mstone = Services.appinfo.platformVersion; |
|
430 |
|
431 let savedBuildID = null; |
|
432 try { |
|
433 savedBuildID = aPrefBranch.getCharPref("gecko.buildID"); |
|
434 } catch (e) {} |
|
435 |
|
436 let buildID = Services.appinfo.platformBuildID; |
|
437 |
|
438 aPrefBranch.setCharPref("gecko.mstone", mstone); |
|
439 aPrefBranch.setCharPref("gecko.buildID", buildID); |
|
440 |
|
441 return ((mstone != savedmstone) || (buildID != savedBuildID)); |
|
442 }, |
|
443 |
|
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 } |
|
466 |
|
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 } |
|
475 |
|
476 let dev1 = aRoot1.developer; |
|
477 let dev2 = aRoot2.developer; |
|
478 if ((dev1 && !dev2) || (dev2 && !dev1)) { |
|
479 return false; |
|
480 } |
|
481 |
|
482 return (!dev1 && !dev2) || |
|
483 (dev1.name === dev2.name && dev1.url === dev2.url); |
|
484 } |
|
485 |
|
486 // 2. For each locale, check if the name and dev info are the same. |
|
487 if (!checkNameAndDev(aManifest1, aManifest2)) { |
|
488 return false; |
|
489 } |
|
490 |
|
491 for (let locale in aManifest1.locales) { |
|
492 if (!checkNameAndDev(aManifest1.locales[locale], |
|
493 aManifest2.locales[locale])) { |
|
494 return false; |
|
495 } |
|
496 } |
|
497 |
|
498 // Nothing failed. |
|
499 return true; |
|
500 }, |
|
501 |
|
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(); |
|
506 |
|
507 try { |
|
508 let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); |
|
509 file.initWithPath(aPath); |
|
510 |
|
511 let channel = NetUtil.newChannel(file); |
|
512 channel.contentType = "application/json"; |
|
513 |
|
514 NetUtil.asyncFetch(channel, function(aStream, aResult) { |
|
515 if (!Components.isSuccessCode(aResult)) { |
|
516 deferred.resolve(null); |
|
517 |
|
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 } |
|
523 |
|
524 Cu.reportError("AppsUtils: Could not read from json file " + aPath); |
|
525 return; |
|
526 } |
|
527 |
|
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"; |
|
533 |
|
534 // Read json file into a string |
|
535 let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream, |
|
536 aStream.available()) || "")); |
|
537 aStream.close(); |
|
538 |
|
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 } |
|
551 |
|
552 return deferred.promise; |
|
553 }, |
|
554 |
|
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); |
|
563 |
|
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); |
|
570 |
|
571 function toHexString(charCode) { |
|
572 return ("0" + charCode.toString(16)).slice(-2); |
|
573 } |
|
574 |
|
575 // Convert the binary hash data to a hex string. |
|
576 return [toHexString(hash.charCodeAt(i)) for (i in hash)].join(""); |
|
577 } |
|
578 } |
|
579 |
|
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; |
|
590 |
|
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 }; |
|
601 |
|
602 ManifestHelper.prototype = { |
|
603 _localeProp: function(aProp) { |
|
604 if (this._localeRoot[aProp] != undefined) |
|
605 return this._localeRoot[aProp]; |
|
606 return this._manifest[aProp]; |
|
607 }, |
|
608 |
|
609 get name() { |
|
610 return this._localeProp("name"); |
|
611 }, |
|
612 |
|
613 get description() { |
|
614 return this._localeProp("description"); |
|
615 }, |
|
616 |
|
617 get type() { |
|
618 return this._localeProp("type"); |
|
619 }, |
|
620 |
|
621 get version() { |
|
622 return this._localeProp("version"); |
|
623 }, |
|
624 |
|
625 get launch_path() { |
|
626 return this._localeProp("launch_path"); |
|
627 }, |
|
628 |
|
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 }, |
|
634 |
|
635 get icons() { |
|
636 return this._localeProp("icons"); |
|
637 }, |
|
638 |
|
639 get appcache_path() { |
|
640 return this._localeProp("appcache_path"); |
|
641 }, |
|
642 |
|
643 get orientation() { |
|
644 return this._localeProp("orientation"); |
|
645 }, |
|
646 |
|
647 get package_path() { |
|
648 return this._localeProp("package_path"); |
|
649 }, |
|
650 |
|
651 get size() { |
|
652 return this._manifest["size"] || 0; |
|
653 }, |
|
654 |
|
655 get permissions() { |
|
656 if (this._manifest.permissions) { |
|
657 return this._manifest.permissions; |
|
658 } |
|
659 return {}; |
|
660 }, |
|
661 |
|
662 get biggestIconURL() { |
|
663 let icons = this._localeProp("icons"); |
|
664 if (!icons) { |
|
665 return null; |
|
666 } |
|
667 |
|
668 let iconSizes = Object.keys(icons); |
|
669 if (iconSizes.length == 0) { |
|
670 return null; |
|
671 } |
|
672 |
|
673 iconSizes.sort((a, b) => a - b); |
|
674 let biggestIconSize = iconSizes.pop(); |
|
675 let biggestIcon = icons[biggestIconSize]; |
|
676 let biggestIconURL = this._origin.resolve(biggestIcon); |
|
677 |
|
678 return biggestIconURL; |
|
679 }, |
|
680 |
|
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 }, |
|
696 |
|
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 } |
|
703 |
|
704 // Search for the l10n entry_points property. |
|
705 let entryPoints = this._localeProp("entry_points"); |
|
706 if (!entryPoints) { |
|
707 return null; |
|
708 } |
|
709 |
|
710 if (entryPoints[aStartPoint]) { |
|
711 return this._origin.resolve(entryPoints[aStartPoint].launch_path || ""); |
|
712 } |
|
713 |
|
714 return null; |
|
715 }, |
|
716 |
|
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 }, |
|
724 |
|
725 fullAppcachePath: function() { |
|
726 let appcachePath = this._localeProp("appcache_path"); |
|
727 return this._origin.resolve(appcachePath ? appcachePath : ""); |
|
728 }, |
|
729 |
|
730 fullPackagePath: function() { |
|
731 let packagePath = this._localeProp("package_path"); |
|
732 return this._origin.resolve(packagePath ? packagePath : ""); |
|
733 }, |
|
734 |
|
735 get role() { |
|
736 return this._manifest.role || ""; |
|
737 } |
|
738 } |