2012-03-06 19:50:58 +00:00
|
|
|
/* 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/. */
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2012-07-03 00:16:55 +00:00
|
|
|
"use strict";
|
|
|
|
|
2012-04-28 07:10:08 +00:00
|
|
|
const Cu = Components.utils;
|
2011-11-28 20:13:26 +00:00
|
|
|
const Cc = Components.classes;
|
|
|
|
const Ci = Components.interfaces;
|
2012-07-11 15:38:33 +00:00
|
|
|
const Cr = Components.results;
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2012-10-31 16:13:28 +00:00
|
|
|
this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"];
|
2011-11-28 20:13:26 +00:00
|
|
|
|
|
|
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
|
|
|
Cu.import("resource://gre/modules/Services.jsm");
|
2011-12-06 04:22:01 +00:00
|
|
|
Cu.import("resource://gre/modules/FileUtils.jsm");
|
2012-07-20 15:41:30 +00:00
|
|
|
Cu.import('resource://gre/modules/ActivitiesService.jsm');
|
2012-08-28 02:43:57 +00:00
|
|
|
Cu.import("resource://gre/modules/AppsUtils.jsm");
|
2012-10-03 05:38:06 +00:00
|
|
|
Cu.import("resource://gre/modules/PermissionsInstaller.jsm");
|
2012-10-23 20:52:57 +00:00
|
|
|
Cu.import("resource://gre/modules/OfflineCacheInstaller.jsm");
|
2012-12-05 03:49:26 +00:00
|
|
|
Cu.import("resource://gre/modules/SystemMessagePermissionsChecker.jsm");
|
2012-12-21 18:28:55 +00:00
|
|
|
Cu.import("resource://gre/modules/AppDownloadManager.jsm");
|
2013-08-02 00:00:22 +00:00
|
|
|
Cu.import("resource://gre/modules/WebappOSUtils.jsm");
|
2013-08-28 21:35:34 +00:00
|
|
|
Cu.import("resource://gre/modules/osfile.jsm");
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2013-05-06 17:41:07 +00:00
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
|
|
XPCOMUtils.defineLazyGetter(this, "libcutils", function() {
|
|
|
|
Cu.import("resource://gre/modules/systemlibs.js");
|
|
|
|
return libcutils;
|
|
|
|
});
|
|
|
|
#endif
|
|
|
|
|
2012-09-27 01:01:20 +00:00
|
|
|
function debug(aMsg) {
|
2012-11-27 16:40:18 +00:00
|
|
|
//dump("-*-*- Webapps.jsm : " + aMsg + "\n");
|
2012-09-27 01:01:20 +00:00
|
|
|
}
|
|
|
|
|
2013-07-07 20:41:55 +00:00
|
|
|
function supportUseCurrentProfile() {
|
|
|
|
return Services.prefs.getBoolPref("dom.webapps.useCurrentProfile");
|
|
|
|
}
|
|
|
|
|
2013-04-30 13:01:46 +00:00
|
|
|
function supportSystemMessages() {
|
2013-07-07 20:41:55 +00:00
|
|
|
return Services.prefs.getBoolPref("dom.sysmsg.enabled");
|
2013-04-30 13:01:46 +00:00
|
|
|
}
|
|
|
|
|
2012-12-13 17:54:49 +00:00
|
|
|
// Minimum delay between two progress events while downloading, in ms.
|
2013-10-17 12:47:58 +00:00
|
|
|
const MIN_PROGRESS_EVENT_DELAY = 1500;
|
2012-12-13 17:54:49 +00:00
|
|
|
|
2012-04-17 14:11:53 +00:00
|
|
|
const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org";
|
|
|
|
|
2011-11-28 20:13:26 +00:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "NetUtil", function() {
|
|
|
|
Cu.import("resource://gre/modules/NetUtil.jsm");
|
|
|
|
return NetUtil;
|
|
|
|
});
|
|
|
|
|
2012-08-27 14:13:02 +00:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "ppmm",
|
|
|
|
"@mozilla.org/parentprocessmessagemanager;1",
|
|
|
|
"nsIMessageBroadcaster");
|
2012-03-06 19:50:58 +00:00
|
|
|
|
2012-07-20 15:41:30 +00:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "cpmm",
|
|
|
|
"@mozilla.org/childprocessmessagemanager;1",
|
2012-08-27 14:13:02 +00:00
|
|
|
"nsIMessageSender");
|
2012-07-20 15:41:30 +00:00
|
|
|
|
2013-08-16 09:48:37 +00:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "interAppCommService", function() {
|
|
|
|
return Cc["@mozilla.org/inter-app-communication-service;1"]
|
|
|
|
.getService(Ci.nsIInterAppCommService);
|
|
|
|
});
|
|
|
|
|
2013-10-02 17:27:07 +00:00
|
|
|
XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService",
|
|
|
|
"@mozilla.org/datastore-service;1",
|
|
|
|
"nsIDataStoreService");
|
|
|
|
|
2012-07-03 00:16:55 +00:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "msgmgr", function() {
|
2012-07-11 15:38:33 +00:00
|
|
|
return Cc["@mozilla.org/system-message-internal;1"]
|
|
|
|
.getService(Ci.nsISystemMessagesInternal);
|
2012-07-03 00:16:55 +00:00
|
|
|
});
|
|
|
|
|
2012-12-05 16:50:32 +00:00
|
|
|
XPCOMUtils.defineLazyGetter(this, "updateSvc", function() {
|
|
|
|
return Cc["@mozilla.org/offlinecacheupdate-service;1"]
|
|
|
|
.getService(Ci.nsIOfflineCacheUpdateService);
|
|
|
|
});
|
|
|
|
|
2012-03-23 23:39:15 +00:00
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
|
|
const DIRECTORY_NAME = "webappsDir";
|
2012-08-15 21:02:15 +00:00
|
|
|
#elifdef ANDROID
|
|
|
|
const DIRECTORY_NAME = "webappsDir";
|
2012-03-23 23:39:15 +00:00
|
|
|
#else
|
2012-04-17 14:11:53 +00:00
|
|
|
// If we're executing in the context of the webapp runtime, the data files
|
|
|
|
// are in a different directory (currently the Firefox profile that installed
|
|
|
|
// the webapp); otherwise, they're in the current profile.
|
|
|
|
const DIRECTORY_NAME = WEBAPP_RUNTIME ? "WebappRegD" : "ProfD";
|
2012-03-23 23:39:15 +00:00
|
|
|
#endif
|
|
|
|
|
2013-06-26 12:18:34 +00:00
|
|
|
// We'll use this to identify privileged apps that have been preinstalled
|
|
|
|
// For those apps we'll set
|
|
|
|
// STORE_ID_PENDING_PREFIX + installOrigin
|
|
|
|
// as the storeID. This ensures it's unique and can't be set from a legit
|
|
|
|
// store even by error.
|
|
|
|
const STORE_ID_PENDING_PREFIX = "#unknownID#";
|
|
|
|
|
2012-10-31 16:13:28 +00:00
|
|
|
this.DOMApplicationRegistry = {
|
2011-11-28 20:13:26 +00:00
|
|
|
appsFile: null,
|
|
|
|
webapps: { },
|
2012-08-28 02:43:57 +00:00
|
|
|
children: [ ],
|
2012-06-29 20:46:21 +00:00
|
|
|
allAppsLaunchable: false,
|
2011-11-28 20:13:26 +00:00
|
|
|
|
|
|
|
init: function() {
|
2012-03-13 00:33:10 +00:00
|
|
|
this.messages = ["Webapps:Install", "Webapps:Uninstall",
|
2012-10-02 20:38:51 +00:00
|
|
|
"Webapps:GetSelf", "Webapps:CheckInstalled",
|
2012-08-28 02:43:57 +00:00
|
|
|
"Webapps:GetInstalled", "Webapps:GetNotInstalled",
|
|
|
|
"Webapps:Launch", "Webapps:GetAll",
|
2013-01-31 21:35:13 +00:00
|
|
|
"Webapps:InstallPackage",
|
2012-09-18 17:34:55 +00:00
|
|
|
"Webapps:GetList", "Webapps:RegisterForMessages",
|
2012-10-19 18:17:05 +00:00
|
|
|
"Webapps:UnregisterForMessages",
|
2012-09-29 17:57:18 +00:00
|
|
|
"Webapps:CancelDownload", "Webapps:CheckForUpdate",
|
2012-10-30 00:17:47 +00:00
|
|
|
"Webapps:Download", "Webapps:ApplyDownload",
|
2013-02-13 19:55:45 +00:00
|
|
|
"Webapps:Install:Return:Ack",
|
2012-10-01 20:29:59 +00:00
|
|
|
"child-process-shutdown"];
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2012-09-26 22:03:25 +00:00
|
|
|
this.frameMessages = ["Webapps:ClearBrowserData"];
|
|
|
|
|
2012-03-13 00:33:10 +00:00
|
|
|
this.messages.forEach((function(msgName) {
|
2012-03-06 19:50:58 +00:00
|
|
|
ppmm.addMessageListener(msgName, this);
|
2011-11-28 20:13:26 +00:00
|
|
|
}).bind(this));
|
|
|
|
|
2012-09-19 16:28:55 +00:00
|
|
|
cpmm.addMessageListener("Activities:Register:OK", this);
|
|
|
|
|
2012-03-13 00:33:10 +00:00
|
|
|
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
|
|
|
|
2012-12-21 18:28:55 +00:00
|
|
|
AppDownloadManager.registerCancelFunction(this.cancelDownload.bind(this));
|
|
|
|
|
2012-07-11 15:38:33 +00:00
|
|
|
this.appsFile = FileUtils.getFile(DIRECTORY_NAME,
|
|
|
|
["webapps", "webapps.json"], true);
|
2011-12-06 04:22:01 +00:00
|
|
|
|
2012-09-19 16:28:55 +00:00
|
|
|
this.loadAndUpdateApps();
|
|
|
|
},
|
2012-08-29 22:58:31 +00:00
|
|
|
|
2012-09-19 16:28:55 +00:00
|
|
|
// 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);
|
2012-10-23 20:52:57 +00:00
|
|
|
if (file && file.exists()) {
|
2012-09-19 16:28:55 +00:00
|
|
|
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) {
|
2013-01-31 06:50:28 +00:00
|
|
|
let app = this.webapps[id];
|
2013-05-30 17:34:27 +00:00
|
|
|
if (!app) {
|
|
|
|
delete this.webapps[id];
|
|
|
|
continue;
|
|
|
|
}
|
2012-12-22 13:56:21 +00:00
|
|
|
|
2013-01-31 06:50:28 +00:00
|
|
|
app.id = id;
|
2012-12-22 13:56:21 +00:00
|
|
|
|
2012-09-19 16:28:55 +00:00
|
|
|
// Make sure we have a localId
|
2013-01-31 06:50:28 +00:00
|
|
|
if (app.localId === undefined) {
|
|
|
|
app.localId = this._nextLocalId();
|
2012-09-19 16:28:55 +00:00
|
|
|
}
|
|
|
|
|
2013-01-31 06:50:28 +00:00
|
|
|
if (app.basePath === undefined) {
|
|
|
|
app.basePath = appDir.path;
|
2012-09-19 16:28:55 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Default to removable apps.
|
2013-01-31 06:50:28 +00:00
|
|
|
if (app.removable === undefined) {
|
|
|
|
app.removable = true;
|
2012-08-29 21:20:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Default to a non privileged status.
|
2013-01-31 06:50:28 +00:00
|
|
|
if (app.appStatus === undefined) {
|
|
|
|
app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED;
|
2012-08-29 21:20:03 +00:00
|
|
|
}
|
2012-12-10 23:49:02 +00:00
|
|
|
|
|
|
|
// Default to NO_APP_ID and not in browser.
|
2013-01-31 06:50:28 +00:00
|
|
|
if (app.installerAppId === undefined) {
|
|
|
|
app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID;
|
2012-12-10 23:49:02 +00:00
|
|
|
}
|
2013-01-31 06:50:28 +00:00
|
|
|
if (app.installerIsBrowser === undefined) {
|
|
|
|
app.installerIsBrowser = false;
|
2012-12-10 23:49:02 +00:00
|
|
|
}
|
2013-01-06 23:56:40 +00:00
|
|
|
|
2013-01-31 06:50:28 +00:00
|
|
|
// Default installState to "installed", and reset if we shutdown
|
|
|
|
// during an update.
|
|
|
|
if (app.installState === undefined ||
|
|
|
|
app.installState === "updating") {
|
|
|
|
app.installState = "installed";
|
2013-01-06 23:56:40 +00:00
|
|
|
}
|
2013-01-31 06:50:28 +00:00
|
|
|
|
2013-01-25 09:57:14 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2013-09-11 12:15:48 +00:00
|
|
|
// Default role to "".
|
|
|
|
if (this.webapps[id].role === undefined) {
|
|
|
|
this.webapps[id].role = "";
|
|
|
|
}
|
|
|
|
|
2013-01-31 06:50:28 +00:00
|
|
|
// 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;
|
2012-08-29 21:20:03 +00:00
|
|
|
};
|
2012-09-19 16:28:55 +00:00
|
|
|
}
|
|
|
|
aNext();
|
|
|
|
}).bind(this));
|
|
|
|
} else {
|
|
|
|
aNext();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-10-17 04:30:43 +00:00
|
|
|
// Notify we are starting with registering apps.
|
|
|
|
notifyAppsRegistryStart: function notifyAppsRegistryStart() {
|
|
|
|
Services.obs.notifyObservers(this, "webapps-registry-start", null);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Notify we are done with registering apps and save a copy of the registry.
|
|
|
|
notifyAppsRegistryReady: function notifyAppsRegistryReady() {
|
2012-09-19 16:28:55 +00:00
|
|
|
Services.obs.notifyObservers(this, "webapps-registry-ready", null);
|
|
|
|
this._saveApps();
|
|
|
|
},
|
|
|
|
|
2013-05-14 19:00:09 +00:00
|
|
|
// Ensure that the .to property in redirects is a relative URL.
|
|
|
|
sanitizeRedirects: function sanitizeRedirects(aSource) {
|
|
|
|
if (!aSource) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
let res = [];
|
|
|
|
for (let i = 0; i < aSource.length; i++) {
|
|
|
|
let redirect = aSource[i];
|
|
|
|
if (redirect.from && redirect.to &&
|
|
|
|
isAbsoluteURI(redirect.from) &&
|
|
|
|
!isAbsoluteURI(redirect.to)) {
|
|
|
|
res.push(redirect);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return res.length > 0 ? res : null;
|
|
|
|
},
|
|
|
|
|
2012-10-17 04:30:43 +00:00
|
|
|
// Registers all the activities and system messages.
|
2012-11-03 03:28:32 +00:00
|
|
|
registerAppsHandlers: function registerAppsHandlers(aRunUpdate) {
|
2012-10-17 04:30:43 +00:00
|
|
|
this.notifyAppsRegistryStart();
|
2012-09-19 16:28:55 +00:00
|
|
|
let ids = [];
|
|
|
|
for (let id in this.webapps) {
|
|
|
|
ids.push({ id: id });
|
|
|
|
}
|
2013-04-30 13:01:46 +00:00
|
|
|
if (supportSystemMessages()) {
|
|
|
|
this._processManifestForIds(ids, aRunUpdate);
|
|
|
|
} else {
|
2013-09-11 12:15:48 +00:00
|
|
|
// Read the CSPs and roles. If MOZ_SYS_MSG is defined this is done on
|
2013-04-30 13:01:46 +00:00
|
|
|
// _processManifestForIds so as to not reading the manifests
|
|
|
|
// twice
|
|
|
|
this._readManifests(ids, (function readCSPs(aResults) {
|
|
|
|
aResults.forEach(function registerManifest(aResult) {
|
2013-10-01 18:01:08 +00:00
|
|
|
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.
|
|
|
|
delete this.webapps[aResult.id];
|
|
|
|
return;
|
|
|
|
}
|
2013-05-14 19:00:09 +00:00
|
|
|
let app = this.webapps[aResult.id];
|
|
|
|
app.csp = aResult.manifest.csp || "";
|
2013-09-11 12:15:48 +00:00
|
|
|
app.role = aResult.manifest.role || "";
|
2013-05-14 19:00:09 +00:00
|
|
|
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
|
|
|
|
app.redirects = this.sanitizeRedirects(aResult.redirects);
|
|
|
|
}
|
2013-04-30 13:01:46 +00:00
|
|
|
}, this);
|
|
|
|
}).bind(this));
|
2012-10-19 10:43:17 +00:00
|
|
|
|
2013-04-30 13:01:46 +00:00
|
|
|
// Nothing else to do but notifying we're ready.
|
|
|
|
this.notifyAppsRegistryReady();
|
|
|
|
}
|
2012-09-19 16:28:55 +00:00
|
|
|
},
|
|
|
|
|
2013-10-02 17:27:07 +00:00
|
|
|
updateDataStoreForApp: function(aId) {
|
|
|
|
if (!this.webapps[aId]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create or Update the DataStore for this app
|
|
|
|
this._readManifests([{ id: aId }], (function(aResult) {
|
|
|
|
this.updateDataStore(this.webapps[aId].localId,
|
2013-10-02 17:27:23 +00:00
|
|
|
this.webapps[aId].origin,
|
2013-10-02 17:27:07 +00:00
|
|
|
this.webapps[aId].manifestURL,
|
|
|
|
aResult[0].manifest);
|
|
|
|
}).bind(this));
|
|
|
|
},
|
|
|
|
|
2012-10-10 16:16:49 +00:00
|
|
|
updatePermissionsForApp: function updatePermissionsForApp(aId) {
|
2012-12-10 20:51:10 +00:00
|
|
|
if (!this.webapps[aId]) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-10 16:16:49 +00:00
|
|
|
// Install the permissions for this app, as if we were updating
|
|
|
|
// to cleanup the old ones if needed.
|
2013-06-20 16:51:18 +00:00
|
|
|
// TODO It's not clear what this should do when there are multiple profiles.
|
2013-07-07 20:41:55 +00:00
|
|
|
if (supportUseCurrentProfile()) {
|
|
|
|
this._readManifests([{ id: aId }], (function(aResult) {
|
|
|
|
let data = aResult[0];
|
|
|
|
PermissionsInstaller.installPermissions({
|
|
|
|
manifest: data.manifest,
|
|
|
|
manifestURL: this.webapps[aId].manifestURL,
|
|
|
|
origin: this.webapps[aId].origin
|
|
|
|
}, true, function() {
|
|
|
|
debug("Error installing permissions for " + aId);
|
|
|
|
});
|
|
|
|
}).bind(this));
|
|
|
|
}
|
2012-10-10 16:16:49 +00:00
|
|
|
},
|
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
updateOfflineCacheForApp: function updateOfflineCacheForApp(aId) {
|
|
|
|
let app = this.webapps[aId];
|
2013-05-15 20:51:41 +00:00
|
|
|
this._readManifests([{ id: aId }], function(aResult) {
|
|
|
|
let manifest = new ManifestHelper(aResult[0].manifest, app.origin);
|
|
|
|
OfflineCacheInstaller.installCache({
|
|
|
|
cachePath: app.cachePath,
|
|
|
|
appId: aId,
|
|
|
|
origin: Services.io.newURI(app.origin, null, null),
|
|
|
|
localId: app.localId,
|
|
|
|
appcache_path: manifest.fullAppcachePath()
|
|
|
|
});
|
2012-10-23 20:52:57 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-01-09 15:06:07 +00:00
|
|
|
// Installs a 3rd party app.
|
|
|
|
installPreinstalledApp: function installPreinstalledApp(aId) {
|
2012-12-11 21:45:37 +00:00
|
|
|
#ifdef MOZ_WIDGET_GONK
|
2012-12-10 20:51:10 +00:00
|
|
|
let app = this.webapps[aId];
|
|
|
|
let baseDir;
|
|
|
|
try {
|
2012-12-22 13:56:21 +00:00
|
|
|
baseDir = FileUtils.getDir("coreAppsDir", ["webapps", aId], false);
|
2013-01-10 07:53:17 +00:00
|
|
|
if (!baseDir.exists()) {
|
|
|
|
return;
|
2013-07-15 00:53:41 +00:00
|
|
|
} else if (!baseDir.directoryEntries.hasMoreElements()) {
|
|
|
|
debug("Error: Core app in " + baseDir.path + " is empty");
|
|
|
|
return;
|
2013-01-10 07:53:17 +00:00
|
|
|
}
|
2012-12-10 20:51:10 +00:00
|
|
|
} catch(e) {
|
|
|
|
// In ENG builds, we don't have apps in coreAppsDir.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-09 15:06:07 +00:00
|
|
|
let filesToMove;
|
|
|
|
let isPackage;
|
|
|
|
|
2012-12-10 20:51:10 +00:00
|
|
|
let updateFile = baseDir.clone();
|
|
|
|
updateFile.append("update.webapp");
|
|
|
|
if (!updateFile.exists()) {
|
2013-01-09 15:06:07 +00:00
|
|
|
// The update manifest is missing, this is a hosted app only if there is
|
|
|
|
// no application.zip
|
|
|
|
let appFile = baseDir.clone();
|
|
|
|
appFile.append("application.zip");
|
|
|
|
if (appFile.exists()) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
isPackage = false;
|
|
|
|
filesToMove = ["manifest.webapp"];
|
|
|
|
} else {
|
|
|
|
isPackage = true;
|
|
|
|
filesToMove = ["application.zip", "update.webapp"];
|
2012-12-10 20:51:10 +00:00
|
|
|
}
|
|
|
|
|
2013-01-09 15:06:07 +00:00
|
|
|
debug("Installing 3rd party app : " + aId +
|
2012-12-10 20:51:10 +00:00
|
|
|
" from " + baseDir.path);
|
|
|
|
|
|
|
|
// We copy this app to DIRECTORY_NAME/$aId, and set the base path as needed.
|
|
|
|
let destDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
|
|
|
|
|
2013-01-09 15:06:07 +00:00
|
|
|
filesToMove.forEach(function(aFile) {
|
2012-12-10 20:51:10 +00:00
|
|
|
let file = baseDir.clone();
|
|
|
|
file.append(aFile);
|
2013-07-15 00:53:41 +00:00
|
|
|
try {
|
|
|
|
file.copyTo(destDir, aFile);
|
|
|
|
} catch(e) {
|
|
|
|
debug("Error: Failed to copy " + file.path + " to " + destDir.path);
|
|
|
|
}
|
2012-12-10 20:51:10 +00:00
|
|
|
});
|
|
|
|
|
2013-02-01 19:17:32 +00:00
|
|
|
app.installState = "installed";
|
2013-05-15 20:51:41 +00:00
|
|
|
app.cachePath = app.basePath;
|
2012-12-10 20:51:10 +00:00
|
|
|
app.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true)
|
|
|
|
.path;
|
2013-01-09 15:06:07 +00:00
|
|
|
|
|
|
|
if (!isPackage) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-12-10 20:51:10 +00:00
|
|
|
app.origin = "app://" + aId;
|
|
|
|
|
2013-06-26 12:18:34 +00:00
|
|
|
// Do this for all preinstalled apps... we can't know at this
|
|
|
|
// point if the updates will be signed or not and it doesn't
|
|
|
|
// hurt to have it always.
|
|
|
|
app.storeId = STORE_ID_PENDING_PREFIX + app.installOrigin;
|
|
|
|
|
2012-12-10 20:51:10 +00:00
|
|
|
// Extract the manifest.webapp file from application.zip.
|
|
|
|
let zipFile = baseDir.clone();
|
|
|
|
zipFile.append("application.zip");
|
|
|
|
let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
|
|
|
|
.createInstance(Ci.nsIZipReader);
|
|
|
|
try {
|
|
|
|
debug("Opening " + zipFile.path);
|
|
|
|
zipReader.open(zipFile);
|
|
|
|
if (!zipReader.hasEntry("manifest.webapp")) {
|
|
|
|
throw "MISSING_MANIFEST";
|
|
|
|
}
|
|
|
|
let manifestFile = destDir.clone();
|
|
|
|
manifestFile.append("manifest.webapp");
|
|
|
|
zipReader.extract("manifest.webapp", manifestFile);
|
|
|
|
} catch(e) {
|
|
|
|
// If we are unable to extract the manifest, cleanup and remove this app.
|
|
|
|
debug("Cleaning up: " + e);
|
|
|
|
destDir.remove(true);
|
|
|
|
delete this.webapps[aId];
|
|
|
|
} finally {
|
|
|
|
zipReader.close();
|
|
|
|
}
|
2012-12-11 21:45:37 +00:00
|
|
|
#endif
|
2012-12-10 20:51:10 +00:00
|
|
|
},
|
|
|
|
|
2012-09-19 16:28:55 +00:00
|
|
|
// Implements the core of bug 787439
|
2012-10-23 20:52:57 +00:00
|
|
|
// if at first run, go through these steps:
|
2012-09-19 16:28:55 +00:00
|
|
|
// a. load the core apps registry.
|
|
|
|
// b. uninstall any core app from the current registry but not in the
|
|
|
|
// 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()
|
2012-10-23 20:52:57 +00:00
|
|
|
installSystemApps: function installSystemApps(aNext) {
|
|
|
|
let file;
|
|
|
|
try {
|
|
|
|
file = FileUtils.getFile("coreAppsDir", ["webapps", "webapps.json"], false);
|
|
|
|
} catch(e) { }
|
2012-09-19 16:28:55 +00:00
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
if (file && file.exists()) {
|
|
|
|
// a
|
|
|
|
this._loadJSONAsync(file, (function loadCoreRegistry(aData) {
|
|
|
|
if (!aData) {
|
|
|
|
aNext();
|
|
|
|
return;
|
|
|
|
}
|
2012-09-19 16:28:55 +00:00
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
// 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);
|
2013-08-16 18:08:00 +00:00
|
|
|
permMgr.removePermissionsForApp(localId, false);
|
2012-10-23 20:52:57 +00:00
|
|
|
Services.cookies.removeCookiesForApp(localId, false);
|
|
|
|
this._clearPrivateData(localId, false);
|
2013-05-24 10:07:10 +00:00
|
|
|
delete this.webapps[id];
|
2012-10-23 20:52:57 +00:00
|
|
|
}
|
2012-09-19 16:28:55 +00:00
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
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;
|
2012-09-19 16:28:55 +00:00
|
|
|
|
2012-12-22 13:56:21 +00:00
|
|
|
this.webapps[id].id = id;
|
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
// Create a new localId.
|
|
|
|
this.webapps[id].localId = this._nextLocalId();
|
2012-09-19 16:28:55 +00:00
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
// Core apps are not removable.
|
|
|
|
if (this.webapps[id].removable === undefined) {
|
|
|
|
this.webapps[id].removable = false;
|
2012-09-19 16:28:55 +00:00
|
|
|
}
|
|
|
|
}
|
2012-10-23 20:52:57 +00:00
|
|
|
}
|
|
|
|
aNext();
|
|
|
|
}).bind(this));
|
|
|
|
} else {
|
|
|
|
aNext();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-10-30 07:11:17 +00:00
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
|
|
fixIndexedDb: function fixIndexedDb() {
|
|
|
|
debug("Fixing indexedDb folder names");
|
|
|
|
let idbDir = FileUtils.getDir("indexedDBPDir", ["indexedDB"]);
|
|
|
|
|
2013-09-11 04:18:36 +00:00
|
|
|
if (!idbDir.exists() || !idbDir.isDirectory()) {
|
2012-10-30 07:11:17 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let re = /^(\d+)\+(.*)\+(f|t)$/;
|
|
|
|
|
|
|
|
let entries = idbDir.directoryEntries;
|
|
|
|
while (entries.hasMoreElements()) {
|
|
|
|
let entry = entries.getNext().QueryInterface(Ci.nsIFile);
|
|
|
|
if (!entry.isDirectory()) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
let newName = entry.leafName.replace(re, "$1+$3+$2");
|
|
|
|
if (newName != entry.leafName) {
|
|
|
|
try {
|
|
|
|
entry.moveTo(idbDir, newName);
|
|
|
|
} catch(e) { }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
#endif
|
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
loadAndUpdateApps: function loadAndUpdateApps() {
|
|
|
|
let runUpdate = AppsUtils.isFirstRun(Services.prefs);
|
|
|
|
|
2012-10-30 07:11:17 +00:00
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
|
|
if (runUpdate) {
|
|
|
|
this.fixIndexedDb();
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
2012-10-23 20:52:57 +00:00
|
|
|
let onAppsLoaded = (function onAppsLoaded() {
|
|
|
|
if (runUpdate) {
|
2012-12-10 20:51:10 +00:00
|
|
|
// At first run, install preloaded apps and set up their permissions.
|
2012-10-10 16:16:49 +00:00
|
|
|
for (let id in this.webapps) {
|
2013-01-09 15:06:07 +00:00
|
|
|
this.installPreinstalledApp(id);
|
2012-12-10 20:51:10 +00:00
|
|
|
if (!this.webapps[id]) {
|
|
|
|
continue;
|
|
|
|
}
|
2012-10-23 20:52:57 +00:00
|
|
|
this.updateOfflineCacheForApp(id);
|
2012-12-10 20:51:10 +00:00
|
|
|
this.updatePermissionsForApp(id);
|
2012-10-10 16:16:49 +00:00
|
|
|
}
|
2012-12-10 20:51:10 +00:00
|
|
|
// Need to update the persisted list of apps since
|
2013-01-09 15:06:07 +00:00
|
|
|
// installPreinstalledApp() removes the ones failing to install.
|
2012-12-10 20:51:10 +00:00
|
|
|
this._saveApps();
|
2012-08-29 21:20:03 +00:00
|
|
|
}
|
2013-10-02 17:27:23 +00:00
|
|
|
|
|
|
|
// DataStores must be initialized at startup.
|
|
|
|
for (let id in this.webapps) {
|
|
|
|
this.updateDataStoreForApp(id);
|
|
|
|
}
|
|
|
|
|
2012-11-03 03:28:32 +00:00
|
|
|
this.registerAppsHandlers(runUpdate);
|
2012-10-23 20:52:57 +00:00
|
|
|
}).bind(this);
|
|
|
|
|
|
|
|
this.loadCurrentRegistry((function() {
|
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
|
|
// if first run, merge the system apps.
|
|
|
|
if (runUpdate)
|
|
|
|
this.installSystemApps(onAppsLoaded);
|
|
|
|
else
|
|
|
|
onAppsLoaded();
|
2012-09-19 16:28:55 +00:00
|
|
|
#else
|
2012-10-23 20:52:57 +00:00
|
|
|
onAppsLoaded();
|
2012-09-19 16:28:55 +00:00
|
|
|
#endif
|
2012-08-29 21:20:03 +00:00
|
|
|
}).bind(this));
|
2011-12-05 23:26:18 +00:00
|
|
|
},
|
|
|
|
|
2013-10-02 17:27:23 +00:00
|
|
|
updateDataStore: function(aId, aOrigin, aManifestURL, aManifest) {
|
2013-10-02 17:27:14 +00:00
|
|
|
if ('datastores-owned' in aManifest) {
|
|
|
|
for (let name in aManifest['datastores-owned']) {
|
|
|
|
let readonly = "access" in aManifest['datastores-owned'][name]
|
|
|
|
? aManifest['datastores-owned'][name].access == 'readonly'
|
|
|
|
: false;
|
|
|
|
|
2013-10-02 17:27:23 +00:00
|
|
|
dataStoreService.installDataStore(aId, name, aOrigin, aManifestURL,
|
|
|
|
readonly);
|
2013-10-02 17:27:14 +00:00
|
|
|
}
|
2013-10-02 17:27:07 +00:00
|
|
|
}
|
|
|
|
|
2013-10-02 17:27:14 +00:00
|
|
|
if ('datastores-access' in aManifest) {
|
|
|
|
for (let name in aManifest['datastores-access']) {
|
|
|
|
let readonly = ("readonly" in aManifest['datastores-access'][name]) &&
|
|
|
|
!aManifest['datastores-access'][name].readonly
|
|
|
|
? false : true;
|
2013-10-02 17:27:07 +00:00
|
|
|
|
2013-10-02 17:27:23 +00:00
|
|
|
dataStoreService.installAccessDataStore(aId, name, aOrigin,
|
|
|
|
aManifestURL, readonly);
|
2013-10-02 17:27:14 +00:00
|
|
|
}
|
2013-10-02 17:27:07 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// |aEntryPoint| is either the entry_point name or the null in which case we
|
2012-09-13 18:47:34 +00:00
|
|
|
// use the root of the manifest.
|
2013-08-16 09:48:37 +00:00
|
|
|
//
|
|
|
|
// TODO Bug 908094 Refine _registerSystemMessagesForEntryPoint(...).
|
2012-09-13 18:47:34 +00:00
|
|
|
_registerSystemMessagesForEntryPoint: function(aManifest, aApp, aEntryPoint) {
|
|
|
|
let root = aManifest;
|
|
|
|
if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
|
|
|
|
root = aManifest.entry_points[aEntryPoint];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!root.messages || !Array.isArray(root.messages) ||
|
|
|
|
root.messages.length == 0) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-03 05:38:03 +00:00
|
|
|
let manifest = new ManifestHelper(aManifest, aApp.origin);
|
2012-09-13 18:47:34 +00:00
|
|
|
let launchPath = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint), null, null);
|
|
|
|
let manifestURL = Services.io.newURI(aApp.manifestURL, null, null);
|
|
|
|
root.messages.forEach(function registerPages(aMessage) {
|
|
|
|
let href = launchPath;
|
|
|
|
let messageName;
|
|
|
|
if (typeof(aMessage) === "object" && Object.keys(aMessage).length === 1) {
|
|
|
|
messageName = Object.keys(aMessage)[0];
|
2013-02-04 13:59:32 +00:00
|
|
|
let uri;
|
|
|
|
try {
|
|
|
|
uri = manifest.resolveFromOrigin(aMessage[messageName]);
|
|
|
|
} catch(e) {
|
|
|
|
debug("system message url (" + aMessage[messageName] + ") is invalid, skipping. " +
|
|
|
|
"Error is: " + e);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
href = Services.io.newURI(uri, null, null);
|
2012-09-13 18:47:34 +00:00
|
|
|
} else {
|
|
|
|
messageName = aMessage;
|
|
|
|
}
|
2012-12-05 03:49:26 +00:00
|
|
|
|
|
|
|
if (SystemMessagePermissionsChecker
|
|
|
|
.isSystemMessagePermittedToRegister(messageName,
|
|
|
|
aApp.origin,
|
|
|
|
aManifest)) {
|
|
|
|
msgmgr.registerPage(messageName, href, manifestURL);
|
|
|
|
}
|
2012-09-13 18:47:34 +00:00
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2013-08-16 09:48:37 +00:00
|
|
|
// |aEntryPoint| is either the entry_point name or the null in which case we
|
|
|
|
// use the root of the manifest.
|
|
|
|
//
|
|
|
|
// TODO Bug 908094 Refine _registerInterAppConnectionsForEntryPoint(...).
|
|
|
|
_registerInterAppConnectionsForEntryPoint: function(aManifest, aApp,
|
|
|
|
aEntryPoint) {
|
|
|
|
let root = aManifest;
|
|
|
|
if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
|
|
|
|
root = aManifest.entry_points[aEntryPoint];
|
|
|
|
}
|
|
|
|
|
|
|
|
let connections = root.connections;
|
|
|
|
if (!connections) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ((typeof connections) !== "object") {
|
|
|
|
debug("|connections| is not an object. Skipping: " + connections);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let manifest = new ManifestHelper(aManifest, aApp.origin);
|
|
|
|
let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint),
|
|
|
|
null, null);
|
|
|
|
let manifestURI = Services.io.newURI(aApp.manifestURL, null, null);
|
|
|
|
|
|
|
|
for (let keyword in connections) {
|
|
|
|
let connection = connections[keyword];
|
|
|
|
|
|
|
|
// Resolve the handler path from origin. If |handler_path| is absent,
|
|
|
|
// use |launch_path| as default.
|
|
|
|
let fullHandlerPath;
|
|
|
|
let handlerPath = connection.handler_path;
|
|
|
|
if (handlerPath) {
|
|
|
|
try {
|
|
|
|
fullHandlerPath = manifest.resolveFromOrigin(handlerPath);
|
|
|
|
} catch(e) {
|
|
|
|
debug("Connection's handler path is invalid. Skipping: keyword: " +
|
|
|
|
keyword + " handler_path: " + handlerPath);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
let handlerPageURI = fullHandlerPath
|
|
|
|
? Services.io.newURI(fullHandlerPath, null, null)
|
|
|
|
: launchPathURI;
|
|
|
|
|
|
|
|
if (SystemMessagePermissionsChecker
|
|
|
|
.isSystemMessagePermittedToRegister("connection",
|
|
|
|
aApp.origin,
|
|
|
|
aManifest)) {
|
|
|
|
msgmgr.registerPage("connection", handlerPageURI, manifestURI);
|
|
|
|
}
|
|
|
|
|
|
|
|
interAppCommService.
|
|
|
|
registerConnection(keyword,
|
|
|
|
handlerPageURI,
|
|
|
|
manifestURI,
|
|
|
|
connection.description,
|
|
|
|
AppsUtils.getAppManifestStatus(manifest),
|
|
|
|
connection.rules);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-07-03 00:16:55 +00:00
|
|
|
_registerSystemMessages: function(aManifest, aApp) {
|
2012-09-13 18:47:34 +00:00
|
|
|
this._registerSystemMessagesForEntryPoint(aManifest, aApp, null);
|
|
|
|
|
|
|
|
if (!aManifest.entry_points) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let entryPoint in aManifest.entry_points) {
|
|
|
|
this._registerSystemMessagesForEntryPoint(aManifest, aApp, entryPoint);
|
2012-07-03 00:16:55 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-08-16 09:48:37 +00:00
|
|
|
_registerInterAppConnections: function(aManifest, aApp) {
|
|
|
|
this._registerInterAppConnectionsForEntryPoint(aManifest, aApp, null);
|
|
|
|
|
|
|
|
if (!aManifest.entry_points) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let entryPoint in aManifest.entry_points) {
|
|
|
|
this._registerInterAppConnectionsForEntryPoint(aManifest, aApp,
|
|
|
|
entryPoint);
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// |aEntryPoint| is either the entry_point name or the null in which case we
|
2012-09-13 18:47:34 +00:00
|
|
|
// use the root of the manifest.
|
2012-11-03 03:28:32 +00:00
|
|
|
_createActivitiesToRegister: function(aManifest, aApp, aEntryPoint, aRunUpdate) {
|
2012-10-18 07:32:15 +00:00
|
|
|
let activitiesToRegister = [];
|
2012-09-13 18:47:34 +00:00
|
|
|
let root = aManifest;
|
|
|
|
if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
|
|
|
|
root = aManifest.entry_points[aEntryPoint];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!root.activities) {
|
2012-10-18 07:32:15 +00:00
|
|
|
return activitiesToRegister;
|
2012-07-20 15:41:30 +00:00
|
|
|
}
|
|
|
|
|
2012-10-03 05:38:03 +00:00
|
|
|
let manifest = new ManifestHelper(aManifest, aApp.origin);
|
2012-09-13 18:47:34 +00:00
|
|
|
for (let activity in root.activities) {
|
|
|
|
let description = root.activities[activity];
|
2013-02-10 18:54:13 +00:00
|
|
|
let href = description.href;
|
|
|
|
if (!href) {
|
|
|
|
href = manifest.launch_path;
|
2012-08-15 02:40:12 +00:00
|
|
|
}
|
2013-02-04 13:59:32 +00:00
|
|
|
|
|
|
|
try {
|
2013-02-10 18:54:13 +00:00
|
|
|
href = manifest.resolveFromOrigin(href);
|
2013-02-04 13:59:32 +00:00
|
|
|
} catch (e) {
|
2013-02-10 18:54:13 +00:00
|
|
|
debug("Activity href (" + href + ") is invalid, skipping. " +
|
2013-02-04 13:59:32 +00:00
|
|
|
"Error is: " + e);
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2013-02-12 23:19:50 +00:00
|
|
|
// Make a copy of the description object since we don't want to modify
|
|
|
|
// the manifest itself, but need to register with a resolved URI.
|
|
|
|
let newDesc = {};
|
|
|
|
for (let prop in description) {
|
|
|
|
newDesc[prop] = description[prop];
|
|
|
|
}
|
|
|
|
newDesc.href = href;
|
|
|
|
|
2013-02-04 13:59:32 +00:00
|
|
|
debug('_createActivitiesToRegister: ' + aApp.manifestURL + ', activity ' +
|
2013-02-12 23:19:50 +00:00
|
|
|
activity + ', description.href is ' + newDesc.href);
|
2012-11-03 03:28:32 +00:00
|
|
|
|
|
|
|
if (aRunUpdate) {
|
|
|
|
activitiesToRegister.push({ "manifest": aApp.manifestURL,
|
|
|
|
"name": activity,
|
|
|
|
"icon": manifest.iconURLForSize(128),
|
2013-02-12 23:19:50 +00:00
|
|
|
"description": newDesc });
|
2012-11-03 03:28:32 +00:00
|
|
|
}
|
|
|
|
|
2013-02-10 18:54:13 +00:00
|
|
|
let launchPath = Services.io.newURI(href, null, null);
|
2012-07-20 15:41:30 +00:00
|
|
|
let manifestURL = Services.io.newURI(aApp.manifestURL, null, null);
|
2012-12-05 03:49:26 +00:00
|
|
|
|
|
|
|
if (SystemMessagePermissionsChecker
|
|
|
|
.isSystemMessagePermittedToRegister("activity",
|
|
|
|
aApp.origin,
|
|
|
|
aManifest)) {
|
|
|
|
msgmgr.registerPage("activity", launchPath, manifestURL);
|
|
|
|
}
|
2012-07-20 15:41:30 +00:00
|
|
|
}
|
2012-10-18 07:32:15 +00:00
|
|
|
return activitiesToRegister;
|
2012-07-20 15:41:30 +00:00
|
|
|
},
|
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// |aAppsToRegister| contains an array of apps to be registered, where
|
|
|
|
// each element is an object in the format of {manifest: foo, app: bar}.
|
2012-11-03 03:28:32 +00:00
|
|
|
_registerActivitiesForApps: function(aAppsToRegister, aRunUpdate) {
|
2012-10-18 07:32:15 +00:00
|
|
|
// Collect the activities to be registered for root and entry_points.
|
|
|
|
let activitiesToRegister = [];
|
|
|
|
aAppsToRegister.forEach(function (aApp) {
|
|
|
|
let manifest = aApp.manifest;
|
|
|
|
let app = aApp.app;
|
|
|
|
activitiesToRegister.push.apply(activitiesToRegister,
|
2012-11-03 03:28:32 +00:00
|
|
|
this._createActivitiesToRegister(manifest, app, null, aRunUpdate));
|
2012-10-18 07:32:15 +00:00
|
|
|
|
|
|
|
if (!manifest.entry_points) {
|
|
|
|
return;
|
|
|
|
}
|
2012-09-13 18:47:34 +00:00
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
for (let entryPoint in manifest.entry_points) {
|
|
|
|
activitiesToRegister.push.apply(activitiesToRegister,
|
2012-11-03 03:28:32 +00:00
|
|
|
this._createActivitiesToRegister(manifest, app, entryPoint, aRunUpdate));
|
2012-10-18 07:32:15 +00:00
|
|
|
}
|
|
|
|
}, this);
|
2012-09-13 18:47:34 +00:00
|
|
|
|
2012-11-03 03:28:32 +00:00
|
|
|
if (!aRunUpdate || activitiesToRegister.length == 0) {
|
|
|
|
this.notifyAppsRegistryReady();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// Send the array carrying all the activities to be registered.
|
|
|
|
cpmm.sendAsyncMessage("Activities:Register", activitiesToRegister);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Better to directly use |_registerActivitiesForApps()| if we have
|
|
|
|
// multiple apps to be registered for activities.
|
2012-11-03 03:28:32 +00:00
|
|
|
_registerActivities: function(aManifest, aApp, aRunUpdate) {
|
|
|
|
this._registerActivitiesForApps([{ manifest: aManifest, app: aApp }], aRunUpdate);
|
2012-09-13 18:47:34 +00:00
|
|
|
},
|
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// |aEntryPoint| is either the entry_point name or the null in which case we
|
|
|
|
// use the root of the manifest.
|
|
|
|
_createActivitiesToUnregister: function(aManifest, aApp, aEntryPoint) {
|
|
|
|
let activitiesToUnregister = [];
|
2012-09-13 18:47:34 +00:00
|
|
|
let root = aManifest;
|
|
|
|
if (aEntryPoint && aManifest.entry_points[aEntryPoint]) {
|
|
|
|
root = aManifest.entry_points[aEntryPoint];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!root.activities) {
|
2012-10-18 07:32:15 +00:00
|
|
|
return activitiesToUnregister;
|
2012-07-20 15:41:30 +00:00
|
|
|
}
|
|
|
|
|
2012-09-13 18:47:34 +00:00
|
|
|
for (let activity in root.activities) {
|
|
|
|
let description = root.activities[activity];
|
2012-10-18 07:32:15 +00:00
|
|
|
activitiesToUnregister.push({ "manifest": aApp.manifestURL,
|
2013-08-08 22:04:03 +00:00
|
|
|
"name": activity,
|
|
|
|
"description": description });
|
2012-07-20 15:41:30 +00:00
|
|
|
}
|
2012-10-18 07:32:15 +00:00
|
|
|
return activitiesToUnregister;
|
2012-07-20 15:41:30 +00:00
|
|
|
},
|
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// |aAppsToUnregister| contains an array of apps to be unregistered, where
|
|
|
|
// each element is an object in the format of {manifest: foo, app: bar}.
|
|
|
|
_unregisterActivitiesForApps: function(aAppsToUnregister) {
|
|
|
|
// Collect the activities to be unregistered for root and entry_points.
|
|
|
|
let activitiesToUnregister = [];
|
|
|
|
aAppsToUnregister.forEach(function (aApp) {
|
|
|
|
let manifest = aApp.manifest;
|
|
|
|
let app = aApp.app;
|
|
|
|
activitiesToUnregister.push.apply(activitiesToUnregister,
|
|
|
|
this._createActivitiesToUnregister(manifest, app, null));
|
|
|
|
|
|
|
|
if (!manifest.entry_points) {
|
|
|
|
return;
|
|
|
|
}
|
2012-09-13 18:47:34 +00:00
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
for (let entryPoint in manifest.entry_points) {
|
|
|
|
activitiesToUnregister.push.apply(activitiesToUnregister,
|
|
|
|
this._createActivitiesToUnregister(manifest, app, entryPoint));
|
|
|
|
}
|
|
|
|
}, this);
|
2012-09-13 18:47:34 +00:00
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// Send the array carrying all the activities to be unregistered.
|
|
|
|
cpmm.sendAsyncMessage("Activities:Unregister", activitiesToUnregister);
|
2012-09-13 18:47:34 +00:00
|
|
|
},
|
|
|
|
|
2012-10-18 07:32:15 +00:00
|
|
|
// Better to directly use |_unregisterActivitiesForApps()| if we have
|
|
|
|
// multiple apps to be unregistered for activities.
|
|
|
|
_unregisterActivities: function(aManifest, aApp) {
|
|
|
|
this._unregisterActivitiesForApps([{ manifest: aManifest, app: aApp }]);
|
2012-10-17 04:30:43 +00:00
|
|
|
},
|
|
|
|
|
2012-11-03 03:28:32 +00:00
|
|
|
_processManifestForIds: function(aIds, aRunUpdate) {
|
2012-09-12 01:17:01 +00:00
|
|
|
this._readManifests(aIds, (function registerManifests(aResults) {
|
2012-10-18 07:32:15 +00:00
|
|
|
let appsToRegister = [];
|
2012-09-12 01:17:01 +00:00
|
|
|
aResults.forEach(function registerManifest(aResult) {
|
|
|
|
let app = this.webapps[aResult.id];
|
|
|
|
let manifest = aResult.manifest;
|
2013-10-01 18:01:08 +00:00
|
|
|
if (!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.
|
|
|
|
delete this.webapps[aResult.id];
|
|
|
|
return;
|
|
|
|
}
|
2012-09-12 01:17:01 +00:00
|
|
|
app.name = manifest.name;
|
2012-10-19 10:43:17 +00:00
|
|
|
app.csp = manifest.csp || "";
|
2013-09-11 12:15:48 +00:00
|
|
|
app.role = manifest.role || "";
|
2013-05-14 19:00:09 +00:00
|
|
|
if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
|
|
|
|
app.redirects = this.sanitizeRedirects(manifest.redirects);
|
|
|
|
}
|
2012-09-12 01:17:01 +00:00
|
|
|
this._registerSystemMessages(manifest, app);
|
2013-08-16 09:48:37 +00:00
|
|
|
this._registerInterAppConnections(manifest, app);
|
2012-10-18 07:32:15 +00:00
|
|
|
appsToRegister.push({ manifest: manifest, app: app });
|
2012-09-12 01:17:01 +00:00
|
|
|
}, this);
|
2012-11-03 03:28:32 +00:00
|
|
|
this._registerActivitiesForApps(appsToRegister, aRunUpdate);
|
2012-07-03 00:16:55 +00:00
|
|
|
}).bind(this));
|
|
|
|
},
|
|
|
|
|
2012-03-13 00:33:10 +00:00
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
|
|
if (aTopic == "xpcom-shutdown") {
|
|
|
|
this.messages.forEach((function(msgName) {
|
|
|
|
ppmm.removeMessageListener(msgName, this);
|
|
|
|
}).bind(this));
|
|
|
|
Services.obs.removeObserver(this, "xpcom-shutdown");
|
2013-06-12 23:28:11 +00:00
|
|
|
cpmm = null;
|
2012-03-13 00:33:10 +00:00
|
|
|
ppmm = null;
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
_loadJSONAsync: function(aFile, aCallback) {
|
2011-11-28 20:13:26 +00:00
|
|
|
try {
|
2011-12-05 23:26:18 +00:00
|
|
|
let channel = NetUtil.newChannel(aFile);
|
2011-11-28 20:13:26 +00:00
|
|
|
channel.contentType = "application/json";
|
|
|
|
NetUtil.asyncFetch(channel, function(aStream, aResult) {
|
|
|
|
if (!Components.isSuccessCode(aResult)) {
|
2012-07-11 15:38:33 +00:00
|
|
|
Cu.reportError("DOMApplicationRegistry: Could not read from json file "
|
|
|
|
+ aFile.path);
|
2011-12-05 23:26:18 +00:00
|
|
|
if (aCallback)
|
|
|
|
aCallback(null);
|
2011-11-28 20:13:26 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read json file into a string
|
|
|
|
let data = null;
|
|
|
|
try {
|
2012-07-31 10:06:56 +00:00
|
|
|
// 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,
|
|
|
|
aStream.available()) || ""));
|
2011-11-28 20:13:26 +00:00
|
|
|
aStream.close();
|
2011-12-05 23:26:18 +00:00
|
|
|
if (aCallback)
|
|
|
|
aCallback(data);
|
2011-11-28 20:13:26 +00:00
|
|
|
} catch (ex) {
|
2012-09-22 03:40:03 +00:00
|
|
|
Cu.reportError("DOMApplicationRegistry: Could not parse JSON: " +
|
2012-10-23 20:52:57 +00:00
|
|
|
aFile.path + " " + ex + "\n" + ex.stack);
|
2011-12-05 23:26:18 +00:00
|
|
|
if (aCallback)
|
|
|
|
aCallback(null);
|
2011-11-28 20:13:26 +00:00
|
|
|
}
|
|
|
|
});
|
|
|
|
} catch (ex) {
|
2012-07-11 15:38:33 +00:00
|
|
|
Cu.reportError("DOMApplicationRegistry: Could not read from " +
|
2012-10-23 20:52:57 +00:00
|
|
|
aFile.path + " : " + ex + "\n" + ex.stack);
|
2011-12-05 23:26:18 +00:00
|
|
|
if (aCallback)
|
|
|
|
aCallback(null);
|
2011-11-28 20:13:26 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-09-13 09:33:52 +00:00
|
|
|
addMessageListener: function(aMsgNames, aApp, aMm) {
|
2012-09-18 17:34:55 +00:00
|
|
|
aMsgNames.forEach(function (aMsgName) {
|
2013-09-13 09:33:52 +00:00
|
|
|
let man = aApp && aApp.manifestURL;
|
2012-09-18 17:34:55 +00:00
|
|
|
if (!(aMsgName in this.children)) {
|
|
|
|
this.children[aMsgName] = [];
|
|
|
|
}
|
2012-10-19 18:17:05 +00:00
|
|
|
|
|
|
|
let mmFound = this.children[aMsgName].some(function(mmRef) {
|
|
|
|
if (mmRef.mm === aMm) {
|
|
|
|
mmRef.refCount++;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
|
|
|
|
|
|
|
if (!mmFound) {
|
|
|
|
this.children[aMsgName].push({
|
|
|
|
mm: aMm,
|
|
|
|
refCount: 1
|
|
|
|
});
|
2013-09-13 09:33:52 +00:00
|
|
|
|
|
|
|
// If it wasn't registered before, let's update its state
|
2013-10-17 12:47:58 +00:00
|
|
|
if ((aMsgName === 'Webapps:FireEvent') ||
|
|
|
|
(aMsgName === 'Webapps:UpdateState')) {
|
2013-09-13 09:33:52 +00:00
|
|
|
if (man) {
|
|
|
|
let app = this.getAppByManifestURL(aApp.manifestURL);
|
|
|
|
if (app && ((aApp.installState !== app.installState) ||
|
|
|
|
(aApp.downloading !== app.downloading))) {
|
|
|
|
debug("Got a registration from an outdated app: " +
|
|
|
|
aApp.manifestURL);
|
|
|
|
let aEvent ={
|
|
|
|
type: app.installState,
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
manifest: app.manifest
|
|
|
|
};
|
|
|
|
aMm.sendAsyncMessage(aMsgName, aEvent);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2012-10-19 18:17:05 +00:00
|
|
|
}
|
2012-09-18 17:34:55 +00:00
|
|
|
}, this);
|
|
|
|
},
|
|
|
|
|
2012-10-19 18:17:05 +00:00
|
|
|
removeMessageListener: function(aMsgNames, aMm) {
|
|
|
|
if (aMsgNames.length === 1 &&
|
|
|
|
aMsgNames[0] === "Webapps:Internal:AllMessages") {
|
2012-11-09 00:53:31 +00:00
|
|
|
for (let msgName in this.children) {
|
|
|
|
let msg = this.children[msgName];
|
2012-10-19 18:17:05 +00:00
|
|
|
|
|
|
|
for (let mmI = msg.length - 1; mmI >= 0; mmI -= 1) {
|
|
|
|
let mmRef = msg[mmI];
|
|
|
|
if (mmRef.mm === aMm) {
|
|
|
|
msg.splice(mmI, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (msg.length === 0) {
|
2012-11-09 00:53:31 +00:00
|
|
|
delete this.children[msgName];
|
2012-10-19 18:17:05 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
aMsgNames.forEach(function(aMsgName) {
|
|
|
|
if (!(aMsgName in this.children)) {
|
|
|
|
return;
|
|
|
|
}
|
2012-10-01 20:29:59 +00:00
|
|
|
|
2012-10-19 18:17:05 +00:00
|
|
|
let removeIndex;
|
|
|
|
this.children[aMsgName].some(function(mmRef, index) {
|
|
|
|
if (mmRef.mm === aMm) {
|
|
|
|
mmRef.refCount--;
|
|
|
|
if (mmRef.refCount === 0) {
|
|
|
|
removeIndex = index;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
});
|
2012-09-18 17:34:55 +00:00
|
|
|
|
2012-10-19 18:17:05 +00:00
|
|
|
if (removeIndex) {
|
|
|
|
this.children[aMsgName].splice(removeIndex, 1);
|
2012-09-18 17:34:55 +00:00
|
|
|
}
|
2012-10-19 18:17:05 +00:00
|
|
|
}, this);
|
2012-09-18 17:34:55 +00:00
|
|
|
},
|
|
|
|
|
2011-11-28 20:13:26 +00:00
|
|
|
receiveMessage: function(aMessage) {
|
2012-06-26 20:12:33 +00:00
|
|
|
// nsIPrefBranch throws if pref does not exist, faster to simply write
|
|
|
|
// the pref instead of first checking if it is false.
|
|
|
|
Services.prefs.setBoolPref("dom.mozApps.used", true);
|
|
|
|
|
2012-09-30 03:08:01 +00:00
|
|
|
// We need to check permissions for calls coming from mozApps.mgmt.
|
2013-01-11 12:50:46 +00:00
|
|
|
// These are: getAll(), getNotInstalled(), applyDownload() and uninstall().
|
2012-09-30 03:08:01 +00:00
|
|
|
if (["Webapps:GetAll",
|
|
|
|
"Webapps:GetNotInstalled",
|
2013-01-11 12:50:46 +00:00
|
|
|
"Webapps:ApplyDownload",
|
|
|
|
"Webapps:Uninstall"].indexOf(aMessage.name) != -1) {
|
2012-09-30 03:08:01 +00:00
|
|
|
if (!aMessage.target.assertPermission("webapps-manage")) {
|
|
|
|
debug("mozApps message " + aMessage.name +
|
|
|
|
" from a content process with no 'webapps-manage' privileges.");
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-01 20:29:59 +00:00
|
|
|
let msg = aMessage.data || {};
|
2012-09-18 17:34:55 +00:00
|
|
|
let mm = aMessage.target;
|
|
|
|
msg.mm = mm;
|
2011-11-28 20:13:26 +00:00
|
|
|
|
|
|
|
switch (aMessage.name) {
|
|
|
|
case "Webapps:Install":
|
2012-12-10 23:48:59 +00:00
|
|
|
this.doInstall(msg, mm);
|
2011-11-28 20:13:26 +00:00
|
|
|
break;
|
2012-03-06 19:50:58 +00:00
|
|
|
case "Webapps:GetSelf":
|
2012-09-18 17:34:55 +00:00
|
|
|
this.getSelf(msg, mm);
|
2012-03-06 19:50:58 +00:00
|
|
|
break;
|
2011-11-28 20:13:26 +00:00
|
|
|
case "Webapps:Uninstall":
|
2013-05-06 13:51:53 +00:00
|
|
|
this.doUninstall(msg, mm);
|
2011-11-28 20:13:26 +00:00
|
|
|
break;
|
|
|
|
case "Webapps:Launch":
|
2013-05-06 13:51:53 +00:00
|
|
|
this.doLaunch(msg, mm);
|
2011-11-28 20:13:26 +00:00
|
|
|
break;
|
2012-10-02 20:38:51 +00:00
|
|
|
case "Webapps:CheckInstalled":
|
|
|
|
this.checkInstalled(msg, mm);
|
2012-09-25 15:04:24 +00:00
|
|
|
break;
|
2012-03-06 19:50:58 +00:00
|
|
|
case "Webapps:GetInstalled":
|
2012-09-18 17:34:55 +00:00
|
|
|
this.getInstalled(msg, mm);
|
2011-12-15 17:20:57 +00:00
|
|
|
break;
|
2012-06-29 20:46:21 +00:00
|
|
|
case "Webapps:GetNotInstalled":
|
2012-09-18 17:34:55 +00:00
|
|
|
this.getNotInstalled(msg, mm);
|
2012-06-29 20:46:21 +00:00
|
|
|
break;
|
2012-03-06 19:50:58 +00:00
|
|
|
case "Webapps:GetAll":
|
2013-05-06 13:51:53 +00:00
|
|
|
this.doGetAll(msg, mm);
|
2011-11-28 20:13:26 +00:00
|
|
|
break;
|
2012-07-11 15:38:33 +00:00
|
|
|
case "Webapps:InstallPackage":
|
2012-12-10 23:48:59 +00:00
|
|
|
this.doInstallPackage(msg, mm);
|
2012-07-11 15:38:33 +00:00
|
|
|
break;
|
2012-09-18 17:34:55 +00:00
|
|
|
case "Webapps:RegisterForMessages":
|
2013-09-13 09:33:52 +00:00
|
|
|
this.addMessageListener(msg.messages, msg.app, mm);
|
2012-09-18 17:34:55 +00:00
|
|
|
break;
|
2012-10-19 18:17:05 +00:00
|
|
|
case "Webapps:UnregisterForMessages":
|
|
|
|
this.removeMessageListener(msg, mm);
|
|
|
|
break;
|
|
|
|
case "child-process-shutdown":
|
|
|
|
this.removeMessageListener(["Webapps:Internal:AllMessages"], mm);
|
|
|
|
break;
|
2012-08-28 02:43:57 +00:00
|
|
|
case "Webapps:GetList":
|
2013-09-13 09:33:52 +00:00
|
|
|
this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm);
|
2012-08-28 02:43:57 +00:00
|
|
|
return this.webapps;
|
2012-09-29 17:57:18 +00:00
|
|
|
case "Webapps:Download":
|
|
|
|
this.startDownload(msg.manifestURL);
|
|
|
|
break;
|
2012-09-27 01:01:20 +00:00
|
|
|
case "Webapps:CancelDownload":
|
2012-09-29 17:57:18 +00:00
|
|
|
this.cancelDownload(msg.manifestURL);
|
2012-09-27 01:01:20 +00:00
|
|
|
break;
|
|
|
|
case "Webapps:CheckForUpdate":
|
|
|
|
this.checkForUpdate(msg, mm);
|
|
|
|
break;
|
2012-10-30 00:17:47 +00:00
|
|
|
case "Webapps:ApplyDownload":
|
|
|
|
this.applyDownload(msg.manifestURL);
|
2012-09-29 17:57:18 +00:00
|
|
|
break;
|
2012-09-19 16:28:55 +00:00
|
|
|
case "Activities:Register:OK":
|
2012-10-18 07:32:15 +00:00
|
|
|
this.notifyAppsRegistryReady();
|
2012-09-19 16:28:55 +00:00
|
|
|
break;
|
2013-02-13 19:55:45 +00:00
|
|
|
case "Webapps:Install:Return:Ack":
|
|
|
|
this.onInstallSuccessAck(msg.manifestURL);
|
|
|
|
break;
|
2011-11-28 20:13:26 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2013-01-31 21:35:13 +00:00
|
|
|
getAppInfo: function getAppInfo(aAppId) {
|
2013-03-15 14:18:58 +00:00
|
|
|
return AppsUtils.getAppInfo(this.webapps, aAppId);
|
2013-01-31 21:35:13 +00:00
|
|
|
},
|
|
|
|
|
2012-09-18 17:34:55 +00:00
|
|
|
// Some messages can be listened by several content processes:
|
|
|
|
// Webapps:AddApp
|
|
|
|
// Webapps:RemoveApp
|
|
|
|
// Webapps:Install:Return:OK
|
|
|
|
// Webapps:Uninstall:Return:OK
|
2013-01-11 12:50:46 +00:00
|
|
|
// Webapps:Uninstall:Broadcast:Return:OK
|
2013-10-17 12:47:58 +00:00
|
|
|
// Webapps:FireEvent
|
2013-01-09 01:00:01 +00:00
|
|
|
// Webapps:checkForUpdate:Return:OK
|
2013-10-17 12:47:58 +00:00
|
|
|
// Webapps:UpdateState
|
2012-09-18 17:34:55 +00:00
|
|
|
broadcastMessage: function broadcastMessage(aMsgName, aContent) {
|
|
|
|
if (!(aMsgName in this.children)) {
|
|
|
|
return;
|
|
|
|
}
|
2012-10-19 18:17:05 +00:00
|
|
|
this.children[aMsgName].forEach(function(mmRef) {
|
|
|
|
mmRef.mm.sendAsyncMessage(aMsgName, aContent);
|
|
|
|
});
|
2012-09-18 17:34:55 +00:00
|
|
|
},
|
|
|
|
|
2012-08-29 21:20:03 +00:00
|
|
|
_getAppDir: function(aId) {
|
|
|
|
return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true);
|
|
|
|
},
|
|
|
|
|
2013-10-17 12:47:58 +00:00
|
|
|
_writeFile: function _writeFile(aFile, aData, aCallback) {
|
2013-01-25 02:24:17 +00:00
|
|
|
debug("Saving " + aFile.path);
|
2011-11-28 20:13:26 +00:00
|
|
|
// Initialize the file output stream.
|
2011-12-06 04:22:01 +00:00
|
|
|
let ostream = FileUtils.openSafeFileOutputStream(aFile);
|
2011-11-28 20:13:26 +00:00
|
|
|
|
|
|
|
// Obtain a converter to convert our data to a UTF-8 encoded input stream.
|
2012-07-11 15:38:33 +00:00
|
|
|
let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"]
|
|
|
|
.createInstance(Ci.nsIScriptableUnicodeConverter);
|
2011-11-28 20:13:26 +00:00
|
|
|
converter.charset = "UTF-8";
|
|
|
|
|
|
|
|
// Asynchronously copy the data to the file.
|
|
|
|
let istream = converter.convertToInputStream(aData);
|
|
|
|
NetUtil.asyncCopy(istream, ostream, function(rc) {
|
2013-10-17 12:47:58 +00:00
|
|
|
if (aCallback) {
|
|
|
|
aCallback();
|
|
|
|
}
|
2011-11-28 20:13:26 +00:00
|
|
|
});
|
|
|
|
},
|
2011-12-08 13:32:54 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
doLaunch: function (aData, aMm) {
|
|
|
|
this.launch(
|
|
|
|
aData.manifestURL,
|
|
|
|
aData.startPoint,
|
2013-09-06 18:18:21 +00:00
|
|
|
aData.timestamp,
|
2013-05-06 13:51:53 +00:00
|
|
|
function onsuccess() {
|
|
|
|
aMm.sendAsyncMessage("Webapps:Launch:Return:OK", aData);
|
|
|
|
},
|
|
|
|
function onfailure(reason) {
|
|
|
|
aMm.sendAsyncMessage("Webapps:Launch:Return:KO", aData);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
2013-09-06 18:18:21 +00:00
|
|
|
launch: function launch(aManifestURL, aStartPoint, aTimeStamp, aOnSuccess, aOnFailure) {
|
2013-05-06 13:51:53 +00:00
|
|
|
let app = this.getAppByManifestURL(aManifestURL);
|
2012-09-27 01:01:20 +00:00
|
|
|
if (!app) {
|
2013-05-06 13:51:53 +00:00
|
|
|
aOnFailure("NO_SUCH_APP");
|
2012-09-27 01:01:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Fire an error when trying to launch an app that is not
|
|
|
|
// yet fully installed.
|
|
|
|
if (app.installState == "pending") {
|
2013-05-06 13:51:53 +00:00
|
|
|
aOnFailure("PENDING_APP_NOT_LAUNCHABLE");
|
2012-09-27 01:01:20 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
// We have to clone the app object as nsIDOMApplication objects are
|
|
|
|
// stringified as an empty object. (see bug 830376)
|
|
|
|
let appClone = AppsUtils.cloneAppObject(app);
|
|
|
|
appClone.startPoint = aStartPoint;
|
2013-09-06 18:18:21 +00:00
|
|
|
appClone.timestamp = aTimeStamp;
|
2013-05-06 13:51:53 +00:00
|
|
|
Services.obs.notifyObservers(null, "webapps-launch", JSON.stringify(appClone));
|
|
|
|
aOnSuccess();
|
|
|
|
},
|
|
|
|
|
|
|
|
close: function close(aApp) {
|
|
|
|
debug("close");
|
|
|
|
|
|
|
|
// We have to clone the app object as nsIDOMApplication objects are
|
|
|
|
// stringified as an empty object. (see bug 830376)
|
|
|
|
let appClone = AppsUtils.cloneAppObject(aApp);
|
|
|
|
Services.obs.notifyObservers(null, "webapps-close", JSON.stringify(appClone));
|
2012-09-27 01:01:20 +00:00
|
|
|
},
|
|
|
|
|
2012-12-21 18:28:55 +00:00
|
|
|
cancelDownload: function cancelDownload(aManifestURL, aError) {
|
2012-12-18 21:43:13 +00:00
|
|
|
debug("cancelDownload " + aManifestURL);
|
2012-12-21 18:28:55 +00:00
|
|
|
let error = aError || "DOWNLOAD_CANCELED";
|
|
|
|
let download = AppDownloadManager.get(aManifestURL);
|
2012-12-18 21:43:13 +00:00
|
|
|
if (!download) {
|
|
|
|
debug("Could not find a download for " + aManifestURL);
|
2012-09-27 19:34:41 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-17 00:55:50 +00:00
|
|
|
let app = this.webapps[download.appId];
|
|
|
|
|
2012-12-18 21:43:13 +00:00
|
|
|
if (download.cacheUpdate) {
|
|
|
|
try {
|
|
|
|
download.cacheUpdate.cancel();
|
2013-01-25 02:24:17 +00:00
|
|
|
} catch (e) {
|
|
|
|
debug (e);
|
|
|
|
}
|
2012-12-18 21:43:13 +00:00
|
|
|
} else if (download.channel) {
|
2013-01-17 00:55:50 +00:00
|
|
|
try {
|
|
|
|
download.channel.cancel(Cr.NS_BINDING_ABORTED);
|
2013-10-17 12:47:58 +00:00
|
|
|
} catch(e) { }
|
2012-12-18 21:43:13 +00:00
|
|
|
} else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let app = this.webapps[download.appId];
|
2012-09-27 19:34:41 +00:00
|
|
|
app.progress = 0;
|
2012-11-29 14:19:37 +00:00
|
|
|
app.installState = download.previousState;
|
2012-09-29 17:57:18 +00:00
|
|
|
app.downloading = false;
|
2012-09-27 19:34:41 +00:00
|
|
|
this._saveApps((function() {
|
2013-10-17 12:47:58 +00:00
|
|
|
this.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: {
|
|
|
|
progress: 0,
|
|
|
|
installState: download.previousState,
|
|
|
|
downloading: false
|
|
|
|
},
|
|
|
|
error: error,
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
})
|
|
|
|
this.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloaderror",
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
2012-09-27 19:34:41 +00:00
|
|
|
}).bind(this));
|
2012-12-21 18:28:55 +00:00
|
|
|
AppDownloadManager.remove(aManifestURL);
|
2012-09-27 01:01:20 +00:00
|
|
|
},
|
|
|
|
|
2012-10-30 00:17:47 +00:00
|
|
|
startDownload: function startDownload(aManifestURL) {
|
|
|
|
debug("startDownload for " + aManifestURL);
|
|
|
|
let id = this._appIdForManifestURL(aManifestURL);
|
|
|
|
let app = this.webapps[id];
|
2012-09-29 17:57:18 +00:00
|
|
|
if (!app) {
|
2012-10-30 00:17:47 +00:00
|
|
|
debug("startDownload: No app found for " + aManifestURL);
|
2012-09-29 17:57:18 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-08 09:40:52 +00:00
|
|
|
if (app.downloading) {
|
|
|
|
debug("app is already downloading. Ignoring.");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-01 19:17:32 +00:00
|
|
|
// If the caller is trying to start a download but we have nothing to
|
|
|
|
// download, send an error.
|
|
|
|
if (!app.downloadAvailable) {
|
2013-10-17 12:47:58 +00:00
|
|
|
this.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
error: "NO_DOWNLOAD_AVAILABLE",
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
this.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloaderror",
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
2013-02-01 19:17:32 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-11-29 14:19:37 +00:00
|
|
|
// First of all, we check if the download is supposed to update an
|
|
|
|
// already installed application.
|
|
|
|
let isUpdate = (app.installState == "installed");
|
|
|
|
|
2012-11-30 16:57:45 +00:00
|
|
|
// An app download would only be triggered for two reasons: an app
|
|
|
|
// update or while retrying to download a previously failed or canceled
|
|
|
|
// instalation.
|
|
|
|
app.retryingDownload = !isUpdate;
|
|
|
|
|
2012-09-29 17:57:18 +00:00
|
|
|
// We need to get the update manifest here, not the webapp manifest.
|
2013-01-25 02:24:17 +00:00
|
|
|
// If this is an update, the update manifest is staged.
|
2012-09-29 17:57:18 +00:00
|
|
|
let file = FileUtils.getFile(DIRECTORY_NAME,
|
2013-01-25 02:24:17 +00:00
|
|
|
["webapps", id,
|
|
|
|
isUpdate ? "staged-update.webapp"
|
|
|
|
: "update.webapp"],
|
|
|
|
true);
|
2012-09-29 17:57:18 +00:00
|
|
|
|
2012-11-27 04:03:43 +00:00
|
|
|
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) {
|
|
|
|
let jsonManifest = aResults[0].manifest;
|
|
|
|
let manifest = new ManifestHelper(jsonManifest, app.origin);
|
|
|
|
|
|
|
|
if (manifest.appcache_path) {
|
|
|
|
debug("appcache found");
|
2013-08-29 14:00:33 +00:00
|
|
|
this.startOfflineCacheDownload(manifest, app, null, isUpdate);
|
2012-11-27 04:03:43 +00:00
|
|
|
} else {
|
2013-10-17 12:47:58 +00:00
|
|
|
// Hosted app with no appcache, nothing to do, but we fire a
|
|
|
|
// downloaded event.
|
2012-12-03 19:38:09 +00:00
|
|
|
debug("No appcache found, sending 'downloaded' for " + aManifestURL);
|
|
|
|
app.downloadAvailable = false;
|
2013-10-17 12:47:58 +00:00
|
|
|
DOMApplicationRegistry._saveApps(function() {
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifest: jsonManifest,
|
|
|
|
manifestURL: aManifestURL
|
|
|
|
});
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloadsuccess",
|
|
|
|
manifestURL: aManifestURL
|
|
|
|
});
|
|
|
|
});
|
2012-11-27 04:03:43 +00:00
|
|
|
}
|
|
|
|
}).bind(this));
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-09-29 17:57:18 +00:00
|
|
|
this._loadJSONAsync(file, (function(aJSON) {
|
|
|
|
if (!aJSON) {
|
2013-10-17 12:47:58 +00:00
|
|
|
debug("startDownload: No update manifest found at " + file.path + " " +
|
|
|
|
aManifestURL);
|
2012-09-29 17:57:18 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2012-10-30 00:17:47 +00:00
|
|
|
let manifest = new ManifestHelper(aJSON, app.installOrigin);
|
2012-12-11 20:58:38 +00:00
|
|
|
this.downloadPackage(manifest, {
|
|
|
|
manifestURL: aManifestURL,
|
|
|
|
origin: app.origin,
|
2013-01-22 22:01:42 +00:00
|
|
|
installOrigin: app.installOrigin,
|
2012-12-11 20:58:38 +00:00
|
|
|
downloadSize: app.downloadSize
|
|
|
|
}, isUpdate, function(aId, aManifest) {
|
2012-09-29 17:57:18 +00:00
|
|
|
// 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() { });
|
2013-05-30 17:34:27 +00:00
|
|
|
|
|
|
|
app = DOMApplicationRegistry.webapps[aId];
|
2012-09-29 17:57:18 +00:00
|
|
|
// Set state and fire events.
|
|
|
|
app.downloading = false;
|
2012-10-30 00:17:47 +00:00
|
|
|
app.downloadAvailable = false;
|
2012-09-29 17:57:18 +00:00
|
|
|
app.readyToApplyDownload = true;
|
2012-11-17 03:37:41 +00:00
|
|
|
app.updateTime = Date.now();
|
2012-09-29 17:57:18 +00:00
|
|
|
DOMApplicationRegistry._saveApps(function() {
|
2013-10-17 12:47:58 +00:00
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: aManifestURL
|
|
|
|
});
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloadsuccess",
|
|
|
|
manifestURL: aManifestURL
|
|
|
|
});
|
2012-11-27 16:40:18 +00:00
|
|
|
if (app.installState == "pending") {
|
|
|
|
// We restarted a failed download, apply it automatically.
|
|
|
|
DOMApplicationRegistry.applyDownload(aManifestURL);
|
|
|
|
}
|
2012-09-29 17:57:18 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}).bind(this));
|
|
|
|
},
|
|
|
|
|
|
|
|
applyDownload: function applyDownload(aManifestURL) {
|
2012-10-30 00:17:47 +00:00
|
|
|
debug("applyDownload for " + aManifestURL);
|
2012-11-30 16:57:45 +00:00
|
|
|
let id = this._appIdForManifestURL(aManifestURL);
|
|
|
|
let app = this.webapps[id];
|
2012-09-29 17:57:18 +00:00
|
|
|
if (!app || (app && !app.readyToApplyDownload)) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
// We need to get the old manifest to unregister web activities.
|
2013-08-28 12:10:55 +00:00
|
|
|
this.getManifestFor(aManifestURL, (function(aOldManifest) {
|
2013-02-28 19:22:31 +00:00
|
|
|
debug("Old manifest: " + JSON.stringify(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();
|
|
|
|
manFile.append("manifest.webapp");
|
|
|
|
let appFile = tmpDir.clone();
|
|
|
|
appFile.append("application.zip");
|
2012-09-29 17:57:18 +00:00
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
|
|
|
|
appFile.moveTo(dir, "application.zip");
|
|
|
|
manFile.moveTo(dir, "manifest.webapp");
|
2012-09-29 17:57:18 +00:00
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
// Move the staged update manifest to a non staged one.
|
|
|
|
let staged = dir.clone();
|
|
|
|
staged.append("staged-update.webapp");
|
2013-01-25 02:24:17 +00:00
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
// If we are applying after a restarted download, we have no
|
|
|
|
// staged update manifest.
|
|
|
|
if (staged.exists()) {
|
|
|
|
staged.moveTo(dir, "update.webapp");
|
|
|
|
}
|
2013-01-25 02:24:17 +00:00
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
try {
|
|
|
|
tmpDir.remove(true);
|
|
|
|
} catch(e) { }
|
2012-09-29 17:57:18 +00:00
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
// Clean up the deprecated manifest cache if needed.
|
|
|
|
if (id in this._manifestCache) {
|
|
|
|
delete this._manifestCache[id];
|
2013-01-25 02:24:17 +00:00
|
|
|
}
|
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
// Flush the zip reader cache to make sure we use the new application.zip
|
|
|
|
// when re-launching the application.
|
|
|
|
let zipFile = dir.clone();
|
|
|
|
zipFile.append("application.zip");
|
|
|
|
Services.obs.notifyObservers(zipFile, "flush-cache-entry", null);
|
2012-11-30 16:57:45 +00:00
|
|
|
|
2013-02-28 19:22:31 +00:00
|
|
|
// Get the manifest, and set properties.
|
2013-08-28 12:10:55 +00:00
|
|
|
this.getManifestFor(aManifestURL, (function(aData) {
|
2013-02-28 19:22:31 +00:00
|
|
|
debug("New manifest: " + JSON.stringify(aData));
|
|
|
|
app.downloading = false;
|
|
|
|
app.downloadAvailable = false;
|
|
|
|
app.downloadSize = 0;
|
|
|
|
app.installState = "installed";
|
|
|
|
app.readyToApplyDownload = false;
|
|
|
|
|
|
|
|
// Update the staged properties.
|
|
|
|
if (app.staged) {
|
|
|
|
for (let prop in app.staged) {
|
|
|
|
app[prop] = app.staged[prop];
|
|
|
|
}
|
|
|
|
delete app.staged;
|
|
|
|
}
|
|
|
|
|
|
|
|
delete app.retryingDownload;
|
|
|
|
|
|
|
|
this._saveApps((function() {
|
|
|
|
// Update the handlers and permissions for this app.
|
|
|
|
this.updateAppHandlers(aOldManifest, aData, app);
|
2013-07-07 20:41:55 +00:00
|
|
|
if (supportUseCurrentProfile()) {
|
|
|
|
PermissionsInstaller.installPermissions(
|
|
|
|
{ manifest: aData,
|
|
|
|
origin: app.origin,
|
|
|
|
manifestURL: app.manifestURL },
|
|
|
|
true);
|
|
|
|
}
|
2013-10-02 17:27:23 +00:00
|
|
|
this.updateDataStore(this.webapps[id].localId, app.origin,
|
|
|
|
app.manifestURL, aData);
|
2013-10-17 12:47:58 +00:00
|
|
|
this.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifest: aData,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
this.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloadapplied",
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
2013-02-28 19:22:31 +00:00
|
|
|
}).bind(this));
|
|
|
|
}).bind(this));
|
2012-10-10 21:29:07 +00:00
|
|
|
}).bind(this));
|
2012-09-29 17:57:18 +00:00
|
|
|
},
|
|
|
|
|
2013-08-29 14:00:33 +00:00
|
|
|
startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) {
|
2012-12-18 21:43:13 +00:00
|
|
|
if (!aManifest.appcache_path) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// If the manifest has an appcache_path property, use it to populate the
|
|
|
|
// appcache.
|
|
|
|
let appcacheURI = Services.io.newURI(aManifest.fullAppcachePath(),
|
|
|
|
null, null);
|
|
|
|
let docURI = Services.io.newURI(aManifest.fullLaunchPath(), null, null);
|
|
|
|
|
|
|
|
// We determine the app's 'installState' according to its previous
|
|
|
|
// state. Cancelled downloads should remain as 'pending'. Successfully
|
|
|
|
// installed apps should morph to 'updating'.
|
|
|
|
if (aIsUpdate) {
|
|
|
|
aApp.installState = "updating";
|
|
|
|
}
|
|
|
|
|
2013-02-19 08:37:49 +00:00
|
|
|
// We set the 'downloading' flag and update the apps registry right before
|
|
|
|
// starting the app download/update.
|
2012-12-18 21:43:13 +00:00
|
|
|
aApp.downloading = true;
|
2013-02-11 08:38:51 +00:00
|
|
|
aApp.progress = 0;
|
2013-02-19 08:37:49 +00:00
|
|
|
DOMApplicationRegistry._saveApps((function() {
|
2013-10-17 12:47:58 +00:00
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: {
|
|
|
|
downloading: true,
|
|
|
|
installState: aApp.installState,
|
|
|
|
progress: 0
|
|
|
|
},
|
|
|
|
manifestURL: aApp.manifestURL
|
|
|
|
});
|
2013-02-19 08:37:49 +00:00
|
|
|
let cacheUpdate = aProfileDir
|
|
|
|
? updateSvc.scheduleCustomProfileUpdate(appcacheURI, docURI, aProfileDir)
|
|
|
|
: updateSvc.scheduleAppUpdate(appcacheURI, docURI, aApp.localId, false);
|
|
|
|
|
|
|
|
// We save the download details for potential further usage like
|
|
|
|
// cancelling it.
|
|
|
|
let download = {
|
|
|
|
cacheUpdate: cacheUpdate,
|
|
|
|
appId: this._appIdForManifestURL(aApp.manifestURL),
|
|
|
|
previousState: aIsUpdate ? "installed" : "pending"
|
|
|
|
};
|
|
|
|
AppDownloadManager.add(aApp.manifestURL, download);
|
|
|
|
|
|
|
|
cacheUpdate.addObserver(new AppcacheObserver(aApp), false);
|
|
|
|
}).bind(this));
|
2012-09-27 01:01:20 +00:00
|
|
|
},
|
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
// Returns the MD5 hash of a file, doing async IO off the main thread.
|
|
|
|
computeFileHash: function computeFileHash(aFile, aCallback) {
|
|
|
|
const CHUNK_SIZE = 16384;
|
|
|
|
|
|
|
|
// Return the two-digit hexadecimal code for a byte.
|
|
|
|
function toHexString(charCode) {
|
|
|
|
return ("0" + charCode.toString(16)).slice(-2);
|
|
|
|
}
|
|
|
|
|
|
|
|
let hasher = Cc["@mozilla.org/security/hash;1"]
|
|
|
|
.createInstance(Ci.nsICryptoHash);
|
|
|
|
// We want to use the MD5 algorithm.
|
|
|
|
hasher.init(hasher.MD5);
|
|
|
|
|
|
|
|
OS.File.open(aFile.path, { read: true }).then(
|
|
|
|
function opened(file) {
|
|
|
|
let readChunk = function readChunk() {
|
|
|
|
file.read(CHUNK_SIZE).then(
|
|
|
|
function readSuccess(array) {
|
|
|
|
hasher.update(array, array.length);
|
|
|
|
if (array.length == CHUNK_SIZE) {
|
|
|
|
readChunk();
|
|
|
|
} else {
|
2013-09-10 15:49:57 +00:00
|
|
|
file.close();
|
2013-01-18 17:54:06 +00:00
|
|
|
// We're passing false to get the binary hash and not base64.
|
|
|
|
let hash = hasher.finish(false);
|
|
|
|
// convert the binary hash data to a hex string.
|
|
|
|
aCallback([toHexString(hash.charCodeAt(i)) for (i in hash)]
|
|
|
|
.join(""));
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function readError() {
|
|
|
|
debug("Error reading " + aFile.path);
|
|
|
|
aCallback(null);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
readChunk();
|
|
|
|
},
|
|
|
|
function openError() {
|
|
|
|
debug("Error opening " + aFile.path);
|
|
|
|
aCallback(null);
|
|
|
|
}
|
|
|
|
);
|
|
|
|
},
|
|
|
|
|
|
|
|
// Returns the MD5 hash of the manifest.
|
|
|
|
computeManifestHash: function(aManifest) {
|
2013-08-02 00:00:22 +00:00
|
|
|
return AppsUtils.computeHash(JSON.stringify(aManifest));
|
2013-01-18 17:54:06 +00:00
|
|
|
},
|
|
|
|
|
2013-05-14 19:00:09 +00:00
|
|
|
// Updates the redirect mapping, activities and system message handlers.
|
2013-02-28 19:22:31 +00:00
|
|
|
// aOldManifest can be null if we don't have any handler to unregister.
|
|
|
|
updateAppHandlers: function(aOldManifest, aNewManifest, aApp) {
|
|
|
|
debug("updateAppHandlers: old=" + aOldManifest + " new=" + aNewManifest);
|
|
|
|
this.notifyAppsRegistryStart();
|
2013-05-14 19:00:09 +00:00
|
|
|
if (aApp.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) {
|
|
|
|
aApp.redirects = this.sanitizeRedirects(aNewManifest.redirects);
|
|
|
|
}
|
2013-04-30 13:01:46 +00:00
|
|
|
|
|
|
|
if (supportSystemMessages()) {
|
|
|
|
if (aOldManifest) {
|
|
|
|
this._unregisterActivities(aOldManifest, aApp);
|
|
|
|
}
|
|
|
|
this._registerSystemMessages(aNewManifest, aApp);
|
|
|
|
this._registerActivities(aNewManifest, aApp, true);
|
2013-08-16 09:48:37 +00:00
|
|
|
this._registerInterAppConnections(aNewManifest, aApp);
|
2013-04-30 13:01:46 +00:00
|
|
|
} else {
|
|
|
|
// Nothing else to do but notifying we're ready.
|
|
|
|
this.notifyAppsRegistryReady();
|
2013-02-28 19:22:31 +00:00
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-09-27 01:01:20 +00:00
|
|
|
checkForUpdate: function(aData, aMm) {
|
2012-10-30 00:17:47 +00:00
|
|
|
debug("checkForUpdate for " + aData.manifestURL);
|
2013-02-08 09:40:52 +00:00
|
|
|
|
|
|
|
function sendError(aError) {
|
|
|
|
aData.error = aError;
|
|
|
|
aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
|
|
|
|
}
|
|
|
|
|
2012-10-30 00:17:47 +00:00
|
|
|
let id = this._appIdForManifestURL(aData.manifestURL);
|
|
|
|
let app = this.webapps[id];
|
2012-10-18 10:32:03 +00:00
|
|
|
|
2012-09-27 01:01:20 +00:00
|
|
|
function updatePackagedApp(aManifest) {
|
|
|
|
debug("updatePackagedApp");
|
2012-10-30 00:17:47 +00:00
|
|
|
|
|
|
|
// if the app manifestURL has a app:// scheme, we can't have an
|
|
|
|
// update.
|
|
|
|
if (app.manifestURL.startsWith("app://")) {
|
|
|
|
aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-07 17:10:32 +00:00
|
|
|
// Store the new update manifest.
|
|
|
|
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true);
|
|
|
|
let manFile = dir.clone();
|
2013-01-25 02:24:17 +00:00
|
|
|
manFile.append("staged-update.webapp");
|
2013-01-07 17:10:32 +00:00
|
|
|
this._writeFile(manFile, JSON.stringify(aManifest), function() { });
|
|
|
|
|
2012-10-03 05:38:03 +00:00
|
|
|
let manifest = new ManifestHelper(aManifest, app.manifestURL);
|
2012-09-29 17:57:18 +00:00
|
|
|
// A package is available: set downloadAvailable to fire the matching
|
|
|
|
// event.
|
|
|
|
app.downloadAvailable = true;
|
|
|
|
app.downloadSize = manifest.size;
|
2013-10-17 12:47:58 +00:00
|
|
|
app.updateManifest = aManifest;
|
2012-09-29 17:57:18 +00:00
|
|
|
DOMApplicationRegistry._saveApps(function() {
|
2013-10-17 12:47:58 +00:00
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloadavailable",
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
requestID: aData.requestID
|
|
|
|
});
|
2012-09-29 17:57:18 +00:00
|
|
|
});
|
2012-09-27 01:01:20 +00:00
|
|
|
}
|
|
|
|
|
2013-01-10 17:06:59 +00:00
|
|
|
// A hosted app is updated if the app manifest or the appcache needs
|
|
|
|
// updating. Even if the app manifest has not changed, we still check
|
|
|
|
// for changes in the app cache.
|
|
|
|
// 'aNewManifest' would contain the updated app manifest if
|
|
|
|
// it has actually been updated, while 'aOldManifest' contains the
|
|
|
|
// stored app manifest.
|
|
|
|
function updateHostedApp(aOldManifest, aNewManifest) {
|
2012-12-03 19:38:09 +00:00
|
|
|
debug("updateHostedApp " + aData.manifestURL);
|
2012-09-27 01:01:20 +00:00
|
|
|
|
2012-12-05 03:49:26 +00:00
|
|
|
// Clean up the deprecated manifest cache if needed.
|
2012-11-03 03:28:32 +00:00
|
|
|
if (id in this._manifestCache) {
|
|
|
|
delete this._manifestCache[id];
|
|
|
|
}
|
|
|
|
|
2013-02-19 08:37:49 +00:00
|
|
|
app.manifest = aNewManifest || aOldManifest;
|
|
|
|
|
2013-01-10 17:06:59 +00:00
|
|
|
let manifest;
|
|
|
|
if (aNewManifest) {
|
2013-02-28 19:22:31 +00:00
|
|
|
this.updateAppHandlers(aOldManifest, aNewManifest, app);
|
|
|
|
|
2013-01-10 17:06:59 +00:00
|
|
|
// 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() { });
|
|
|
|
manifest = new ManifestHelper(aNewManifest, app.origin);
|
2013-02-19 08:37:49 +00:00
|
|
|
|
2013-07-07 20:41:55 +00:00
|
|
|
if (supportUseCurrentProfile()) {
|
|
|
|
// Update the permissions for this app.
|
|
|
|
PermissionsInstaller.installPermissions({
|
|
|
|
manifest: app.manifest,
|
|
|
|
origin: app.origin,
|
|
|
|
manifestURL: aData.manifestURL
|
|
|
|
}, true);
|
|
|
|
}
|
|
|
|
|
2013-10-02 17:27:23 +00:00
|
|
|
this.updateDataStore(this.webapps[id].localId, app.origin,
|
|
|
|
app.manifestURL, app.manifest);
|
2013-10-02 17:27:07 +00:00
|
|
|
|
2013-02-19 08:37:49 +00:00
|
|
|
app.name = manifest.name;
|
|
|
|
app.csp = manifest.csp || "";
|
2013-09-11 12:15:48 +00:00
|
|
|
app.role = manifest.role || "";
|
2013-05-22 13:08:59 +00:00
|
|
|
app.updateTime = Date.now();
|
2013-01-10 17:06:59 +00:00
|
|
|
} else {
|
|
|
|
manifest = new ManifestHelper(aOldManifest, app.origin);
|
|
|
|
}
|
2012-09-27 01:01:20 +00:00
|
|
|
|
|
|
|
// Update the registry.
|
|
|
|
this.webapps[id] = app;
|
2012-10-30 00:17:47 +00:00
|
|
|
this._saveApps(function() {
|
2013-01-17 00:55:50 +00:00
|
|
|
let reg = DOMApplicationRegistry;
|
2012-12-05 16:50:32 +00:00
|
|
|
if (!manifest.appcache_path) {
|
2013-10-17 12:47:58 +00:00
|
|
|
reg.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifest: app.manifest,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
reg.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloadapplied",
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
requestID: aData.requestID
|
|
|
|
});
|
2012-12-05 16:50:32 +00:00
|
|
|
} else {
|
|
|
|
// Check if the appcache is updatable, and send "downloadavailable" or
|
|
|
|
// "downloadapplied".
|
|
|
|
let updateObserver = {
|
2012-12-14 19:29:25 +00:00
|
|
|
observe: function(aSubject, aTopic, aObsData) {
|
2013-01-14 17:47:55 +00:00
|
|
|
debug("updateHostedApp: updateSvc.checkForUpdate return for " +
|
|
|
|
app.manifestURL + " - event is " + aTopic);
|
2013-10-17 12:47:58 +00:00
|
|
|
let eventType =
|
2012-12-05 16:50:32 +00:00
|
|
|
aTopic == "offline-cache-update-available" ? "downloadavailable"
|
|
|
|
: "downloadapplied";
|
2013-10-17 12:47:58 +00:00
|
|
|
app.downloadAvailable = (eventType == "downloadavailable");
|
|
|
|
reg._saveApps(function() {
|
|
|
|
reg.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifest: app.manifest,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
reg.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: eventType,
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
requestID: aData.requestID
|
|
|
|
});
|
|
|
|
});
|
2012-12-05 16:50:32 +00:00
|
|
|
}
|
2013-10-17 12:47:58 +00:00
|
|
|
};
|
2013-01-14 17:47:55 +00:00
|
|
|
debug("updateHostedApp: updateSvc.checkForUpdate for " +
|
|
|
|
manifest.fullAppcachePath());
|
|
|
|
updateSvc.checkForUpdate(Services.io.newURI(manifest.fullAppcachePath(), null, null),
|
2012-12-05 16:50:32 +00:00
|
|
|
app.localId, false, updateObserver);
|
|
|
|
}
|
2012-12-20 20:08:58 +00:00
|
|
|
delete app.manifest;
|
2012-10-30 00:17:47 +00:00
|
|
|
});
|
2013-02-19 08:37:49 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// We cannot update an app that does not exists.
|
|
|
|
if (!app) {
|
|
|
|
sendError("NO_SUCH_APP");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We cannot update an app that is not fully installed.
|
|
|
|
if (app.installState !== "installed") {
|
|
|
|
sendError("PENDING_APP_NOT_UPDATABLE");
|
|
|
|
return;
|
|
|
|
}
|
2012-09-27 01:01:20 +00:00
|
|
|
|
2013-02-19 08:37:49 +00:00
|
|
|
// We may be able to remove this when Bug 839071 is fixed.
|
|
|
|
if (app.downloading) {
|
|
|
|
sendError("APP_IS_DOWNLOADING");
|
|
|
|
return;
|
2012-09-27 01:01:20 +00:00
|
|
|
}
|
|
|
|
|
2013-01-10 17:06:59 +00:00
|
|
|
// For non-removable hosted apps that lives in the core apps dir we
|
|
|
|
// only check the appcache because we can't modify the manifest even
|
|
|
|
// if it has changed.
|
2013-01-09 15:06:07 +00:00
|
|
|
let onlyCheckAppCache = false;
|
|
|
|
|
|
|
|
#ifdef MOZ_WIDGET_GONK
|
2013-10-17 12:47:58 +00:00
|
|
|
let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false);
|
|
|
|
onlyCheckAppCache = (app.basePath == appDir.path);
|
2013-01-09 15:06:07 +00:00
|
|
|
#endif
|
|
|
|
|
|
|
|
if (onlyCheckAppCache) {
|
2012-12-17 19:11:05 +00:00
|
|
|
// Bail out for packaged apps.
|
|
|
|
if (app.origin.startsWith("app://")) {
|
|
|
|
aData.error = "NOT_UPDATABLE";
|
|
|
|
aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// We need the manifest to check if we have an appcache.
|
|
|
|
this._readManifests([{ id: id }], function(aResult) {
|
|
|
|
let manifest = aResult[0].manifest;
|
|
|
|
if (!manifest.appcache_path) {
|
|
|
|
aData.error = "NOT_UPDATABLE";
|
|
|
|
aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
debug("Checking only appcache for " + aData.manifestURL);
|
|
|
|
// Check if the appcache is updatable, and send "downloadavailable" or
|
|
|
|
// "downloadapplied".
|
|
|
|
let updateObserver = {
|
|
|
|
observe: function(aSubject, aTopic, aObsData) {
|
2013-01-14 17:47:55 +00:00
|
|
|
debug("onlyCheckAppCache updateSvc.checkForUpdate return for " +
|
|
|
|
app.manifestURL + " - event is " + aTopic);
|
|
|
|
if (aTopic == "offline-cache-update-available") {
|
2013-01-10 17:06:59 +00:00
|
|
|
app.downloadAvailable = true;
|
2013-02-19 08:37:49 +00:00
|
|
|
this._saveApps(function() {
|
2013-10-17 12:47:58 +00:00
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloadavailable",
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
requestID: aData.requestID
|
|
|
|
});
|
2013-02-19 08:37:49 +00:00
|
|
|
});
|
2012-12-17 19:11:05 +00:00
|
|
|
} else {
|
|
|
|
aData.error = "NOT_UPDATABLE";
|
|
|
|
aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
|
|
|
|
}
|
|
|
|
}
|
2013-10-17 12:47:58 +00:00
|
|
|
};
|
2013-01-14 17:47:55 +00:00
|
|
|
let helper = new ManifestHelper(manifest);
|
|
|
|
debug("onlyCheckAppCache - launch updateSvc.checkForUpdate for " +
|
|
|
|
helper.fullAppcachePath());
|
|
|
|
updateSvc.checkForUpdate(Services.io.newURI(helper.fullAppcachePath(), null, null),
|
2012-12-17 19:11:05 +00:00
|
|
|
app.localId, false, updateObserver);
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-14 17:23:58 +00:00
|
|
|
// On xhr load request event
|
|
|
|
function onload(xhr, oldManifest) {
|
2013-01-09 15:06:07 +00:00
|
|
|
debug("Got http status=" + xhr.status + " for " + aData.manifestURL);
|
2013-01-18 17:54:06 +00:00
|
|
|
let oldHash = app.manifestHash;
|
2013-01-25 02:24:17 +00:00
|
|
|
let isPackage = app.origin.startsWith("app://");
|
2013-01-18 17:54:06 +00:00
|
|
|
|
2012-09-27 01:01:20 +00:00
|
|
|
if (xhr.status == 200) {
|
2012-12-03 19:38:09 +00:00
|
|
|
let manifest = xhr.response;
|
|
|
|
if (manifest == null) {
|
2012-09-27 01:01:20 +00:00
|
|
|
sendError("MANIFEST_PARSE_ERROR");
|
|
|
|
return;
|
|
|
|
}
|
2013-01-18 17:54:06 +00:00
|
|
|
|
2013-01-11 14:08:12 +00:00
|
|
|
if (!AppsUtils.checkManifest(manifest, app)) {
|
2012-09-27 01:01:20 +00:00
|
|
|
sendError("INVALID_MANIFEST");
|
2013-01-10 17:06:59 +00:00
|
|
|
return;
|
2012-11-18 22:43:38 +00:00
|
|
|
} else if (!AppsUtils.checkInstallAllowed(manifest, app.installOrigin)) {
|
|
|
|
sendError("INSTALL_FROM_DENIED");
|
2013-01-10 17:06:59 +00:00
|
|
|
return;
|
2012-09-27 01:01:20 +00:00
|
|
|
} else {
|
2013-02-14 17:23:58 +00:00
|
|
|
AppsUtils.ensureSameAppName(oldManifest, manifest, app);
|
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
let hash = this.computeManifestHash(manifest);
|
|
|
|
debug("Manifest hash = " + hash);
|
2013-01-25 02:24:17 +00:00
|
|
|
if (isPackage) {
|
|
|
|
if (!app.staged) {
|
|
|
|
app.staged = { };
|
|
|
|
}
|
|
|
|
app.staged.manifestHash = hash;
|
|
|
|
app.staged.etag = xhr.getResponseHeader("Etag");
|
|
|
|
} else {
|
|
|
|
app.manifestHash = hash;
|
|
|
|
app.etag = xhr.getResponseHeader("Etag");
|
|
|
|
}
|
2013-01-18 17:54:06 +00:00
|
|
|
|
2012-09-27 01:01:20 +00:00
|
|
|
app.lastCheckedUpdate = Date.now();
|
2013-01-25 02:24:17 +00:00
|
|
|
if (isPackage) {
|
2013-01-18 17:54:06 +00:00
|
|
|
if (oldHash != hash) {
|
|
|
|
updatePackagedApp.call(this, manifest);
|
|
|
|
} else {
|
2013-10-17 12:47:58 +00:00
|
|
|
this._saveApps(function() {
|
|
|
|
// Like if we got a 304, just send a 'downloadapplied'
|
|
|
|
// or downloadavailable event.
|
|
|
|
let eventType = app.downloadAvailable ? "downloadavailable"
|
|
|
|
: "downloadapplied";
|
|
|
|
aMm.sendAsyncMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
aMm.sendAsyncMessage("Webapps:FireEvent", {
|
|
|
|
eventType: eventType,
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
requestID: aData.requestID
|
|
|
|
});
|
|
|
|
});
|
2013-01-18 17:54:06 +00:00
|
|
|
}
|
2012-09-27 01:01:20 +00:00
|
|
|
} else {
|
2013-02-14 17:23:58 +00:00
|
|
|
// Update only the appcache if the manifest has not changed
|
|
|
|
// based on the hash value.
|
|
|
|
updateHostedApp.call(this, oldManifest,
|
|
|
|
oldHash == hash ? null : manifest);
|
2012-09-27 01:01:20 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else if (xhr.status == 304) {
|
2013-01-10 17:06:59 +00:00
|
|
|
// The manifest has not changed.
|
2013-01-25 02:24:17 +00:00
|
|
|
if (isPackage) {
|
2013-01-10 17:06:59 +00:00
|
|
|
app.lastCheckedUpdate = Date.now();
|
2013-10-17 12:47:58 +00:00
|
|
|
this._saveApps(function() {
|
|
|
|
// If the app is a packaged app, we just send a 'downloadapplied'
|
|
|
|
// or downloadavailable event.
|
|
|
|
let eventType = app.downloadAvailable ? "downloadavailable"
|
|
|
|
: "downloadapplied";
|
|
|
|
aMm.sendAsyncMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
aMm.sendAsyncMessage("Webapps:FireEvent", {
|
|
|
|
eventType: eventType,
|
|
|
|
manifestURL: app.manifestURL,
|
|
|
|
requestID: aData.requestID
|
|
|
|
});
|
|
|
|
});
|
2013-01-10 17:06:59 +00:00
|
|
|
} else {
|
|
|
|
// For hosted apps, even if the manifest has not changed, we check
|
|
|
|
// for offline cache updates.
|
2013-02-14 17:23:58 +00:00
|
|
|
updateHostedApp.call(this, oldManifest, null);
|
2012-09-27 01:01:20 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sendError("MANIFEST_URL_ERROR");
|
|
|
|
}
|
2013-02-14 17:23:58 +00:00
|
|
|
}
|
2012-09-27 01:01:20 +00:00
|
|
|
|
2013-02-14 17:23:58 +00:00
|
|
|
// Try to download a new manifest.
|
2013-05-06 17:41:07 +00:00
|
|
|
function doRequest(oldManifest, headers) {
|
|
|
|
headers = headers || [];
|
2013-02-14 17:23:58 +00:00
|
|
|
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
|
|
|
.createInstance(Ci.nsIXMLHttpRequest);
|
|
|
|
xhr.open("GET", aData.manifestURL, true);
|
|
|
|
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
2013-05-06 17:41:07 +00:00
|
|
|
headers.forEach(function(aHeader) {
|
|
|
|
debug("Adding header: " + aHeader.name + ": " + aHeader.value);
|
|
|
|
xhr.setRequestHeader(aHeader.name, aHeader.value);
|
|
|
|
});
|
2013-02-14 17:23:58 +00:00
|
|
|
xhr.responseType = "json";
|
|
|
|
if (app.etag) {
|
|
|
|
debug("adding manifest etag:" + app.etag);
|
|
|
|
xhr.setRequestHeader("If-None-Match", app.etag);
|
|
|
|
}
|
|
|
|
xhr.channel.notificationCallbacks =
|
|
|
|
this.createLoadContext(app.installerAppId, app.installerIsBrowser);
|
2012-09-27 01:01:20 +00:00
|
|
|
|
2013-02-14 17:23:58 +00:00
|
|
|
xhr.addEventListener("load", onload.bind(this, xhr, oldManifest), false);
|
|
|
|
xhr.addEventListener("error", (function() {
|
|
|
|
sendError("NETWORK_ERROR");
|
|
|
|
}).bind(this), false);
|
|
|
|
|
|
|
|
debug("Checking manifest at " + aData.manifestURL);
|
|
|
|
xhr.send(null);
|
|
|
|
}
|
|
|
|
|
|
|
|
// Read the current app manifest file
|
|
|
|
this._readManifests([{ id: id }], (function(aResult) {
|
2013-05-06 17:41:07 +00:00
|
|
|
let extraHeaders = [];
|
|
|
|
#ifdef MOZ_WIDGET_GONK
|
|
|
|
let pingManifestURL;
|
|
|
|
try {
|
|
|
|
pingManifestURL = Services.prefs.getCharPref("ping.manifestURL");
|
|
|
|
} catch(e) { }
|
|
|
|
|
|
|
|
if (pingManifestURL && pingManifestURL == aData.manifestURL) {
|
|
|
|
// Get the device info.
|
|
|
|
let device = libcutils.property_get("ro.product.model");
|
|
|
|
extraHeaders.push({ name: "X-MOZ-B2G-DEVICE",
|
|
|
|
value: device || "unknown" });
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
doRequest.call(this, aResult[0].manifest, extraHeaders);
|
2013-02-14 17:23:58 +00:00
|
|
|
}).bind(this));
|
2012-09-27 01:01:20 +00:00
|
|
|
},
|
|
|
|
|
2012-12-10 23:49:02 +00:00
|
|
|
// Creates a nsILoadContext object with a given appId and isBrowser flag.
|
|
|
|
createLoadContext: function createLoadContext(aAppId, aIsBrowser) {
|
|
|
|
return {
|
|
|
|
associatedWindow: null,
|
|
|
|
topWindow : null,
|
|
|
|
appId: aAppId,
|
|
|
|
isInBrowserElement: aIsBrowser,
|
|
|
|
usePrivateBrowsing: false,
|
|
|
|
isContent: false,
|
|
|
|
|
|
|
|
isAppOfType: function(appType) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
},
|
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
2012-12-10 23:48:59 +00:00
|
|
|
// Downloads the manifest and run checks, then eventually triggers the
|
|
|
|
// installation UI.
|
|
|
|
doInstall: function doInstall(aData, aMm) {
|
2013-01-21 19:13:35 +00:00
|
|
|
let app = aData.app;
|
|
|
|
|
2012-12-10 23:48:59 +00:00
|
|
|
let sendError = function sendError(aError) {
|
|
|
|
aData.error = aError;
|
|
|
|
aMm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
|
|
|
|
Cu.reportError("Error installing app from: " + app.installOrigin +
|
|
|
|
": " + aError);
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
// Hosted apps can't be trusted or certified, so just check that the
|
|
|
|
// manifest doesn't ask for those.
|
|
|
|
function checkAppStatus(aManifest) {
|
|
|
|
let manifestStatus = aManifest.type || "web";
|
2013-01-09 22:34:13 +00:00
|
|
|
return manifestStatus === "web";
|
2012-12-10 23:48:59 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
|
|
|
.createInstance(Ci.nsIXMLHttpRequest);
|
|
|
|
xhr.open("GET", app.manifestURL, true);
|
2013-01-09 10:02:08 +00:00
|
|
|
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
2012-12-10 23:49:02 +00:00
|
|
|
xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId,
|
|
|
|
aData.isBrowser);
|
2012-12-10 23:48:59 +00:00
|
|
|
xhr.responseType = "json";
|
2012-12-10 23:49:02 +00:00
|
|
|
|
2012-12-10 23:48:59 +00:00
|
|
|
xhr.addEventListener("load", (function() {
|
|
|
|
if (xhr.status == 200) {
|
|
|
|
if (!AppsUtils.checkManifestContentType(app.installOrigin, app.origin,
|
|
|
|
xhr.getResponseHeader("content-type"))) {
|
|
|
|
sendError("INVALID_MANIFEST");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
app.manifest = xhr.response;
|
|
|
|
if (!app.manifest) {
|
|
|
|
sendError("MANIFEST_PARSE_ERROR");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-08-02 00:00:22 +00:00
|
|
|
// Disallow multiple hosted apps installations from the same origin for now.
|
|
|
|
// We will remove this code after multiple apps per origin are supported (bug 778277).
|
|
|
|
// This will also disallow reinstalls from the same origin for now.
|
|
|
|
for (let id in this.webapps) {
|
|
|
|
if (this.webapps[id].origin == app.origin &&
|
|
|
|
!this.webapps[id].packageHash &&
|
|
|
|
this._isLaunchable(this.webapps[id])) {
|
|
|
|
sendError("MULTIPLE_APPS_PER_ORIGIN_FORBIDDEN");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-11 14:08:12 +00:00
|
|
|
if (!AppsUtils.checkManifest(app.manifest, app)) {
|
2012-12-10 23:48:59 +00:00
|
|
|
sendError("INVALID_MANIFEST");
|
|
|
|
} else if (!AppsUtils.checkInstallAllowed(app.manifest, app.installOrigin)) {
|
|
|
|
sendError("INSTALL_FROM_DENIED");
|
|
|
|
} else if (!checkAppStatus(app.manifest)) {
|
|
|
|
sendError("INVALID_SECURITY_LEVEL");
|
|
|
|
} else {
|
|
|
|
app.etag = xhr.getResponseHeader("Etag");
|
2013-01-18 17:54:06 +00:00
|
|
|
app.manifestHash = this.computeManifestHash(app.manifest);
|
2013-01-04 18:21:31 +00:00
|
|
|
// We allow bypassing the install confirmation process to facilitate
|
|
|
|
// automation.
|
|
|
|
let prefName = "dom.mozApps.auto_confirm_install";
|
|
|
|
if (Services.prefs.prefHasUserValue(prefName) &&
|
|
|
|
Services.prefs.getBoolPref(prefName)) {
|
|
|
|
this.confirmInstall(aData);
|
|
|
|
} else {
|
|
|
|
Services.obs.notifyObservers(aMm, "webapps-ask-install",
|
|
|
|
JSON.stringify(aData));
|
|
|
|
}
|
2012-12-10 23:48:59 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
sendError("MANIFEST_URL_ERROR");
|
|
|
|
}
|
|
|
|
}).bind(this), false);
|
|
|
|
|
|
|
|
xhr.addEventListener("error", (function() {
|
|
|
|
sendError("NETWORK_ERROR");
|
|
|
|
}).bind(this), false);
|
|
|
|
|
|
|
|
xhr.send(null);
|
|
|
|
},
|
|
|
|
|
|
|
|
doInstallPackage: function doInstallPackage(aData, aMm) {
|
2013-01-21 19:13:35 +00:00
|
|
|
let app = aData.app;
|
|
|
|
|
2012-12-10 23:48:59 +00:00
|
|
|
let sendError = function sendError(aError) {
|
|
|
|
aData.error = aError;
|
|
|
|
aMm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
|
|
|
|
Cu.reportError("Error installing packaged app from: " +
|
|
|
|
app.installOrigin + ": " + aError);
|
|
|
|
}.bind(this);
|
|
|
|
|
|
|
|
let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"]
|
|
|
|
.createInstance(Ci.nsIXMLHttpRequest);
|
|
|
|
xhr.open("GET", app.manifestURL, true);
|
2013-01-09 10:02:08 +00:00
|
|
|
xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
2012-12-10 23:49:02 +00:00
|
|
|
xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId,
|
|
|
|
aData.isBrowser);
|
2012-12-10 23:48:59 +00:00
|
|
|
xhr.responseType = "json";
|
|
|
|
|
|
|
|
xhr.addEventListener("load", (function() {
|
|
|
|
if (xhr.status == 200) {
|
|
|
|
if (!AppsUtils.checkManifestContentType(app.installOrigin, app.origin,
|
|
|
|
xhr.getResponseHeader("content-type"))) {
|
|
|
|
sendError("INVALID_MANIFEST");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let manifest = app.updateManifest = xhr.response;
|
|
|
|
if (!manifest) {
|
|
|
|
sendError("MANIFEST_PARSE_ERROR");
|
|
|
|
return;
|
|
|
|
}
|
2013-01-18 17:54:06 +00:00
|
|
|
|
2013-08-02 00:00:39 +00:00
|
|
|
// Disallow reinstalls from the same manifest URL for now.
|
2013-09-13 12:32:47 +00:00
|
|
|
let id = this._appIdForManifestURL(app.manifestURL);
|
|
|
|
if (id !== null && this._isLaunchable(this.webapps[id])) {
|
2013-08-02 00:00:39 +00:00
|
|
|
sendError("REINSTALL_FORBIDDEN");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-11 14:08:12 +00:00
|
|
|
if (!(AppsUtils.checkManifest(manifest, app) &&
|
2012-12-10 23:48:59 +00:00
|
|
|
manifest.package_path)) {
|
|
|
|
sendError("INVALID_MANIFEST");
|
|
|
|
} else if (!AppsUtils.checkInstallAllowed(manifest, app.installOrigin)) {
|
|
|
|
sendError("INSTALL_FROM_DENIED");
|
|
|
|
} else {
|
|
|
|
app.etag = xhr.getResponseHeader("Etag");
|
2013-01-18 17:54:06 +00:00
|
|
|
app.manifestHash = this.computeManifestHash(manifest);
|
2013-01-14 17:47:55 +00:00
|
|
|
debug("at install package got app etag=" + app.etag);
|
2013-05-20 09:23:32 +00:00
|
|
|
// We allow bypassing the install confirmation process to facilitate
|
|
|
|
// automation.
|
|
|
|
let prefName = "dom.mozApps.auto_confirm_install";
|
|
|
|
if (Services.prefs.prefHasUserValue(prefName) &&
|
|
|
|
Services.prefs.getBoolPref(prefName)) {
|
|
|
|
this.confirmInstall(aData);
|
|
|
|
} else {
|
|
|
|
Services.obs.notifyObservers(aMm, "webapps-ask-install",
|
|
|
|
JSON.stringify(aData));
|
|
|
|
}
|
2012-12-10 23:48:59 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
sendError("MANIFEST_URL_ERROR");
|
|
|
|
}
|
|
|
|
}).bind(this), false);
|
|
|
|
|
|
|
|
xhr.addEventListener("error", (function() {
|
|
|
|
sendError("NETWORK_ERROR");
|
|
|
|
}).bind(this), false);
|
|
|
|
|
|
|
|
xhr.send(null);
|
|
|
|
},
|
|
|
|
|
2011-11-28 20:13:26 +00:00
|
|
|
denyInstall: function(aData) {
|
2012-07-11 15:38:33 +00:00
|
|
|
let packageId = aData.app.packageId;
|
|
|
|
if (packageId) {
|
|
|
|
let dir = FileUtils.getDir("TmpD", ["webapps", packageId],
|
|
|
|
true, true);
|
|
|
|
try {
|
|
|
|
dir.remove(true);
|
|
|
|
} catch(e) {
|
|
|
|
}
|
|
|
|
}
|
2012-09-18 17:34:55 +00:00
|
|
|
aData.mm.sendAsyncMessage("Webapps:Install:Return:KO", aData);
|
2011-11-28 20:13:26 +00:00
|
|
|
},
|
2011-12-08 13:32:54 +00:00
|
|
|
|
2013-02-13 19:55:45 +00:00
|
|
|
// This function is called after we called the onsuccess callback on the
|
|
|
|
// content side. This let the webpage the opportunity to set event handlers
|
|
|
|
// on the app before we start firing progress events.
|
|
|
|
queuedDownload: {},
|
2013-08-06 14:58:44 +00:00
|
|
|
queuedPackageDownload: {},
|
2013-02-13 19:55:45 +00:00
|
|
|
|
|
|
|
onInstallSuccessAck: function onInstallSuccessAck(aManifestURL) {
|
2013-10-23 23:50:57 +00:00
|
|
|
// If we are offline, register to run when we'll be online.
|
|
|
|
if (Services.io.offline) {
|
|
|
|
let onlineWrapper = {
|
|
|
|
observe: function(aSubject, aTopic, aData) {
|
|
|
|
Services.obs.removeObserver(onlineWrapper,
|
|
|
|
"network:offline-status-changed");
|
|
|
|
DOMApplicationRegistry.onInstallSuccessAck(aManifestURL);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
Services.obs.addObserver(onlineWrapper,
|
|
|
|
"network:offline-status-changed", false);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-08-06 14:58:44 +00:00
|
|
|
let cacheDownload = this.queuedDownload[aManifestURL];
|
|
|
|
if (cacheDownload) {
|
|
|
|
this.startOfflineCacheDownload(cacheDownload.manifest,
|
|
|
|
cacheDownload.app,
|
2013-08-29 14:00:33 +00:00
|
|
|
cacheDownload.profileDir);
|
2013-08-06 14:58:44 +00:00
|
|
|
delete this.queuedDownload[aManifestURL];
|
|
|
|
|
2013-02-13 19:55:45 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-08-06 14:58:44 +00:00
|
|
|
|
|
|
|
let packageDownload = this.queuedPackageDownload[aManifestURL];
|
|
|
|
if (packageDownload) {
|
|
|
|
let manifest = packageDownload.manifest;
|
|
|
|
let appObject = packageDownload.app;
|
|
|
|
let installSuccessCallback = packageDownload.callback;
|
|
|
|
|
|
|
|
delete this.queuedPackageDownload[aManifestURL];
|
|
|
|
|
|
|
|
this.downloadPackage(manifest, appObject, false, (function(aId, aManifest) {
|
|
|
|
// Move the zip out of TmpD.
|
|
|
|
let app = DOMApplicationRegistry.webapps[aId];
|
|
|
|
let zipFile = FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true);
|
|
|
|
let dir = this._getAppDir(aId);
|
|
|
|
zipFile.moveTo(dir, "application.zip");
|
|
|
|
let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true);
|
|
|
|
try {
|
|
|
|
tmpDir.remove(true);
|
|
|
|
} catch(e) { }
|
|
|
|
|
2013-09-06 21:51:24 +00:00
|
|
|
// Save the manifest and clear the manifest cache, since it may contain
|
|
|
|
// the update manifest.
|
2013-08-06 14:58:44 +00:00
|
|
|
let manFile = dir.clone();
|
|
|
|
manFile.append("manifest.webapp");
|
|
|
|
this._writeFile(manFile, JSON.stringify(aManifest), function() { });
|
2013-09-06 21:51:24 +00:00
|
|
|
if (this._manifestCache[aId]) {
|
|
|
|
delete this._manifestCache[aId];
|
|
|
|
}
|
|
|
|
|
2013-08-06 14:58:44 +00:00
|
|
|
// Set state and fire events.
|
|
|
|
app.installState = "installed";
|
|
|
|
app.downloading = false;
|
|
|
|
app.downloadAvailable = false;
|
|
|
|
this._saveApps((function() {
|
|
|
|
this.updateAppHandlers(null, aManifest, appObject);
|
|
|
|
this.broadcastMessage("Webapps:AddApp", { id: aId, app: appObject });
|
2013-10-04 17:01:14 +00:00
|
|
|
Services.obs.notifyObservers(null, "webapps-installed",
|
|
|
|
JSON.stringify({ manifestURL: appObject.manifestURL }));
|
2013-08-06 14:58:44 +00:00
|
|
|
|
|
|
|
if (supportUseCurrentProfile()) {
|
|
|
|
// Update the permissions for this app.
|
2013-10-17 12:47:58 +00:00
|
|
|
PermissionsInstaller.installPermissions({
|
|
|
|
manifest: aManifest,
|
|
|
|
origin: appObject.origin,
|
|
|
|
manifestURL: appObject.manifestURL
|
|
|
|
}, true);
|
2013-08-06 14:58:44 +00:00
|
|
|
}
|
2013-10-02 17:27:07 +00:00
|
|
|
|
2013-10-02 17:27:23 +00:00
|
|
|
this.updateDataStore(this.webapps[aId].localId, appObject.origin,
|
|
|
|
appObject.manifestURL, aManifest);
|
2013-10-02 17:27:07 +00:00
|
|
|
|
2013-10-17 12:47:58 +00:00
|
|
|
this.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifest: aManifest,
|
|
|
|
manifestURL: appObject.manifestURL
|
|
|
|
});
|
|
|
|
this.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: ["downloadsuccess", "downloadapplied"],
|
|
|
|
manifestURL: appObject.manifestURL
|
|
|
|
});
|
2013-08-06 14:58:44 +00:00
|
|
|
if (installSuccessCallback) {
|
2013-09-13 12:32:47 +00:00
|
|
|
installSuccessCallback(aManifest, zipFile.path);
|
2013-08-06 14:58:44 +00:00
|
|
|
}
|
|
|
|
}).bind(this));
|
|
|
|
}).bind(this));
|
|
|
|
}
|
2013-02-13 19:55:45 +00:00
|
|
|
},
|
|
|
|
|
2013-08-29 14:00:33 +00:00
|
|
|
confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) {
|
2012-09-28 22:16:29 +00:00
|
|
|
let isReinstall = false;
|
2011-11-28 20:13:26 +00:00
|
|
|
let app = aData.app;
|
2012-08-29 21:20:03 +00:00
|
|
|
app.removable = true;
|
2012-09-27 19:34:41 +00:00
|
|
|
|
2013-08-28 12:10:55 +00:00
|
|
|
let id = this._appIdForManifestURL(app.manifestURL);
|
|
|
|
let localId = this.getAppLocalIdByManifestURL(app.manifestURL);
|
2013-01-08 12:18:47 +00:00
|
|
|
|
2012-07-11 15:38:33 +00:00
|
|
|
// Installing an application again is considered as an update.
|
2011-11-28 20:13:26 +00:00
|
|
|
if (id) {
|
2012-09-28 22:16:29 +00:00
|
|
|
isReinstall = true;
|
2012-08-29 21:20:03 +00:00
|
|
|
let dir = this._getAppDir(id);
|
2011-11-28 20:13:26 +00:00
|
|
|
try {
|
|
|
|
dir.remove(true);
|
|
|
|
} catch(e) {
|
|
|
|
}
|
2012-04-28 07:10:08 +00:00
|
|
|
} else {
|
|
|
|
id = this.makeAppId();
|
2012-07-09 10:25:41 +00:00
|
|
|
localId = this._nextLocalId();
|
2011-11-28 20:13:26 +00:00
|
|
|
}
|
2012-12-22 13:56:21 +00:00
|
|
|
app.id = id;
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2012-09-27 19:34:41 +00:00
|
|
|
let manifestName = "manifest.webapp";
|
|
|
|
if (aData.isPackage) {
|
2012-07-11 15:38:33 +00:00
|
|
|
// Override the origin with the correct id.
|
|
|
|
app.origin = "app://" + id;
|
2012-09-27 19:34:41 +00:00
|
|
|
|
|
|
|
// For packaged apps, keep the update manifest distinct from the app
|
|
|
|
// manifest.
|
|
|
|
manifestName = "update.webapp";
|
2012-07-11 15:38:33 +00:00
|
|
|
}
|
|
|
|
|
2012-08-28 02:43:57 +00:00
|
|
|
let appObject = AppsUtils.cloneAppObject(app);
|
2012-08-28 02:43:57 +00:00
|
|
|
appObject.appStatus = app.appStatus || Ci.nsIPrincipal.APP_STATUS_INSTALLED;
|
2012-10-31 21:02:37 +00:00
|
|
|
|
2012-06-18 19:49:35 +00:00
|
|
|
appObject.installTime = app.installTime = Date.now();
|
2012-09-27 01:01:20 +00:00
|
|
|
appObject.lastUpdateCheck = app.lastUpdateCheck = Date.now();
|
2011-12-06 04:22:01 +00:00
|
|
|
|
2012-12-22 13:56:21 +00:00
|
|
|
appObject.id = id;
|
2012-07-09 10:25:41 +00:00
|
|
|
appObject.localId = localId;
|
2012-09-06 00:37:41 +00:00
|
|
|
appObject.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path;
|
2013-08-02 00:00:39 +00:00
|
|
|
let dir = this._getAppDir(id);
|
2011-11-28 20:13:26 +00:00
|
|
|
let manFile = dir.clone();
|
2012-09-27 19:34:41 +00:00
|
|
|
manFile.append(manifestName);
|
|
|
|
let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest;
|
|
|
|
this._writeFile(manFile, JSON.stringify(jsonManifest), function() { });
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2012-10-03 05:38:03 +00:00
|
|
|
let manifest = new ManifestHelper(jsonManifest, app.origin);
|
2012-09-27 01:01:20 +00:00
|
|
|
|
|
|
|
if (manifest.appcache_path) {
|
|
|
|
appObject.installState = "pending";
|
|
|
|
appObject.downloadAvailable = true;
|
|
|
|
appObject.downloading = true;
|
2012-09-27 19:34:41 +00:00
|
|
|
appObject.downloadSize = 0;
|
|
|
|
appObject.readyToApplyDownload = false;
|
|
|
|
} else if (manifest.package_path) {
|
|
|
|
appObject.installState = "pending";
|
|
|
|
appObject.downloadAvailable = true;
|
|
|
|
appObject.downloading = true;
|
|
|
|
appObject.downloadSize = manifest.size;
|
2012-09-27 01:01:20 +00:00
|
|
|
appObject.readyToApplyDownload = false;
|
|
|
|
} else {
|
|
|
|
appObject.installState = "installed";
|
|
|
|
appObject.downloadAvailable = false;
|
|
|
|
appObject.downloading = false;
|
|
|
|
appObject.readyToApplyDownload = false;
|
|
|
|
}
|
|
|
|
|
2012-09-27 19:34:41 +00:00
|
|
|
appObject.name = manifest.name;
|
2012-10-19 10:43:17 +00:00
|
|
|
appObject.csp = manifest.csp || "";
|
2013-09-11 12:15:48 +00:00
|
|
|
appObject.role = manifest.role || "";
|
2012-09-26 23:49:43 +00:00
|
|
|
|
2012-12-10 23:49:02 +00:00
|
|
|
appObject.installerAppId = aData.appId;
|
|
|
|
appObject.installerIsBrowser = aData.isBrowser;
|
|
|
|
|
2012-09-27 01:01:20 +00:00
|
|
|
this.webapps[id] = appObject;
|
2012-10-03 05:38:06 +00:00
|
|
|
|
2012-10-10 21:29:07 +00:00
|
|
|
// For package apps, the permissions are not in the mini-manifest, so
|
|
|
|
// don't update the permissions yet.
|
|
|
|
if (!aData.isPackage) {
|
2013-07-07 20:41:55 +00:00
|
|
|
if (supportUseCurrentProfile()) {
|
|
|
|
PermissionsInstaller.installPermissions({ origin: appObject.origin,
|
|
|
|
manifestURL: appObject.manifestURL,
|
|
|
|
manifest: jsonManifest },
|
|
|
|
isReinstall, (function() {
|
|
|
|
this.uninstall(aData, aData.mm);
|
|
|
|
}).bind(this));
|
|
|
|
}
|
2013-10-02 17:27:07 +00:00
|
|
|
|
2013-10-02 17:27:23 +00:00
|
|
|
this.updateDataStore(this.webapps[id].localId, this.webapps[id].origin,
|
|
|
|
this.webapps[id].manifestURL, jsonManifest);
|
2012-10-10 21:29:07 +00:00
|
|
|
}
|
2012-10-03 05:38:06 +00:00
|
|
|
|
2012-09-27 19:34:41 +00:00
|
|
|
["installState", "downloadAvailable",
|
|
|
|
"downloading", "downloadSize", "readyToApplyDownload"].forEach(function(aProp) {
|
|
|
|
aData.app[aProp] = appObject[aProp];
|
|
|
|
});
|
2012-06-11 18:41:46 +00:00
|
|
|
|
2013-08-06 13:58:47 +00:00
|
|
|
if (manifest.appcache_path) {
|
|
|
|
this.queuedDownload[app.manifestURL] = {
|
|
|
|
manifest: manifest,
|
|
|
|
app: appObject,
|
2013-08-29 14:00:33 +00:00
|
|
|
profileDir: aProfileDir
|
2013-08-06 13:58:47 +00:00
|
|
|
}
|
2013-02-13 19:55:45 +00:00
|
|
|
}
|
|
|
|
|
2013-05-20 09:23:32 +00:00
|
|
|
// 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.
|
2013-08-28 03:40:16 +00:00
|
|
|
this._saveApps((function() {
|
|
|
|
this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject });
|
|
|
|
this.broadcastMessage("Webapps:Install:Return:OK", aData);
|
2013-10-04 17:01:14 +00:00
|
|
|
Services.obs.notifyObservers(null, "webapps-installed",
|
|
|
|
JSON.stringify({ manifestURL: app.manifestURL }));
|
2013-08-28 03:40:16 +00:00
|
|
|
}).bind(this));
|
2012-06-11 18:41:46 +00:00
|
|
|
|
2012-09-27 19:34:41 +00:00
|
|
|
if (!aData.isPackage) {
|
2013-02-28 19:22:31 +00:00
|
|
|
this.updateAppHandlers(null, app.manifest, app);
|
2013-05-09 15:20:37 +00:00
|
|
|
if (aInstallSuccessCallback) {
|
2013-08-02 00:00:39 +00:00
|
|
|
aInstallSuccessCallback(app.manifest);
|
2013-05-09 15:20:37 +00:00
|
|
|
}
|
2012-10-17 04:30:43 +00:00
|
|
|
}
|
2012-07-03 00:16:55 +00:00
|
|
|
|
2012-09-27 19:34:41 +00:00
|
|
|
if (manifest.package_path) {
|
2013-07-31 19:34:19 +00:00
|
|
|
// If it is a local app then it must been installed from a local file
|
|
|
|
// instead of web.
|
|
|
|
let origPath = jsonManifest.package_path;
|
|
|
|
if (aData.app.localInstallPath) {
|
|
|
|
jsonManifest.package_path = "file://" + aData.app.localInstallPath;
|
|
|
|
}
|
2012-09-29 17:57:18 +00:00
|
|
|
// origin for install apps is meaningless here, since it's app:// and this
|
|
|
|
// can't be used to resolve package paths.
|
2012-10-03 05:38:03 +00:00
|
|
|
manifest = new ManifestHelper(jsonManifest, app.manifestURL);
|
2013-07-24 17:15:37 +00:00
|
|
|
|
2013-08-06 14:58:44 +00:00
|
|
|
this.queuedPackageDownload[app.manifestURL] = {
|
|
|
|
manifest: manifest,
|
|
|
|
app: appObject,
|
|
|
|
callback: aInstallSuccessCallback
|
2013-07-31 19:34:19 +00:00
|
|
|
};
|
2013-10-23 23:50:57 +00:00
|
|
|
}
|
2013-07-31 19:34:19 +00:00
|
|
|
|
2013-10-23 23:50:57 +00:00
|
|
|
if (aData.forceSuccessAck) {
|
|
|
|
// If it's a local install, there's no content process so just
|
|
|
|
// ack the install.
|
|
|
|
this.onInstallSuccessAck(app.manifestURL);
|
2012-09-27 19:34:41 +00:00
|
|
|
}
|
2011-11-28 20:13:26 +00:00
|
|
|
},
|
2011-12-08 13:32:54 +00:00
|
|
|
|
2012-07-09 10:25:41 +00:00
|
|
|
_nextLocalId: function() {
|
2012-09-06 01:07:21 +00:00
|
|
|
let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1;
|
2013-09-13 12:22:24 +00:00
|
|
|
|
|
|
|
while (this.getManifestURLByLocalId(id)) {
|
|
|
|
id++;
|
|
|
|
}
|
|
|
|
|
2012-09-06 01:07:21 +00:00
|
|
|
Services.prefs.setIntPref("dom.mozApps.maxLocalId", id);
|
2013-04-22 20:28:20 +00:00
|
|
|
Services.prefs.savePrefFile(null);
|
2012-09-06 01:07:21 +00:00
|
|
|
return id;
|
2012-07-09 10:25:41 +00:00
|
|
|
},
|
|
|
|
|
2012-09-27 19:34:41 +00:00
|
|
|
_appIdForManifestURL: function(aURI) {
|
|
|
|
for (let id in this.webapps) {
|
|
|
|
if (this.webapps[id].manifestURL == aURI)
|
|
|
|
return id;
|
|
|
|
}
|
|
|
|
return null;
|
|
|
|
},
|
|
|
|
|
2012-04-28 07:10:08 +00:00
|
|
|
makeAppId: function() {
|
|
|
|
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
|
|
|
return uuidGenerator.generateUUID().toString();
|
|
|
|
},
|
|
|
|
|
2011-12-08 13:32:54 +00:00
|
|
|
_saveApps: function(aCallback) {
|
2012-09-27 01:01:20 +00:00
|
|
|
this._writeFile(this.appsFile, JSON.stringify(this.webapps, null, 2), function() {
|
2011-12-08 13:32:54 +00:00
|
|
|
if (aCallback)
|
|
|
|
aCallback();
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
/**
|
|
|
|
* Asynchronously reads a list of manifests
|
|
|
|
*/
|
2012-11-03 03:28:32 +00:00
|
|
|
|
|
|
|
_manifestCache: {},
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
_readManifests: function(aData, aFinalCallback, aIndex) {
|
2011-12-15 17:20:57 +00:00
|
|
|
if (!aData.length) {
|
|
|
|
aFinalCallback(aData);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
let index = aIndex || 0;
|
|
|
|
let id = aData[index].id;
|
2012-05-22 21:38:34 +00:00
|
|
|
|
2012-11-03 03:28:32 +00:00
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2012-05-22 21:38:34 +00:00
|
|
|
// the manifest file used to be named manifest.json, so fallback on this.
|
2013-03-15 14:18:58 +00:00
|
|
|
let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath()
|
|
|
|
? "coreAppsDir" : DIRECTORY_NAME;
|
2012-08-29 21:20:03 +00:00
|
|
|
let file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.webapp"], true);
|
2012-09-27 19:34:41 +00:00
|
|
|
if (!file.exists()) {
|
|
|
|
file = FileUtils.getFile(baseDir, ["webapps", id, "update.webapp"], true);
|
|
|
|
}
|
2012-05-22 21:38:34 +00:00
|
|
|
if (!file.exists()) {
|
2012-08-29 21:20:03 +00:00
|
|
|
file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.json"], true);
|
2012-05-22 21:38:34 +00:00
|
|
|
}
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
this._loadJSONAsync(file, (function(aJSON) {
|
2012-11-03 03:28:32 +00:00
|
|
|
aData[index].manifest = this._manifestCache[id] = aJSON;
|
2011-12-05 23:26:18 +00:00
|
|
|
if (index == aData.length - 1)
|
|
|
|
aFinalCallback(aData);
|
|
|
|
else
|
|
|
|
this._readManifests(aData, aFinalCallback, index + 1);
|
2012-04-28 07:10:08 +00:00
|
|
|
}).bind(this));
|
2011-11-28 20:13:26 +00:00
|
|
|
},
|
2011-12-08 13:32:54 +00:00
|
|
|
|
2012-09-29 17:57:18 +00:00
|
|
|
downloadPackage: function(aManifest, aApp, aIsUpdate, aOnSuccess) {
|
2012-07-11 15:38:33 +00:00
|
|
|
// Here are the steps when installing a package:
|
|
|
|
// - create a temp directory where to store the app.
|
|
|
|
// - download the zip in this directory.
|
2012-12-14 04:35:58 +00:00
|
|
|
// - check the signature on the zip.
|
2012-07-11 15:38:33 +00:00
|
|
|
// - extract the manifest from the zip and check it.
|
|
|
|
// - ask confirmation to the user.
|
|
|
|
// - add the new app to the registry.
|
|
|
|
// If we fail at any step, we backout the previous ones and return an error.
|
|
|
|
|
2012-11-29 14:19:37 +00:00
|
|
|
debug("downloadPackage " + JSON.stringify(aApp));
|
2012-07-11 15:38:33 +00:00
|
|
|
|
2013-07-31 19:34:19 +00:00
|
|
|
let fullPackagePath = aManifest.fullPackagePath();
|
|
|
|
|
|
|
|
// Check if it's a local file install (we've downloaded/sideloaded the
|
|
|
|
// package already or it did exist on the build).
|
|
|
|
|
|
|
|
let isLocalFileInstall =
|
|
|
|
Services.io.extractScheme(fullPackagePath) === 'file';
|
|
|
|
|
2012-09-27 19:34:41 +00:00
|
|
|
let id = this._appIdForManifestURL(aApp.manifestURL);
|
|
|
|
let app = this.webapps[id];
|
2012-07-11 15:38:33 +00:00
|
|
|
|
2012-11-15 09:35:37 +00:00
|
|
|
let self = this;
|
2012-07-11 15:38:33 +00:00
|
|
|
// Removes the directory we created, and sends an error to the DOM side.
|
|
|
|
function cleanup(aError) {
|
2012-09-27 19:34:41 +00:00
|
|
|
debug("Cleanup: " + aError);
|
|
|
|
let dir = FileUtils.getDir("TmpD", ["webapps", id], true, true);
|
2012-07-11 15:38:33 +00:00
|
|
|
try {
|
|
|
|
dir.remove(true);
|
|
|
|
} catch (e) { }
|
2012-11-21 18:09:10 +00:00
|
|
|
|
2012-12-21 18:28:55 +00:00
|
|
|
let download = AppDownloadManager.get(aApp.manifestURL);
|
2012-11-29 14:19:37 +00:00
|
|
|
app.downloading = false;
|
2013-01-25 02:24:17 +00:00
|
|
|
|
2013-01-25 02:24:17 +00:00
|
|
|
// If there were not enough storage to download the package we
|
2012-12-07 18:13:27 +00:00
|
|
|
// won't have a record of the download details, so we just set the
|
2013-01-25 02:24:17 +00:00
|
|
|
// installState to 'pending' at first download and to 'installed' when
|
|
|
|
// updating.
|
|
|
|
app.installState = download ? download.previousState
|
|
|
|
: aIsUpdate ? "installed"
|
|
|
|
: "pending";
|
|
|
|
|
|
|
|
if (app.staged) {
|
|
|
|
delete app.staged;
|
|
|
|
}
|
|
|
|
|
2013-10-17 12:47:58 +00:00
|
|
|
self._saveApps(function() {
|
|
|
|
self.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
error: aError,
|
|
|
|
manifestURL: aApp.manifestURL
|
|
|
|
});
|
|
|
|
self.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "downloaderror",
|
|
|
|
manifestURL: aApp.manifestURL
|
|
|
|
});
|
|
|
|
});
|
2012-12-21 18:28:55 +00:00
|
|
|
AppDownloadManager.remove(aApp.manifestURL);
|
2012-07-11 15:38:33 +00:00
|
|
|
}
|
|
|
|
|
2013-10-17 12:47:58 +00:00
|
|
|
function sendProgressEvent(aProgress) {
|
|
|
|
self.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: {
|
|
|
|
progress: aProgress
|
|
|
|
},
|
|
|
|
manifestURL: aApp.manifestURL
|
|
|
|
});
|
|
|
|
self.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "progress",
|
|
|
|
manifestURL: aApp.manifestURL
|
|
|
|
});
|
2013-02-11 08:38:51 +00:00
|
|
|
}
|
|
|
|
|
2013-01-25 09:57:14 +00:00
|
|
|
// aStoreId must be a string of the form
|
|
|
|
// <installOrigin>#<storeId from ids.json>
|
|
|
|
// aStoreVersion must be a positive integer.
|
|
|
|
function checkForStoreIdMatch(aStoreId, aStoreVersion) {
|
|
|
|
// Things to check:
|
|
|
|
// 1. if it's a update:
|
2013-06-26 12:18:34 +00:00
|
|
|
// a. We should already have this storeId, or the original storeId must start
|
|
|
|
// with STORE_ID_PENDING_PREFIX
|
2013-01-25 09:57:14 +00:00
|
|
|
// b. The manifestURL for the stored app should be the same one we're
|
|
|
|
// updating
|
|
|
|
// c. And finally the version of the update should be higher than the one
|
|
|
|
// on the already installed package
|
|
|
|
// 2. else
|
|
|
|
// a. We should not have this storeId on the list
|
|
|
|
// We're currently launching WRONG_APP_STORE_ID for all the mismatch kind of
|
|
|
|
// errors, and APP_STORE_VERSION_ROLLBACK for the version error.
|
|
|
|
|
|
|
|
// Does an app with this storeID exist already?
|
|
|
|
let appId = self.getAppLocalIdByStoreId(aStoreId);
|
|
|
|
let isInstalled = appId != Ci.nsIScriptSecurityManager.NO_APP_ID;
|
|
|
|
if (aIsUpdate) {
|
2013-06-26 12:18:34 +00:00
|
|
|
let isDifferent = app.localId !== appId;
|
|
|
|
let isPending = app.storeId.indexOf(STORE_ID_PENDING_PREFIX) == 0;
|
|
|
|
|
|
|
|
if ((!isInstalled && !isPending) || (isInstalled && isDifferent)) {
|
2013-01-25 09:57:14 +00:00
|
|
|
throw "WRONG_APP_STORE_ID";
|
|
|
|
}
|
2013-06-26 12:18:34 +00:00
|
|
|
|
|
|
|
if (!isPending && (app.storeVersion >= aStoreVersion)) {
|
2013-01-25 09:57:14 +00:00
|
|
|
throw "APP_STORE_VERSION_ROLLBACK";
|
|
|
|
}
|
2013-06-26 12:18:34 +00:00
|
|
|
|
2013-01-25 09:57:14 +00:00
|
|
|
} else if (isInstalled) {
|
|
|
|
throw "WRONG_APP_STORE_ID";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-15 09:35:37 +00:00
|
|
|
function download() {
|
|
|
|
debug("About to download " + aManifest.fullPackagePath());
|
|
|
|
|
2013-07-31 19:34:19 +00:00
|
|
|
let requestChannel;
|
|
|
|
if (isLocalFileInstall) {
|
|
|
|
requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
|
|
|
|
.QueryInterface(Ci.nsIFileChannel);
|
|
|
|
} else {
|
|
|
|
requestChannel = NetUtil.newChannel(aManifest.fullPackagePath())
|
|
|
|
.QueryInterface(Ci.nsIHttpChannel);
|
|
|
|
requestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (app.packageEtag && !isLocalFileInstall) {
|
2013-01-14 17:47:55 +00:00
|
|
|
debug("Add If-None-Match header: " + app.packageEtag);
|
2012-12-31 08:29:31 +00:00
|
|
|
requestChannel.setRequestHeader("If-None-Match", app.packageEtag, false);
|
2012-12-21 18:28:58 +00:00
|
|
|
}
|
|
|
|
|
2012-12-21 18:28:55 +00:00
|
|
|
AppDownloadManager.add(aApp.manifestURL,
|
|
|
|
{
|
|
|
|
channel: requestChannel,
|
|
|
|
appId: id,
|
|
|
|
previousState: aIsUpdate ? "installed" : "pending"
|
|
|
|
}
|
|
|
|
);
|
2012-12-13 17:54:49 +00:00
|
|
|
|
|
|
|
let lastProgressTime = 0;
|
2013-02-11 08:38:51 +00:00
|
|
|
|
2012-11-15 09:35:37 +00:00
|
|
|
requestChannel.notificationCallbacks = {
|
|
|
|
QueryInterface: function notifQI(aIID) {
|
|
|
|
if (aIID.equals(Ci.nsISupports) ||
|
2012-12-10 23:49:02 +00:00
|
|
|
aIID.equals(Ci.nsIProgressEventSink) ||
|
|
|
|
aIID.equals(Ci.nsILoadContext))
|
2012-11-15 09:35:37 +00:00
|
|
|
return this;
|
|
|
|
|
|
|
|
throw Cr.NS_ERROR_NO_INTERFACE;
|
|
|
|
},
|
|
|
|
getInterface: function notifGI(aIID) {
|
|
|
|
return this.QueryInterface(aIID);
|
|
|
|
},
|
|
|
|
onProgress: function notifProgress(aRequest, aContext,
|
|
|
|
aProgress, aProgressMax) {
|
|
|
|
app.progress = aProgress;
|
2012-12-13 17:54:49 +00:00
|
|
|
let now = Date.now();
|
|
|
|
if (now - lastProgressTime > MIN_PROGRESS_EVENT_DELAY) {
|
2012-12-14 19:29:25 +00:00
|
|
|
debug("onProgress: " + aProgress + "/" + aProgressMax);
|
2013-10-17 12:47:58 +00:00
|
|
|
sendProgressEvent(aProgress);
|
2012-12-13 17:54:49 +00:00
|
|
|
lastProgressTime = now;
|
2013-01-11 15:32:06 +00:00
|
|
|
self._saveApps();
|
2012-12-13 17:54:49 +00:00
|
|
|
}
|
2012-11-15 09:35:37 +00:00
|
|
|
},
|
2012-12-10 23:49:02 +00:00
|
|
|
onStatus: function notifStatus(aRequest, aContext, aStatus, aStatusArg) { },
|
|
|
|
|
|
|
|
// nsILoadContext
|
|
|
|
appId: app.installerAppId,
|
|
|
|
isInBrowserElement: app.installerIsBrowser,
|
|
|
|
usePrivateBrowsing: false,
|
|
|
|
isContent: false,
|
|
|
|
associatedWindow: null,
|
|
|
|
topWindow : null,
|
|
|
|
isAppOfType: function(appType) {
|
|
|
|
throw Cr.NS_ERROR_NOT_IMPLEMENTED;
|
|
|
|
}
|
2012-07-11 15:38:33 +00:00
|
|
|
}
|
2012-11-29 14:19:37 +00:00
|
|
|
|
|
|
|
// We set the 'downloading' flag to true right before starting the fetch.
|
|
|
|
app.downloading = true;
|
|
|
|
// We determine the app's 'installState' according to its previous
|
|
|
|
// state. Cancelled download should remain as 'pending'. Successfully
|
|
|
|
// installed apps should morph to 'updating'.
|
|
|
|
app.installState = aIsUpdate ? "updating" : "pending";
|
|
|
|
|
2013-02-11 08:38:51 +00:00
|
|
|
// initialize the progress to 0 right now
|
|
|
|
app.progress = 0;
|
|
|
|
|
2012-12-14 19:29:25 +00:00
|
|
|
// Staging the zip in TmpD until all the checks are done.
|
|
|
|
let zipFile = FileUtils.getFile("TmpD",
|
|
|
|
["webapps", id, "application.zip"], true);
|
|
|
|
|
|
|
|
// We need an output stream to write the channel content to the zip file.
|
|
|
|
let outputStream = Cc["@mozilla.org/network/file-output-stream;1"]
|
|
|
|
.createInstance(Ci.nsIFileOutputStream);
|
|
|
|
// write, create, truncate
|
|
|
|
outputStream.init(zipFile, 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, {
|
2012-12-31 08:29:31 +00:00
|
|
|
onStartRequest: function(aRequest, aContext) {
|
2013-01-25 02:24:17 +00:00
|
|
|
// Nothing to do there anymore.
|
2012-12-31 08:29:31 +00:00
|
|
|
},
|
2013-01-25 02:24:17 +00:00
|
|
|
|
2012-12-14 19:29:25 +00:00
|
|
|
onStopRequest: function(aRequest, aContext, aStatusCode) {
|
|
|
|
debug("onStopRequest " + aStatusCode);
|
|
|
|
bufferedOutputStream.close();
|
|
|
|
outputStream.close();
|
|
|
|
|
2013-01-09 15:51:28 +00:00
|
|
|
if (!Components.isSuccessCode(aStatusCode)) {
|
|
|
|
cleanup("NETWORK_ERROR");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-02-01 19:17:32 +00:00
|
|
|
// If we get a 4XX or a 5XX http status, bail out like if we had a
|
|
|
|
// network error.
|
|
|
|
let responseStatus = requestChannel.responseStatus;
|
|
|
|
if (responseStatus >= 400 && responseStatus <= 599) {
|
2013-02-08 10:07:46 +00:00
|
|
|
// unrecoverable error, don't bug the user
|
|
|
|
app.downloadAvailable = false;
|
2013-02-01 19:17:32 +00:00
|
|
|
cleanup("NETWORK_ERROR");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
self.computeFileHash(zipFile, function onHashComputed(aHash) {
|
|
|
|
debug("packageHash=" + aHash);
|
2013-02-01 19:17:32 +00:00
|
|
|
let newPackage = (responseStatus != 304) &&
|
2013-01-18 17:54:06 +00:00
|
|
|
(aHash != app.packageHash);
|
|
|
|
|
|
|
|
if (!newPackage) {
|
|
|
|
// The package's Etag or hash has not changed.
|
|
|
|
// We send a "applied" event right away.
|
|
|
|
app.downloading = false;
|
|
|
|
app.downloadAvailable = false;
|
|
|
|
app.downloadSize = 0;
|
|
|
|
app.installState = "installed";
|
|
|
|
app.readyToApplyDownload = false;
|
2013-09-05 09:14:54 +00:00
|
|
|
if (app.staged && app.staged.manifestHash) {
|
|
|
|
// If we're here then the manifest has changed but the package
|
|
|
|
// hasn't. Let's clear this, so we don't keep offering
|
|
|
|
// a bogus update to the user
|
|
|
|
app.manifestHash = app.staged.manifestHash;
|
|
|
|
app.etag = app.staged.etag || app.etag;
|
|
|
|
app.staged = {};
|
|
|
|
// Move the staged update manifest to a non staged one.
|
|
|
|
let dirPath = self._getAppDir(id).path;
|
|
|
|
|
|
|
|
// We don't really mind much if this fails.
|
|
|
|
OS.File.move(OS.Path.join(dirPath, "staged-update.webapp"),
|
|
|
|
OS.Path.join(dirPath, "update.webapp"));
|
|
|
|
}
|
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
// Save the updated registry, and cleanup the tmp directory.
|
2013-10-17 12:47:58 +00:00
|
|
|
self._saveApps(function() {
|
|
|
|
self.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: aApp.manifestURL
|
|
|
|
});
|
|
|
|
self.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
manifestURL: aApp.manifestURL,
|
|
|
|
eventType: ["downloadsuccess", "downloadapplied"]
|
|
|
|
});
|
|
|
|
});
|
2013-01-18 17:54:06 +00:00
|
|
|
let file = FileUtils.getFile("TmpD", ["webapps", id], false);
|
|
|
|
if (file && file.exists()) {
|
|
|
|
file.remove(true);
|
|
|
|
}
|
|
|
|
return;
|
2012-12-21 18:28:58 +00:00
|
|
|
}
|
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
let certdb;
|
2012-12-14 04:35:58 +00:00
|
|
|
try {
|
2013-01-18 17:54:06 +00:00
|
|
|
certdb = Cc["@mozilla.org/security/x509certdb;1"]
|
|
|
|
.getService(Ci.nsIX509CertDB);
|
|
|
|
} catch (e) {
|
2013-02-08 10:07:46 +00:00
|
|
|
// unrecoverable error, don't bug the user
|
|
|
|
app.downloadAvailable = false;
|
2013-01-18 17:54:06 +00:00
|
|
|
cleanup("CERTDB_ERROR");
|
|
|
|
return;
|
|
|
|
}
|
2012-12-14 04:35:58 +00:00
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
certdb.openSignedJARFileAsync(zipFile, function(aRv, aZipReader) {
|
|
|
|
let zipReader;
|
2012-12-31 08:32:14 +00:00
|
|
|
try {
|
2013-01-18 17:54:06 +00:00
|
|
|
let isSigned;
|
|
|
|
if (Components.isSuccessCode(aRv)) {
|
|
|
|
isSigned = true;
|
|
|
|
zipReader = aZipReader;
|
2013-02-19 08:39:53 +00:00
|
|
|
} else if (aRv == Cr.NS_ERROR_FILE_CORRUPTED) {
|
|
|
|
throw "APP_PACKAGE_CORRUPTED";
|
2013-01-18 17:54:06 +00:00
|
|
|
} else if (aRv != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED) {
|
|
|
|
throw "INVALID_SIGNATURE";
|
|
|
|
} else {
|
|
|
|
isSigned = false;
|
|
|
|
zipReader = Cc["@mozilla.org/libjar/zip-reader;1"]
|
|
|
|
.createInstance(Ci.nsIZipReader);
|
|
|
|
zipReader.open(zipFile);
|
|
|
|
}
|
|
|
|
|
|
|
|
// XXX Security: You CANNOT safely add a new app store for
|
|
|
|
// installing privileged apps just by modifying this pref and
|
|
|
|
// adding the signing cert for that store to the cert trust
|
|
|
|
// database. *Any* origin listed can install apps signed with
|
|
|
|
// *any* certificate trusted; we don't try to maintain a strong
|
|
|
|
// association between certificate with installOrign. The
|
|
|
|
// expectation here is that in production builds the pref will
|
|
|
|
// contain exactly one origin. However, in custom development
|
|
|
|
// builds it may contain more than one origin so we can test
|
|
|
|
// different stages (dev, staging, prod) of the same app store.
|
|
|
|
//
|
|
|
|
// Only allow signed apps to be installed from a whitelist of
|
|
|
|
// domains, and require all packages installed from any of the
|
|
|
|
// domains on the whitelist to be signed. This is a stopgap until
|
|
|
|
// we have a real story for handling multiple app stores signing
|
|
|
|
// apps.
|
|
|
|
let signedAppOriginsStr =
|
|
|
|
Services.prefs.getCharPref(
|
|
|
|
"dom.mozApps.signed_apps_installable_from");
|
2013-07-31 19:34:19 +00:00
|
|
|
// If it's a local install and it's signed then we assume
|
|
|
|
// the app origin is a valid signer.
|
|
|
|
let isSignedAppOrigin = (isSigned && isLocalFileInstall) ||
|
|
|
|
signedAppOriginsStr.split(",").
|
|
|
|
indexOf(aApp.installOrigin) > -1;
|
2013-01-18 17:54:06 +00:00
|
|
|
if (!isSigned && isSignedAppOrigin) {
|
|
|
|
// Packaged apps installed from these origins must be signed;
|
|
|
|
// if not, assume somebody stripped the signature.
|
|
|
|
throw "INVALID_SIGNATURE";
|
|
|
|
} else if (isSigned && !isSignedAppOrigin) {
|
|
|
|
// Other origins are *prohibited* from installing signed apps.
|
2013-02-19 08:39:53 +00:00
|
|
|
// One reason is that our app revocation mechanism requires
|
2013-01-18 17:54:06 +00:00
|
|
|
// strong cooperation from the host of the mini-manifest, which
|
|
|
|
// we assume to be under the control of the install origin,
|
|
|
|
// even if it has a different origin.
|
|
|
|
throw "INSTALL_FROM_DENIED";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!zipReader.hasEntry("manifest.webapp")) {
|
|
|
|
throw "MISSING_MANIFEST";
|
|
|
|
}
|
|
|
|
|
|
|
|
let istream = zipReader.getInputStream("manifest.webapp");
|
|
|
|
|
|
|
|
// 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";
|
|
|
|
|
|
|
|
let manifest = JSON.parse(converter.ConvertToUnicode(NetUtil.readInputStreamToString(istream,
|
|
|
|
istream.available()) || ""));
|
|
|
|
|
|
|
|
if (!AppsUtils.checkManifest(manifest, app)) {
|
|
|
|
throw "INVALID_MANIFEST";
|
|
|
|
}
|
|
|
|
|
2013-02-27 11:39:15 +00:00
|
|
|
// For app updates we don't forbid apps to rename themselves but
|
|
|
|
// we still retain the old name of the app. In the future we
|
|
|
|
// will use UI to allow updates to rename an app after we check
|
|
|
|
// with the user that the rename is ok.
|
|
|
|
if (aIsUpdate) {
|
|
|
|
// Call ensureSameAppName before compareManifests as `manifest`
|
|
|
|
// has been normalized to avoid app rename.
|
|
|
|
AppsUtils.ensureSameAppName(aManifest._manifest, manifest,
|
|
|
|
app);
|
|
|
|
}
|
2013-02-14 17:23:58 +00:00
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
if (!AppsUtils.compareManifests(manifest,
|
|
|
|
aManifest._manifest)) {
|
|
|
|
throw "MANIFEST_MISMATCH";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!AppsUtils.checkInstallAllowed(manifest, aApp.installOrigin)) {
|
|
|
|
throw "INSTALL_FROM_DENIED";
|
|
|
|
}
|
|
|
|
|
2013-07-31 19:34:19 +00:00
|
|
|
// Local file installs can be privileged even without the signature.
|
|
|
|
let maxStatus = isSigned || isLocalFileInstall
|
|
|
|
? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED
|
|
|
|
: Ci.nsIPrincipal.APP_STATUS_INSTALLED;
|
2013-01-18 17:54:06 +00:00
|
|
|
|
|
|
|
if (AppsUtils.getAppManifestStatus(manifest) > maxStatus) {
|
|
|
|
throw "INVALID_SECURITY_LEVEL";
|
|
|
|
}
|
|
|
|
app.appStatus = AppsUtils.getAppManifestStatus(manifest);
|
2013-01-25 02:24:17 +00:00
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
// Save the new Etag for the package.
|
2013-01-25 02:24:17 +00:00
|
|
|
if (aIsUpdate) {
|
|
|
|
if (!app.staged) {
|
|
|
|
app.staged = { };
|
|
|
|
}
|
2013-01-31 21:35:11 +00:00
|
|
|
try {
|
|
|
|
app.staged.packageEtag =
|
|
|
|
requestChannel.getResponseHeader("Etag");
|
|
|
|
} catch(e) { }
|
2013-01-25 02:24:17 +00:00
|
|
|
app.staged.packageHash = aHash;
|
|
|
|
app.staged.appStatus =
|
|
|
|
AppsUtils.getAppManifestStatus(manifest);
|
|
|
|
} else {
|
2013-01-31 21:35:11 +00:00
|
|
|
try {
|
|
|
|
app.packageEtag = requestChannel.getResponseHeader("Etag");
|
|
|
|
} catch(e) { }
|
2013-01-25 02:24:17 +00:00
|
|
|
app.packageHash = aHash;
|
|
|
|
app.appStatus = AppsUtils.getAppManifestStatus(manifest);
|
2013-01-18 17:54:06 +00:00
|
|
|
}
|
|
|
|
|
2013-05-30 17:34:27 +00:00
|
|
|
// Check if the app declares which origin it will use.
|
|
|
|
if (isSigned &&
|
|
|
|
app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED &&
|
|
|
|
manifest.origin !== undefined) {
|
|
|
|
|
|
|
|
let uri;
|
|
|
|
try {
|
|
|
|
uri = Services.io.newURI(manifest.origin, null, null);
|
|
|
|
} catch(e) {
|
|
|
|
throw "INVALID_ORIGIN";
|
|
|
|
}
|
|
|
|
|
|
|
|
if (uri.scheme != "app") {
|
|
|
|
throw "INVALID_ORIGIN";
|
|
|
|
}
|
|
|
|
|
2013-07-22 09:10:14 +00:00
|
|
|
if (aIsUpdate) {
|
|
|
|
// Changing the origin during an update is not allowed.
|
|
|
|
if (uri.prePath != app.origin) {
|
|
|
|
throw "INVALID_ORIGIN_CHANGE";
|
|
|
|
}
|
|
|
|
// Nothing else to do for an update... since the
|
|
|
|
// origin can't change we don't need to move the
|
|
|
|
// app nor can we have a duplicated origin
|
|
|
|
} else {
|
|
|
|
debug("Setting origin to " + uri.prePath +
|
|
|
|
" for " + app.manifestURL);
|
|
|
|
let newId = uri.prePath.substring(6); // "app://".length
|
|
|
|
|
|
|
|
if (newId in self.webapps) {
|
|
|
|
throw "DUPLICATE_ORIGIN";
|
|
|
|
}
|
|
|
|
app.origin = uri.prePath;
|
|
|
|
|
|
|
|
app.id = newId;
|
|
|
|
self.webapps[newId] = app;
|
|
|
|
delete self.webapps[id];
|
|
|
|
|
|
|
|
// Rename the directories where the files are installed.
|
|
|
|
[DIRECTORY_NAME, "TmpD"].forEach(function(aDir) {
|
|
|
|
let parent = FileUtils.getDir(aDir,
|
|
|
|
["webapps"], true, true);
|
|
|
|
let dir = FileUtils.getDir(aDir,
|
|
|
|
["webapps", id], true, true);
|
|
|
|
dir.moveTo(parent, newId);
|
|
|
|
});
|
|
|
|
|
|
|
|
// Signals that we need to swap the old id with the new app.
|
|
|
|
self.broadcastMessage("Webapps:RemoveApp", { id: id });
|
|
|
|
self.broadcastMessage("Webapps:AddApp", { id: newId,
|
|
|
|
app: app });
|
2013-05-30 17:34:27 +00:00
|
|
|
}
|
2013-07-22 09:10:14 +00:00
|
|
|
}
|
2013-05-30 17:34:27 +00:00
|
|
|
|
2013-07-22 09:10:14 +00:00
|
|
|
// Get ids.json if the file is signed
|
|
|
|
if (isSigned) {
|
|
|
|
let idsStream;
|
|
|
|
try {
|
|
|
|
idsStream = zipReader.getInputStream("META-INF/ids.json");
|
|
|
|
} catch (e) {
|
|
|
|
throw zipReader.hasEntry("META-INF/ids.json")
|
|
|
|
? e
|
|
|
|
: "MISSING_IDS_JSON";
|
|
|
|
}
|
|
|
|
let ids =
|
|
|
|
JSON.parse(
|
|
|
|
converter.ConvertToUnicode(
|
|
|
|
NetUtil.readInputStreamToString(
|
|
|
|
idsStream, idsStream.available()) || ""));
|
|
|
|
if ((!ids.id) || !Number.isInteger(ids.version) ||
|
|
|
|
(ids.version <= 0)) {
|
|
|
|
throw "INVALID_IDS_JSON";
|
2013-05-30 17:34:27 +00:00
|
|
|
}
|
2013-07-22 09:10:14 +00:00
|
|
|
let storeId = aApp.installOrigin + "#" + ids.id;
|
|
|
|
checkForStoreIdMatch(storeId, ids.version);
|
|
|
|
app.storeId = storeId;
|
|
|
|
app.storeVersion = ids.version;
|
2013-05-30 17:34:27 +00:00
|
|
|
}
|
|
|
|
|
2013-01-18 17:54:06 +00:00
|
|
|
if (aOnSuccess) {
|
2013-05-30 17:34:27 +00:00
|
|
|
aOnSuccess(app.id, manifest);
|
2013-01-18 17:54:06 +00:00
|
|
|
}
|
2012-12-31 08:32:14 +00:00
|
|
|
} catch (e) {
|
2013-01-18 17:54:06 +00:00
|
|
|
// Something bad happened when reading the package.
|
2013-02-19 08:39:53 +00:00
|
|
|
// Unrecoverable error, don't bug the user.
|
|
|
|
// Apps with installState 'pending' does not produce any
|
|
|
|
// notification, so we are safe with its current
|
2013-05-30 17:34:27 +00:00
|
|
|
// downloadAvailable state.
|
2013-04-05 23:04:26 +00:00
|
|
|
if (app.installState !== "pending") {
|
2013-02-19 08:39:53 +00:00
|
|
|
app.downloadAvailable = false;
|
|
|
|
}
|
2013-01-18 17:54:06 +00:00
|
|
|
if (typeof e == 'object') {
|
|
|
|
Cu.reportError("Error while reading package:" + e);
|
|
|
|
cleanup("INVALID_PACKAGE");
|
|
|
|
} else {
|
|
|
|
cleanup(e);
|
|
|
|
}
|
|
|
|
} finally {
|
|
|
|
AppDownloadManager.remove(aApp.manifestURL);
|
|
|
|
if (zipReader)
|
|
|
|
zipReader.close();
|
2012-12-14 04:35:58 +00:00
|
|
|
}
|
2013-01-18 17:54:06 +00:00
|
|
|
});
|
2012-12-14 04:35:58 +00:00
|
|
|
});
|
2012-12-14 19:29:25 +00:00
|
|
|
}
|
2012-07-11 15:38:33 +00:00
|
|
|
});
|
2012-12-14 19:29:25 +00:00
|
|
|
|
|
|
|
requestChannel.asyncOpen(listener, null);
|
2013-02-11 08:38:51 +00:00
|
|
|
|
|
|
|
// send a first progress event to correctly set the DOM object's properties
|
2013-10-17 12:47:58 +00:00
|
|
|
sendProgressEvent(0);
|
2012-11-15 09:35:37 +00:00
|
|
|
};
|
|
|
|
|
2013-03-25 20:03:58 +00:00
|
|
|
let checkDownloadSize = function (freeBytes) {
|
|
|
|
if (freeBytes) {
|
|
|
|
debug("Free storage: " + freeBytes + ". Download size: " +
|
|
|
|
aApp.downloadSize);
|
|
|
|
if (freeBytes <=
|
|
|
|
aApp.downloadSize + AppDownloadManager.MIN_REMAINING_FREESPACE) {
|
|
|
|
cleanup("INSUFFICIENT_STORAGE");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
download();
|
|
|
|
};
|
|
|
|
|
2013-07-29 19:53:14 +00:00
|
|
|
let navigator = Services.wm.getMostRecentWindow("navigator:browser")
|
|
|
|
.navigator;
|
|
|
|
let deviceStorage = null;
|
|
|
|
|
|
|
|
if (navigator.getDeviceStorage) {
|
|
|
|
deviceStorage = navigator.getDeviceStorage("apps");
|
|
|
|
}
|
|
|
|
|
2013-03-25 20:03:58 +00:00
|
|
|
if (deviceStorage) {
|
|
|
|
let req = deviceStorage.freeSpace();
|
|
|
|
req.onsuccess = req.onerror = function statResult(e) {
|
|
|
|
// Even if we could not retrieve the device storage free space, we try
|
|
|
|
// to download the package.
|
|
|
|
if (!e.target.result) {
|
|
|
|
download();
|
2012-11-15 09:35:37 +00:00
|
|
|
return;
|
|
|
|
}
|
2013-03-25 20:03:58 +00:00
|
|
|
|
|
|
|
let freeBytes = e.target.result;
|
|
|
|
checkDownloadSize(freeBytes);
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// deviceStorage isn't available, so use FileUtils to find the size of available storage.
|
|
|
|
let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true);
|
|
|
|
try {
|
|
|
|
checkDownloadSize(dir.diskSpaceAvailable);
|
|
|
|
} catch(ex) {
|
|
|
|
// If disk space information isn't available, we'll end up here.
|
|
|
|
// We should either proceed anyway, otherwise devices that support neither
|
|
|
|
// deviceStorage nor diskSpaceAvailable will never be able to install packaged apps.
|
|
|
|
download();
|
2012-11-15 09:35:37 +00:00
|
|
|
}
|
|
|
|
}
|
2012-07-11 15:38:33 +00:00
|
|
|
},
|
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
doUninstall: function(aData, aMm) {
|
|
|
|
this.uninstall(aData.manifestURL,
|
|
|
|
function onsuccess() {
|
|
|
|
aMm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData);
|
|
|
|
},
|
|
|
|
function onfailure() {
|
|
|
|
// Fall-through, fails to uninstall the desired app because:
|
|
|
|
// - we cannot find the app to be uninstalled.
|
|
|
|
// - the app to be uninstalled is not removable.
|
|
|
|
aMm.sendAsyncMessage("Webapps:Uninstall:Return:KO", aData);
|
2013-01-30 03:54:05 +00:00
|
|
|
}
|
2013-05-06 13:51:53 +00:00
|
|
|
);
|
|
|
|
},
|
2012-08-29 21:20:03 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
uninstall: function(aManifestURL, aOnSuccess, aOnFailure) {
|
|
|
|
debug("uninstall " + aManifestURL);
|
2012-11-03 03:28:32 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
let app = this.getAppByManifestURL(aManifestURL);
|
|
|
|
if (!app) {
|
|
|
|
aOnFailure("NO_SUCH_APP");
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let id = app.id;
|
2012-09-28 16:24:45 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
if (!app.removable) {
|
|
|
|
debug("Error: cannot uninstall a non-removable app.");
|
|
|
|
aOnFailure("NON_REMOVABLE_APP");
|
|
|
|
return;
|
|
|
|
}
|
2012-09-28 16:24:45 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
// Check if we are downloading something for this app, and cancel the
|
|
|
|
// download if needed.
|
|
|
|
this.cancelDownload(app.manifestURL);
|
2012-04-28 07:10:08 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
// Clean up the deprecated manifest cache if needed.
|
|
|
|
if (id in this._manifestCache) {
|
|
|
|
delete this._manifestCache[id];
|
|
|
|
}
|
2012-07-20 15:41:30 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
// Clear private data first.
|
|
|
|
this._clearPrivateData(app.localId, false);
|
2012-07-20 15:41:30 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
// Then notify observers.
|
|
|
|
// We have to clone the app object as nsIDOMApplication objects are
|
|
|
|
// stringified as an empty object. (see bug 830376)
|
|
|
|
let appClone = AppsUtils.cloneAppObject(app);
|
|
|
|
Services.obs.notifyObservers(null, "webapps-uninstall", JSON.stringify(appClone));
|
2012-07-20 15:41:30 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
if (supportSystemMessages()) {
|
|
|
|
this._readManifests([{ id: id }], (function unregisterManifest(aResult) {
|
|
|
|
this._unregisterActivities(aResult[0].manifest, app);
|
2012-07-20 15:41:30 +00:00
|
|
|
}).bind(this));
|
|
|
|
}
|
2012-09-28 16:24:45 +00:00
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
let dir = this._getAppDir(id);
|
|
|
|
try {
|
|
|
|
dir.remove(true);
|
|
|
|
} catch (e) {}
|
|
|
|
|
|
|
|
delete this.webapps[id];
|
|
|
|
|
|
|
|
this._saveApps((function() {
|
|
|
|
this.broadcastMessage("Webapps:Uninstall:Broadcast:Return:OK", appClone);
|
|
|
|
// Catch exception on callback call to ensure notifying observers after
|
|
|
|
try {
|
2013-07-19 14:14:22 +00:00
|
|
|
if (aOnSuccess) {
|
|
|
|
aOnSuccess();
|
|
|
|
}
|
2013-07-18 14:20:55 +00:00
|
|
|
} catch(ex) {
|
2013-05-06 13:51:53 +00:00
|
|
|
Cu.reportError("DOMApplicationRegistry: Exception on app uninstall: " +
|
|
|
|
ex + "\n" + ex.stack);
|
|
|
|
}
|
|
|
|
this.broadcastMessage("Webapps:RemoveApp", { id: id });
|
|
|
|
}).bind(this));
|
2011-11-28 20:13:26 +00:00
|
|
|
},
|
2011-12-08 13:32:54 +00:00
|
|
|
|
2012-09-18 17:34:55 +00:00
|
|
|
getSelf: function(aData, aMm) {
|
2011-11-28 20:13:26 +00:00
|
|
|
aData.apps = [];
|
2012-09-25 15:04:24 +00:00
|
|
|
|
|
|
|
if (aData.appId == Ci.nsIScriptSecurityManager.NO_APP_ID ||
|
|
|
|
aData.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) {
|
|
|
|
aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
let tmp = [];
|
2012-03-06 19:50:58 +00:00
|
|
|
|
2012-09-25 15:04:24 +00:00
|
|
|
for (let id in this.webapps) {
|
|
|
|
if (this.webapps[id].origin == aData.origin &&
|
|
|
|
this.webapps[id].localId == aData.appId &&
|
2013-08-02 00:00:22 +00:00
|
|
|
this._isLaunchable(this.webapps[id])) {
|
2012-09-25 15:04:24 +00:00
|
|
|
let app = AppsUtils.cloneAppObject(this.webapps[id]);
|
|
|
|
aData.apps.push(app);
|
|
|
|
tmp.push({ id: id });
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!aData.apps.length) {
|
|
|
|
aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
|
|
|
|
return;
|
2011-11-28 20:13:26 +00:00
|
|
|
}
|
|
|
|
|
2012-03-06 19:50:58 +00:00
|
|
|
this._readManifests(tmp, (function(aResult) {
|
|
|
|
for (let i = 0; i < aResult.length; i++)
|
|
|
|
aData.apps[i].manifest = aResult[i].manifest;
|
2012-09-18 17:34:55 +00:00
|
|
|
aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData);
|
2012-03-06 19:50:58 +00:00
|
|
|
}).bind(this));
|
|
|
|
},
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2012-10-02 20:38:51 +00:00
|
|
|
checkInstalled: function(aData, aMm) {
|
|
|
|
aData.app = null;
|
|
|
|
let tmp = [];
|
2012-09-25 15:04:24 +00:00
|
|
|
|
|
|
|
for (let appId in this.webapps) {
|
2013-09-13 12:07:04 +00:00
|
|
|
if (this.webapps[appId].manifestURL == aData.manifestURL &&
|
|
|
|
this._isLaunchable(this.webapps[appId])) {
|
2012-10-02 20:38:51 +00:00
|
|
|
aData.app = AppsUtils.cloneAppObject(this.webapps[appId]);
|
|
|
|
tmp.push({ id: appId });
|
2012-09-25 15:04:24 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-10-02 20:38:51 +00:00
|
|
|
this._readManifests(tmp, (function(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));
|
2012-09-25 15:04:24 +00:00
|
|
|
},
|
|
|
|
|
2012-09-18 17:34:55 +00:00
|
|
|
getInstalled: function(aData, aMm) {
|
2012-03-06 19:50:58 +00:00
|
|
|
aData.apps = [];
|
|
|
|
let tmp = [];
|
|
|
|
|
2012-06-29 20:46:21 +00:00
|
|
|
for (let id in this.webapps) {
|
|
|
|
if (this.webapps[id].installOrigin == aData.origin &&
|
2013-08-02 00:00:22 +00:00
|
|
|
this._isLaunchable(this.webapps[id])) {
|
2012-08-28 02:43:57 +00:00
|
|
|
aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
|
2012-03-06 19:50:58 +00:00
|
|
|
tmp.push({ id: id });
|
2011-11-28 20:13:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
this._readManifests(tmp, (function(aResult) {
|
|
|
|
for (let i = 0; i < aResult.length; i++)
|
|
|
|
aData.apps[i].manifest = aResult[i].manifest;
|
2012-09-18 17:34:55 +00:00
|
|
|
aMm.sendAsyncMessage("Webapps:GetInstalled:Return:OK", aData);
|
2011-12-05 23:26:18 +00:00
|
|
|
}).bind(this));
|
2011-11-28 20:13:26 +00:00
|
|
|
},
|
|
|
|
|
2012-09-18 17:34:55 +00:00
|
|
|
getNotInstalled: function(aData, aMm) {
|
2012-06-29 20:46:21 +00:00
|
|
|
aData.apps = [];
|
|
|
|
let tmp = [];
|
|
|
|
|
|
|
|
for (let id in this.webapps) {
|
2013-08-02 00:00:22 +00:00
|
|
|
if (!this._isLaunchable(this.webapps[id])) {
|
2012-08-28 02:43:57 +00:00
|
|
|
aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id]));
|
2012-06-29 20:46:21 +00:00
|
|
|
tmp.push({ id: id });
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
this._readManifests(tmp, (function(aResult) {
|
|
|
|
for (let i = 0; i < aResult.length; i++)
|
|
|
|
aData.apps[i].manifest = aResult[i].manifest;
|
2012-09-18 17:34:55 +00:00
|
|
|
aMm.sendAsyncMessage("Webapps:GetNotInstalled:Return:OK", aData);
|
2012-06-29 20:46:21 +00:00
|
|
|
}).bind(this));
|
|
|
|
},
|
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
doGetAll: function(aData, aMm) {
|
|
|
|
this.getAll(function (apps) {
|
|
|
|
aData.apps = apps;
|
|
|
|
aMm.sendAsyncMessage("Webapps:GetAll:Return:OK", aData);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
getAll: function(aCallback) {
|
|
|
|
debug("getAll");
|
|
|
|
let apps = [];
|
2011-12-05 23:26:18 +00:00
|
|
|
let tmp = [];
|
2011-11-28 20:13:26 +00:00
|
|
|
|
2012-04-28 07:10:08 +00:00
|
|
|
for (let id in this.webapps) {
|
2012-08-28 02:43:57 +00:00
|
|
|
let app = AppsUtils.cloneAppObject(this.webapps[id]);
|
2013-08-02 00:00:22 +00:00
|
|
|
if (!this._isLaunchable(app))
|
2012-06-29 20:46:21 +00:00
|
|
|
continue;
|
|
|
|
|
2013-05-06 13:51:53 +00:00
|
|
|
apps.push(app);
|
2011-12-05 23:26:18 +00:00
|
|
|
tmp.push({ id: id });
|
2011-11-28 20:13:26 +00:00
|
|
|
}
|
|
|
|
|
2011-12-05 23:26:18 +00:00
|
|
|
this._readManifests(tmp, (function(aResult) {
|
|
|
|
for (let i = 0; i < aResult.length; i++)
|
2013-05-06 13:51:53 +00:00
|
|
|
apps[i].manifest = aResult[i].manifest;
|
|
|
|
aCallback(apps);
|
2011-12-05 23:26:18 +00:00
|
|
|
}).bind(this));
|
2011-11-28 20:13:26 +00:00
|
|
|
},
|
|
|
|
|
2013-08-28 12:10:55 +00:00
|
|
|
getManifestFor: function(aManifestURL, aCallback) {
|
2011-12-05 23:26:18 +00:00
|
|
|
if (!aCallback)
|
|
|
|
return;
|
|
|
|
|
2013-08-28 12:10:55 +00:00
|
|
|
let id = this._appIdForManifestURL(aManifestURL);
|
2012-11-30 16:57:45 +00:00
|
|
|
let app = this.webapps[id];
|
|
|
|
if (!id || (app.installState == "pending" && !app.retryingDownload)) {
|
2011-12-05 23:26:18 +00:00
|
|
|
aCallback(null);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
this._readManifests([{ id: id }], function(aResult) {
|
|
|
|
aCallback(aResult[0].manifest);
|
|
|
|
});
|
2011-12-08 13:32:54 +00:00
|
|
|
},
|
|
|
|
|
2012-05-16 10:40:47 +00:00
|
|
|
getAppByManifestURL: function(aManifestURL) {
|
2012-08-28 02:43:57 +00:00
|
|
|
return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL);
|
2012-05-16 10:40:47 +00:00
|
|
|
},
|
2012-07-09 10:25:41 +00:00
|
|
|
|
2012-10-19 10:43:17 +00:00
|
|
|
getCSPByLocalId: function(aLocalId) {
|
|
|
|
debug("getCSPByLocalId:" + aLocalId);
|
|
|
|
return AppsUtils.getCSPByLocalId(this.webapps, aLocalId);
|
|
|
|
},
|
|
|
|
|
2013-01-25 09:57:14 +00:00
|
|
|
getAppLocalIdByStoreId: function(aStoreId) {
|
|
|
|
debug("getAppLocalIdByStoreId:" + aStoreId);
|
|
|
|
return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId);
|
|
|
|
},
|
|
|
|
|
2012-08-08 16:41:47 +00:00
|
|
|
getAppByLocalId: function(aLocalId) {
|
2012-08-28 02:43:57 +00:00
|
|
|
return AppsUtils.getAppByLocalId(this.webapps, aLocalId);
|
2012-08-08 16:41:47 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getManifestURLByLocalId: function(aLocalId) {
|
2012-08-28 02:43:57 +00:00
|
|
|
return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId);
|
2012-08-08 16:41:47 +00:00
|
|
|
},
|
|
|
|
|
2012-07-09 10:25:41 +00:00
|
|
|
getAppLocalIdByManifestURL: function(aManifestURL) {
|
2012-08-28 02:43:57 +00:00
|
|
|
return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL);
|
2012-07-09 10:25:41 +00:00
|
|
|
},
|
|
|
|
|
2012-12-22 13:56:21 +00:00
|
|
|
getCoreAppsBasePath: function() {
|
2013-03-15 14:18:58 +00:00
|
|
|
return AppsUtils.getCoreAppsBasePath();
|
2012-12-22 13:56:21 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
getWebAppsBasePath: function getWebAppsBasePath() {
|
|
|
|
return FileUtils.getDir(DIRECTORY_NAME, ["webapps"], false).path;
|
|
|
|
},
|
|
|
|
|
2013-08-02 00:00:22 +00:00
|
|
|
_isLaunchable: function(aApp) {
|
2012-06-29 20:46:21 +00:00
|
|
|
if (this.allAppsLaunchable)
|
|
|
|
return true;
|
|
|
|
|
2013-08-02 00:00:22 +00:00
|
|
|
return WebappOSUtils.isLaunchable(aApp);
|
2012-09-26 22:03:25 +00:00
|
|
|
},
|
|
|
|
|
2013-05-20 17:20:19 +00:00
|
|
|
_notifyCategoryAndObservers: function(subject, topic, data, msg) {
|
2012-09-26 22:03:25 +00:00
|
|
|
const serviceMarker = "service,";
|
|
|
|
|
|
|
|
// First create observers from the category manager.
|
|
|
|
let cm =
|
|
|
|
Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager);
|
|
|
|
let enumerator = cm.enumerateCategory(topic);
|
|
|
|
|
|
|
|
let observers = [];
|
|
|
|
|
|
|
|
while (enumerator.hasMoreElements()) {
|
|
|
|
let entry =
|
|
|
|
enumerator.getNext().QueryInterface(Ci.nsISupportsCString).data;
|
|
|
|
let contractID = cm.getCategoryEntry(topic, entry);
|
|
|
|
|
|
|
|
let factoryFunction;
|
|
|
|
if (contractID.substring(0, serviceMarker.length) == serviceMarker) {
|
|
|
|
contractID = contractID.substring(serviceMarker.length);
|
|
|
|
factoryFunction = "getService";
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
factoryFunction = "createInstance";
|
|
|
|
}
|
|
|
|
|
|
|
|
try {
|
|
|
|
let handler = Cc[contractID][factoryFunction]();
|
|
|
|
if (handler) {
|
|
|
|
let observer = handler.QueryInterface(Ci.nsIObserver);
|
|
|
|
observers.push(observer);
|
|
|
|
}
|
|
|
|
} catch(e) { }
|
|
|
|
}
|
|
|
|
|
|
|
|
// Next enumerate the registered observers.
|
|
|
|
enumerator = Services.obs.enumerateObservers(topic);
|
|
|
|
while (enumerator.hasMoreElements()) {
|
2012-09-28 16:24:45 +00:00
|
|
|
try {
|
|
|
|
let observer = enumerator.getNext().QueryInterface(Ci.nsIObserver);
|
|
|
|
if (observers.indexOf(observer) == -1) {
|
|
|
|
observers.push(observer);
|
|
|
|
}
|
|
|
|
} catch (e) { }
|
2012-09-26 22:03:25 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
observers.forEach(function (observer) {
|
|
|
|
try {
|
|
|
|
observer.observe(subject, topic, data);
|
|
|
|
} catch(e) { }
|
|
|
|
});
|
2013-05-20 17:20:19 +00:00
|
|
|
// Send back an answer to the child.
|
2013-06-07 16:52:58 +00:00
|
|
|
if (msg) {
|
|
|
|
ppmm.broadcastAsyncMessage("Webapps:ClearBrowserData:Return", msg);
|
|
|
|
}
|
2012-09-26 22:03:25 +00:00
|
|
|
},
|
|
|
|
|
|
|
|
registerBrowserElementParentForApp: function(bep, appId) {
|
|
|
|
let mm = bep._mm;
|
|
|
|
|
|
|
|
// Make a listener function that holds on to this appId.
|
|
|
|
let listener = this.receiveAppMessage.bind(this, appId);
|
|
|
|
|
|
|
|
this.frameMessages.forEach(function(msgName) {
|
|
|
|
mm.addMessageListener(msgName, listener);
|
|
|
|
});
|
|
|
|
},
|
|
|
|
|
|
|
|
receiveAppMessage: function(appId, message) {
|
|
|
|
switch (message.name) {
|
|
|
|
case "Webapps:ClearBrowserData":
|
2013-05-20 17:20:19 +00:00
|
|
|
this._clearPrivateData(appId, true, message.data);
|
2012-09-26 22:03:25 +00:00
|
|
|
break;
|
|
|
|
}
|
2012-09-28 16:24:45 +00:00
|
|
|
},
|
|
|
|
|
2013-05-20 17:20:19 +00:00
|
|
|
_clearPrivateData: function(appId, browserOnly, msg) {
|
2012-09-28 16:24:45 +00:00
|
|
|
let subject = {
|
|
|
|
appId: appId,
|
|
|
|
browserOnly: browserOnly,
|
|
|
|
QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams])
|
|
|
|
};
|
2013-05-20 17:20:19 +00:00
|
|
|
this._notifyCategoryAndObservers(subject, "webapps-clear-data", null, msg);
|
2012-04-28 07:10:08 +00:00
|
|
|
}
|
2011-11-28 20:13:26 +00:00
|
|
|
};
|
|
|
|
|
2012-06-11 18:41:46 +00:00
|
|
|
/**
|
|
|
|
* Appcache download observer
|
|
|
|
*/
|
2012-07-03 00:16:55 +00:00
|
|
|
let AppcacheObserver = function(aApp) {
|
2012-11-27 04:03:43 +00:00
|
|
|
debug("Creating AppcacheObserver for " + aApp.origin +
|
|
|
|
" - " + aApp.installState);
|
2012-06-11 18:41:46 +00:00
|
|
|
this.app = aApp;
|
2012-09-27 01:01:20 +00:00
|
|
|
this.startStatus = aApp.installState;
|
2012-12-13 17:54:49 +00:00
|
|
|
this.lastProgressTime = 0;
|
2013-10-17 12:47:58 +00:00
|
|
|
// Send a first progress event to correctly set the DOM object's properties.
|
2013-02-11 08:38:51 +00:00
|
|
|
this._sendProgressEvent();
|
2012-06-11 18:41:46 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
AppcacheObserver.prototype = {
|
|
|
|
// nsIOfflineCacheUpdateObserver implementation
|
2013-02-11 08:38:51 +00:00
|
|
|
_sendProgressEvent: function() {
|
|
|
|
let app = this.app;
|
2013-10-17 12:47:58 +00:00
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: "progress",
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
2013-02-11 08:38:51 +00:00
|
|
|
},
|
|
|
|
|
2012-06-11 18:41:46 +00:00
|
|
|
updateStateChanged: function appObs_Update(aUpdate, aState) {
|
|
|
|
let mustSave = false;
|
|
|
|
let app = this.app;
|
|
|
|
|
2012-11-27 04:03:43 +00:00
|
|
|
debug("Offline cache state change for " + app.origin + " : " + aState);
|
|
|
|
|
2013-02-11 08:38:51 +00:00
|
|
|
var self = this;
|
2013-01-11 10:58:33 +00:00
|
|
|
let setStatus = function appObs_setStatus(aStatus, aProgress) {
|
2013-02-11 08:38:51 +00:00
|
|
|
debug("Offlinecache setStatus to " + aStatus + " with progress " +
|
2013-10-17 12:47:58 +00:00
|
|
|
aProgress + " for " + app.origin);
|
2012-09-27 01:01:20 +00:00
|
|
|
mustSave = (app.installState != aStatus);
|
2013-10-17 12:47:58 +00:00
|
|
|
|
2012-09-27 01:01:20 +00:00
|
|
|
app.installState = aStatus;
|
2013-01-11 10:58:33 +00:00
|
|
|
app.progress = aProgress;
|
2013-10-17 12:47:58 +00:00
|
|
|
if (aStatus != "installed") {
|
|
|
|
self._sendProgressEvent();
|
|
|
|
return;
|
2012-11-29 14:19:37 +00:00
|
|
|
}
|
2013-10-17 12:47:58 +00:00
|
|
|
|
|
|
|
app.updateTime = Date.now();
|
|
|
|
app.downloading = false;
|
|
|
|
app.downloadAvailable = false;
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
eventType: ["downloadsuccess", "downloadapplied"],
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
2012-09-27 01:01:20 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
let setError = function appObs_setError(aError) {
|
2012-11-27 04:03:43 +00:00
|
|
|
debug("Offlinecache setError to " + aError);
|
2012-10-04 18:18:39 +00:00
|
|
|
app.downloading = false;
|
2013-10-17 12:47:58 +00:00
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", {
|
|
|
|
app: app,
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
|
|
|
DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", {
|
|
|
|
error: aError,
|
|
|
|
eventType: "downloaderror",
|
|
|
|
manifestURL: app.manifestURL
|
|
|
|
});
|
2012-10-04 18:18:39 +00:00
|
|
|
mustSave = true;
|
2012-06-11 18:41:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch (aState) {
|
|
|
|
case Ci.nsIOfflineCacheUpdateObserver.STATE_ERROR:
|
|
|
|
aUpdate.removeObserver(this);
|
2013-01-25 02:24:17 +00:00
|
|
|
AppDownloadManager.remove(app.manifestURL);
|
2012-09-27 01:01:20 +00:00
|
|
|
setError("APP_CACHE_DOWNLOAD_ERROR");
|
2012-06-11 18:41:46 +00:00
|
|
|
break;
|
|
|
|
case Ci.nsIOfflineCacheUpdateObserver.STATE_NOUPDATE:
|
|
|
|
case Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED:
|
|
|
|
aUpdate.removeObserver(this);
|
2013-01-25 02:24:17 +00:00
|
|
|
AppDownloadManager.remove(app.manifestURL);
|
2013-01-11 10:58:33 +00:00
|
|
|
setStatus("installed", aUpdate.byteProgress);
|
2012-06-11 18:41:46 +00:00
|
|
|
break;
|
|
|
|
case Ci.nsIOfflineCacheUpdateObserver.STATE_DOWNLOADING:
|
2013-01-11 10:58:33 +00:00
|
|
|
setStatus(this.startStatus, aUpdate.byteProgress);
|
2012-06-11 18:41:46 +00:00
|
|
|
break;
|
2013-10-17 12:47:58 +00:00
|
|
|
case Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMSTARTED:
|
2012-12-13 17:54:49 +00:00
|
|
|
case Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMPROGRESS:
|
|
|
|
let now = Date.now();
|
|
|
|
if (now - this.lastProgressTime > MIN_PROGRESS_EVENT_DELAY) {
|
2013-01-11 10:58:33 +00:00
|
|
|
setStatus(this.startStatus, aUpdate.byteProgress);
|
2012-12-13 17:54:49 +00:00
|
|
|
this.lastProgressTime = now;
|
|
|
|
}
|
|
|
|
break;
|
2012-06-11 18:41:46 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// Status changed, update the stored version.
|
|
|
|
if (mustSave) {
|
|
|
|
DOMApplicationRegistry._saveApps();
|
|
|
|
}
|
|
|
|
},
|
|
|
|
|
|
|
|
applicationCacheAvailable: function appObs_CacheAvail(aApplicationCache) {
|
|
|
|
// Nothing to do.
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2011-11-28 20:13:26 +00:00
|
|
|
DOMApplicationRegistry.init();
|