diff --git a/b2g/chrome/content/shell.js b/b2g/chrome/content/shell.js
index adc94b5d48f..d48980d8635 100644
--- a/b2g/chrome/content/shell.js
+++ b/b2g/chrome/content/shell.js
@@ -816,7 +816,7 @@ var AlertsHelper = {
this._listeners[uid] = listener;
let app = DOMApplicationRegistry.getAppByManifestURL(listener.manifestURL);
- DOMApplicationRegistry.getManifestFor(app.manifestURL, function(manifest) {
+ DOMApplicationRegistry.getManifestFor(app.manifestURL).then((manifest) => {
let helper = new ManifestHelper(manifest, app.origin);
let getNotificationURLFor = function(messages) {
if (!messages)
@@ -873,7 +873,7 @@ var AlertsHelper = {
// If we have a manifest URL, get the icon and title from the manifest
// to prevent spoofing.
let app = DOMApplicationRegistry.getAppByManifestURL(manifestUrl);
- DOMApplicationRegistry.getManifestFor(manifestUrl, function(aManifest) {
+ DOMApplicationRegistry.getManifestFor(manifestUrl).then((aManifest) => {
let helper = new ManifestHelper(aManifest, app.origin);
send(helper.name, helper.iconURLForSize(128));
});
@@ -972,7 +972,7 @@ var WebappsHelper = {
switch(topic) {
case "webapps-launch":
- DOMApplicationRegistry.getManifestFor(json.manifestURL, function(aManifest) {
+ DOMApplicationRegistry.getManifestFor(json.manifestURL).then((aManifest) => {
if (!aManifest)
return;
diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml
index 824ebddbcc7..cea3eda870a 100644
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml
index 1fb60999250..0d13dbfdbf1 100644
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml
index 824ebddbcc7..cea3eda870a 100644
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json
index 622de6aa0d6..b0de69e6861 100644
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
{
- "revision": "6ee0334bda22b4848d99e3a44e29eab3de815f52",
+ "revision": "18bd82325a82f5b9a3a4b976e213515cd3e5866b",
"repo_path": "/integration/gaia-central"
}
diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml
index cc80701aea0..97a212e1142 100644
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml
index 5dbf627a872..e0477b31603 100644
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,7 +10,7 @@
-
+
diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml
index 9f6a2ea833b..b91d60ca92f 100644
--- a/b2g/config/inari/sources.xml
+++ b/b2g/config/inari/sources.xml
@@ -12,7 +12,7 @@
-
+
diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml
index 6cc70cce37d..a3bbf36cce6 100644
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/sources.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml
index 82dcda84b2d..63dc9c0d9a6 100644
--- a/b2g/config/mako/sources.xml
+++ b/b2g/config/mako/sources.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml
index a17e19e0317..c478353d9d2 100644
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -11,7 +11,7 @@
-
+
diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js
index 2601cbc8789..fca1ee8d023 100644
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -508,6 +508,49 @@ WebappsApplication.prototype = {
}.bind(this));
},
+ addReceipt: function(receipt) {
+ let request = this.createRequest();
+
+ this.addMessageListeners(["Webapps:AddReceipt:Return:OK",
+ "Webapps:AddReceipt:Return:KO"]);
+
+ cpmm.sendAsyncMessage("Webapps:AddReceipt", { manifestURL: this.manifestURL,
+ receipt: receipt,
+ oid: this._id,
+ requestID: this.getRequestId(request) });
+
+ return request;
+ },
+
+ removeReceipt: function(receipt) {
+ let request = this.createRequest();
+
+ this.addMessageListeners(["Webapps:RemoveReceipt:Return:OK",
+ "Webapps:RemoveReceipt:Return:KO"]);
+
+ cpmm.sendAsyncMessage("Webapps:RemoveReceipt", { manifestURL: this.manifestURL,
+ receipt: receipt,
+ oid: this._id,
+ requestID: this.getRequestId(request) });
+
+ return request;
+ },
+
+ replaceReceipt: function(oldReceipt, newReceipt) {
+ let request = this.createRequest();
+
+ this.addMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
+ "Webapps:ReplaceReceipt:Return:KO"]);
+
+ cpmm.sendAsyncMessage("Webapps:ReplaceReceipt", { manifestURL: this.manifestURL,
+ newReceipt: newReceipt,
+ oldReceipt: oldReceipt,
+ oid: this._id,
+ requestID: this.getRequestId(request) });
+
+ return request;
+ },
+
uninit: function() {
this._onprogress = null;
cpmm.sendAsyncMessage("Webapps:UnregisterForMessages", [
@@ -638,6 +681,39 @@ WebappsApplication.prototype = {
});
req.resolve(connections);
break;
+ case "Webapps:AddReceipt:Return:OK":
+ this.removeMessageListeners(["Webapps:AddReceipt:Return:OK",
+ "Webapps:AddReceipt:Return:KO"]);
+ this.receipts = msg.receipts;
+ Services.DOMRequest.fireSuccess(req, null);
+ break;
+ case "Webapps:AddReceipt:Return:KO":
+ this.removeMessageListeners(["Webapps:AddReceipt:Return:OK",
+ "Webapps:AddReceipt:Return:KO"]);
+ Services.DOMRequest.fireError(req, msg.error);
+ break;
+ case "Webapps:RemoveReceipt:Return:OK":
+ this.removeMessageListeners(["Webapps:RemoveReceipt:Return:OK",
+ "Webapps:RemoveReceipt:Return:KO"]);
+ this.receipts = msg.receipts;
+ Services.DOMRequest.fireSuccess(req, null);
+ break;
+ case "Webapps:RemoveReceipt:Return:KO":
+ this.removeMessageListeners(["Webapps:RemoveReceipt:Return:OK",
+ "Webapps:RemoveReceipt:Return:KO"]);
+ Services.DOMRequest.fireError(req, msg.error);
+ break;
+ case "Webapps:ReplaceReceipt:Return:OK":
+ this.removeMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
+ "Webapps:ReplaceReceipt:Return:KO"]);
+ this.receipts = msg.receipts;
+ Services.DOMRequest.fireSuccess(req, null);
+ break;
+ case "Webapps:ReplaceReceipt:Return:KO":
+ this.removeMessageListeners(["Webapps:ReplaceReceipt:Return:OK",
+ "Webapps:ReplaceReceipt:Return:KO"]);
+ Services.DOMRequest.fireError(req, msg.error);
+ break;
}
},
diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm
index 0f447224886..fd3d854f419 100755
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -128,6 +128,7 @@ XPCOMUtils.defineLazyGetter(this, "updateSvc", function() {
const STORE_ID_PENDING_PREFIX = "#unknownID#";
this.DOMApplicationRegistry = {
+ // Path to the webapps.json file where we store the registry data.
appsFile: null,
webapps: { },
children: [ ],
@@ -143,7 +144,8 @@ this.DOMApplicationRegistry = {
"Webapps:UnregisterForMessages",
"Webapps:CancelDownload", "Webapps:CheckForUpdate",
"Webapps:Download", "Webapps:ApplyDownload",
- "Webapps:Install:Return:Ack",
+ "Webapps:Install:Return:Ack", "Webapps:AddReceipt",
+ "Webapps:RemoveReceipt", "Webapps:ReplaceReceipt",
"child-process-shutdown"];
this.frameMessages = ["Webapps:ClearBrowserData"];
@@ -160,87 +162,82 @@ this.DOMApplicationRegistry = {
AppDownloadManager.registerCancelFunction(this.cancelDownload.bind(this));
this.appsFile = FileUtils.getFile(DIRECTORY_NAME,
- ["webapps", "webapps.json"], true);
+ ["webapps", "webapps.json"], true).path;
this.loadAndUpdateApps();
},
// loads the current registry, that could be empty on first run.
- // aNext() is called after we load the current webapps list.
- loadCurrentRegistry: function loadCurrentRegistry(aNext) {
- let file = FileUtils.getFile(DIRECTORY_NAME, ["webapps", "webapps.json"], false);
- if (file && file.exists()) {
- this._loadJSONAsync(file, (function loadRegistry(aData) {
- if (aData) {
- this.webapps = aData;
- let appDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false);
- for (let id in this.webapps) {
- let app = this.webapps[id];
- if (!app) {
- delete this.webapps[id];
- continue;
- }
+ loadCurrentRegistry: function() {
+ return this._loadJSONAsync(this.appsFile).then((aData) => {
+ if (!aData) {
+ return;
+ }
- app.id = id;
-
- // Make sure we have a localId
- if (app.localId === undefined) {
- app.localId = this._nextLocalId();
- }
-
- if (app.basePath === undefined) {
- app.basePath = appDir.path;
- }
-
- // Default to removable apps.
- if (app.removable === undefined) {
- app.removable = true;
- }
-
- // Default to a non privileged status.
- if (app.appStatus === undefined) {
- app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
- }
-
- // Default to NO_APP_ID and not in browser.
- if (app.installerAppId === undefined) {
- app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
- }
- if (app.installerIsBrowser === undefined) {
- app.installerIsBrowser = false;
- }
-
- // Default installState to "installed", and reset if we shutdown
- // during an update.
- if (app.installState === undefined ||
- app.installState === "updating") {
- app.installState = "installed";
- }
-
- // Default storeId to "" and storeVersion to 0
- if (this.webapps[id].storeId === undefined) {
- this.webapps[id].storeId = "";
- }
- if (this.webapps[id].storeVersion === undefined) {
- this.webapps[id].storeVersion = 0;
- }
-
- // Default role to "".
- if (this.webapps[id].role === undefined) {
- this.webapps[id].role = "";
- }
-
- // At startup we can't be downloading, and the $TMP directory
- // will be empty so we can't just apply a staged update.
- app.downloading = false;
- app.readyToApplyDownload = false;
- };
+ this.webapps = aData;
+ let appDir = OS.Path.dirname(this.appsFile);
+ for (let id in this.webapps) {
+ let app = this.webapps[id];
+ if (!app) {
+ delete this.webapps[id];
+ continue;
}
- aNext();
- }).bind(this));
- } else {
- aNext();
- }
+
+ app.id = id;
+
+ // Make sure we have a localId
+ if (app.localId === undefined) {
+ app.localId = this._nextLocalId();
+ }
+
+ if (app.basePath === undefined) {
+ app.basePath = appDir;
+ }
+
+ // Default to removable apps.
+ if (app.removable === undefined) {
+ app.removable = true;
+ }
+
+ // Default to a non privileged status.
+ if (app.appStatus === undefined) {
+ app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
+ }
+
+ // Default to NO_APP_ID and not in browser.
+ if (app.installerAppId === undefined) {
+ app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
+ }
+ if (app.installerIsBrowser === undefined) {
+ app.installerIsBrowser = false;
+ }
+
+ // Default installState to "installed", and reset if we shutdown
+ // during an update.
+ if (app.installState === undefined ||
+ app.installState === "updating") {
+ app.installState = "installed";
+ }
+
+ // Default storeId to "" and storeVersion to 0
+ if (this.webapps[id].storeId === undefined) {
+ this.webapps[id].storeId = "";
+ }
+ if (this.webapps[id].storeVersion === undefined) {
+ this.webapps[id].storeVersion = 0;
+ }
+
+ // Default role to "".
+ if (this.webapps[id].role === undefined) {
+ this.webapps[id].role = "";
+ }
+
+ // At startup we can't be downloading, and the $TMP directory
+ // will be empty so we can't just apply a staged update.
+ app.downloading = false;
+ app.readyToApplyDownload = false;
+ }
+ });
},
// Notify we are starting with registering apps.
@@ -273,7 +270,7 @@ this.DOMApplicationRegistry = {
},
// Registers all the activities and system messages.
- registerAppsHandlers: function registerAppsHandlers(aRunUpdate) {
+ registerAppsHandlers: function(aRunUpdate) {
this.notifyAppsRegistryStart();
let ids = [];
for (let id in this.webapps) {
@@ -285,8 +282,8 @@ this.DOMApplicationRegistry = {
// Read the CSPs and roles. If MOZ_SYS_MSG is defined this is done on
// _processManifestForIds so as to not reading the manifests
// twice
- this._readManifests(ids, (function readCSPs(aResults) {
- aResults.forEach(function registerManifest(aResult) {
+ this._readManifests(ids).then((aResults) => {
+ aResults.forEach((aResult) => {
if (!aResult.manifest) {
// If we can't load the manifest, we probably have a corrupted
// registry. We delete the app since we can't do anything with it.
@@ -299,8 +296,8 @@ this.DOMApplicationRegistry = {
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
app.redirects = this.sanitizeRedirects(aResult.redirects);
}
- }, this);
- }).bind(this));
+ });
+ });
// Nothing else to do but notifying we're ready.
this.notifyAppsRegistryReady();
@@ -313,14 +310,14 @@ this.DOMApplicationRegistry = {
}
// Create or Update the DataStore for this app
- this._readManifests([{ id: aId }], (function(aResult) {
+ this._readManifests([{ id: aId }]).then((aResult) => {
let app = this.webapps[aId];
this.updateDataStore(app.localId, app.origin, app.manifestURL,
aResult[0].manifest, app.appStatus);
- }).bind(this));
+ });
},
- updatePermissionsForApp: function updatePermissionsForApp(aId) {
+ updatePermissionsForApp: function(aId) {
if (!this.webapps[aId]) {
return;
}
@@ -329,7 +326,7 @@ this.DOMApplicationRegistry = {
// to cleanup the old ones if needed.
// TODO It's not clear what this should do when there are multiple profiles.
if (supportUseCurrentProfile()) {
- this._readManifests([{ id: aId }], (function(aResult) {
+ this._readManifests([{ id: aId }]).then((aResult) => {
let data = aResult[0];
PermissionsInstaller.installPermissions({
manifest: data.manifest,
@@ -338,13 +335,13 @@ this.DOMApplicationRegistry = {
}, true, function() {
debug("Error installing permissions for " + aId);
});
- }).bind(this));
+ });
}
},
- updateOfflineCacheForApp: function updateOfflineCacheForApp(aId) {
+ updateOfflineCacheForApp: function(aId) {
let app = this.webapps[aId];
- this._readManifests([{ id: aId }], function(aResult) {
+ this._readManifests([{ id: aId }]).then((aResult) => {
let manifest = new ManifestHelper(aResult[0].manifest, app.origin);
OfflineCacheInstaller.installCache({
cachePath: app.cachePath,
@@ -413,8 +410,7 @@ this.DOMApplicationRegistry = {
app.installState = "installed";
app.cachePath = app.basePath;
- app.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true)
- .path;
+ app.basePath = OS.Path.dirname(this.appsFile);
if (!isPackage) {
return;
@@ -484,63 +480,62 @@ this.DOMApplicationRegistry = {
// new core apps registry.
// c. for all apps in the new core registry, install them if they are not
// yet in the current registry, and run installPermissions()
- installSystemApps: function installSystemApps(aNext) {
- let file;
- try {
- file = FileUtils.getFile("coreAppsDir", ["webapps", "webapps.json"], false);
- } catch(e) { }
+ installSystemApps: function() {
+ return Task.spawn(function() {
+ let file;
+ try {
+ file = FileUtils.getFile("coreAppsDir", ["webapps", "webapps.json"], false);
+ } catch(e) { }
+
+ if (!file || !file.exists()) {
+ return;
+ }
- if (file && file.exists()) {
// a
- this._loadJSONAsync(file, (function loadCoreRegistry(aData) {
- if (!aData) {
- aNext();
- return;
- }
+ let data = yield this._loadJSONAsync(file.path);
+ if (!data) {
+ return;
+ }
- // b : core apps are not removable.
- for (let id in this.webapps) {
- if (id in aData || this.webapps[id].removable)
- continue;
- // Remove the permissions, cookies and private data for this app.
- let localId = this.webapps[id].localId;
- let permMgr = Cc["@mozilla.org/permissionmanager;1"]
- .getService(Ci.nsIPermissionManager);
- permMgr.removePermissionsForApp(localId, false);
- Services.cookies.removeCookiesForApp(localId, false);
- this._clearPrivateData(localId, false);
- delete this.webapps[id];
- }
+ // b : core apps are not removable.
+ for (let id in this.webapps) {
+ if (id in data || this.webapps[id].removable)
+ continue;
+ // Remove the permissions, cookies and private data for this app.
+ let localId = this.webapps[id].localId;
+ let permMgr = Cc["@mozilla.org/permissionmanager;1"]
+ .getService(Ci.nsIPermissionManager);
+ permMgr.removePermissionsForApp(localId, false);
+ Services.cookies.removeCookiesForApp(localId, false);
+ this._clearPrivateData(localId, false);
+ delete this.webapps[id];
+ }
- let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
- // c
- for (let id in aData) {
- // Core apps have ids matching their domain name (eg: dialer.gaiamobile.org)
- // Use that property to check if they are new or not.
- if (!(id in this.webapps)) {
- this.webapps[id] = aData[id];
- this.webapps[id].basePath = appDir.path;
+ let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
+ // c
+ for (let id in data) {
+ // Core apps have ids matching their domain name (eg: dialer.gaiamobile.org)
+ // Use that property to check if they are new or not.
+ if (!(id in this.webapps)) {
+ this.webapps[id] = data[id];
+ this.webapps[id].basePath = appDir.path;
- this.webapps[id].id = id;
+ this.webapps[id].id = id;
- // Create a new localId.
- this.webapps[id].localId = this._nextLocalId();
+ // Create a new localId.
+ this.webapps[id].localId = this._nextLocalId();
- // Core apps are not removable.
- if (this.webapps[id].removable === undefined) {
- this.webapps[id].removable = false;
- }
+ // Core apps are not removable.
+ if (this.webapps[id].removable === undefined) {
+ this.webapps[id].removable = false;
}
}
- aNext();
- }).bind(this));
- } else {
- aNext();
- }
+ }
+ }.bind(this)).then(null, Cu.reportError);
},
#ifdef MOZ_WIDGET_GONK
- fixIndexedDb: function fixIndexedDb() {
+ fixIndexedDb: function() {
debug("Fixing indexedDb folder names");
let idbDir = FileUtils.getDir("indexedDBPDir", ["indexedDB"]);
@@ -567,17 +562,23 @@ this.DOMApplicationRegistry = {
},
#endif
- loadAndUpdateApps: function loadAndUpdateApps() {
- let runUpdate = AppsUtils.isFirstRun(Services.prefs);
+ loadAndUpdateApps: function() {
+ return Task.spawn(function() {
+ let runUpdate = AppsUtils.isFirstRun(Services.prefs);
#ifdef MOZ_WIDGET_GONK
- if (runUpdate) {
- this.fixIndexedDb();
- }
+ if (runUpdate) {
+ this.fixIndexedDb();
+ }
#endif
- let onAppsLoaded = (function onAppsLoaded() {
+ yield this.loadCurrentRegistry();
+
if (runUpdate) {
+#ifdef MOZ_WIDGET_GONK
+ yield this.installSystemApps();
+#endif
+
// At first run, install preloaded apps and set up their permissions.
for (let id in this.webapps) {
this.installPreinstalledApp(id);
@@ -599,19 +600,7 @@ this.DOMApplicationRegistry = {
}
this.registerAppsHandlers(runUpdate);
- }).bind(this);
-
- this.loadCurrentRegistry((function() {
-#ifdef MOZ_WIDGET_GONK
- // if first run, merge the system apps.
- if (runUpdate)
- this.installSystemApps(onAppsLoaded);
- else
- onAppsLoaded();
-#else
- onAppsLoaded();
-#endif
- }).bind(this));
+ }.bind(this)).then(null, Cu.reportError);
},
updateDataStore: function(aId, aOrigin, aManifestURL, aManifest, aAppStatus) {
@@ -929,9 +918,9 @@ this.DOMApplicationRegistry = {
},
_processManifestForIds: function(aIds, aRunUpdate) {
- this._readManifests(aIds, (function registerManifests(aResults) {
+ this._readManifests(aIds).then((aResults) => {
let appsToRegister = [];
- aResults.forEach(function registerManifest(aResult) {
+ aResults.forEach((aResult) => {
let app = this.webapps[aResult.id];
let manifest = aResult.manifest;
if (!manifest) {
@@ -949,9 +938,9 @@ this.DOMApplicationRegistry = {
this._registerSystemMessages(manifest, app);
this._registerInterAppConnections(manifest, app);
appsToRegister.push({ manifest: manifest, app: app });
- }, this);
+ });
this._registerActivitiesForApps(appsToRegister, aRunUpdate);
- }).bind(this));
+ });
},
observe: function(aSubject, aTopic, aData) {
@@ -968,45 +957,46 @@ this.DOMApplicationRegistry = {
}
},
- _loadJSONAsync: function(aFile, aCallback) {
+ _loadJSONAsync: function(aPath) {
+ let deferred = Promise.defer();
+
try {
- let channel = NetUtil.newChannel(aFile);
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath(aPath);
+ let channel = NetUtil.newChannel(file);
channel.contentType = "application/json";
NetUtil.asyncFetch(channel, function(aStream, aResult) {
if (!Components.isSuccessCode(aResult)) {
Cu.reportError("DOMApplicationRegistry: Could not read from json file "
- + aFile.path);
- if (aCallback)
- aCallback(null);
- return;
+ + aPath);
+ deferred.resolve(null);
}
- // Read json file into a string
- let data = null;
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";
- data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
+ // Read json file into a string
+ let data = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(aStream,
aStream.available()) || ""));
aStream.close();
- if (aCallback)
- aCallback(data);
+
+ deferred.resolve(data);
} catch (ex) {
Cu.reportError("DOMApplicationRegistry: Could not parse JSON: " +
- aFile.path + " " + ex + "\n" + ex.stack);
- if (aCallback)
- aCallback(null);
+ aPath + " " + ex + "\n" + ex.stack);
+ deferred.resolve(null);
}
});
} catch (ex) {
Cu.reportError("DOMApplicationRegistry: Could not read from " +
- aFile.path + " : " + ex + "\n" + ex.stack);
- if (aCallback)
- aCallback(null);
+ aPath + " : " + ex + "\n" + ex.stack);
+ deferred.resolve(null);
}
+
+ return deferred.promise;
},
addMessageListener: function(aMsgNames, aApp, aMm) {
@@ -1186,6 +1176,15 @@ this.DOMApplicationRegistry = {
case "Webapps:Install:Return:Ack":
this.onInstallSuccessAck(msg.manifestURL);
break;
+ case "Webapps:AddReceipt":
+ this.addReceipt(msg, mm);
+ break;
+ case "Webapps:RemoveReceipt":
+ this.removeReceipt(msg, mm);
+ break;
+ case "Webapps:ReplaceReceipt":
+ this.replaceReceipt(msg, mm);
+ break;
}
},
@@ -1215,23 +1214,13 @@ this.DOMApplicationRegistry = {
return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
},
- _writeFile: function _writeFile(aFile, aData, aCallback) {
- debug("Saving " + aFile.path);
- // Initialize the file output stream.
- let ostream = FileUtils.openSafeFileOutputStream(aFile);
+ _writeFile: function(aPath, aData) {
+ debug("Saving " + aPath);
- // Obtain a converter to convert our data to a UTF-8 encoded input stream.
- let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
- .createInstance(Ci.nsIScriptableUnicodeConverter);
- converter.charset = "UTF-8";
-
- // Asynchronously copy the data to the file.
- let istream = converter.convertToInputStream(aData);
- NetUtil.asyncCopy(istream, ostream, function(rc) {
- if (aCallback) {
- aCallback();
- }
- });
+ return OS.File.writeAtomic(aPath,
+ new TextEncoder().encode(aData),
+ { tmpPath: aPath + ".tmp" })
+ .then(null, Cu.reportError);
},
doLaunch: function (aData, aMm) {
@@ -1305,7 +1294,7 @@ this.DOMApplicationRegistry = {
return;
}
- this._saveApps((function() {
+ this._saveApps().then(() => {
this.broadcastMessage("Webapps:UpdateState", {
app: {
progress: 0,
@@ -1319,7 +1308,7 @@ this.DOMApplicationRegistry = {
eventType: "downloaderror",
manifestURL: app.manifestURL
});
- }).bind(this));
+ });
AppDownloadManager.remove(aManifestURL);
},
@@ -1371,7 +1360,7 @@ this.DOMApplicationRegistry = {
if (!file.exists()) {
// This is a hosted app, let's check if it has an appcache
// and download it.
- this._readManifests([{ id: id }], (function readManifest(aResults) {
+ this._readManifests([{ id: id }]).then((aResults) => {
let jsonManifest = aResults[0].manifest;
let manifest = new ManifestHelper(jsonManifest, app.origin);
@@ -1383,24 +1372,24 @@ this.DOMApplicationRegistry = {
// downloaded event.
debug("No appcache found, sending 'downloaded' for " + aManifestURL);
app.downloadAvailable = false;
- DOMApplicationRegistry._saveApps(function() {
- DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+ this._saveApps().then(() => {
+ this.broadcastMessage("Webapps:UpdateState", {
app: app,
manifest: jsonManifest,
manifestURL: aManifestURL
});
- DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+ this.broadcastMessage("Webapps:FireEvent", {
eventType: "downloadsuccess",
manifestURL: aManifestURL
});
});
}
- }).bind(this));
+ });
return;
}
- this._loadJSONAsync(file, (function(aJSON) {
+ this._loadJSONAsync(file.path).then((aJSON) => {
if (!aJSON) {
debug("startDownload: No update manifest found at " + file.path + " " +
aManifestURL);
@@ -1416,14 +1405,10 @@ this.DOMApplicationRegistry = {
}, isUpdate).then(function([aId, aManifest]) {
// Success! Keep the zip in of TmpD, we'll move it out when
// applyDownload() will be called.
- let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
-
// Save the manifest in TmpD also
- let manFile = tmpDir.clone();
- manFile.append("manifest.webapp");
- DOMApplicationRegistry._writeFile(manFile,
- JSON.stringify(aManifest),
- function() { });
+ let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId,
+ "manifest.webapp");
+ DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest));
app = DOMApplicationRegistry.webapps[aId];
// Set state and fire events.
@@ -1431,7 +1416,7 @@ this.DOMApplicationRegistry = {
app.downloadAvailable = false;
app.readyToApplyDownload = true;
app.updateTime = Date.now();
- DOMApplicationRegistry._saveApps(function() {
+ DOMApplicationRegistry._saveApps().then(() => {
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
app: app,
manifestURL: aManifestURL
@@ -1446,7 +1431,7 @@ this.DOMApplicationRegistry = {
}
});
});
- }).bind(this));
+ });
},
applyDownload: function applyDownload(aManifestURL) {
@@ -1458,7 +1443,7 @@ this.DOMApplicationRegistry = {
}
// We need to get the old manifest to unregister web activities.
- this.getManifestFor(aManifestURL, (function(aOldManifest) {
+ this.getManifestFor(aManifestURL).then((aOldManifest) => {
// Move the application.zip and manifest.webapp files out of TmpD
let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
let manFile = tmpDir.clone();
@@ -1496,7 +1481,7 @@ this.DOMApplicationRegistry = {
Services.obs.notifyObservers(zipFile, "flush-cache-entry", null);
// Get the manifest, and set properties.
- this.getManifestFor(aManifestURL, (function(aData) {
+ this.getManifestFor(aManifestURL).then((aData) => {
app.downloading = false;
app.downloadAvailable = false;
app.downloadSize = 0;
@@ -1513,7 +1498,7 @@ this.DOMApplicationRegistry = {
delete app.retryingDownload;
- this._saveApps((function() {
+ this._saveApps().then(() => {
// Update the handlers and permissions for this app.
this.updateAppHandlers(aOldManifest, aData, app);
if (supportUseCurrentProfile()) {
@@ -1534,9 +1519,9 @@ this.DOMApplicationRegistry = {
eventType: "downloadapplied",
manifestURL: app.manifestURL
});
- }).bind(this));
- }).bind(this));
- }).bind(this));
+ });
+ });
+ });
},
startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) {
@@ -1561,7 +1546,7 @@ this.DOMApplicationRegistry = {
// starting the app download/update.
aApp.downloading = true;
aApp.progress = 0;
- DOMApplicationRegistry._saveApps((function() {
+ DOMApplicationRegistry._saveApps().then(() => {
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
app: {
downloading: true,
@@ -1585,7 +1570,7 @@ this.DOMApplicationRegistry = {
cacheUpdate.addObserver(new AppcacheObserver(aApp), false);
- }).bind(this));
+ });
},
// Returns the MD5 hash of the manifest.
@@ -1637,10 +1622,9 @@ this.DOMApplicationRegistry = {
}
// Store the new update manifest.
- let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
- let manFile = dir.clone();
- manFile.append("staged-update.webapp");
- this._writeFile(manFile, JSON.stringify(aManifest), function() { });
+ let dir = this._getAppDir(id).path;
+ let manFile = OS.Path.join(dir, "staged-update.webapp");
+ this._writeFile(manFile, JSON.stringify(aManifest));
let manifest = new ManifestHelper(aManifest, app.manifestURL);
// A package is available: set downloadAvailable to fire the matching
@@ -1648,12 +1632,12 @@ this.DOMApplicationRegistry = {
app.downloadAvailable = true;
app.downloadSize = manifest.size;
app.updateManifest = aManifest;
- DOMApplicationRegistry._saveApps(function() {
- DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+ this._saveApps().then(() => {
+ this.broadcastMessage("Webapps:UpdateState", {
app: app,
manifestURL: app.manifestURL
});
- DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+ this.broadcastMessage("Webapps:FireEvent", {
eventType: "downloadavailable",
manifestURL: app.manifestURL,
requestID: aData.requestID
@@ -1682,10 +1666,9 @@ this.DOMApplicationRegistry = {
this.updateAppHandlers(aOldManifest, aNewManifest, app);
// Store the new manifest.
- let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
- let manFile = dir.clone();
- manFile.append("manifest.webapp");
- this._writeFile(manFile, JSON.stringify(aNewManifest), function() { });
+ let dir = this._getAppDir(id).path;
+ let manFile = OS.Path.join(dir, "manifest.webapp");
+ this._writeFile(manFile, JSON.stringify(aNewManifest));
manifest = new ManifestHelper(aNewManifest, app.origin);
if (supportUseCurrentProfile()) {
@@ -1710,7 +1693,7 @@ this.DOMApplicationRegistry = {
// Update the registry.
this.webapps[id] = app;
- this._saveApps(function() {
+ this._saveApps().then(() => {
let reg = DOMApplicationRegistry;
if (!manifest.appcache_path) {
reg.broadcastMessage("Webapps:UpdateState", {
@@ -1734,7 +1717,7 @@ this.DOMApplicationRegistry = {
aTopic == "offline-cache-update-available" ? "downloadavailable"
: "downloadapplied";
app.downloadAvailable = (eventType == "downloadavailable");
- reg._saveApps(function() {
+ reg._saveApps().then(() => {
reg.broadcastMessage("Webapps:UpdateState", {
app: app,
manifest: app.manifest,
@@ -1795,7 +1778,7 @@ this.DOMApplicationRegistry = {
}
// We need the manifest to check if we have an appcache.
- this._readManifests([{ id: id }], function(aResult) {
+ this._readManifests([{ id: id }]).then((aResult) => {
let manifest = aResult[0].manifest;
if (!manifest.appcache_path) {
aData.error = "NOT_UPDATABLE";
@@ -1812,12 +1795,12 @@ this.DOMApplicationRegistry = {
app.manifestURL + " - event is " + aTopic);
if (aTopic == "offline-cache-update-available") {
app.downloadAvailable = true;
- this._saveApps(function() {
- DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
+ this._saveApps().then(() => {
+ this.broadcastMessage("Webapps:UpdateState", {
app: app,
manifestURL: app.manifestURL
});
- DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
+ this.broadcastMessage("Webapps:FireEvent", {
eventType: "downloadavailable",
manifestURL: app.manifestURL,
requestID: aData.requestID
@@ -1878,7 +1861,7 @@ this.DOMApplicationRegistry = {
if (oldHash != hash) {
updatePackagedApp.call(this, manifest);
} else {
- this._saveApps(function() {
+ this._saveApps().then(() => {
// Like if we got a 304, just send a 'downloadapplied'
// or downloadavailable event.
let eventType = app.downloadAvailable ? "downloadavailable"
@@ -1905,7 +1888,7 @@ this.DOMApplicationRegistry = {
// The manifest has not changed.
if (isPackage) {
app.lastCheckedUpdate = Date.now();
- this._saveApps(function() {
+ this._saveApps().then(() => {
// If the app is a packaged app, we just send a 'downloadapplied'
// or downloadavailable event.
let eventType = app.downloadAvailable ? "downloadavailable"
@@ -1959,7 +1942,7 @@ this.DOMApplicationRegistry = {
}
// Read the current app manifest file
- this._readManifests([{ id: id }], (function(aResult) {
+ this._readManifests([{ id: id }]).then((aResult) => {
let extraHeaders = [];
#ifdef MOZ_WIDGET_GONK
let pingManifestURL;
@@ -1975,7 +1958,7 @@ this.DOMApplicationRegistry = {
}
#endif
doRequest.call(this, aResult[0].manifest, extraHeaders);
- }).bind(this));
+ });
},
// Creates a nsILoadContext object with a given appId and isBrowser flag.
@@ -2312,8 +2295,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
appObject.localId = aLocalId;
- appObject.basePath =
- FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path;
+ appObject.basePath = OS.Path.dirname(this.appsFile);
appObject.name = aManifest.name;
appObject.csp = aManifest.csp || "";
appObject.role = aManifest.role || "";
@@ -2325,13 +2307,13 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
_writeManifestFile: function(aId, aIsPackage, aJsonManifest) {
debug("_writeManifestFile");
- let dir = this._getAppDir(aId);
- let manFile = dir.clone();
// For packaged apps, keep the update manifest distinct from the app manifest.
let manifestName = aIsPackage ? "update.webapp" : "manifest.webapp";
- manFile.append(manifestName);
- this._writeFile(manFile, JSON.stringify(aJsonManifest), function() { });
+
+ let dir = this._getAppDir(aId).path;
+ let manFile = OS.Path.join(dir, manifestName);
+ this._writeFile(manFile, JSON.stringify(aJsonManifest));
},
confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) {
@@ -2404,7 +2386,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
// We notify about the successful installation via mgmt.oninstall and the
// corresponging DOMRequest.onsuccess event as soon as the app is properly
// saved in the registry.
- this._saveApps((function() {
+ this._saveApps().then(() => {
this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
if (aData.isPackage && aData.autoInstall) {
// Skip directly to onInstallSuccessAck, since there isn't
@@ -2419,7 +2401,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
Services.obs.notifyObservers(null, "webapps-installed",
JSON.stringify({ manifestURL: app.manifestURL }));
- }).bind(this));
+ });
if (!aData.isPackage) {
this.updateAppHandlers(null, app.manifest, app);
@@ -2489,14 +2471,13 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
} catch(e) { }
// Save the manifest
- let manFile = dir.clone();
- manFile.append("manifest.webapp");
- this._writeFile(manFile, JSON.stringify(aManifest), function() { });
+ let manFile = OS.Path.join(dir.path, "manifest.webapp");
+ this._writeFile(manFile, JSON.stringify(aManifest));
// Set state and fire events.
app.installState = "installed";
app.downloading = false;
app.downloadAvailable = false;
- this._saveApps((function() {
+ this._saveApps().then(() => {
this.updateAppHandlers(null, aManifest, aNewApp);
this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp });
Services.obs.notifyObservers(null, "webapps-installed",
@@ -2526,7 +2507,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
if (aInstallSuccessCallback) {
aInstallSuccessCallback(aManifest, zipFile.path);
}
- }).bind(this));
+ });
},
_nextLocalId: function() {
@@ -2554,9 +2535,8 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
return uuidGenerator.generateUUID().toString();
},
- _saveApps: function(aCallback) {
- this._writeFile(this.appsFile, JSON.stringify(this.webapps, null, 2),
- aCallback);
+ _saveApps: function() {
+ return this._writeFile(this.appsFile, JSON.stringify(this.webapps, null, 2));
},
/**
@@ -2565,43 +2545,38 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
_manifestCache: {},
- _readManifests: function(aData, aFinalCallback, aIndex) {
- if (!aData.length) {
- aFinalCallback(aData);
- return;
- }
+ _readManifests: function(aData) {
+ return Task.spawn(function*() {
+ if (!aData.length) {
+ return aData;
+ }
- let index = aIndex || 0;
- let id = aData[index].id;
+ for (let elem of aData) {
+ let id = elem.id;
- // Use the cached manifest instead of reading the file again from disk.
- if (id in this._manifestCache) {
- aData[index].manifest = this._manifestCache[id];
- if (index == aData.length - 1)
- aFinalCallback(aData);
- else
- this._readManifests(aData, aFinalCallback, index + 1);
- return;
- }
+ if (!this._manifestCache[id]) {
+ // the manifest file used to be named manifest.json, so fallback on this.
+ let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath()
+ ? "coreAppsDir" : DIRECTORY_NAME;
- // the manifest file used to be named manifest.json, so fallback on this.
- let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath()
- ? "coreAppsDir" : DIRECTORY_NAME;
- let file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.webapp"], true);
- if (!file.exists()) {
- file = FileUtils.getFile(baseDir, ["webapps", id, "update.webapp"], true);
- }
- if (!file.exists()) {
- file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.json"], true);
- }
+ let file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.webapp"], true);
- this._loadJSONAsync(file, (function(aJSON) {
- aData[index].manifest = this._manifestCache[id] = aJSON;
- if (index == aData.length - 1)
- aFinalCallback(aData);
- else
- this._readManifests(aData, aFinalCallback, index + 1);
- }).bind(this));
+ if (!file.exists()) {
+ file = FileUtils.getFile(baseDir, ["webapps", id, "update.webapp"], true);
+ }
+
+ if (!file.exists()) {
+ file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.json"], true);
+ }
+
+ this._manifestCache[id] = yield this._loadJSONAsync(file.path);
+ }
+
+ elem.manifest = this._manifestCache[id];
+ }
+
+ return aData;
+ }.bind(this)).then(null, Cu.reportError);
},
downloadPackage: function(aManifest, aNewApp, aIsUpdate, aOnSuccess) {
@@ -2959,7 +2934,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
// Save the updated registry, and cleanup the tmp directory.
- this._saveApps((function() {
+ this._saveApps().then(() => {
this.broadcastMessage("Webapps:UpdateState", {
app: aOldApp,
manifestURL: aNewApp.manifestURL
@@ -2968,7 +2943,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
manifestURL: aNewApp.manifestURL,
eventType: ["downloadsuccess", "downloadapplied"]
});
- }).bind(this));
+ });
let file = FileUtils.getFile("TmpD", ["webapps", aId], false);
if (file && file.exists()) {
file.remove(true);
@@ -3342,7 +3317,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
delete aOldApp.staged;
}
- this._saveApps((function() {
+ this._saveApps().then(() => {
this.broadcastMessage("Webapps:UpdateState", {
app: aOldApp,
error: aError,
@@ -3352,7 +3327,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
eventType: "downloaderror",
manifestURL: aNewApp.manifestURL
});
- }).bind(this));
+ });
AppDownloadManager.remove(aNewApp.manifestURL);
},
@@ -3405,9 +3380,9 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
Services.obs.notifyObservers(null, "webapps-uninstall", JSON.stringify(appClone));
if (supportSystemMessages()) {
- this._readManifests([{ id: id }], (function unregisterManifest(aResult) {
+ this._readManifests([{ id: id }]).then((aResult) => {
this._unregisterActivities(aResult[0].manifest, app);
- }).bind(this));
+ });
}
let dir = this._getAppDir(id);
@@ -3417,7 +3392,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
delete this.webapps[id];
- this._saveApps((function() {
+ this._saveApps().then(() => {
this.broadcastMessage("Webapps:Uninstall:Broadcast:Return:OK", appClone);
// Catch exception on callback call to ensure notifying observers after
try {
@@ -3429,7 +3404,7 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
ex + "\n" + ex.stack);
}
this.broadcastMessage("Webapps:RemoveApp", { id: id });
- }).bind(this));
+ });
},
getSelf: function(aData, aMm) {
@@ -3459,11 +3434,11 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
return;
}
- this._readManifests(tmp, (function(aResult) {
+ this._readManifests(tmp).then((aResult) => {
for (let i = 0; i < aResult.length; i++)
aData.apps[i].manifest = aResult[i].manifest;
aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
- }).bind(this));
+ });
},
checkInstalled: function(aData, aMm) {
@@ -3479,13 +3454,13 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
}
- this._readManifests(tmp, (function(aResult) {
+ this._readManifests(tmp).then((aResult) => {
for (let i = 0; i < aResult.length; i++) {
aData.app.manifest = aResult[i].manifest;
break;
}
aMm.sendAsyncMessage("Webapps:CheckInstalled:Return:OK", aData);
- }).bind(this));
+ });
},
getInstalled: function(aData, aMm) {
@@ -3500,11 +3475,11 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
}
- this._readManifests(tmp, (function(aResult) {
+ this._readManifests(tmp).then((aResult) => {
for (let i = 0; i < aResult.length; i++)
aData.apps[i].manifest = aResult[i].manifest;
aMm.sendAsyncMessage("Webapps:GetInstalled:Return:OK", aData);
- }).bind(this));
+ });
},
getNotInstalled: function(aData, aMm) {
@@ -3518,11 +3493,11 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
}
}
- this._readManifests(tmp, (function(aResult) {
+ this._readManifests(tmp).then((aResult) => {
for (let i = 0; i < aResult.length; i++)
aData.apps[i].manifest = aResult[i].manifest;
aMm.sendAsyncMessage("Webapps:GetNotInstalled:Return:OK", aData);
- }).bind(this));
+ });
},
doGetAll: function(aData, aMm) {
@@ -3546,26 +3521,209 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
tmp.push({ id: id });
}
- this._readManifests(tmp, (function(aResult) {
+ this._readManifests(tmp).then((aResult) => {
for (let i = 0; i < aResult.length; i++)
apps[i].manifest = aResult[i].manifest;
aCallback(apps);
- }).bind(this));
+ });
},
- getManifestFor: function(aManifestURL, aCallback) {
- if (!aCallback)
- return;
+ /* Check if |data| is actually a receipt */
+ isReceipt: function(data) {
+ try {
+ // The receipt data shouldn't be too big (allow up to 1 MiB of data)
+ const MAX_RECEIPT_SIZE = 1048576;
- let id = this._appIdForManifestURL(aManifestURL);
- let app = this.webapps[id];
- if (!id || (app.installState == "pending" && !app.retryingDownload)) {
- aCallback(null);
+ if (data.length > MAX_RECEIPT_SIZE) {
+ return "RECEIPT_TOO_BIG";
+ }
+
+ // Marketplace receipts are JWK + "~" + JWT
+ // Other receipts may contain only the JWT
+ let receiptParts = data.split('~');
+ let jwtData = null;
+ if (receiptParts.length == 2) {
+ jwtData = receiptParts[1];
+ } else {
+ jwtData = receiptParts[0];
+ }
+
+ let segments = jwtData.split('.');
+ if (segments.length != 3) {
+ return "INVALID_SEGMENTS_NUMBER";
+ }
+
+ // We need to translate the base64 alphabet used in JWT to our base64 alphabet
+ // before calling atob.
+ let decodedReceipt = JSON.parse(atob(segments[1].replace(/-/g, '+')
+ .replace(/_/g, '/')));
+ if (!decodedReceipt) {
+ return "INVALID_RECEIPT_ENCODING";
+ }
+
+ // Required values for a receipt
+ if (!decodedReceipt.typ) {
+ return "RECEIPT_TYPE_REQUIRED";
+ }
+ if (!decodedReceipt.product) {
+ return "RECEIPT_PRODUCT_REQUIRED";
+ }
+ if (!decodedReceipt.user) {
+ return "RECEIPT_USER_REQUIRED";
+ }
+ if (!decodedReceipt.iss) {
+ return "RECEIPT_ISS_REQUIRED";
+ }
+ if (!decodedReceipt.nbf) {
+ return "RECEIPT_NBF_REQUIRED";
+ }
+ if (!decodedReceipt.iat) {
+ return "RECEIPT_IAT_REQUIRED";
+ }
+
+ let allowedTypes = [ "purchase-receipt", "developer-receipt",
+ "reviewer-receipt", "test-receipt" ];
+ if (allowedTypes.indexOf(decodedReceipt.typ) < 0) {
+ return "RECEIPT_TYPE_UNSUPPORTED";
+ }
+ } catch (e) {
+ return "RECEIPT_ERROR";
+ }
+
+ return null;
+ },
+
+ addReceipt: function(aData, aMm) {
+ debug("addReceipt " + aData.manifestURL);
+
+ let receipt = aData.receipt;
+
+ if (!receipt) {
+ aData.error = "INVALID_PARAMETERS";
+ aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData);
return;
}
- this._readManifests([{ id: id }], function(aResult) {
- aCallback(aResult[0].manifest);
+ let error = this.isReceipt(receipt);
+ if (error) {
+ aData.error = error;
+ aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData);
+ return;
+ }
+
+ let id = this._appIdForManifestURL(aData.manifestURL);
+ let app = this.webapps[id];
+
+ if (!app.receipts) {
+ app.receipts = [];
+ } else if (app.receipts.length > 500) {
+ aData.error = "TOO_MANY_RECEIPTS";
+ aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData);
+ return;
+ }
+
+ let index = app.receipts.indexOf(receipt);
+ if (index >= 0) {
+ aData.error = "RECEIPT_ALREADY_EXISTS";
+ aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData);
+ return;
+ }
+
+ app.receipts.push(receipt);
+
+ this._saveApps().then(() => {
+ aData.receipts = app.receipts;
+ aMm.sendAsyncMessage("Webapps:AddReceipt:Return:OK", aData);
+ });
+ },
+
+ removeReceipt: function(aData, aMm) {
+ debug("removeReceipt " + aData.manifestURL);
+
+ let receipt = aData.receipt;
+
+ if (!receipt) {
+ aData.error = "INVALID_PARAMETERS";
+ aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData);
+ return;
+ }
+
+ let id = this._appIdForManifestURL(aData.manifestURL);
+ let app = this.webapps[id];
+
+ if (!app.receipts) {
+ aData.error = "NO_SUCH_RECEIPT";
+ aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData);
+ return;
+ }
+
+ let index = app.receipts.indexOf(receipt);
+ if (index == -1) {
+ aData.error = "NO_SUCH_RECEIPT";
+ aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData);
+ return;
+ }
+
+ app.receipts.splice(index, 1);
+
+ this._saveApps().then(() => {
+ aData.receipts = app.receipts;
+ aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:OK", aData);
+ });
+ },
+
+ replaceReceipt: function(aData, aMm) {
+ debug("replaceReceipt " + aData.manifestURL);
+
+ let oldReceipt = aData.oldReceipt;
+ let newReceipt = aData.newReceipt;
+
+ if (!oldReceipt || !newReceipt) {
+ aData.error = "INVALID_PARAMETERS";
+ aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:KO", aData);
+ return;
+ }
+
+ let error = this.isReceipt(newReceipt);
+ if (error) {
+ aData.error = error;
+ aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:KO", aData);
+ return;
+ }
+
+ let id = this._appIdForManifestURL(aData.manifestURL);
+ let app = this.webapps[id];
+
+ if (!app.receipts) {
+ aData.error = "NO_SUCH_RECEIPT";
+ aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData);
+ return;
+ }
+
+ let oldIndex = app.receipts.indexOf(oldReceipt);
+ if (oldIndex == -1) {
+ aData.error = "NO_SUCH_RECEIPT";
+ aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:KO", aData);
+ return;
+ }
+
+ app.receipts[oldIndex] = newReceipt;
+
+ this._saveApps().then(() => {
+ aData.receipts = app.receipts;
+ aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:OK", aData);
+ });
+ },
+
+ getManifestFor: function(aManifestURL) {
+ let id = this._appIdForManifestURL(aManifestURL);
+ let app = this.webapps[id];
+ if (!id || (app.installState == "pending" && !app.retryingDownload)) {
+ return Promise.resolve(null);
+ }
+
+ return this._readManifests([{ id: id }]).then((aResult) => {
+ return aResult[0].manifest;
});
},
@@ -3599,8 +3757,8 @@ onInstallSuccessAck: function onInstallSuccessAck(aManifestURL,
return AppsUtils.getCoreAppsBasePath();
},
- getWebAppsBasePath: function getWebAppsBasePath() {
- return FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false).path;
+ getWebAppsBasePath: function() {
+ return OS.Path.dirname(this.appsFile);
},
_isLaunchable: function(aApp) {
diff --git a/dom/apps/tests/mochitest.ini b/dom/apps/tests/mochitest.ini
index 8f04c4188b7..45a251a25ee 100644
--- a/dom/apps/tests/mochitest.ini
+++ b/dom/apps/tests/mochitest.ini
@@ -15,3 +15,4 @@ support-files =
[test_packaged_app_install.html]
[test_packaged_app_update.html]
[test_uninstall_errors.html]
+[test_receipt_operations.html]
diff --git a/dom/apps/tests/test_receipt_operations.html b/dom/apps/tests/test_receipt_operations.html
new file mode 100644
index 00000000000..fdc460476df
--- /dev/null
+++ b/dom/apps/tests/test_receipt_operations.html
@@ -0,0 +1,250 @@
+
+
+
+
+ Test for Bug {757226} Implement mozApps app.replaceReceipt
+
+
+
+
+
+
+Mozilla Bug {757226}
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dom/devicestorage/nsDeviceStorage.cpp b/dom/devicestorage/nsDeviceStorage.cpp
index 4485dec7cdc..b4bf85e1ff3 100644
--- a/dom/devicestorage/nsDeviceStorage.cpp
+++ b/dom/devicestorage/nsDeviceStorage.cpp
@@ -894,6 +894,16 @@ DeviceStorageFile::AppendRelativePath(const nsAString& aPath) {
if (!mFile) {
return;
}
+ if (!IsSafePath(aPath)) {
+ // All of the APIs (in the child) do checks to verify that the path is
+ // valid and return PERMISSION_DENIED if a non-safe path is entered.
+ // This check is done in the parent and prevents a compromised
+ // child from bypassing the check. It shouldn't be possible for this
+ // code path to be taken with a non-compromised child.
+ NS_WARNING("Unsafe path detected - ignoring");
+ NS_WARNING(NS_LossyConvertUTF16toASCII(aPath).get());
+ return;
+ }
#if defined(XP_WIN)
// replace forward slashes with backslashes,
// since nsLocalFileWin chokes on them
diff --git a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl
index f53dba271cf..b6088ecfe6f 100644
--- a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl
+++ b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl
@@ -7,7 +7,7 @@
interface nsIDOMDOMRequest;
-[scriptable, uuid(4081390c-08cf-11e3-9200-b3c0a8744b20)]
+[scriptable, uuid(f8cb08ed-588e-465f-b2b3-a4b0afde711a)]
interface mozIDOMApplication : nsISupports
{
readonly attribute jsval manifest;
@@ -100,6 +100,11 @@ interface mozIDOMApplication : nsISupports
[optional] in jsval rules); // nsISupports is a Promise.
nsISupports getConnections(); // nsISupports is a Promise.
+
+ /* Receipts handling functions */
+ nsIDOMDOMRequest addReceipt(in DOMString receipt);
+ nsIDOMDOMRequest removeReceipt(in DOMString receipt);
+ nsIDOMDOMRequest replaceReceipt(in DOMString oldReceipt, in DOMString newReceipt);
};
[scriptable, uuid(cf742022-5ba3-11e2-868f-03310341b006)]
diff --git a/toolkit/devtools/server/actors/webapps.js b/toolkit/devtools/server/actors/webapps.js
index 283a8edfc30..b565bf44742 100644
--- a/toolkit/devtools/server/actors/webapps.js
+++ b/toolkit/devtools/server/actors/webapps.js
@@ -165,12 +165,12 @@ WebappsActor.prototype = {
reg.webapps[aId] = aApp;
reg.updatePermissionsForApp(aId);
- reg._readManifests([{ id: aId }], function(aResult) {
+ reg._readManifests([{ id: aId }]).then((aResult) => {
let manifest = aResult[0].manifest;
aApp.name = manifest.name;
reg.updateAppHandlers(null, manifest, aApp);
- reg._saveApps(function() {
+ reg._saveApps().then(() => {
aApp.manifest = manifest;
// Needed to evict manifest cache on content side
@@ -264,17 +264,8 @@ WebappsActor.prototype = {
if (aManifest) {
return promise.resolve(aManifest);
} else {
- let deferred = promise.defer();
- let manFile = aDir.clone();
- manFile.append("manifest.webapp");
- DOMApplicationRegistry._loadJSONAsync(manFile, function(aManifest) {
- if (!aManifest) {
- deferred.reject("Error parsing manifest.webapp.");
- } else {
- deferred.resolve(aManifest);
- }
- });
- return deferred.promise;
+ let manFile = OS.Path.join(aDir.path, "manifest.webapp");
+ return AppsUtils.loadJSONAsync(manFile);
}
}
function checkSideloading(aManifest) {
@@ -285,13 +276,10 @@ WebappsActor.prototype = {
// The destination directory for this app.
let installDir = DOMApplicationRegistry._getAppDir(aId);
if (aManifest) {
- let deferred = promise.defer();
- let manFile = installDir.clone();
- manFile.append("manifest.webapp");
- DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest), function () {
- deferred.resolve(aAppType);
+ let manFile = OS.Path.join(installDir.path, "manifest.webapp");
+ return DOMApplicationRegistry._writeFile(manFile, JSON.stringify(aManifest)).then(() => {
+ return aAppType;
});
- return deferred.promise;
} else {
let manFile = aDir.clone();
manFile.append("manifest.webapp");
@@ -304,21 +292,16 @@ WebappsActor.prototype = {
return { metadata: aMetadata, appType: aAppType };
}
// Read the origin and manifest url from metadata.json
- let deferred = promise.defer();
- let metaFile = aDir.clone();
- metaFile.append("metadata.json");
- DOMApplicationRegistry._loadJSONAsync(metaFile, function(aMetadata) {
+ let metaFile = OS.Path.join(aDir.path, "metadata.json");
+ return AppsUtils.loadJSONAsync(metaFile).then((aMetadata) => {
if (!aMetadata) {
- deferred.reject("Error parsing metadata.json.");
- return;
+ throw("Error parsing metadata.json.");
}
if (!aMetadata.origin) {
- deferred.reject("Missing 'origin' property in metadata.json");
- return;
+ throw("Missing 'origin' property in metadata.json");
}
- deferred.resolve({ metadata: aMetadata, appType: aAppType });
+ return { metadata: aMetadata, appType: aAppType };
});
- return deferred.promise;
}
let runnable = {
run: function run() {
@@ -645,7 +628,7 @@ WebappsActor.prototype = {
let reg = DOMApplicationRegistry;
let id = reg._appIdForManifestURL(aManifestURL);
- reg._readManifests([{ id: id }], function (aResults) {
+ reg._readManifests([{ id: id }]).then((aResults) => {
deferred.resolve(aResults[0].manifest);
});