mirror of
https://repo.dactyloidae.xyz/Dactyloidae/UXP.git
synced 2026-10-05 06:47:31 +09:00
import FIREFOX_52_6_0esr_RELEASE from mozilla-esr52 hg repo
This commit is contained in:
commit
dcd9973243
150858 changed files with 23884658 additions and 0 deletions
114
dom/apps/AppsService.js
Normal file
114
dom/apps/AppsService.js
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict"
|
||||
|
||||
function debug(s) {
|
||||
//dump("-*- AppsService.js: " + s + "\n");
|
||||
}
|
||||
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
const APPS_SERVICE_CID = Components.ID("{05072afa-92fe-45bf-ae22-39b69c117058}");
|
||||
|
||||
function AppsService()
|
||||
{
|
||||
debug("AppsService Constructor");
|
||||
this.inParent = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime)
|
||||
.processType == Ci.nsIXULRuntime.PROCESS_TYPE_DEFAULT;
|
||||
debug("inParent: " + this.inParent);
|
||||
if (!this.inParent) {
|
||||
Cu.import("resource://gre/modules/AppsServiceChild.jsm");
|
||||
}
|
||||
}
|
||||
|
||||
AppsService.prototype = {
|
||||
|
||||
isInvalidId: function(localId) {
|
||||
return (localId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
|
||||
localId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID);
|
||||
},
|
||||
|
||||
getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
|
||||
debug("GetAppByManifestURL( " + aManifestURL + " )");
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getManifestFor: function getManifestFor(aManifestURL) {
|
||||
debug("getManifestFor(" + aManifestURL + ")");
|
||||
if (this.inParent) {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
} else {
|
||||
return Promise.reject(
|
||||
new Error("Calling getManifestFor() from child is not supported"));
|
||||
}
|
||||
},
|
||||
|
||||
getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
|
||||
debug("getAppLocalIdByManifestURL( " + aManifestURL + " )");
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getAppLocalIdByStoreId: function getAppLocalIdByStoreId(aStoreId) {
|
||||
debug("getAppLocalIdByStoreId( " + aStoreId + " )");
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getAppByLocalId: function getAppByLocalId(aLocalId) {
|
||||
debug("getAppByLocalId( " + aLocalId + " )");
|
||||
if (this.isInvalidId(aLocalId)) {
|
||||
return null;
|
||||
}
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
|
||||
debug("getManifestURLByLocalId( " + aLocalId + " )");
|
||||
if (this.isInvalidId(aLocalId)) {
|
||||
return null;
|
||||
}
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getCoreAppsBasePath: function getCoreAppsBasePath() {
|
||||
debug("getCoreAppsBasePath()");
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getWebAppsBasePath: function getWebAppsBasePath() {
|
||||
debug("getWebAppsBasePath()");
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
areAnyAppsInstalled: function() {
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getAppInfo: function getAppInfo(aAppId) {
|
||||
debug("getAppInfo()");
|
||||
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
||||
},
|
||||
|
||||
getScopeByLocalId: function(aLocalId) {
|
||||
debug("getScopeByLocalId( " + aLocalId + " )");
|
||||
if (this.isInvalidId(aLocalId)) {
|
||||
return null;
|
||||
}
|
||||
// TODO : implement properly!
|
||||
// We just return null for now to not break PushService.jsm
|
||||
return null;
|
||||
},
|
||||
|
||||
classID : APPS_SERVICE_CID,
|
||||
QueryInterface : XPCOMUtils.generateQI([Ci.nsIAppsService])
|
||||
}
|
||||
|
||||
this.NSGetFactory = XPCOMUtils.generateNSGetFactory([AppsService])
|
||||
2
dom/apps/AppsService.manifest
Normal file
2
dom/apps/AppsService.manifest
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
component {05072afa-92fe-45bf-ae22-39b69c117058} AppsService.js
|
||||
contract @mozilla.org/AppsService;1 {05072afa-92fe-45bf-ae22-39b69c117058}
|
||||
408
dom/apps/AppsServiceChild.jsm
Normal file
408
dom/apps/AppsServiceChild.jsm
Normal file
|
|
@ -0,0 +1,408 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
|
||||
// This module exposes a subset of the functionalities of the parent DOM
|
||||
// Registry to content processes, to be used from the AppsService component.
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry", "WrappedManifestCache"];
|
||||
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
|
||||
function debug(s) {
|
||||
//dump("-*- AppsServiceChild.jsm: " + s + "\n");
|
||||
}
|
||||
|
||||
const APPS_IPC_MSG_NAMES = [
|
||||
"Webapps:AddApp",
|
||||
"Webapps:RemoveApp",
|
||||
"Webapps:UpdateApp",
|
||||
"Webapps:CheckForUpdate:Return:KO",
|
||||
"Webapps:FireEvent",
|
||||
"Webapps:UpdateState"
|
||||
];
|
||||
|
||||
// A simple cache for the wrapped manifests.
|
||||
this.WrappedManifestCache = {
|
||||
_cache: { },
|
||||
|
||||
// Gets an entry from the cache, and populates the cache if needed.
|
||||
get: function mcache_get(aManifestURL, aManifest, aWindow, aInnerWindowID) {
|
||||
if (!aManifest) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!(aManifestURL in this._cache)) {
|
||||
this._cache[aManifestURL] = { };
|
||||
}
|
||||
|
||||
let winObjs = this._cache[aManifestURL];
|
||||
if (!(aInnerWindowID in winObjs)) {
|
||||
winObjs[aInnerWindowID] = Cu.cloneInto(aManifest, aWindow);
|
||||
}
|
||||
|
||||
return winObjs[aInnerWindowID];
|
||||
},
|
||||
|
||||
// Invalidates an entry in the cache.
|
||||
evict: function mcache_evict(aManifestURL, aInnerWindowID) {
|
||||
debug("Evicting manifest " + aManifestURL + " window ID " +
|
||||
aInnerWindowID);
|
||||
if (aManifestURL in this._cache) {
|
||||
let winObjs = this._cache[aManifestURL];
|
||||
if (aInnerWindowID in winObjs) {
|
||||
delete winObjs[aInnerWindowID];
|
||||
}
|
||||
|
||||
if (Object.keys(winObjs).length == 0) {
|
||||
delete this._cache[aManifestURL];
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
// Clear the cache on memory pressure.
|
||||
this._cache = { };
|
||||
Cu.forceGC();
|
||||
},
|
||||
|
||||
init: function() {
|
||||
Services.obs.addObserver(this, "memory-pressure", false);
|
||||
}
|
||||
};
|
||||
|
||||
this.WrappedManifestCache.init();
|
||||
|
||||
|
||||
// DOMApplicationRegistry keeps a cache containing a list of apps in the device.
|
||||
// This information is updated with the data received from the main process and
|
||||
// it is queried by the DOM objects to set their state.
|
||||
// This module handle all the messages broadcasted from the parent process,
|
||||
// including DOM events, which are dispatched to the corresponding DOM objects.
|
||||
|
||||
this.DOMApplicationRegistry = {
|
||||
// DOMApps will hold a list of arrays of weak references to
|
||||
// mozIDOMApplication objects indexed by manifest URL.
|
||||
DOMApps: {},
|
||||
|
||||
ready: false,
|
||||
webapps: null,
|
||||
|
||||
init: function init() {
|
||||
this.cpmm = Cc["@mozilla.org/childprocessmessagemanager;1"]
|
||||
.getService(Ci.nsISyncMessageSender);
|
||||
|
||||
APPS_IPC_MSG_NAMES.forEach((function(aMsgName) {
|
||||
this.cpmm.addMessageListener(aMsgName, this);
|
||||
}).bind(this));
|
||||
|
||||
this.webapps = { };
|
||||
// We need a fast mapping from localId -> app, so we add an index.
|
||||
// We also add the manifest to the app object.
|
||||
this.localIdIndex = { };
|
||||
for (let id in this.webapps) {
|
||||
let app = this.webapps[id];
|
||||
this.localIdIndex[app.localId] = app;
|
||||
app.manifest = list.manifests[id];
|
||||
}
|
||||
|
||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||
},
|
||||
|
||||
observe: function(aSubject, aTopic, aData) {
|
||||
// cpmm.addMessageListener causes the DOMApplicationRegistry object to
|
||||
// live forever if we don't clean up properly.
|
||||
this.webapps = null;
|
||||
this.DOMApps = null;
|
||||
|
||||
APPS_IPC_MSG_NAMES.forEach((aMsgName) => {
|
||||
this.cpmm.removeMessageListener(aMsgName, this);
|
||||
});
|
||||
},
|
||||
|
||||
receiveMessage: function receiveMessage(aMessage) {
|
||||
debug("Received " + aMessage.name + " message.");
|
||||
let msg = aMessage.data;
|
||||
switch (aMessage.name) {
|
||||
case "Webapps:AddApp":
|
||||
this.webapps[msg.id] = msg.app;
|
||||
this.localIdIndex[msg.app.localId] = msg.app;
|
||||
if (msg.manifest) {
|
||||
this.webapps[msg.id].manifest = msg.manifest;
|
||||
}
|
||||
break;
|
||||
case "Webapps:RemoveApp":
|
||||
delete this.DOMApps[this.webapps[msg.id].manifestURL];
|
||||
delete this.localIdIndex[this.webapps[msg.id].localId];
|
||||
delete this.webapps[msg.id];
|
||||
break;
|
||||
case "Webapps:UpdateApp":
|
||||
let app = this.webapps[msg.oldId];
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (msg.app) {
|
||||
for (let prop in msg.app) {
|
||||
app[prop] = msg.app[prop];
|
||||
}
|
||||
}
|
||||
|
||||
this.webapps[msg.newId] = app;
|
||||
this.localIdIndex[app.localId] = app;
|
||||
delete this.webapps[msg.oldId];
|
||||
|
||||
let apps = this.DOMApps[msg.app.manifestURL];
|
||||
if (!apps) {
|
||||
return;
|
||||
}
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let domApp = apps[i].get();
|
||||
if (!domApp || domApp._window === null) {
|
||||
apps.splice(i, 1);
|
||||
continue;
|
||||
}
|
||||
domApp._proxy = new Proxy(domApp, {
|
||||
get: function(target, prop) {
|
||||
if (!DOMApplicationRegistry.webapps[msg.newId]) {
|
||||
return;
|
||||
}
|
||||
return DOMApplicationRegistry.webapps[msg.newId][prop];
|
||||
},
|
||||
set: function(target, prop, val) {
|
||||
if (!DOMApplicationRegistry.webapps[msg.newId]) {
|
||||
return;
|
||||
}
|
||||
DOMApplicationRegistry.webapps[msg.newId][prop] = val;
|
||||
return;
|
||||
},
|
||||
});
|
||||
}
|
||||
break;
|
||||
case "Webapps:FireEvent":
|
||||
this._fireEvent(aMessage);
|
||||
break;
|
||||
case "Webapps:UpdateState":
|
||||
this._updateState(msg);
|
||||
break;
|
||||
case "Webapps:CheckForUpdate:Return:KO":
|
||||
let DOMApps = this.DOMApps[msg.manifestURL];
|
||||
if (!DOMApps || !msg.requestID) {
|
||||
return;
|
||||
}
|
||||
DOMApps.forEach((DOMApp) => {
|
||||
let domApp = DOMApp.get();
|
||||
if (domApp && msg.requestID) {
|
||||
domApp._fireRequestResult(aMessage, true /* aIsError */);
|
||||
}
|
||||
});
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* mozIDOMApplication management
|
||||
*/
|
||||
|
||||
// Every time a DOM app is created, we save a weak reference to it that will
|
||||
// be used to dispatch events and fire request results.
|
||||
addDOMApp: function(aApp, aManifestURL, aId) {
|
||||
let weakRef = Cu.getWeakReference(aApp);
|
||||
|
||||
if (!this.DOMApps[aManifestURL]) {
|
||||
this.DOMApps[aManifestURL] = [];
|
||||
}
|
||||
|
||||
let apps = this.DOMApps[aManifestURL];
|
||||
|
||||
// Get rid of dead weak references.
|
||||
for (let i = 0; i < apps.length; i++) {
|
||||
let app = apps[i].get();
|
||||
if (!app || app._window === null) {
|
||||
apps.splice(i, 1);
|
||||
}
|
||||
}
|
||||
|
||||
apps.push(weakRef);
|
||||
|
||||
// Each DOM app contains a proxy object used to build their state. We
|
||||
// return the handler for this proxy object with traps to get and set
|
||||
// app properties kept in the DOMApplicationRegistry app cache.
|
||||
return {
|
||||
get: function(target, prop) {
|
||||
if (!DOMApplicationRegistry.webapps[aId]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (prop in DOMApplicationRegistry.webapps[aId]) {
|
||||
return DOMApplicationRegistry.webapps[aId][prop];
|
||||
}
|
||||
return null;
|
||||
},
|
||||
set: function(target, prop, val) {
|
||||
if (!DOMApplicationRegistry.webapps[aId]) {
|
||||
return;
|
||||
}
|
||||
DOMApplicationRegistry.webapps[aId][prop] = val;
|
||||
return;
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
_fireEvent: function(aMessage) {
|
||||
let msg = aMessage.data;
|
||||
debug("_fireEvent " + JSON.stringify(msg));
|
||||
if (!this.DOMApps || !msg.manifestURL || !msg.eventType) {
|
||||
return;
|
||||
}
|
||||
|
||||
let DOMApps = this.DOMApps[msg.manifestURL];
|
||||
if (!DOMApps) {
|
||||
return;
|
||||
}
|
||||
|
||||
// The parent might ask childs to trigger more than one event in one
|
||||
// shot, so in order to avoid needless IPC we allow an array for the
|
||||
// 'eventType' IPC message field.
|
||||
if (!Array.isArray(msg.eventType)) {
|
||||
msg.eventType = [msg.eventType];
|
||||
}
|
||||
|
||||
DOMApps.forEach((DOMApp) => {
|
||||
let domApp = DOMApp.get();
|
||||
if (!domApp) {
|
||||
return;
|
||||
}
|
||||
msg.eventType.forEach((aEventType) => {
|
||||
if ('on' + aEventType in domApp) {
|
||||
domApp._fireEvent(aEventType);
|
||||
}
|
||||
});
|
||||
|
||||
if (msg.requestID) {
|
||||
aMessage.data.result = msg.manifestURL;
|
||||
domApp._fireRequestResult(aMessage);
|
||||
}
|
||||
});
|
||||
},
|
||||
|
||||
_updateState: function(aMessage) {
|
||||
if (!this.DOMApps || !aMessage.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
let app = this.webapps[aMessage.id];
|
||||
if (!app) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aMessage.app) {
|
||||
for (let prop in aMessage.app) {
|
||||
app[prop] = aMessage.app[prop];
|
||||
}
|
||||
}
|
||||
|
||||
if ("error" in aMessage) {
|
||||
app.downloadError = aMessage.error;
|
||||
}
|
||||
|
||||
if (aMessage.manifest) {
|
||||
app.manifest = aMessage.manifest;
|
||||
// Evict the wrapped manifest cache for all the affected DOM objects.
|
||||
let DOMApps = this.DOMApps[app.manifestURL];
|
||||
if (!DOMApps) {
|
||||
return;
|
||||
}
|
||||
DOMApps.forEach((DOMApp) => {
|
||||
let domApp = DOMApp.get();
|
||||
if (!domApp) {
|
||||
return;
|
||||
}
|
||||
WrappedManifestCache.evict(app.manifestURL, domApp.innerWindowID);
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
getAll: function(aCallback) {
|
||||
debug("getAll()\n");
|
||||
if (!aCallback || typeof aCallback !== "function") {
|
||||
return;
|
||||
}
|
||||
|
||||
let res = [];
|
||||
for (let id in this.webapps) {
|
||||
res.push(this.webapps[id]);
|
||||
}
|
||||
aCallback(res);
|
||||
},
|
||||
|
||||
getAdditionalLanguages: function(aManifestURL) {
|
||||
for (let id in this.webapps) {
|
||||
if (this.webapps[id].manifestURL == aManifestURL) {
|
||||
return this.webapps[id].additionalLanguages || {};
|
||||
}
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
/**
|
||||
* nsIAppsService API
|
||||
*/
|
||||
getAppByManifestURL: function getAppByManifestURL(aManifestURL) {
|
||||
debug("getAppByManifestURL " + aManifestURL);
|
||||
return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
|
||||
},
|
||||
|
||||
getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aManifestURL) {
|
||||
debug("getAppLocalIdByManifestURL " + aManifestURL);
|
||||
return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
|
||||
},
|
||||
|
||||
getAppLocalIdByStoreId: function(aStoreId) {
|
||||
debug("getAppLocalIdByStoreId:" + aStoreId);
|
||||
return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId);
|
||||
},
|
||||
|
||||
getAppByLocalId: function getAppByLocalId(aLocalId) {
|
||||
debug("getAppByLocalId " + aLocalId + " - ready: " + this.ready);
|
||||
let app = this.localIdIndex[aLocalId];
|
||||
if (!app) {
|
||||
debug("Ouch, No app!");
|
||||
return null;
|
||||
}
|
||||
|
||||
return new mozIApplication(app);
|
||||
},
|
||||
|
||||
getManifestURLByLocalId: function getManifestURLByLocalId(aLocalId) {
|
||||
debug("getManifestURLByLocalId " + aLocalId);
|
||||
return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
|
||||
},
|
||||
|
||||
getCoreAppsBasePath: function getCoreAppsBasePath() {
|
||||
debug("getCoreAppsBasePath() not yet supported on child!");
|
||||
return null;
|
||||
},
|
||||
|
||||
getWebAppsBasePath: function getWebAppsBasePath() {
|
||||
debug("getWebAppsBasePath() not yet supported on child!");
|
||||
return null;
|
||||
},
|
||||
|
||||
areAnyAppsInstalled: function() {
|
||||
return AppsUtils.areAnyAppsInstalled(this.webapps);
|
||||
},
|
||||
|
||||
getAppInfo: function getAppInfo(aAppId) {
|
||||
return AppsUtils.getAppInfo(this.webapps, aAppId);
|
||||
}
|
||||
}
|
||||
|
||||
DOMApplicationRegistry.init();
|
||||
904
dom/apps/AppsUtils.jsm
Normal file
904
dom/apps/AppsUtils.jsm
Normal file
|
|
@ -0,0 +1,904 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Cu = Components.utils;
|
||||
const Cc = Components.classes;
|
||||
const Ci = Components.interfaces;
|
||||
const Cr = Components.results;
|
||||
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/Promise.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
|
||||
"resource://gre/modules/NetUtil.jsm");
|
||||
|
||||
XPCOMUtils.defineLazyServiceGetter(this, "appsService",
|
||||
"@mozilla.org/AppsService;1",
|
||||
"nsIAppsService");
|
||||
|
||||
// Shared code for AppsServiceChild.jsm, Webapps.jsm and Webapps.js
|
||||
|
||||
this.EXPORTED_SYMBOLS =
|
||||
["AppsUtils", "ManifestHelper", "isAbsoluteURI", "mozIApplication"];
|
||||
|
||||
function debug(s) {
|
||||
//dump("-*- AppsUtils.jsm: " + s + "\n");
|
||||
}
|
||||
|
||||
this.isAbsoluteURI = function(aURI) {
|
||||
let foo = Services.io.newURI("http://foo", null, null);
|
||||
let bar = Services.io.newURI("http://bar", null, null);
|
||||
return Services.io.newURI(aURI, null, foo).prePath != foo.prePath ||
|
||||
Services.io.newURI(aURI, null, bar).prePath != bar.prePath;
|
||||
}
|
||||
|
||||
this.mozIApplication = function(aApp) {
|
||||
_setAppProperties(this, aApp);
|
||||
}
|
||||
|
||||
mozIApplication.prototype = {
|
||||
hasPermission: function(aPermission) {
|
||||
// This helper checks an URI inside |aApp|'s origin and part of |aApp| has a
|
||||
// specific permission. It is not checking if browsers inside |aApp| have such
|
||||
// permission.
|
||||
let perm = Services.perms.testExactPermissionFromPrincipal(this.principal,
|
||||
aPermission);
|
||||
return (perm === Ci.nsIPermissionManager.ALLOW_ACTION);
|
||||
},
|
||||
|
||||
get principal() {
|
||||
if (this._principal) {
|
||||
return this._principal;
|
||||
}
|
||||
|
||||
this._principal = null;
|
||||
|
||||
try {
|
||||
this._principal = Services.scriptSecurityManager.createCodebasePrincipal(
|
||||
Services.io.newURI(this.origin, null, null),
|
||||
{appId: this.localId});
|
||||
} catch(e) {
|
||||
dump("Could not create app principal " + e + "\n");
|
||||
}
|
||||
|
||||
return this._principal;
|
||||
},
|
||||
|
||||
QueryInterface: function(aIID) {
|
||||
if (aIID.equals(Ci.mozIApplication) ||
|
||||
aIID.equals(Ci.nsISupports))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
function _setAppProperties(aObj, aApp) {
|
||||
aObj.name = aApp.name;
|
||||
aObj.csp = aApp.csp;
|
||||
aObj.installOrigin = aApp.installOrigin;
|
||||
aObj.origin = aApp.origin;
|
||||
#ifdef MOZ_WIDGET_ANDROID
|
||||
aObj.apkPackageName = aApp.apkPackageName;
|
||||
#endif
|
||||
aObj.receipts = aApp.receipts ? JSON.parse(JSON.stringify(aApp.receipts)) : null;
|
||||
aObj.installTime = aApp.installTime;
|
||||
aObj.manifestURL = aApp.manifestURL;
|
||||
aObj.appStatus = aApp.appStatus;
|
||||
aObj.removable = aApp.removable;
|
||||
aObj.id = aApp.id;
|
||||
aObj.localId = aApp.localId;
|
||||
aObj.basePath = aApp.basePath;
|
||||
aObj.progress = aApp.progress || 0.0;
|
||||
aObj.installState = aApp.installState || "installed";
|
||||
aObj.downloadAvailable = aApp.downloadAvailable;
|
||||
aObj.downloading = aApp.downloading;
|
||||
aObj.readyToApplyDownload = aApp.readyToApplyDownload;
|
||||
aObj.downloadSize = aApp.downloadSize || 0;
|
||||
aObj.lastUpdateCheck = aApp.lastUpdateCheck;
|
||||
aObj.updateTime = aApp.updateTime;
|
||||
aObj.etag = aApp.etag;
|
||||
aObj.packageEtag = aApp.packageEtag;
|
||||
aObj.manifestHash = aApp.manifestHash;
|
||||
aObj.packageHash = aApp.packageHash;
|
||||
aObj.staged = aApp.staged;
|
||||
aObj.installerAppId = aApp.installerAppId || Ci.nsIScriptSecurityManager.NO_APP_ID;
|
||||
aObj.installerIsBrowser = !!aApp.installerIsBrowser;
|
||||
aObj.storeId = aApp.storeId || "";
|
||||
aObj.storeVersion = aApp.storeVersion || 0;
|
||||
aObj.role = aApp.role || "";
|
||||
aObj.kind = aApp.kind;
|
||||
aObj.enabled = aApp.enabled !== undefined ? aApp.enabled : true;
|
||||
aObj.sideloaded = aApp.sideloaded;
|
||||
aObj.extensionVersion = aApp.extensionVersion;
|
||||
aObj.blockedStatus =
|
||||
aApp.blockedStatus !== undefined ? aApp.blockedStatus
|
||||
: Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
|
||||
aObj.blocklistId = aApp.blocklistId;
|
||||
}
|
||||
|
||||
this.AppsUtils = {
|
||||
// Clones a app, without the manifest.
|
||||
cloneAppObject: function(aApp) {
|
||||
let obj = {};
|
||||
_setAppProperties(obj, aApp);
|
||||
return obj;
|
||||
},
|
||||
|
||||
// Creates a nsILoadContext object with a given appId and inIsolatedMozBrowser
|
||||
// flag.
|
||||
createLoadContext: function createLoadContext(aAppId, aInIsolatedMozBrowser) {
|
||||
return {
|
||||
associatedWindow: null,
|
||||
topWindow : null,
|
||||
appId: aAppId,
|
||||
isInIsolatedMozBrowserElement: aInIsolatedMozBrowser,
|
||||
originAttributes: {
|
||||
appId: aAppId,
|
||||
inIsolatedMozBrowser: aInIsolatedMozBrowser
|
||||
},
|
||||
usePrivateBrowsing: false,
|
||||
isContent: false,
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext,
|
||||
Ci.nsIInterfaceRequestor,
|
||||
Ci.nsISupports]),
|
||||
getInterface: function(iid) {
|
||||
if (iid.equals(Ci.nsILoadContext))
|
||||
return this;
|
||||
throw Cr.NS_ERROR_NO_INTERFACE;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// Sends data downloaded from aRequestChannel to a file
|
||||
// identified by aId and aFileName.
|
||||
getFile: function(aRequestChannel, aId, aFileName) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
// Staging the file in TmpD until all the checks are done.
|
||||
let file = FileUtils.getFile("TmpD", ["webapps", aId, aFileName], true);
|
||||
|
||||
// We need an output stream to write the channel content to the out file.
|
||||
let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
|
||||
.createInstance(Ci.nsIFileOutputStream);
|
||||
// write, create, truncate
|
||||
outputStream.init(file, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0);
|
||||
let bufferedOutputStream =
|
||||
Cc['@mozilla.org/network/buffered-output-stream;1']
|
||||
.createInstance(Ci.nsIBufferedOutputStream);
|
||||
bufferedOutputStream.init(outputStream, 1024);
|
||||
|
||||
// Create a listener that will give data to the file output stream.
|
||||
let listener = Cc["@mozilla.org/network/simple-stream-listener;1"]
|
||||
.createInstance(Ci.nsISimpleStreamListener);
|
||||
|
||||
listener.init(bufferedOutputStream, {
|
||||
onStartRequest: function(aRequest, aContext) {
|
||||
// Nothing to do there anymore.
|
||||
},
|
||||
|
||||
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
||||
bufferedOutputStream.close();
|
||||
outputStream.close();
|
||||
|
||||
if (!Components.isSuccessCode(aStatusCode)) {
|
||||
deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: true});
|
||||
return;
|
||||
}
|
||||
|
||||
// If we get a 4XX or a 5XX http status, bail out like if we had a
|
||||
// network error.
|
||||
let responseStatus = aRequestChannel.responseStatus;
|
||||
if (responseStatus >= 400 && responseStatus <= 599) {
|
||||
// unrecoverable error, don't bug the user
|
||||
deferred.reject({ msg: "NETWORK_ERROR", downloadAvailable: false});
|
||||
return;
|
||||
}
|
||||
|
||||
deferred.resolve(file);
|
||||
}
|
||||
});
|
||||
aRequestChannel.asyncOpen2(listener);
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
// Eliminate query and hash string.
|
||||
getFilePath: function(aPagePath) {
|
||||
let urlParser = Cc["@mozilla.org/network/url-parser;1?auth=no"]
|
||||
.getService(Ci.nsIURLParser);
|
||||
let uriData = [aPagePath, aPagePath.length, {}, {}, {}, {}, {}, {}];
|
||||
urlParser.parsePath.apply(urlParser, uriData);
|
||||
let [{value: pathPos}, {value: pathLen}] = uriData.slice(2, 4);
|
||||
return aPagePath.substr(pathPos, pathLen);
|
||||
},
|
||||
|
||||
getAppByManifestURL: function getAppByManifestURL(aApps, aManifestURL) {
|
||||
debug("getAppByManifestURL " + aManifestURL);
|
||||
// This could be O(1) if |webapps| was a dictionary indexed on manifestURL
|
||||
// which should be the unique app identifier.
|
||||
// It's currently O(n).
|
||||
for (let id in aApps) {
|
||||
let app = aApps[id];
|
||||
if (app.manifestURL == aManifestURL) {
|
||||
return new mozIApplication(app);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getManifestFor: function getManifestFor(aManifestURL) {
|
||||
debug("getManifestFor(" + aManifestURL + ")");
|
||||
return DOMApplicationRegistry.getManifestFor(aManifestURL);
|
||||
},
|
||||
|
||||
getAppLocalIdByManifestURL: function getAppLocalIdByManifestURL(aApps, aManifestURL) {
|
||||
debug("getAppLocalIdByManifestURL " + aManifestURL);
|
||||
for (let id in aApps) {
|
||||
if (aApps[id].manifestURL == aManifestURL) {
|
||||
return aApps[id].localId;
|
||||
}
|
||||
}
|
||||
|
||||
return Ci.nsIScriptSecurityManager.NO_APP_ID;
|
||||
},
|
||||
|
||||
getAppLocalIdByStoreId: function(aApps, aStoreId) {
|
||||
debug("getAppLocalIdByStoreId:" + aStoreId);
|
||||
for (let id in aApps) {
|
||||
if (aApps[id].storeId == aStoreId) {
|
||||
return aApps[id].localId;
|
||||
}
|
||||
}
|
||||
|
||||
return Ci.nsIScriptSecurityManager.NO_APP_ID;
|
||||
},
|
||||
|
||||
getAppByLocalId: function getAppByLocalId(aApps, aLocalId) {
|
||||
debug("getAppByLocalId " + aLocalId);
|
||||
for (let id in aApps) {
|
||||
let app = aApps[id];
|
||||
if (app.localId == aLocalId) {
|
||||
return new mozIApplication(app);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
getManifestURLByLocalId: function getManifestURLByLocalId(aApps, aLocalId) {
|
||||
debug("getManifestURLByLocalId " + aLocalId);
|
||||
for (let id in aApps) {
|
||||
let app = aApps[id];
|
||||
if (app.localId == aLocalId) {
|
||||
return app.manifestURL;
|
||||
}
|
||||
}
|
||||
|
||||
return "";
|
||||
},
|
||||
|
||||
areAnyAppsInstalled: function(aApps) {
|
||||
return Object.getOwnPropertyNames(aApps).length > 0;
|
||||
},
|
||||
|
||||
getCoreAppsBasePath: function getCoreAppsBasePath() {
|
||||
debug("getCoreAppsBasePath()");
|
||||
try {
|
||||
return FileUtils.getDir("coreAppsDir", ["webapps"], false).path;
|
||||
} catch(e) {
|
||||
return null;
|
||||
}
|
||||
},
|
||||
|
||||
getAppInfo: function getAppInfo(aApps, aAppId) {
|
||||
let app = aApps[aAppId];
|
||||
|
||||
if (!app) {
|
||||
debug("No webapp for " + aAppId);
|
||||
return null;
|
||||
}
|
||||
|
||||
// We can have 3rd party apps that are non-removable,
|
||||
// so we can't use the 'removable' property for isCoreApp
|
||||
// Instead, we check if the app is installed under /system/b2g
|
||||
let isCoreApp = false;
|
||||
|
||||
#ifdef MOZ_WIDGET_GONK
|
||||
isCoreApp = app.basePath == this.getCoreAppsBasePath();
|
||||
#endif
|
||||
debug(app.basePath + " isCoreApp: " + isCoreApp);
|
||||
|
||||
return { "path": app.basePath + "/" + app.id,
|
||||
"isCoreApp": isCoreApp };
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove potential HTML tags from displayable fields in the manifest.
|
||||
* We check name, description, developer name, and permission description
|
||||
*/
|
||||
sanitizeManifest: function(aManifest) {
|
||||
let sanitizer = Cc["@mozilla.org/parserutils;1"]
|
||||
.getService(Ci.nsIParserUtils);
|
||||
if (!sanitizer) {
|
||||
return;
|
||||
}
|
||||
|
||||
function sanitize(aStr) {
|
||||
return sanitizer.convertToPlainText(aStr,
|
||||
Ci.nsIDocumentEncoder.OutputRaw, 0);
|
||||
}
|
||||
|
||||
function sanitizeEntryPoint(aRoot) {
|
||||
aRoot.name = sanitize(aRoot.name);
|
||||
|
||||
if (aRoot.description) {
|
||||
aRoot.description = sanitize(aRoot.description);
|
||||
}
|
||||
|
||||
if (aRoot.developer && aRoot.developer.name) {
|
||||
aRoot.developer.name = sanitize(aRoot.developer.name);
|
||||
}
|
||||
|
||||
if (aRoot.permissions) {
|
||||
for (let permission in aRoot.permissions) {
|
||||
if (aRoot.permissions[permission].description) {
|
||||
aRoot.permissions[permission].description =
|
||||
sanitize(aRoot.permissions[permission].description);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// First process the main section, then the entry points.
|
||||
sanitizeEntryPoint(aManifest);
|
||||
|
||||
if (aManifest.entry_points) {
|
||||
for (let entry in aManifest.entry_points) {
|
||||
sanitizeEntryPoint(aManifest.entry_points[entry]);
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* From https://developer.mozilla.org/en/OpenWebApps/The_Manifest
|
||||
* Only the name property is mandatory.
|
||||
*/
|
||||
checkManifest: function(aManifest, app) {
|
||||
if (aManifest.name == undefined)
|
||||
return false;
|
||||
|
||||
this.sanitizeManifest(aManifest);
|
||||
|
||||
// launch_path, entry_points launch paths, message hrefs, and activity hrefs can't be absolute
|
||||
if (aManifest.launch_path && isAbsoluteURI(aManifest.launch_path))
|
||||
return false;
|
||||
|
||||
function checkAbsoluteEntryPoints(entryPoints) {
|
||||
for (let name in entryPoints) {
|
||||
if (entryPoints[name].launch_path && isAbsoluteURI(entryPoints[name].launch_path)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (checkAbsoluteEntryPoints(aManifest.entry_points))
|
||||
return false;
|
||||
|
||||
for (let localeName in aManifest.locales) {
|
||||
if (checkAbsoluteEntryPoints(aManifest.locales[localeName].entry_points)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (aManifest.activities) {
|
||||
for (let activityName in aManifest.activities) {
|
||||
let activity = aManifest.activities[activityName];
|
||||
if (activity.href && isAbsoluteURI(activity.href)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// |messages| is an array of items, where each item is either a string or
|
||||
// a {name: href} object.
|
||||
let messages = aManifest.messages;
|
||||
if (messages) {
|
||||
if (!Array.isArray(messages)) {
|
||||
return false;
|
||||
}
|
||||
for (let item of aManifest.messages) {
|
||||
if (typeof item == "object") {
|
||||
let keys = Object.keys(item);
|
||||
if (keys.length != 1) {
|
||||
return false;
|
||||
}
|
||||
if (isAbsoluteURI(item[keys[0]])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The 'size' field must be a positive integer.
|
||||
if (aManifest.size) {
|
||||
aManifest.size = parseInt(aManifest.size);
|
||||
if (Number.isNaN(aManifest.size) || aManifest.size < 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// The 'role' field must be a string.
|
||||
if (aManifest.role && (typeof aManifest.role !== "string")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
checkManifestContentType: function
|
||||
checkManifestContentType(aInstallOrigin, aWebappOrigin, aContentType) {
|
||||
let hadCharset = { };
|
||||
let charset = { };
|
||||
let netutil = Cc["@mozilla.org/network/util;1"].getService(Ci.nsINetUtil);
|
||||
let contentType = netutil.parseResponseContentType(aContentType, charset, hadCharset);
|
||||
if (aInstallOrigin != aWebappOrigin &&
|
||||
!(contentType == "application/x-web-app-manifest+json" ||
|
||||
contentType == "application/manifest+json")) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
allowUnsignedAddons: false, // for testing purposes.
|
||||
|
||||
/**
|
||||
* Checks if the app role is allowed:
|
||||
* Only certified apps can be themes.
|
||||
* Only privileged or certified apps can be addons.
|
||||
* @param aRole : the role assigned to this app.
|
||||
* @param aStatus : the APP_STATUS_* for this app.
|
||||
*/
|
||||
checkAppRole: function(aRole, aStatus) {
|
||||
try {
|
||||
// Anything is possible in developer mode.
|
||||
if (Services.prefs.getBoolPref("dom.apps.developer_mode")) {
|
||||
return true;
|
||||
}
|
||||
} catch(e) {}
|
||||
|
||||
if (aRole == "theme" && aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED) {
|
||||
return false;
|
||||
}
|
||||
if (!this.allowUnsignedAddons &&
|
||||
(aRole == "addon" &&
|
||||
aStatus !== Ci.nsIPrincipal.APP_STATUS_CERTIFIED &&
|
||||
aStatus !== Ci.nsIPrincipal.APP_STATUS_PRIVILEGED)) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
},
|
||||
|
||||
/**
|
||||
* Method to apply modifications to webapp manifests file saved internally.
|
||||
* For now, only ensure app can't rename itself.
|
||||
*/
|
||||
ensureSameAppName: function ensureSameAppName(aOldManifest, aNewManifest, aApp) {
|
||||
// Ensure that app name can't be updated
|
||||
aNewManifest.name = aApp.name;
|
||||
|
||||
let defaultShortName =
|
||||
new ManifestHelper(aOldManifest, aApp.origin, aApp.manifestURL).short_name;
|
||||
aNewManifest.short_name = defaultShortName;
|
||||
|
||||
// Nor through localized names
|
||||
if ("locales" in aNewManifest) {
|
||||
for (let locale in aNewManifest.locales) {
|
||||
let newLocaleEntry = aNewManifest.locales[locale];
|
||||
|
||||
let oldLocaleEntry = aOldManifest && "locales" in aOldManifest &&
|
||||
locale in aOldManifest.locales && aOldManifest.locales[locale];
|
||||
|
||||
if (newLocaleEntry.name) {
|
||||
// In case previous manifest didn't had a name,
|
||||
// we use the default app name
|
||||
newLocaleEntry.name =
|
||||
(oldLocaleEntry && oldLocaleEntry.name) || aApp.name;
|
||||
}
|
||||
if (newLocaleEntry.short_name) {
|
||||
newLocaleEntry.short_name =
|
||||
(oldLocaleEntry && oldLocaleEntry.short_name) || defaultShortName;
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Determines whether the manifest allows installs for the given origin.
|
||||
* @param object aManifest
|
||||
* @param string aInstallOrigin
|
||||
* @return boolean
|
||||
**/
|
||||
checkInstallAllowed: function checkInstallAllowed(aManifest, aInstallOrigin) {
|
||||
if (!aManifest.installs_allowed_from) {
|
||||
return true;
|
||||
}
|
||||
|
||||
function cbCheckAllowedOrigin(aOrigin) {
|
||||
return aOrigin == "*" || aOrigin == aInstallOrigin;
|
||||
}
|
||||
|
||||
return aManifest.installs_allowed_from.some(cbCheckAllowedOrigin);
|
||||
},
|
||||
|
||||
/**
|
||||
* Determine the type of app (app, privileged, certified)
|
||||
* that is installed by the manifest
|
||||
* @param object aManifest
|
||||
* @returns integer
|
||||
**/
|
||||
getAppManifestStatus: function getAppManifestStatus(aManifest) {
|
||||
let type = aManifest.type || "web";
|
||||
|
||||
switch(type) {
|
||||
case "web":
|
||||
return Ci.nsIPrincipal.APP_STATUS_INSTALLED;
|
||||
case "privileged":
|
||||
return Ci.nsIPrincipal.APP_STATUS_PRIVILEGED;
|
||||
case "certified":
|
||||
return Ci.nsIPrincipal.APP_STATUS_CERTIFIED;
|
||||
default:
|
||||
throw new Error("Webapps.jsm: Undetermined app manifest type");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if two manifests have the same set of properties and that the
|
||||
* values of these properties are the same, in each locale.
|
||||
* Manifests here are raw json ones.
|
||||
*/
|
||||
compareManifests: function compareManifests(aManifest1, aManifest2) {
|
||||
// 1. check if we have the same locales in both manifests.
|
||||
let locales1 = [];
|
||||
let locales2 = [];
|
||||
if (aManifest1.locales) {
|
||||
for (let locale in aManifest1.locales) {
|
||||
locales1.push(locale);
|
||||
}
|
||||
}
|
||||
if (aManifest2.locales) {
|
||||
for (let locale in aManifest2.locales) {
|
||||
locales2.push(locale);
|
||||
}
|
||||
}
|
||||
if (locales1.sort().join() !== locales2.sort().join()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Helper function to check the app name and developer information for
|
||||
// two given roots.
|
||||
let checkNameAndDev = function(aRoot1, aRoot2) {
|
||||
let name1 = aRoot1.name;
|
||||
let name2 = aRoot2.name;
|
||||
if (name1 !== name2) {
|
||||
return false;
|
||||
}
|
||||
|
||||
let dev1 = aRoot1.developer;
|
||||
let dev2 = aRoot2.developer;
|
||||
if ((dev1 && !dev2) || (dev2 && !dev1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return (!dev1 && !dev2) ||
|
||||
(dev1.name === dev2.name && dev1.url === dev2.url);
|
||||
}
|
||||
|
||||
// 2. For each locale, check if the name and dev info are the same.
|
||||
if (!checkNameAndDev(aManifest1, aManifest2)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for (let locale in aManifest1.locales) {
|
||||
if (!checkNameAndDev(aManifest1.locales[locale],
|
||||
aManifest2.locales[locale])) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
// Nothing failed.
|
||||
return true;
|
||||
},
|
||||
|
||||
// Asynchronously loads a JSON file. aPath is a string representing the path
|
||||
// of the file to be read.
|
||||
loadJSONAsync: function(aPath) {
|
||||
let deferred = Promise.defer();
|
||||
|
||||
try {
|
||||
let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
|
||||
file.initWithPath(aPath);
|
||||
|
||||
let channel = NetUtil.newChannel({
|
||||
uri: NetUtil.newURI(file),
|
||||
loadUsingSystemPrincipal: true});
|
||||
|
||||
channel.contentType = "application/json";
|
||||
|
||||
NetUtil.asyncFetch(channel, function(aStream, aResult) {
|
||||
if (!Components.isSuccessCode(aResult)) {
|
||||
deferred.resolve(null);
|
||||
|
||||
if (aResult == Cr.NS_ERROR_FILE_NOT_FOUND) {
|
||||
// We expect this under certain circumstances, like for webapps.json
|
||||
// on firstrun, so we return early without reporting an error.
|
||||
return;
|
||||
}
|
||||
|
||||
Cu.reportError("AppsUtils: Could not read from json file " + aPath);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// Obtain a converter to read from a UTF-8 encoded input stream.
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
|
||||
// Read json file into a string
|
||||
let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
|
||||
aStream.available()) || ""));
|
||||
aStream.close();
|
||||
|
||||
deferred.resolve(data);
|
||||
} catch (ex) {
|
||||
Cu.reportError("AppsUtils: Could not parse JSON: " +
|
||||
aPath + " " + ex + "\n" + ex.stack);
|
||||
deferred.resolve(null);
|
||||
}
|
||||
});
|
||||
} catch (ex) {
|
||||
Cu.reportError("AppsUtils: Could not read from " +
|
||||
aPath + " : " + ex + "\n" + ex.stack);
|
||||
deferred.resolve(null);
|
||||
}
|
||||
|
||||
return deferred.promise;
|
||||
},
|
||||
|
||||
// Returns the hash of a string, with MD5 as a default hashing function.
|
||||
computeHash: function(aString, aAlgorithm = "MD5") {
|
||||
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
||||
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
||||
converter.charset = "UTF-8";
|
||||
let result = {};
|
||||
// Data is an array of bytes.
|
||||
let data = converter.convertToByteArray(aString, result);
|
||||
|
||||
let hasher = Cc["@mozilla.org/security/hash;1"]
|
||||
.createInstance(Ci.nsICryptoHash);
|
||||
hasher.initWithString(aAlgorithm);
|
||||
hasher.update(data, data.length);
|
||||
// We're passing false to get the binary hash and not base64.
|
||||
let hash = hasher.finish(false);
|
||||
|
||||
function toHexString(charCode) {
|
||||
return ("0" + charCode.toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
// Convert the binary hash data to a hex string.
|
||||
return Array.from(hash, (c, i) => toHexString(hash.charCodeAt(i))).join("");
|
||||
},
|
||||
|
||||
// Returns the hash for a JS object.
|
||||
computeObjectHash: function(aObject) {
|
||||
return this.computeHash(JSON.stringify(aObject));
|
||||
},
|
||||
|
||||
getAppManifestURLFromWindow: function(aWindow) {
|
||||
let appId = aWindow.document.nodePrincipal.appId;
|
||||
if (appId === Ci.nsIScriptSecurityManager.NO_APP_ID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return appsService.getManifestURLByLocalId(appId);
|
||||
},
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper object to access manifest information with locale support
|
||||
*/
|
||||
this.ManifestHelper = function(aManifest, aOrigin, aManifestURL, aLang) {
|
||||
// If the app is packaged, we resolve uris against the origin.
|
||||
// If it's not, against the manifest url.
|
||||
|
||||
if (!aOrigin || !aManifestURL) {
|
||||
throw Error("ManifestHelper needs both origin and manifestURL");
|
||||
}
|
||||
|
||||
this._baseURI = Services.io.newURI(
|
||||
aOrigin.startsWith("app://") ? aOrigin : aManifestURL, null, null);
|
||||
|
||||
// We keep the manifest url in all cases since we need it to
|
||||
// resolve the package path for packaged apps.
|
||||
this._manifestURL = Services.io.newURI(aManifestURL, null, null);
|
||||
|
||||
this._manifest = aManifest;
|
||||
|
||||
let locale = aLang;
|
||||
if (!locale) {
|
||||
let chrome = Cc["@mozilla.org/chrome/chrome-registry;1"]
|
||||
.getService(Ci.nsIXULChromeRegistry)
|
||||
.QueryInterface(Ci.nsIToolkitChromeRegistry);
|
||||
locale = chrome.getSelectedLocale("global").toLowerCase();
|
||||
}
|
||||
|
||||
this._localeRoot = this._manifest;
|
||||
|
||||
if (this._manifest.locales && this._manifest.locales[locale]) {
|
||||
this._localeRoot = this._manifest.locales[locale];
|
||||
}
|
||||
else if (this._manifest.locales) {
|
||||
// try with the language part of the locale ("en" for en-GB) only
|
||||
let lang = locale.split('-')[0];
|
||||
if (lang != locale && this._manifest.locales[lang])
|
||||
this._localeRoot = this._manifest.locales[lang];
|
||||
}
|
||||
};
|
||||
|
||||
ManifestHelper.prototype = {
|
||||
_localeProp: function(aProp) {
|
||||
if (this._localeRoot[aProp] != undefined)
|
||||
return this._localeRoot[aProp];
|
||||
return (aProp in this._manifest) ? this._manifest[aProp] : undefined;
|
||||
},
|
||||
|
||||
get name() {
|
||||
return this._localeProp("name");
|
||||
},
|
||||
|
||||
get short_name() {
|
||||
return this._localeProp("short_name");
|
||||
},
|
||||
|
||||
get description() {
|
||||
return this._localeProp("description");
|
||||
},
|
||||
|
||||
get type() {
|
||||
return this._localeProp("type");
|
||||
},
|
||||
|
||||
get version() {
|
||||
return this._localeProp("version");
|
||||
},
|
||||
|
||||
get launch_path() {
|
||||
return this._localeProp("launch_path");
|
||||
},
|
||||
|
||||
get developer() {
|
||||
// Default to {} in order to avoid exception in code
|
||||
// that doesn't check for null `developer`
|
||||
return this._localeProp("developer") || {};
|
||||
},
|
||||
|
||||
get icons() {
|
||||
return this._localeProp("icons");
|
||||
},
|
||||
|
||||
get appcache_path() {
|
||||
return this._localeProp("appcache_path");
|
||||
},
|
||||
|
||||
get orientation() {
|
||||
return this._localeProp("orientation");
|
||||
},
|
||||
|
||||
get package_path() {
|
||||
return this._localeProp("package_path");
|
||||
},
|
||||
|
||||
get size() {
|
||||
return this._manifest["size"] || 0;
|
||||
},
|
||||
|
||||
get permissions() {
|
||||
if (this._manifest.permissions) {
|
||||
return this._manifest.permissions;
|
||||
}
|
||||
return {};
|
||||
},
|
||||
|
||||
biggestIconURL: function(predicate) {
|
||||
let icons = this._localeProp("icons");
|
||||
if (!icons) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let iconSizes = Object.keys(icons).sort((a, b) => a - b)
|
||||
.filter(predicate || (() => true));
|
||||
if (iconSizes.length == 0) {
|
||||
return null;
|
||||
}
|
||||
let biggestIconSize = iconSizes.pop();
|
||||
let biggestIcon = icons[biggestIconSize];
|
||||
let biggestIconURL = this._baseURI.resolve(biggestIcon);
|
||||
|
||||
return biggestIconURL;
|
||||
},
|
||||
|
||||
iconURLForSize: function(aSize) {
|
||||
let icons = this._localeProp("icons");
|
||||
if (!icons)
|
||||
return null;
|
||||
let dist = 100000;
|
||||
let icon = null;
|
||||
for (let size in icons) {
|
||||
let iSize = parseInt(size);
|
||||
if (Math.abs(iSize - aSize) < dist) {
|
||||
icon = this._baseURI.resolve(icons[size]);
|
||||
dist = Math.abs(iSize - aSize);
|
||||
}
|
||||
}
|
||||
return icon;
|
||||
},
|
||||
|
||||
fullLaunchPath: function(aStartPoint) {
|
||||
// If no start point is specified, we use the root launch path.
|
||||
// In all error cases, we just return null.
|
||||
if ((aStartPoint || "") === "") {
|
||||
// W3C start_url takes precedence over mozApps launch_path
|
||||
if (this._localeProp("start_url")) {
|
||||
return this._baseURI.resolve(this._localeProp("start_url") || "/");
|
||||
}
|
||||
return this._baseURI.resolve(this._localeProp("launch_path") || "/");
|
||||
}
|
||||
|
||||
// Search for the l10n entry_points property.
|
||||
let entryPoints = this._localeProp("entry_points");
|
||||
if (!entryPoints) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (entryPoints[aStartPoint]) {
|
||||
return this._baseURI.resolve(entryPoints[aStartPoint].launch_path || "/");
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
|
||||
resolveURL: function(aURI) {
|
||||
// This should be enforced higher up, but check it here just in case.
|
||||
if (isAbsoluteURI(aURI)) {
|
||||
throw new Error("Webapps.jsm: non-relative URI passed to resolve");
|
||||
}
|
||||
return this._baseURI.resolve(aURI);
|
||||
},
|
||||
|
||||
fullAppcachePath: function() {
|
||||
let appcachePath = this._localeProp("appcache_path");
|
||||
return this._baseURI.resolve(appcachePath ? appcachePath : "/");
|
||||
},
|
||||
|
||||
fullPackagePath: function() {
|
||||
let packagePath = this._localeProp("package_path");
|
||||
return this._manifestURL.resolve(packagePath ? packagePath : "/");
|
||||
},
|
||||
|
||||
get role() {
|
||||
return this._manifest.role || "";
|
||||
},
|
||||
|
||||
get csp() {
|
||||
return this._manifest.csp || "";
|
||||
}
|
||||
}
|
||||
210
dom/apps/PermissionsInstaller.jsm
Normal file
210
dom/apps/PermissionsInstaller.jsm
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
||||
Cu.import("resource://gre/modules/PermissionSettings.jsm");
|
||||
Cu.import("resource://gre/modules/PermissionsTable.jsm");
|
||||
|
||||
this.EXPORTED_SYMBOLS = ["PermissionsInstaller"];
|
||||
const UNKNOWN_ACTION = Ci.nsIPermissionManager.UNKNOWN_ACTION;
|
||||
const ALLOW_ACTION = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
const DENY_ACTION = Ci.nsIPermissionManager.DENY_ACTION;
|
||||
const PROMPT_ACTION = Ci.nsIPermissionManager.PROMPT_ACTION;
|
||||
|
||||
// Permission access flags
|
||||
const READONLY = "readonly";
|
||||
const CREATEONLY = "createonly";
|
||||
const READCREATE = "readcreate";
|
||||
const READWRITE = "readwrite";
|
||||
|
||||
const PERM_TO_STRING = ["unknown", "allow", "deny", "prompt"];
|
||||
|
||||
function debug(aMsg) {
|
||||
//dump("-*-*- PermissionsInstaller.jsm : " + aMsg + "\n");
|
||||
}
|
||||
|
||||
this.PermissionsInstaller = {
|
||||
/**
|
||||
* Install permissisions or remove deprecated permissions upon re-install.
|
||||
* @param object aApp
|
||||
* The just-installed app configuration.
|
||||
* The properties used are manifestURL, origin and manifest.
|
||||
* @param boolean aIsReinstall
|
||||
* Indicates the app was just re-installed
|
||||
* @param function aOnError
|
||||
* A function called if an error occurs
|
||||
* @returns void
|
||||
**/
|
||||
installPermissions: function installPermissions(aApp, aIsReinstall,
|
||||
aOnError) {
|
||||
try {
|
||||
let newManifest =
|
||||
new ManifestHelper(aApp.manifest, aApp.origin, aApp.manifestURL);
|
||||
if (!newManifest.permissions && !aIsReinstall) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (aIsReinstall) {
|
||||
// Compare the original permissions against the new permissions
|
||||
// Remove any deprecated Permissions
|
||||
|
||||
if (newManifest.permissions) {
|
||||
// Expand permission names.
|
||||
let newPermNames = [];
|
||||
for (let permName in newManifest.permissions) {
|
||||
let expandedPermNames =
|
||||
expandPermissions(permName,
|
||||
newManifest.permissions[permName].access);
|
||||
newPermNames = newPermNames.concat(expandedPermNames);
|
||||
}
|
||||
|
||||
newPermNames.push("indexedDB");
|
||||
|
||||
// Add the appcache related permissions.
|
||||
if (newManifest.appcache_path) {
|
||||
newPermNames = newPermNames.concat(["offline-app", "pin-app"]);
|
||||
}
|
||||
|
||||
for (let idx in AllPossiblePermissions) {
|
||||
let permName = AllPossiblePermissions[idx];
|
||||
let index = newPermNames.indexOf(permName);
|
||||
if (index == -1) {
|
||||
// See if the permission was installed previously.
|
||||
let permValue =
|
||||
PermissionSettingsModule.getPermission(permName,
|
||||
aApp.manifestURL,
|
||||
aApp.origin,
|
||||
false);
|
||||
if (permValue == "unknown" || permValue == "deny") {
|
||||
// All 'deny' permissions should be preserved
|
||||
continue;
|
||||
}
|
||||
// Remove the deprecated permission
|
||||
PermissionSettingsModule.removePermission(permName,
|
||||
aApp.manifestURL,
|
||||
aApp.origin,
|
||||
false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check to see if the 'webapp' is app/privileged/certified.
|
||||
let appStatus;
|
||||
switch (AppsUtils.getAppManifestStatus(aApp.manifest)) {
|
||||
case Ci.nsIPrincipal.APP_STATUS_CERTIFIED:
|
||||
appStatus = "certified";
|
||||
break;
|
||||
case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED:
|
||||
appStatus = "privileged";
|
||||
break;
|
||||
case Ci.nsIPrincipal.APP_STATUS_INSTALLED:
|
||||
appStatus = "app";
|
||||
break;
|
||||
default:
|
||||
// Cannot determine app type, abort install by throwing an error.
|
||||
throw new Error("PermissionsInstaller.jsm: " +
|
||||
"Cannot determine the app's status. Install cancelled.");
|
||||
break;
|
||||
}
|
||||
|
||||
this._setPermission("indexedDB", "allow", aApp);
|
||||
|
||||
// Add the appcache related permissions. We allow it for all kinds of
|
||||
// apps.
|
||||
if (newManifest.appcache_path) {
|
||||
this._setPermission("offline-app", "allow", aApp);
|
||||
this._setPermission("pin-app", "allow", aApp);
|
||||
}
|
||||
|
||||
for (let permName in newManifest.permissions) {
|
||||
if (!PermissionsTable[permName]) {
|
||||
Cu.reportError("PermissionsInstaller.jsm: '" + permName + "'" +
|
||||
" is not a valid Webapps permission name.");
|
||||
dump("PermissionsInstaller.jsm: '" + permName + "'" +
|
||||
" is not a valid Webapps permission name.");
|
||||
continue;
|
||||
}
|
||||
|
||||
let expandedPermNames =
|
||||
expandPermissions(permName,
|
||||
newManifest.permissions[permName].access);
|
||||
for (let idx in expandedPermNames) {
|
||||
|
||||
let isPromptPermission =
|
||||
PermissionsTable[permName][appStatus] === PROMPT_ACTION;
|
||||
|
||||
// We silently upgrade the permission to whatever the permission
|
||||
// is for certified apps (ALLOW or PROMPT) only if the
|
||||
// following holds true:
|
||||
// * The app is preinstalled
|
||||
// * The permission that would be granted is PROMPT
|
||||
// * The app is privileged
|
||||
let permission =
|
||||
aApp.isPreinstalled && isPromptPermission &&
|
||||
appStatus === "privileged"
|
||||
? PermissionsTable[permName]["certified"]
|
||||
: PermissionsTable[permName][appStatus];
|
||||
|
||||
let permValue = PERM_TO_STRING[permission];
|
||||
if (isPromptPermission) {
|
||||
// If the permission is prompt, keep the current value. This will
|
||||
// work even on a system update, with the caveat that if a
|
||||
// ALLOW/DENY permission is changed to PROMPT then the system should
|
||||
// inform the user that he can now change a permission that he could
|
||||
// not change before.
|
||||
permValue =
|
||||
PermissionSettingsModule.getPermission(expandedPermNames[idx],
|
||||
aApp.manifestURL,
|
||||
aApp.origin,
|
||||
false,
|
||||
aApp.isCachedPackage);
|
||||
if (permValue === "unknown") {
|
||||
permValue = PERM_TO_STRING[permission];
|
||||
}
|
||||
}
|
||||
|
||||
this._setPermission(expandedPermNames[idx], permValue, aApp);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (ex) {
|
||||
dump("Caught webapps install permissions error for " + aApp.origin +
|
||||
" : " + ex + "\n");
|
||||
Cu.reportError(ex);
|
||||
if (aOnError) {
|
||||
aOnError();
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Set a permission value.
|
||||
* @param string aPermName
|
||||
* The permission name.
|
||||
* @param string aPermValue
|
||||
* The permission value.
|
||||
* @param object aApp
|
||||
* The just-installed app configuration.
|
||||
* The properties used are manifestURL, origin, appId, isCachedPackage.
|
||||
* @returns void
|
||||
**/
|
||||
_setPermission: function setPermission(aPermName, aPermValue, aApp) {
|
||||
PermissionSettingsModule.addPermission({
|
||||
type: aPermName,
|
||||
origin: aApp.origin,
|
||||
manifestURL: aApp.manifestURL,
|
||||
value: aPermValue,
|
||||
browserFlag: false,
|
||||
localId: aApp.localId,
|
||||
isCachedPackage: aApp.isCachedPackage,
|
||||
});
|
||||
}
|
||||
};
|
||||
593
dom/apps/PermissionsTable.jsm
Normal file
593
dom/apps/PermissionsTable.jsm
Normal file
|
|
@ -0,0 +1,593 @@
|
|||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
"use strict";
|
||||
|
||||
const Ci = Components.interfaces;
|
||||
const Cu = Components.utils;
|
||||
|
||||
this.EXPORTED_SYMBOLS = [
|
||||
"PermissionsTable",
|
||||
"PermissionsReverseTable",
|
||||
"expandPermissions",
|
||||
"appendAccessToPermName",
|
||||
"isExplicitInPermissionsTable",
|
||||
"AllPossiblePermissions"
|
||||
];
|
||||
|
||||
// Permission access flags
|
||||
const READONLY = "readonly";
|
||||
const CREATEONLY = "createonly";
|
||||
const READCREATE = "readcreate";
|
||||
const READWRITE = "readwrite";
|
||||
|
||||
const UNKNOWN_ACTION = Ci.nsIPermissionManager.UNKNOWN_ACTION;
|
||||
const ALLOW_ACTION = Ci.nsIPermissionManager.ALLOW_ACTION;
|
||||
const DENY_ACTION = Ci.nsIPermissionManager.DENY_ACTION;
|
||||
const PROMPT_ACTION = Ci.nsIPermissionManager.PROMPT_ACTION;
|
||||
|
||||
// Permissions Matrix: https://docs.google.com/spreadsheet/ccc?key=0Akyz_Bqjgf5pdENVekxYRjBTX0dCXzItMnRyUU1RQ0E#gid=0
|
||||
|
||||
// Permissions that are implicit:
|
||||
// battery-status, network-information, vibration,
|
||||
// device-capabilities
|
||||
|
||||
this.PermissionsTable = { geolocation: {
|
||||
app: PROMPT_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: PROMPT_ACTION
|
||||
},
|
||||
"geolocation-noprompt": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
substitute: ["geolocation"]
|
||||
},
|
||||
camera: {
|
||||
app: DENY_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
alarms: {
|
||||
app: ALLOW_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"tcp-socket": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"udp-socket": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"network-events": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
contacts: {
|
||||
app: DENY_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write", "create"]
|
||||
},
|
||||
"device-storage:apps": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read"]
|
||||
},
|
||||
"device-storage:crashes": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read"]
|
||||
},
|
||||
"device-storage:pictures": {
|
||||
app: DENY_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write", "create"]
|
||||
},
|
||||
"device-storage:videos": {
|
||||
app: DENY_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write", "create"]
|
||||
},
|
||||
"device-storage:music": {
|
||||
app: DENY_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write", "create"]
|
||||
},
|
||||
"device-storage:sdcard": {
|
||||
app: DENY_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write", "create"]
|
||||
},
|
||||
sms: {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"speech-recognition": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
browser: {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"browser:universalxss": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"browser:embedded-system-app": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
mobilenetwork: {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
power: {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
push: {
|
||||
app: ALLOW_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
settings: {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write"],
|
||||
additional: ["indexedDB-chrome-settings", "settings-api"]
|
||||
},
|
||||
// This exists purely for tests, no app
|
||||
// should ever use it. It can only be
|
||||
// handed out by SpecialPowers.
|
||||
"settings-clear": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: DENY_ACTION,
|
||||
additional: ["indexedDB-chrome-settings", "settings-api"]
|
||||
},
|
||||
permissions: {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
attention: {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"global-clickthrough-overlay": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"moz-attention": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
substitute: ["attention"]
|
||||
},
|
||||
"webapps-manage": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"homescreen-webapps-manage": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"backgroundservice": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"desktop-notification": {
|
||||
app: ALLOW_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"networkstats-manage": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"wifi-manage": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"systemXHR": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"idle": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"time": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"embed-apps": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"background-sensors": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-channel-normal": {
|
||||
app: ALLOW_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-channel-content": {
|
||||
app: ALLOW_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-channel-notification": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-channel-alarm": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-channel-system": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-channel-telephony": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"moz-audio-channel-telephony": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
substitute: ["audio-channel-telephony"]
|
||||
},
|
||||
"audio-channel-ringer": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"moz-audio-channel-ringer": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
substitute: ["audio-channel-ringer"]
|
||||
},
|
||||
"audio-channel-publicnotification": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"open-remote-window": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"input": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"input-manage": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-capture": {
|
||||
app: PROMPT_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-capture:3gpp": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"audio-capture:3gpp2": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"speaker-control": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"downloads": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"video-capture": {
|
||||
app: PROMPT_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"feature-detection": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
// This permission doesn't actually grant access to
|
||||
// anything. It exists only to check the correctness
|
||||
// of web prompt composed permissions in tests.
|
||||
"test-permission": {
|
||||
app: PROMPT_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write", "create"]
|
||||
},
|
||||
"firefox-accounts": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"moz-firefox-accounts": {
|
||||
app: DENY_ACTION,
|
||||
privileged: PROMPT_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
substitute: ["firefox-accounts"]
|
||||
},
|
||||
"themeable": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"settings:wallpaper.image": {
|
||||
app: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION,
|
||||
access: ["read", "write"],
|
||||
additional: ["settings-api"]
|
||||
},
|
||||
"tv": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"before-after-keyboard-event": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"presentation-device-manage": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"secureelement-manage": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"inputport": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"system-update": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"open-hidden-window": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"moz-extremely-unstable-and-will-change-webcomponents": {
|
||||
app: DENY_ACTION,
|
||||
trusted: DENY_ACTION,
|
||||
privileged: ALLOW_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"system-app-only-audio-channels-in-app": {
|
||||
app: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
},
|
||||
"previously-certified-app": {
|
||||
app: DENY_ACTION,
|
||||
trusted: DENY_ACTION,
|
||||
privileged: DENY_ACTION,
|
||||
certified: ALLOW_ACTION
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Append access modes to the permission name as suffixes.
|
||||
* e.g. permission name 'contacts' with ['read', 'write'] =
|
||||
* ['contacts-read', contacts-write']
|
||||
* @param string aPermName
|
||||
* @param array aAccess
|
||||
* @returns array containing access-appended permission names.
|
||||
**/
|
||||
this.appendAccessToPermName = function appendAccessToPermName(aPermName, aAccess) {
|
||||
if (aAccess.length == 0) {
|
||||
return [aPermName];
|
||||
}
|
||||
return aAccess.map(function(aMode) {
|
||||
return aPermName + "-" + aMode;
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Expand an access string into multiple permission names,
|
||||
* e.g: permission name 'contacts' with 'readwrite' =
|
||||
* ['contacts-read', 'contacts-create', 'contacts-write']
|
||||
* @param string aPermName
|
||||
* @param string aAccess (optional)
|
||||
* @returns array containing expanded permission names.
|
||||
**/
|
||||
this.expandPermissions = function expandPermissions(aPermName, aAccess) {
|
||||
if (!PermissionsTable[aPermName]) {
|
||||
let errorMsg =
|
||||
"PermissionsTable.jsm: expandPermissions: Unknown Permission: " + aPermName;
|
||||
Cu.reportError(errorMsg);
|
||||
dump(errorMsg);
|
||||
return [];
|
||||
}
|
||||
|
||||
const tableEntry = PermissionsTable[aPermName];
|
||||
|
||||
if (tableEntry.substitute && tableEntry.additional) {
|
||||
let errorMsg =
|
||||
"PermissionsTable.jsm: expandPermissions: Can't handle both 'substitute' " +
|
||||
"and 'additional' entries for permission: " + aPermName;
|
||||
Cu.reportError(errorMsg);
|
||||
dump(errorMsg);
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!aAccess && tableEntry.access ||
|
||||
aAccess && !tableEntry.access) {
|
||||
let errorMsg =
|
||||
"PermissionsTable.jsm: expandPermissions: Invalid access for permission " +
|
||||
aPermName + ": " + aAccess + "\n";
|
||||
Cu.reportError(errorMsg);
|
||||
dump(errorMsg);
|
||||
return [];
|
||||
}
|
||||
|
||||
let expandedPermNames = [];
|
||||
|
||||
if (tableEntry.access && aAccess) {
|
||||
let requestedSuffixes = [];
|
||||
switch (aAccess) {
|
||||
case READONLY:
|
||||
requestedSuffixes.push("read");
|
||||
break;
|
||||
case CREATEONLY:
|
||||
requestedSuffixes.push("create");
|
||||
break;
|
||||
case READCREATE:
|
||||
requestedSuffixes.push("read", "create");
|
||||
break;
|
||||
case READWRITE:
|
||||
requestedSuffixes.push("read", "create", "write");
|
||||
break;
|
||||
default:
|
||||
return [];
|
||||
}
|
||||
|
||||
let permArr = appendAccessToPermName(aPermName, requestedSuffixes);
|
||||
|
||||
// Add the same suffix to each of the additions.
|
||||
if (tableEntry.additional) {
|
||||
for (let additional of tableEntry.additional) {
|
||||
permArr = permArr.concat(appendAccessToPermName(additional, requestedSuffixes));
|
||||
}
|
||||
}
|
||||
|
||||
// Only add the suffixed version if the suffix exists in the table.
|
||||
for (let idx in permArr) {
|
||||
let suffix = requestedSuffixes[idx % requestedSuffixes.length];
|
||||
if (tableEntry.access.indexOf(suffix) != -1) {
|
||||
expandedPermNames.push(permArr[idx]);
|
||||
}
|
||||
}
|
||||
} else if (tableEntry.substitute) {
|
||||
expandedPermNames = expandedPermNames.concat(tableEntry.substitute);
|
||||
} else {
|
||||
expandedPermNames.push(aPermName);
|
||||
// Include each of the additions exactly as they appear in the table.
|
||||
if (tableEntry.additional) {
|
||||
expandedPermNames = expandedPermNames.concat(tableEntry.additional);
|
||||
}
|
||||
}
|
||||
|
||||
return expandedPermNames;
|
||||
};
|
||||
|
||||
this.PermissionsReverseTable = {};
|
||||
this.AllPossiblePermissions = [];
|
||||
|
||||
(function () {
|
||||
// PermissionsTable as it is works well for direct searches, but not
|
||||
// so well for reverse ones (that is, if I get something like
|
||||
// device-storage:music-read or indexedDB-chrome-settings-read how
|
||||
// do I know which permission it really is? Hence this table is
|
||||
// born. The idea is that
|
||||
// reverseTable[device-storage:music-read] should return
|
||||
// device-storage:music
|
||||
//
|
||||
// We also need a list of all the possible permissions for things like the
|
||||
// settingsmanager, so construct that while we're at it.
|
||||
for (let permName in PermissionsTable) {
|
||||
let permAliases = [];
|
||||
if (PermissionsTable[permName].access) {
|
||||
permAliases = expandPermissions(permName, "readwrite");
|
||||
} else if (!PermissionsTable[permName].substitute) {
|
||||
permAliases = expandPermissions(permName);
|
||||
}
|
||||
for (let i = 0; i < permAliases.length; i++) {
|
||||
PermissionsReverseTable[permAliases[i]] = permName;
|
||||
AllPossiblePermissions.push(permAliases[i]);
|
||||
}
|
||||
}
|
||||
AllPossiblePermissions =
|
||||
AllPossiblePermissions.concat(["indexedDB", "offline-app", "pin-app"]);
|
||||
})();
|
||||
|
||||
this.isExplicitInPermissionsTable = function(aPermName, aIntStatus) {
|
||||
|
||||
// Check to see if the 'webapp' is app/privileged/certified.
|
||||
let appStatus;
|
||||
switch (aIntStatus) {
|
||||
case Ci.nsIPrincipal.APP_STATUS_CERTIFIED:
|
||||
appStatus = "certified";
|
||||
break;
|
||||
case Ci.nsIPrincipal.APP_STATUS_PRIVILEGED:
|
||||
appStatus = "privileged";
|
||||
break;
|
||||
default: // If it isn't certified or privileged, it's app
|
||||
appStatus = "app";
|
||||
break;
|
||||
}
|
||||
|
||||
let realPerm = PermissionsReverseTable[aPermName];
|
||||
|
||||
if (realPerm) {
|
||||
return (PermissionsTable[realPerm][appStatus] ==
|
||||
Ci.nsIPermissionManager.PROMPT_ACTION);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
22
dom/apps/moz.build
Normal file
22
dom/apps/moz.build
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
|
||||
# vim: set filetype=python:
|
||||
# This Source Code Form is subject to the terms of the Mozilla Public
|
||||
# License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
|
||||
XPCSHELL_TESTS_MANIFESTS += ['tests/unit/xpcshell.ini']
|
||||
|
||||
EXTRA_COMPONENTS += [
|
||||
'AppsService.js',
|
||||
'AppsService.manifest',
|
||||
]
|
||||
|
||||
EXTRA_JS_MODULES += [
|
||||
'AppsServiceChild.jsm',
|
||||
'PermissionsInstaller.jsm',
|
||||
'PermissionsTable.jsm',
|
||||
]
|
||||
|
||||
EXTRA_PP_JS_MODULES += [
|
||||
'AppsUtils.jsm',
|
||||
]
|
||||
163
dom/apps/tests/create_test_receipts.py
Normal file
163
dom/apps/tests/create_test_receipts.py
Normal file
|
|
@ -0,0 +1,163 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
import jwt
|
||||
|
||||
receipt1 = {
|
||||
"typ": "purchase-receipt",
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314356"
|
||||
},
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"nbf": 131360185,
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt2 = {
|
||||
"typ": "purchase-receipt",
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314357"
|
||||
},
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"nbf": 131360185,
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt_without_typ = {
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314358"
|
||||
},
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"nbf": 131360185,
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt_without_product = {
|
||||
"typ": "purchase-receipt",
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"nbf": 131360185,
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt_without_user = {
|
||||
"typ": "purchase-receipt",
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314358"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"nbf": 131360185,
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt_without_iss = {
|
||||
"typ": "purchase-receipt",
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314358"
|
||||
},
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"nbf": 131360185,
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt_without_nbf = {
|
||||
"typ": "purchase-receipt",
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314358"
|
||||
},
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt_without_iat = {
|
||||
"typ": "purchase-receipt",
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314358"
|
||||
},
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"nbf": 131360185,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
receipt_with_wrong_typ = {
|
||||
"typ": "fake",
|
||||
"product": {
|
||||
"url": "https://www.mozilla.org",
|
||||
"storedata": "5169314358"
|
||||
},
|
||||
"user": {
|
||||
"type": "directed-identifier",
|
||||
"value": "4fb35151-2b9b-4ba2-8283-c49d381640bd"
|
||||
},
|
||||
"iss": "http://mochi.test:8888",
|
||||
"nbf": 131360185,
|
||||
"iat": 131360188,
|
||||
"detail": "http://mochi.test:8888/receipt/5169314356",
|
||||
"verify": "http://mochi.test:8888/verify/5169314356",
|
||||
"reissue": "http://mochi.test:8888/reissue/5169314356"
|
||||
}
|
||||
|
||||
print("let valid_receipt1 = \"" + jwt.encode(receipt1, "") + "\";\n")
|
||||
print("let valid_receipt2 = \"" + jwt.encode(receipt2, "") + "\";\n")
|
||||
print("let receipt_without_typ = \"" + jwt.encode(receipt_without_typ, "") + "\";\n")
|
||||
print("let receipt_without_product = \"" + jwt.encode(receipt_without_product, "") + "\";\n")
|
||||
print("let receipt_without_user = \"" + jwt.encode(receipt_without_user, "") + "\";\n")
|
||||
print("let receipt_without_iss = \"" + jwt.encode(receipt_without_iss, "") + "\";\n")
|
||||
print("let receipt_without_nbf = \"" + jwt.encode(receipt_without_nbf, "") + "\";\n")
|
||||
print("let receipt_without_iat = \"" + jwt.encode(receipt_without_iat, "") + "\";\n")
|
||||
print("let receipt_with_wrong_typ = \"" + jwt.encode(receipt_with_wrong_typ, "") + "\";\n")
|
||||
118
dom/apps/tests/head.js
Normal file
118
dom/apps/tests/head.js
Normal file
|
|
@ -0,0 +1,118 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
* http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var { classes: Cc, interfaces: Ci, utils: Cu, results: Cr } = Components;
|
||||
|
||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||
|
||||
function runAll(steps) {
|
||||
SimpleTest.waitForExplicitFinish();
|
||||
|
||||
// Clone the array so we don't modify the original.
|
||||
steps = steps.concat();
|
||||
function next() {
|
||||
if (steps.length) {
|
||||
steps.shift()(next);
|
||||
}
|
||||
else {
|
||||
SimpleTest.finish();
|
||||
}
|
||||
}
|
||||
next();
|
||||
}
|
||||
|
||||
function confirmNextPopup() {
|
||||
var Ci = SpecialPowers.Ci;
|
||||
|
||||
var popupNotifications = SpecialPowers.wrap(window).top.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIWebNavigation).
|
||||
QueryInterface(Ci.nsIDocShell).
|
||||
chromeEventHandler.ownerDocument.defaultView.
|
||||
PopupNotifications;
|
||||
|
||||
var popupPanel = popupNotifications.panel;
|
||||
|
||||
function onPopupShown() {
|
||||
popupPanel.removeEventListener("popupshown", onPopupShown, false);
|
||||
SpecialPowers.wrap(this).childNodes[0].button.doCommand();
|
||||
popupNotifications._dismiss();
|
||||
}
|
||||
popupPanel.addEventListener("popupshown", onPopupShown, false);
|
||||
}
|
||||
|
||||
function promiseNoPopup() {
|
||||
var Ci = SpecialPowers.Ci;
|
||||
|
||||
var popupNotifications = SpecialPowers.wrap(window).top.
|
||||
QueryInterface(Ci.nsIInterfaceRequestor).
|
||||
getInterface(Ci.nsIWebNavigation).
|
||||
QueryInterface(Ci.nsIDocShell).
|
||||
chromeEventHandler.ownerDocument.defaultView.
|
||||
PopupNotifications;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
var tries = 0;
|
||||
var interval = setInterval(function() {
|
||||
if (tries >= 30) {
|
||||
ok(true, "The webapps-install notification didn't appear");
|
||||
moveOn();
|
||||
}
|
||||
|
||||
if (popupNotifications.getNotification("webapps-install")) {
|
||||
ok(false, "Found the webapps-install notification");
|
||||
moveOn();
|
||||
}
|
||||
tries++;
|
||||
}, 100);
|
||||
|
||||
var moveOn = () => {
|
||||
clearInterval(interval);
|
||||
resolve();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// We need to mock the Alerts service, otherwise the alert that is shown
|
||||
// at the end of an installation makes the test leak the app's icon.
|
||||
|
||||
const CID = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator).generateUUID();
|
||||
const ALERTS_SERVICE_CONTRACT_ID = "@mozilla.org/alerts-service;1";
|
||||
const ALERTS_SERVICE_CID = Components.ID(Cc[ALERTS_SERVICE_CONTRACT_ID].number);
|
||||
|
||||
var AlertsService = {
|
||||
classID: Components.ID(CID),
|
||||
|
||||
QueryInterface: XPCOMUtils.generateQI([Ci.nsIFactory,
|
||||
Ci.nsIAlertsService]),
|
||||
|
||||
createInstance: function(aOuter, aIID) {
|
||||
if (aOuter) {
|
||||
throw Cr.NS_ERROR_NO_AGGREGATION;
|
||||
}
|
||||
|
||||
return this.QueryInterface(aIID);
|
||||
},
|
||||
|
||||
init: function() {
|
||||
Components.manager.nsIComponentRegistrar.registerFactory(this.classID,
|
||||
"", ALERTS_SERVICE_CONTRACT_ID, this);
|
||||
},
|
||||
|
||||
restore: function() {
|
||||
Components.manager.nsIComponentRegistrar.registerFactory(ALERTS_SERVICE_CID,
|
||||
"", ALERTS_SERVICE_CONTRACT_ID, null);
|
||||
},
|
||||
|
||||
showAlert: function() {
|
||||
},
|
||||
|
||||
showAlertNotification: function() {
|
||||
},
|
||||
};
|
||||
|
||||
AlertsService.init();
|
||||
|
||||
SimpleTest.registerCleanupFunction(() => {
|
||||
AlertsService.restore();
|
||||
});
|
||||
23
dom/apps/tests/unit/test_manifestHelper.js
Normal file
23
dom/apps/tests/unit/test_manifestHelper.js
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
// Test that W3C start_url takes precedence over mozApps launch_path
|
||||
function run_test() {
|
||||
Components.utils.import("resource:///modules/AppsUtils.jsm");
|
||||
|
||||
let manifest1 = {
|
||||
launch_path: "other.html"
|
||||
};
|
||||
|
||||
let manifest2 = {
|
||||
start_url: "start.html",
|
||||
launch_path: "other.html"
|
||||
};
|
||||
|
||||
var helper = new ManifestHelper(manifest1, "http://foo.com",
|
||||
"http://foo.com/manifest.json");
|
||||
var path = helper.fullLaunchPath();
|
||||
do_check_true(path == "http://foo.com/other.html");
|
||||
|
||||
helper = new ManifestHelper(manifest2, "http://foo.com",
|
||||
"http://foo.com/manifest.json");
|
||||
path = helper.fullLaunchPath();
|
||||
do_check_true(path == "http://foo.com/start.html");
|
||||
}
|
||||
63
dom/apps/tests/unit/test_manifestSanitizer.js
Normal file
63
dom/apps/tests/unit/test_manifestSanitizer.js
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
function testEntryPoint(aRoot) {
|
||||
do_check_true(aRoot.name == "hello world");
|
||||
do_check_true(aRoot.description == "A bold name");
|
||||
do_check_true(aRoot.developer.name == "Blink Inc.");
|
||||
|
||||
let permissions = aRoot.permissions;
|
||||
do_check_true(permissions.contacts.description == "Required for autocompletion in the share screen");
|
||||
do_check_true(permissions.alarms.description == "Required to schedule notifications");
|
||||
}
|
||||
|
||||
function run_test() {
|
||||
Components.utils.import("resource:///modules/AppsUtils.jsm");
|
||||
|
||||
do_check_true(!!AppsUtils);
|
||||
|
||||
// Test manifest, with one entry point.
|
||||
let manifest = {
|
||||
name: "hello <b>world</b>",
|
||||
description: "A bold name",
|
||||
developer: {
|
||||
name: "<blink>Blink</blink> Inc.",
|
||||
url: "http://blink.org"
|
||||
},
|
||||
permissions : {
|
||||
"contacts": {
|
||||
"description": "Required for autocompletion in the <a href='http://shareme.com'>share</a> screen",
|
||||
"access": "readcreate"
|
||||
},
|
||||
"alarms": {
|
||||
"description": "Required to schedule notifications"
|
||||
}
|
||||
},
|
||||
|
||||
entry_points: {
|
||||
"subapp": {
|
||||
name: "hello <b>world</b>",
|
||||
description: "A bold name",
|
||||
developer: {
|
||||
name: "<blink>Blink</blink> Inc.",
|
||||
url: "http://blink.org"
|
||||
},
|
||||
permissions : {
|
||||
"contacts": {
|
||||
"description": "Required for autocompletion in the <a href='http://shareme.com'>share</a> screen",
|
||||
"access": "readcreate"
|
||||
},
|
||||
"alarms": {
|
||||
"description": "Required to schedule notifications"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AppsUtils.sanitizeManifest(manifest);
|
||||
|
||||
// Check the main section and the subapp entry point.
|
||||
testEntryPoint(manifest);
|
||||
testEntryPoint(manifest.entry_points.subapp);
|
||||
}
|
||||
64
dom/apps/tests/unit/test_moziapplication.js
Normal file
64
dom/apps/tests/unit/test_moziapplication.js
Normal file
|
|
@ -0,0 +1,64 @@
|
|||
/* Any copyright is dedicated to the Public Domain.
|
||||
http://creativecommons.org/publicdomain/zero/1.0/ */
|
||||
|
||||
var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
|
||||
Cu.import("resource:///modules/AppsUtils.jsm");
|
||||
|
||||
add_test(() => {
|
||||
let app = {
|
||||
name: "TestApp",
|
||||
csp: "aCsp",
|
||||
installOrigin: "http://installorigin.com",
|
||||
origin: "http://www.example.com",
|
||||
installTime: Date.now(),
|
||||
manifestURL: "http://www.example.com/manifest.webapp",
|
||||
appStatus: Ci.nsIPrincipal.APP_STATUS_NOT_INSTALLED,
|
||||
removable: false,
|
||||
id: 123,
|
||||
localId: 123,
|
||||
basePath: "/",
|
||||
progress: 1.0,
|
||||
installState: "installed",
|
||||
downloadAvailable: false,
|
||||
downloading: false,
|
||||
lastUpdateCheck: Date.now(),
|
||||
updateTime: Date.now(),
|
||||
etag: "aEtag",
|
||||
packageEtag: "aPackageEtag",
|
||||
manifestHash: "aManifestHash",
|
||||
packageHash: "aPackageHash",
|
||||
staged: false,
|
||||
installerAppId: 345,
|
||||
installerIsBrowser: false,
|
||||
storeId: "aStoreId",
|
||||
storeVersion: 1,
|
||||
role: "aRole",
|
||||
kind: "aKind",
|
||||
enabled: true,
|
||||
sideloaded: false
|
||||
};
|
||||
|
||||
let mozapp = new mozIApplication(app);
|
||||
|
||||
Object.keys(app).forEach((key) => {
|
||||
if (key == "principal") {
|
||||
return;
|
||||
}
|
||||
Assert.equal(app[key], mozapp[key],
|
||||
"app[" + key + "] should be equal to mozapp[" + key + "]");
|
||||
});
|
||||
|
||||
Assert.ok(mozapp.principal, "app principal should exist");
|
||||
let expectedPrincipalOrigin = app.origin + "^appId=" + app.localId;
|
||||
Assert.equal(mozapp.principal.origin, expectedPrincipalOrigin,
|
||||
"app principal origin ok");
|
||||
Assert.equal(mozapp.principal.appId, app.localId, "app principal appId ok");
|
||||
Assert.equal(mozapp.principal.isInIsolatedMozBrowserElement, false,
|
||||
"app principal isInIsolatedMozBrowserElement ok");
|
||||
run_next_test();
|
||||
});
|
||||
|
||||
function run_test() {
|
||||
run_next_test();
|
||||
}
|
||||
5
dom/apps/tests/unit/xpcshell.ini
Normal file
5
dom/apps/tests/unit/xpcshell.ini
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
[DEFAULT]
|
||||
|
||||
[test_manifestSanitizer.js]
|
||||
[test_manifestHelper.js]
|
||||
[test_moziapplication.js]
|
||||
Loading…
Add table
Add a link
Reference in a new issue