1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/dom/apps/src/Webapps.jsm Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,4101 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this file, 1.6 + * You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +"use strict"; 1.9 + 1.10 +const Cu = Components.utils; 1.11 +const Cc = Components.classes; 1.12 +const Ci = Components.interfaces; 1.13 +const Cr = Components.results; 1.14 + 1.15 +// Possible errors thrown by the signature verifier. 1.16 +const SEC_ERROR_BASE = Ci.nsINSSErrorsService.NSS_SEC_ERROR_BASE; 1.17 +const SEC_ERROR_EXPIRED_CERTIFICATE = (SEC_ERROR_BASE + 11); 1.18 + 1.19 +// We need this to decide if we should accept or not files signed with expired 1.20 +// certificates. 1.21 +function buildIDToTime() { 1.22 + let platformBuildID = 1.23 + Cc["@mozilla.org/xre/app-info;1"] 1.24 + .getService(Ci.nsIXULAppInfo).platformBuildID; 1.25 + let platformBuildIDDate = new Date(); 1.26 + platformBuildIDDate.setUTCFullYear(platformBuildID.substr(0,4), 1.27 + platformBuildID.substr(4,2) - 1, 1.28 + platformBuildID.substr(6,2)); 1.29 + platformBuildIDDate.setUTCHours(platformBuildID.substr(8,2), 1.30 + platformBuildID.substr(10,2), 1.31 + platformBuildID.substr(12,2)); 1.32 + return platformBuildIDDate.getTime(); 1.33 +} 1.34 + 1.35 +const PLATFORM_BUILD_ID_TIME = buildIDToTime(); 1.36 + 1.37 +this.EXPORTED_SYMBOLS = ["DOMApplicationRegistry"]; 1.38 + 1.39 +Cu.import("resource://gre/modules/XPCOMUtils.jsm"); 1.40 +Cu.import("resource://gre/modules/Services.jsm"); 1.41 +Cu.import("resource://gre/modules/FileUtils.jsm"); 1.42 +Cu.import('resource://gre/modules/ActivitiesService.jsm'); 1.43 +Cu.import("resource://gre/modules/AppsUtils.jsm"); 1.44 +Cu.import("resource://gre/modules/AppDownloadManager.jsm"); 1.45 +Cu.import("resource://gre/modules/osfile.jsm"); 1.46 +Cu.import("resource://gre/modules/Task.jsm"); 1.47 +Cu.import("resource://gre/modules/Promise.jsm"); 1.48 + 1.49 +XPCOMUtils.defineLazyModuleGetter(this, "TrustedRootCertificate", 1.50 + "resource://gre/modules/StoreTrustAnchor.jsm"); 1.51 + 1.52 +XPCOMUtils.defineLazyModuleGetter(this, "PermissionsInstaller", 1.53 + "resource://gre/modules/PermissionsInstaller.jsm"); 1.54 + 1.55 +XPCOMUtils.defineLazyModuleGetter(this, "OfflineCacheInstaller", 1.56 + "resource://gre/modules/OfflineCacheInstaller.jsm"); 1.57 + 1.58 +XPCOMUtils.defineLazyModuleGetter(this, "SystemMessagePermissionsChecker", 1.59 + "resource://gre/modules/SystemMessagePermissionsChecker.jsm"); 1.60 + 1.61 +XPCOMUtils.defineLazyModuleGetter(this, "WebappOSUtils", 1.62 + "resource://gre/modules/WebappOSUtils.jsm"); 1.63 + 1.64 +XPCOMUtils.defineLazyModuleGetter(this, "NetUtil", 1.65 + "resource://gre/modules/NetUtil.jsm"); 1.66 + 1.67 +XPCOMUtils.defineLazyModuleGetter(this, "ScriptPreloader", 1.68 + "resource://gre/modules/ScriptPreloader.jsm"); 1.69 + 1.70 +#ifdef MOZ_WIDGET_GONK 1.71 +XPCOMUtils.defineLazyGetter(this, "libcutils", function() { 1.72 + Cu.import("resource://gre/modules/systemlibs.js"); 1.73 + return libcutils; 1.74 +}); 1.75 +#endif 1.76 + 1.77 +function debug(aMsg) { 1.78 +#ifdef DEBUG 1.79 + dump("-*- Webapps.jsm : " + aMsg + "\n"); 1.80 +#endif 1.81 +} 1.82 + 1.83 +function getNSPRErrorCode(err) { 1.84 + return -1 * ((err) & 0xffff); 1.85 +} 1.86 + 1.87 +function supportUseCurrentProfile() { 1.88 + return Services.prefs.getBoolPref("dom.webapps.useCurrentProfile"); 1.89 +} 1.90 + 1.91 +function supportSystemMessages() { 1.92 + return Services.prefs.getBoolPref("dom.sysmsg.enabled"); 1.93 +} 1.94 + 1.95 +// Minimum delay between two progress events while downloading, in ms. 1.96 +const MIN_PROGRESS_EVENT_DELAY = 1500; 1.97 + 1.98 +const WEBAPP_RUNTIME = Services.appinfo.ID == "webapprt@mozilla.org"; 1.99 + 1.100 +const chromeWindowType = WEBAPP_RUNTIME ? "webapprt:webapp" : "navigator:browser"; 1.101 + 1.102 +XPCOMUtils.defineLazyServiceGetter(this, "ppmm", 1.103 + "@mozilla.org/parentprocessmessagemanager;1", 1.104 + "nsIMessageBroadcaster"); 1.105 + 1.106 +XPCOMUtils.defineLazyServiceGetter(this, "cpmm", 1.107 + "@mozilla.org/childprocessmessagemanager;1", 1.108 + "nsIMessageSender"); 1.109 + 1.110 +XPCOMUtils.defineLazyGetter(this, "interAppCommService", function() { 1.111 + return Cc["@mozilla.org/inter-app-communication-service;1"] 1.112 + .getService(Ci.nsIInterAppCommService); 1.113 +}); 1.114 + 1.115 +XPCOMUtils.defineLazyServiceGetter(this, "dataStoreService", 1.116 + "@mozilla.org/datastore-service;1", 1.117 + "nsIDataStoreService"); 1.118 + 1.119 +XPCOMUtils.defineLazyGetter(this, "msgmgr", function() { 1.120 + return Cc["@mozilla.org/system-message-internal;1"] 1.121 + .getService(Ci.nsISystemMessagesInternal); 1.122 +}); 1.123 + 1.124 +XPCOMUtils.defineLazyGetter(this, "updateSvc", function() { 1.125 + return Cc["@mozilla.org/offlinecacheupdate-service;1"] 1.126 + .getService(Ci.nsIOfflineCacheUpdateService); 1.127 +}); 1.128 + 1.129 +#ifdef MOZ_WIDGET_GONK 1.130 + const DIRECTORY_NAME = "webappsDir"; 1.131 +#elifdef ANDROID 1.132 + const DIRECTORY_NAME = "webappsDir"; 1.133 +#else 1.134 + // If we're executing in the context of the webapp runtime, the data files 1.135 + // are in a different directory (currently the Firefox profile that installed 1.136 + // the webapp); otherwise, they're in the current profile. 1.137 + const DIRECTORY_NAME = WEBAPP_RUNTIME ? "WebappRegD" : "ProfD"; 1.138 +#endif 1.139 + 1.140 +// We'll use this to identify privileged apps that have been preinstalled 1.141 +// For those apps we'll set 1.142 +// STORE_ID_PENDING_PREFIX + installOrigin 1.143 +// as the storeID. This ensures it's unique and can't be set from a legit 1.144 +// store even by error. 1.145 +const STORE_ID_PENDING_PREFIX = "#unknownID#"; 1.146 + 1.147 +this.DOMApplicationRegistry = { 1.148 + // Path to the webapps.json file where we store the registry data. 1.149 + appsFile: null, 1.150 + webapps: { }, 1.151 + children: [ ], 1.152 + allAppsLaunchable: false, 1.153 + _updateHandlers: [ ], 1.154 + 1.155 + init: function() { 1.156 + this.messages = ["Webapps:Install", "Webapps:Uninstall", 1.157 + "Webapps:GetSelf", "Webapps:CheckInstalled", 1.158 + "Webapps:GetInstalled", "Webapps:GetNotInstalled", 1.159 + "Webapps:Launch", "Webapps:GetAll", 1.160 + "Webapps:InstallPackage", 1.161 + "Webapps:GetList", "Webapps:RegisterForMessages", 1.162 + "Webapps:UnregisterForMessages", 1.163 + "Webapps:CancelDownload", "Webapps:CheckForUpdate", 1.164 + "Webapps:Download", "Webapps:ApplyDownload", 1.165 + "Webapps:Install:Return:Ack", "Webapps:AddReceipt", 1.166 + "Webapps:RemoveReceipt", "Webapps:ReplaceReceipt", 1.167 + "child-process-shutdown"]; 1.168 + 1.169 + this.frameMessages = ["Webapps:ClearBrowserData"]; 1.170 + 1.171 + this.messages.forEach((function(msgName) { 1.172 + ppmm.addMessageListener(msgName, this); 1.173 + }).bind(this)); 1.174 + 1.175 + cpmm.addMessageListener("Activities:Register:OK", this); 1.176 + 1.177 + Services.obs.addObserver(this, "xpcom-shutdown", false); 1.178 + Services.obs.addObserver(this, "memory-pressure", false); 1.179 + 1.180 + AppDownloadManager.registerCancelFunction(this.cancelDownload.bind(this)); 1.181 + 1.182 + this.appsFile = FileUtils.getFile(DIRECTORY_NAME, 1.183 + ["webapps", "webapps.json"], true).path; 1.184 + 1.185 + this.loadAndUpdateApps(); 1.186 + }, 1.187 + 1.188 + // loads the current registry, that could be empty on first run. 1.189 + loadCurrentRegistry: function() { 1.190 + return AppsUtils.loadJSONAsync(this.appsFile).then((aData) => { 1.191 + if (!aData) { 1.192 + return; 1.193 + } 1.194 + 1.195 + this.webapps = aData; 1.196 + let appDir = OS.Path.dirname(this.appsFile); 1.197 + for (let id in this.webapps) { 1.198 + let app = this.webapps[id]; 1.199 + if (!app) { 1.200 + delete this.webapps[id]; 1.201 + continue; 1.202 + } 1.203 + 1.204 + app.id = id; 1.205 + 1.206 + // Make sure we have a localId 1.207 + if (app.localId === undefined) { 1.208 + app.localId = this._nextLocalId(); 1.209 + } 1.210 + 1.211 + if (app.basePath === undefined) { 1.212 + app.basePath = appDir; 1.213 + } 1.214 + 1.215 + // Default to removable apps. 1.216 + if (app.removable === undefined) { 1.217 + app.removable = true; 1.218 + } 1.219 + 1.220 + // Default to a non privileged status. 1.221 + if (app.appStatus === undefined) { 1.222 + app.appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED; 1.223 + } 1.224 + 1.225 + // Default to NO_APP_ID and not in browser. 1.226 + if (app.installerAppId === undefined) { 1.227 + app.installerAppId = Ci.nsIScriptSecurityManager.NO_APP_ID; 1.228 + } 1.229 + if (app.installerIsBrowser === undefined) { 1.230 + app.installerIsBrowser = false; 1.231 + } 1.232 + 1.233 + // Default installState to "installed", and reset if we shutdown 1.234 + // during an update. 1.235 + if (app.installState === undefined || 1.236 + app.installState === "updating") { 1.237 + app.installState = "installed"; 1.238 + } 1.239 + 1.240 + // Default storeId to "" and storeVersion to 0 1.241 + if (this.webapps[id].storeId === undefined) { 1.242 + this.webapps[id].storeId = ""; 1.243 + } 1.244 + if (this.webapps[id].storeVersion === undefined) { 1.245 + this.webapps[id].storeVersion = 0; 1.246 + } 1.247 + 1.248 + // Default role to "". 1.249 + if (this.webapps[id].role === undefined) { 1.250 + this.webapps[id].role = ""; 1.251 + } 1.252 + 1.253 + // At startup we can't be downloading, and the $TMP directory 1.254 + // will be empty so we can't just apply a staged update. 1.255 + app.downloading = false; 1.256 + app.readyToApplyDownload = false; 1.257 + } 1.258 + }); 1.259 + }, 1.260 + 1.261 + // Notify we are starting with registering apps. 1.262 + _registryStarted: Promise.defer(), 1.263 + notifyAppsRegistryStart: function notifyAppsRegistryStart() { 1.264 + Services.obs.notifyObservers(this, "webapps-registry-start", null); 1.265 + this._registryStarted.resolve(); 1.266 + }, 1.267 + 1.268 + get registryStarted() { 1.269 + return this._registryStarted.promise; 1.270 + }, 1.271 + 1.272 + // Notify we are done with registering apps and save a copy of the registry. 1.273 + _registryReady: Promise.defer(), 1.274 + notifyAppsRegistryReady: function notifyAppsRegistryReady() { 1.275 + this._registryReady.resolve(); 1.276 + Services.obs.notifyObservers(this, "webapps-registry-ready", null); 1.277 + this._saveApps(); 1.278 + }, 1.279 + 1.280 + get registryReady() { 1.281 + return this._registryReady.promise; 1.282 + }, 1.283 + 1.284 + // Ensure that the .to property in redirects is a relative URL. 1.285 + sanitizeRedirects: function sanitizeRedirects(aSource) { 1.286 + if (!aSource) { 1.287 + return null; 1.288 + } 1.289 + 1.290 + let res = []; 1.291 + for (let i = 0; i < aSource.length; i++) { 1.292 + let redirect = aSource[i]; 1.293 + if (redirect.from && redirect.to && 1.294 + isAbsoluteURI(redirect.from) && 1.295 + !isAbsoluteURI(redirect.to)) { 1.296 + res.push(redirect); 1.297 + } 1.298 + } 1.299 + return res.length > 0 ? res : null; 1.300 + }, 1.301 + 1.302 + // Registers all the activities and system messages. 1.303 + registerAppsHandlers: function(aRunUpdate) { 1.304 + this.notifyAppsRegistryStart(); 1.305 + let ids = []; 1.306 + for (let id in this.webapps) { 1.307 + ids.push({ id: id }); 1.308 + } 1.309 + if (supportSystemMessages()) { 1.310 + this._processManifestForIds(ids, aRunUpdate); 1.311 + } else { 1.312 + // Read the CSPs and roles. If MOZ_SYS_MSG is defined this is done on 1.313 + // _processManifestForIds so as to not reading the manifests 1.314 + // twice 1.315 + this._readManifests(ids).then((aResults) => { 1.316 + aResults.forEach((aResult) => { 1.317 + if (!aResult.manifest) { 1.318 + // If we can't load the manifest, we probably have a corrupted 1.319 + // registry. We delete the app since we can't do anything with it. 1.320 + delete this.webapps[aResult.id]; 1.321 + return; 1.322 + } 1.323 + let app = this.webapps[aResult.id]; 1.324 + app.csp = aResult.manifest.csp || ""; 1.325 + app.role = aResult.manifest.role || ""; 1.326 + if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { 1.327 + app.redirects = this.sanitizeRedirects(aResult.redirects); 1.328 + } 1.329 + }); 1.330 + }); 1.331 + 1.332 + // Nothing else to do but notifying we're ready. 1.333 + this.notifyAppsRegistryReady(); 1.334 + } 1.335 + }, 1.336 + 1.337 + updateDataStoreForApp: function(aId) { 1.338 + if (!this.webapps[aId]) { 1.339 + return; 1.340 + } 1.341 + 1.342 + // Create or Update the DataStore for this app 1.343 + this._readManifests([{ id: aId }]).then((aResult) => { 1.344 + let app = this.webapps[aId]; 1.345 + this.updateDataStore(app.localId, app.origin, app.manifestURL, 1.346 + aResult[0].manifest, app.appStatus); 1.347 + }); 1.348 + }, 1.349 + 1.350 + updatePermissionsForApp: function(aId) { 1.351 + if (!this.webapps[aId]) { 1.352 + return; 1.353 + } 1.354 + 1.355 + // Install the permissions for this app, as if we were updating 1.356 + // to cleanup the old ones if needed. 1.357 + // TODO It's not clear what this should do when there are multiple profiles. 1.358 + if (supportUseCurrentProfile()) { 1.359 + this._readManifests([{ id: aId }]).then((aResult) => { 1.360 + let data = aResult[0]; 1.361 + PermissionsInstaller.installPermissions({ 1.362 + manifest: data.manifest, 1.363 + manifestURL: this.webapps[aId].manifestURL, 1.364 + origin: this.webapps[aId].origin 1.365 + }, true, function() { 1.366 + debug("Error installing permissions for " + aId); 1.367 + }); 1.368 + }); 1.369 + } 1.370 + }, 1.371 + 1.372 + updateOfflineCacheForApp: function(aId) { 1.373 + let app = this.webapps[aId]; 1.374 + this._readManifests([{ id: aId }]).then((aResult) => { 1.375 + let manifest = new ManifestHelper(aResult[0].manifest, app.origin); 1.376 + OfflineCacheInstaller.installCache({ 1.377 + cachePath: app.cachePath, 1.378 + appId: aId, 1.379 + origin: Services.io.newURI(app.origin, null, null), 1.380 + localId: app.localId, 1.381 + appcache_path: manifest.fullAppcachePath() 1.382 + }); 1.383 + }); 1.384 + }, 1.385 + 1.386 + // Installs a 3rd party app. 1.387 + installPreinstalledApp: function installPreinstalledApp(aId) { 1.388 +#ifdef MOZ_WIDGET_GONK 1.389 + let app = this.webapps[aId]; 1.390 + let baseDir; 1.391 + try { 1.392 + baseDir = FileUtils.getDir("coreAppsDir", ["webapps", aId], false); 1.393 + if (!baseDir.exists()) { 1.394 + return; 1.395 + } else if (!baseDir.directoryEntries.hasMoreElements()) { 1.396 + debug("Error: Core app in " + baseDir.path + " is empty"); 1.397 + return; 1.398 + } 1.399 + } catch(e) { 1.400 + // In ENG builds, we don't have apps in coreAppsDir. 1.401 + return; 1.402 + } 1.403 + 1.404 + let filesToMove; 1.405 + let isPackage; 1.406 + 1.407 + let updateFile = baseDir.clone(); 1.408 + updateFile.append("update.webapp"); 1.409 + if (!updateFile.exists()) { 1.410 + // The update manifest is missing, this is a hosted app only if there is 1.411 + // no application.zip 1.412 + let appFile = baseDir.clone(); 1.413 + appFile.append("application.zip"); 1.414 + if (appFile.exists()) { 1.415 + return; 1.416 + } 1.417 + 1.418 + isPackage = false; 1.419 + filesToMove = ["manifest.webapp"]; 1.420 + } else { 1.421 + isPackage = true; 1.422 + filesToMove = ["application.zip", "update.webapp"]; 1.423 + } 1.424 + 1.425 + debug("Installing 3rd party app : " + aId + 1.426 + " from " + baseDir.path); 1.427 + 1.428 + // We copy this app to DIRECTORY_NAME/$aId, and set the base path as needed. 1.429 + let destDir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true); 1.430 + 1.431 + filesToMove.forEach(function(aFile) { 1.432 + let file = baseDir.clone(); 1.433 + file.append(aFile); 1.434 + try { 1.435 + file.copyTo(destDir, aFile); 1.436 + } catch(e) { 1.437 + debug("Error: Failed to copy " + file.path + " to " + destDir.path); 1.438 + } 1.439 + }); 1.440 + 1.441 + app.installState = "installed"; 1.442 + app.cachePath = app.basePath; 1.443 + app.basePath = OS.Path.dirname(this.appsFile); 1.444 + 1.445 + if (!isPackage) { 1.446 + return; 1.447 + } 1.448 + 1.449 + app.origin = "app://" + aId; 1.450 + 1.451 + // Do this for all preinstalled apps... we can't know at this 1.452 + // point if the updates will be signed or not and it doesn't 1.453 + // hurt to have it always. 1.454 + app.storeId = STORE_ID_PENDING_PREFIX + app.installOrigin; 1.455 + 1.456 + // Extract the manifest.webapp file from application.zip. 1.457 + let zipFile = baseDir.clone(); 1.458 + zipFile.append("application.zip"); 1.459 + let zipReader = Cc["@mozilla.org/libjar/zip-reader;1"] 1.460 + .createInstance(Ci.nsIZipReader); 1.461 + try { 1.462 + debug("Opening " + zipFile.path); 1.463 + zipReader.open(zipFile); 1.464 + if (!zipReader.hasEntry("manifest.webapp")) { 1.465 + throw "MISSING_MANIFEST"; 1.466 + } 1.467 + let manifestFile = destDir.clone(); 1.468 + manifestFile.append("manifest.webapp"); 1.469 + zipReader.extract("manifest.webapp", manifestFile); 1.470 + } catch(e) { 1.471 + // If we are unable to extract the manifest, cleanup and remove this app. 1.472 + debug("Cleaning up: " + e); 1.473 + destDir.remove(true); 1.474 + delete this.webapps[aId]; 1.475 + } finally { 1.476 + zipReader.close(); 1.477 + } 1.478 +#endif 1.479 + }, 1.480 + 1.481 + // For hosted apps, uninstall an app served from http:// if we have 1.482 + // one installed from the same url with an https:// scheme. 1.483 + removeIfHttpsDuplicate: function(aId) { 1.484 +#ifdef MOZ_WIDGET_GONK 1.485 + let app = this.webapps[aId]; 1.486 + if (!app || !app.origin.startsWith("http://")) { 1.487 + return; 1.488 + } 1.489 + 1.490 + let httpsManifestURL = 1.491 + "https://" + app.manifestURL.substring("http://".length); 1.492 + 1.493 + // This will uninstall the http apps and remove any data hold by this 1.494 + // app. Bug 948105 tracks data migration from http to https apps. 1.495 + for (let id in this.webapps) { 1.496 + if (this.webapps[id].manifestURL === httpsManifestURL) { 1.497 + debug("Found a http/https match: " + app.manifestURL + " / " + 1.498 + this.webapps[id].manifestURL); 1.499 + this.uninstall(app.manifestURL, function() {}, function() {}); 1.500 + return; 1.501 + } 1.502 + } 1.503 +#endif 1.504 + }, 1.505 + 1.506 + // Implements the core of bug 787439 1.507 + // if at first run, go through these steps: 1.508 + // a. load the core apps registry. 1.509 + // b. uninstall any core app from the current registry but not in the 1.510 + // new core apps registry. 1.511 + // c. for all apps in the new core registry, install them if they are not 1.512 + // yet in the current registry, and run installPermissions() 1.513 + installSystemApps: function() { 1.514 + return Task.spawn(function() { 1.515 + let file; 1.516 + try { 1.517 + file = FileUtils.getFile("coreAppsDir", ["webapps", "webapps.json"], false); 1.518 + } catch(e) { } 1.519 + 1.520 + if (!file || !file.exists()) { 1.521 + return; 1.522 + } 1.523 + 1.524 + // a 1.525 + let data = yield AppsUtils.loadJSONAsync(file.path); 1.526 + if (!data) { 1.527 + return; 1.528 + } 1.529 + 1.530 + // b : core apps are not removable. 1.531 + for (let id in this.webapps) { 1.532 + if (id in data || this.webapps[id].removable) 1.533 + continue; 1.534 + // Remove the permissions, cookies and private data for this app. 1.535 + let localId = this.webapps[id].localId; 1.536 + let permMgr = Cc["@mozilla.org/permissionmanager;1"] 1.537 + .getService(Ci.nsIPermissionManager); 1.538 + permMgr.removePermissionsForApp(localId, false); 1.539 + Services.cookies.removeCookiesForApp(localId, false); 1.540 + this._clearPrivateData(localId, false); 1.541 + delete this.webapps[id]; 1.542 + } 1.543 + 1.544 + let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false); 1.545 + // c 1.546 + for (let id in data) { 1.547 + // Core apps have ids matching their domain name (eg: dialer.gaiamobile.org) 1.548 + // Use that property to check if they are new or not. 1.549 + if (!(id in this.webapps)) { 1.550 + this.webapps[id] = data[id]; 1.551 + this.webapps[id].basePath = appDir.path; 1.552 + 1.553 + this.webapps[id].id = id; 1.554 + 1.555 + // Create a new localId. 1.556 + this.webapps[id].localId = this._nextLocalId(); 1.557 + 1.558 + // Core apps are not removable. 1.559 + if (this.webapps[id].removable === undefined) { 1.560 + this.webapps[id].removable = false; 1.561 + } 1.562 + } else { 1.563 + // we fall into this case if the app is present in /system/b2g/webapps/webapps.json 1.564 + // and in /data/local/webapps/webapps.json: this happens when updating gaia apps 1.565 + // Confere bug 989876 1.566 + this.webapps[id].updateTime = data[id].updateTime; 1.567 + this.webapps[id].lastUpdateCheck = data[id].updateTime; 1.568 + } 1.569 + } 1.570 + }.bind(this)).then(null, Cu.reportError); 1.571 + }, 1.572 + 1.573 + loadAndUpdateApps: function() { 1.574 + return Task.spawn(function() { 1.575 + let runUpdate = AppsUtils.isFirstRun(Services.prefs); 1.576 + 1.577 + yield this.loadCurrentRegistry(); 1.578 + 1.579 + if (runUpdate) { 1.580 +#ifdef MOZ_WIDGET_GONK 1.581 + yield this.installSystemApps(); 1.582 +#endif 1.583 + 1.584 + // At first run, install preloaded apps and set up their permissions. 1.585 + for (let id in this.webapps) { 1.586 + this.installPreinstalledApp(id); 1.587 + this.removeIfHttpsDuplicate(id); 1.588 + if (!this.webapps[id]) { 1.589 + continue; 1.590 + } 1.591 + this.updateOfflineCacheForApp(id); 1.592 + this.updatePermissionsForApp(id); 1.593 + } 1.594 + // Need to update the persisted list of apps since 1.595 + // installPreinstalledApp() removes the ones failing to install. 1.596 + this._saveApps(); 1.597 + } 1.598 + 1.599 + // DataStores must be initialized at startup. 1.600 + for (let id in this.webapps) { 1.601 + this.updateDataStoreForApp(id); 1.602 + } 1.603 + 1.604 + this.registerAppsHandlers(runUpdate); 1.605 + }.bind(this)).then(null, Cu.reportError); 1.606 + }, 1.607 + 1.608 + updateDataStore: function(aId, aOrigin, aManifestURL, aManifest, aAppStatus) { 1.609 + // Just Certified Apps can use DataStores 1.610 + let prefName = "dom.testing.datastore_enabled_for_hosted_apps"; 1.611 + if (aAppStatus != Ci.nsIPrincipal.APP_STATUS_CERTIFIED && 1.612 + (Services.prefs.getPrefType(prefName) == Services.prefs.PREF_INVALID || 1.613 + !Services.prefs.getBoolPref(prefName))) { 1.614 + return; 1.615 + } 1.616 + 1.617 + if ('datastores-owned' in aManifest) { 1.618 + for (let name in aManifest['datastores-owned']) { 1.619 + let readonly = "access" in aManifest['datastores-owned'][name] 1.620 + ? aManifest['datastores-owned'][name].access == 'readonly' 1.621 + : false; 1.622 + 1.623 + dataStoreService.installDataStore(aId, name, aOrigin, aManifestURL, 1.624 + readonly); 1.625 + } 1.626 + } 1.627 + 1.628 + if ('datastores-access' in aManifest) { 1.629 + for (let name in aManifest['datastores-access']) { 1.630 + let readonly = ("readonly" in aManifest['datastores-access'][name]) && 1.631 + !aManifest['datastores-access'][name].readonly 1.632 + ? false : true; 1.633 + 1.634 + dataStoreService.installAccessDataStore(aId, name, aOrigin, 1.635 + aManifestURL, readonly); 1.636 + } 1.637 + } 1.638 + }, 1.639 + 1.640 + // |aEntryPoint| is either the entry_point name or the null in which case we 1.641 + // use the root of the manifest. 1.642 + // 1.643 + // TODO Bug 908094 Refine _registerSystemMessagesForEntryPoint(...). 1.644 + _registerSystemMessagesForEntryPoint: function(aManifest, aApp, aEntryPoint) { 1.645 + let root = aManifest; 1.646 + if (aEntryPoint && aManifest.entry_points[aEntryPoint]) { 1.647 + root = aManifest.entry_points[aEntryPoint]; 1.648 + } 1.649 + 1.650 + if (!root.messages || !Array.isArray(root.messages) || 1.651 + root.messages.length == 0) { 1.652 + return; 1.653 + } 1.654 + 1.655 + let manifest = new ManifestHelper(aManifest, aApp.origin); 1.656 + let launchPath = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint), null, null); 1.657 + let manifestURL = Services.io.newURI(aApp.manifestURL, null, null); 1.658 + root.messages.forEach(function registerPages(aMessage) { 1.659 + let href = launchPath; 1.660 + let messageName; 1.661 + if (typeof(aMessage) === "object" && Object.keys(aMessage).length === 1) { 1.662 + messageName = Object.keys(aMessage)[0]; 1.663 + let uri; 1.664 + try { 1.665 + uri = manifest.resolveFromOrigin(aMessage[messageName]); 1.666 + } catch(e) { 1.667 + debug("system message url (" + aMessage[messageName] + ") is invalid, skipping. " + 1.668 + "Error is: " + e); 1.669 + return; 1.670 + } 1.671 + href = Services.io.newURI(uri, null, null); 1.672 + } else { 1.673 + messageName = aMessage; 1.674 + } 1.675 + 1.676 + if (SystemMessagePermissionsChecker 1.677 + .isSystemMessagePermittedToRegister(messageName, 1.678 + aApp.origin, 1.679 + aManifest)) { 1.680 + msgmgr.registerPage(messageName, href, manifestURL); 1.681 + } 1.682 + }); 1.683 + }, 1.684 + 1.685 + // |aEntryPoint| is either the entry_point name or the null in which case we 1.686 + // use the root of the manifest. 1.687 + // 1.688 + // TODO Bug 908094 Refine _registerInterAppConnectionsForEntryPoint(...). 1.689 + _registerInterAppConnectionsForEntryPoint: function(aManifest, aApp, 1.690 + aEntryPoint) { 1.691 + let root = aManifest; 1.692 + if (aEntryPoint && aManifest.entry_points[aEntryPoint]) { 1.693 + root = aManifest.entry_points[aEntryPoint]; 1.694 + } 1.695 + 1.696 + let connections = root.connections; 1.697 + if (!connections) { 1.698 + return; 1.699 + } 1.700 + 1.701 + if ((typeof connections) !== "object") { 1.702 + debug("|connections| is not an object. Skipping: " + connections); 1.703 + return; 1.704 + } 1.705 + 1.706 + let manifest = new ManifestHelper(aManifest, aApp.origin); 1.707 + let launchPathURI = Services.io.newURI(manifest.fullLaunchPath(aEntryPoint), 1.708 + null, null); 1.709 + let manifestURI = Services.io.newURI(aApp.manifestURL, null, null); 1.710 + 1.711 + for (let keyword in connections) { 1.712 + let connection = connections[keyword]; 1.713 + 1.714 + // Resolve the handler path from origin. If |handler_path| is absent, 1.715 + // use |launch_path| as default. 1.716 + let fullHandlerPath; 1.717 + let handlerPath = connection.handler_path; 1.718 + if (handlerPath) { 1.719 + try { 1.720 + fullHandlerPath = manifest.resolveFromOrigin(handlerPath); 1.721 + } catch(e) { 1.722 + debug("Connection's handler path is invalid. Skipping: keyword: " + 1.723 + keyword + " handler_path: " + handlerPath); 1.724 + continue; 1.725 + } 1.726 + } 1.727 + let handlerPageURI = fullHandlerPath 1.728 + ? Services.io.newURI(fullHandlerPath, null, null) 1.729 + : launchPathURI; 1.730 + 1.731 + if (SystemMessagePermissionsChecker 1.732 + .isSystemMessagePermittedToRegister("connection", 1.733 + aApp.origin, 1.734 + aManifest)) { 1.735 + msgmgr.registerPage("connection", handlerPageURI, manifestURI); 1.736 + } 1.737 + 1.738 + interAppCommService. 1.739 + registerConnection(keyword, 1.740 + handlerPageURI, 1.741 + manifestURI, 1.742 + connection.description, 1.743 + connection.rules); 1.744 + } 1.745 + }, 1.746 + 1.747 + _registerSystemMessages: function(aManifest, aApp) { 1.748 + this._registerSystemMessagesForEntryPoint(aManifest, aApp, null); 1.749 + 1.750 + if (!aManifest.entry_points) { 1.751 + return; 1.752 + } 1.753 + 1.754 + for (let entryPoint in aManifest.entry_points) { 1.755 + this._registerSystemMessagesForEntryPoint(aManifest, aApp, entryPoint); 1.756 + } 1.757 + }, 1.758 + 1.759 + _registerInterAppConnections: function(aManifest, aApp) { 1.760 + this._registerInterAppConnectionsForEntryPoint(aManifest, aApp, null); 1.761 + 1.762 + if (!aManifest.entry_points) { 1.763 + return; 1.764 + } 1.765 + 1.766 + for (let entryPoint in aManifest.entry_points) { 1.767 + this._registerInterAppConnectionsForEntryPoint(aManifest, aApp, 1.768 + entryPoint); 1.769 + } 1.770 + }, 1.771 + 1.772 + // |aEntryPoint| is either the entry_point name or the null in which case we 1.773 + // use the root of the manifest. 1.774 + _createActivitiesToRegister: function(aManifest, aApp, aEntryPoint, aRunUpdate) { 1.775 + let activitiesToRegister = []; 1.776 + let root = aManifest; 1.777 + if (aEntryPoint && aManifest.entry_points[aEntryPoint]) { 1.778 + root = aManifest.entry_points[aEntryPoint]; 1.779 + } 1.780 + 1.781 + if (!root.activities) { 1.782 + return activitiesToRegister; 1.783 + } 1.784 + 1.785 + let manifest = new ManifestHelper(aManifest, aApp.origin); 1.786 + for (let activity in root.activities) { 1.787 + let description = root.activities[activity]; 1.788 + let href = description.href; 1.789 + if (!href) { 1.790 + href = manifest.launch_path; 1.791 + } 1.792 + 1.793 + try { 1.794 + href = manifest.resolveFromOrigin(href); 1.795 + } catch (e) { 1.796 + debug("Activity href (" + href + ") is invalid, skipping. " + 1.797 + "Error is: " + e); 1.798 + continue; 1.799 + } 1.800 + 1.801 + // Make a copy of the description object since we don't want to modify 1.802 + // the manifest itself, but need to register with a resolved URI. 1.803 + let newDesc = {}; 1.804 + for (let prop in description) { 1.805 + newDesc[prop] = description[prop]; 1.806 + } 1.807 + newDesc.href = href; 1.808 + 1.809 + debug('_createActivitiesToRegister: ' + aApp.manifestURL + ', activity ' + 1.810 + activity + ', description.href is ' + newDesc.href); 1.811 + 1.812 + if (aRunUpdate) { 1.813 + activitiesToRegister.push({ "manifest": aApp.manifestURL, 1.814 + "name": activity, 1.815 + "icon": manifest.iconURLForSize(128), 1.816 + "description": newDesc }); 1.817 + } 1.818 + 1.819 + let launchPath = Services.io.newURI(href, null, null); 1.820 + let manifestURL = Services.io.newURI(aApp.manifestURL, null, null); 1.821 + 1.822 + if (SystemMessagePermissionsChecker 1.823 + .isSystemMessagePermittedToRegister("activity", 1.824 + aApp.origin, 1.825 + aManifest)) { 1.826 + msgmgr.registerPage("activity", launchPath, manifestURL); 1.827 + } 1.828 + } 1.829 + return activitiesToRegister; 1.830 + }, 1.831 + 1.832 + // |aAppsToRegister| contains an array of apps to be registered, where 1.833 + // each element is an object in the format of {manifest: foo, app: bar}. 1.834 + _registerActivitiesForApps: function(aAppsToRegister, aRunUpdate) { 1.835 + // Collect the activities to be registered for root and entry_points. 1.836 + let activitiesToRegister = []; 1.837 + aAppsToRegister.forEach(function (aApp) { 1.838 + let manifest = aApp.manifest; 1.839 + let app = aApp.app; 1.840 + activitiesToRegister.push.apply(activitiesToRegister, 1.841 + this._createActivitiesToRegister(manifest, app, null, aRunUpdate)); 1.842 + 1.843 + if (!manifest.entry_points) { 1.844 + return; 1.845 + } 1.846 + 1.847 + for (let entryPoint in manifest.entry_points) { 1.848 + activitiesToRegister.push.apply(activitiesToRegister, 1.849 + this._createActivitiesToRegister(manifest, app, entryPoint, aRunUpdate)); 1.850 + } 1.851 + }, this); 1.852 + 1.853 + if (!aRunUpdate || activitiesToRegister.length == 0) { 1.854 + this.notifyAppsRegistryReady(); 1.855 + return; 1.856 + } 1.857 + 1.858 + // Send the array carrying all the activities to be registered. 1.859 + cpmm.sendAsyncMessage("Activities:Register", activitiesToRegister); 1.860 + }, 1.861 + 1.862 + // Better to directly use |_registerActivitiesForApps()| if we have 1.863 + // multiple apps to be registered for activities. 1.864 + _registerActivities: function(aManifest, aApp, aRunUpdate) { 1.865 + this._registerActivitiesForApps([{ manifest: aManifest, app: aApp }], aRunUpdate); 1.866 + }, 1.867 + 1.868 + // |aEntryPoint| is either the entry_point name or the null in which case we 1.869 + // use the root of the manifest. 1.870 + _createActivitiesToUnregister: function(aManifest, aApp, aEntryPoint) { 1.871 + let activitiesToUnregister = []; 1.872 + let root = aManifest; 1.873 + if (aEntryPoint && aManifest.entry_points[aEntryPoint]) { 1.874 + root = aManifest.entry_points[aEntryPoint]; 1.875 + } 1.876 + 1.877 + if (!root.activities) { 1.878 + return activitiesToUnregister; 1.879 + } 1.880 + 1.881 + for (let activity in root.activities) { 1.882 + let description = root.activities[activity]; 1.883 + activitiesToUnregister.push({ "manifest": aApp.manifestURL, 1.884 + "name": activity, 1.885 + "description": description }); 1.886 + } 1.887 + return activitiesToUnregister; 1.888 + }, 1.889 + 1.890 + // |aAppsToUnregister| contains an array of apps to be unregistered, where 1.891 + // each element is an object in the format of {manifest: foo, app: bar}. 1.892 + _unregisterActivitiesForApps: function(aAppsToUnregister) { 1.893 + // Collect the activities to be unregistered for root and entry_points. 1.894 + let activitiesToUnregister = []; 1.895 + aAppsToUnregister.forEach(function (aApp) { 1.896 + let manifest = aApp.manifest; 1.897 + let app = aApp.app; 1.898 + activitiesToUnregister.push.apply(activitiesToUnregister, 1.899 + this._createActivitiesToUnregister(manifest, app, null)); 1.900 + 1.901 + if (!manifest.entry_points) { 1.902 + return; 1.903 + } 1.904 + 1.905 + for (let entryPoint in manifest.entry_points) { 1.906 + activitiesToUnregister.push.apply(activitiesToUnregister, 1.907 + this._createActivitiesToUnregister(manifest, app, entryPoint)); 1.908 + } 1.909 + }, this); 1.910 + 1.911 + // Send the array carrying all the activities to be unregistered. 1.912 + cpmm.sendAsyncMessage("Activities:Unregister", activitiesToUnregister); 1.913 + }, 1.914 + 1.915 + // Better to directly use |_unregisterActivitiesForApps()| if we have 1.916 + // multiple apps to be unregistered for activities. 1.917 + _unregisterActivities: function(aManifest, aApp) { 1.918 + this._unregisterActivitiesForApps([{ manifest: aManifest, app: aApp }]); 1.919 + }, 1.920 + 1.921 + _processManifestForIds: function(aIds, aRunUpdate) { 1.922 + this._readManifests(aIds).then((aResults) => { 1.923 + let appsToRegister = []; 1.924 + aResults.forEach((aResult) => { 1.925 + let app = this.webapps[aResult.id]; 1.926 + let manifest = aResult.manifest; 1.927 + if (!manifest) { 1.928 + // If we can't load the manifest, we probably have a corrupted 1.929 + // registry. We delete the app since we can't do anything with it. 1.930 + delete this.webapps[aResult.id]; 1.931 + return; 1.932 + } 1.933 + app.name = manifest.name; 1.934 + app.csp = manifest.csp || ""; 1.935 + app.role = manifest.role || ""; 1.936 + if (app.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { 1.937 + app.redirects = this.sanitizeRedirects(manifest.redirects); 1.938 + } 1.939 + this._registerSystemMessages(manifest, app); 1.940 + this._registerInterAppConnections(manifest, app); 1.941 + appsToRegister.push({ manifest: manifest, app: app }); 1.942 + }); 1.943 + this._registerActivitiesForApps(appsToRegister, aRunUpdate); 1.944 + }); 1.945 + }, 1.946 + 1.947 + observe: function(aSubject, aTopic, aData) { 1.948 + if (aTopic == "xpcom-shutdown") { 1.949 + this.messages.forEach((function(msgName) { 1.950 + ppmm.removeMessageListener(msgName, this); 1.951 + }).bind(this)); 1.952 + Services.obs.removeObserver(this, "xpcom-shutdown"); 1.953 + cpmm = null; 1.954 + ppmm = null; 1.955 + } else if (aTopic == "memory-pressure") { 1.956 + // Clear the manifest cache on memory pressure. 1.957 + this._manifestCache = {}; 1.958 + } 1.959 + }, 1.960 + 1.961 + addMessageListener: function(aMsgNames, aApp, aMm) { 1.962 + aMsgNames.forEach(function (aMsgName) { 1.963 + let man = aApp && aApp.manifestURL; 1.964 + if (!(aMsgName in this.children)) { 1.965 + this.children[aMsgName] = []; 1.966 + } 1.967 + 1.968 + let mmFound = this.children[aMsgName].some(function(mmRef) { 1.969 + if (mmRef.mm === aMm) { 1.970 + mmRef.refCount++; 1.971 + return true; 1.972 + } 1.973 + return false; 1.974 + }); 1.975 + 1.976 + if (!mmFound) { 1.977 + this.children[aMsgName].push({ 1.978 + mm: aMm, 1.979 + refCount: 1 1.980 + }); 1.981 + } 1.982 + 1.983 + // If the state reported by the registration is outdated, update it now. 1.984 + if ((aMsgName === 'Webapps:FireEvent') || 1.985 + (aMsgName === 'Webapps:UpdateState')) { 1.986 + if (man) { 1.987 + let app = this.getAppByManifestURL(aApp.manifestURL); 1.988 + if (app && ((aApp.installState !== app.installState) || 1.989 + (aApp.downloading !== app.downloading))) { 1.990 + debug("Got a registration from an outdated app: " + 1.991 + aApp.manifestURL); 1.992 + let aEvent ={ 1.993 + type: app.installState, 1.994 + app: app, 1.995 + manifestURL: app.manifestURL, 1.996 + manifest: app.manifest 1.997 + }; 1.998 + aMm.sendAsyncMessage(aMsgName, aEvent); 1.999 + } 1.1000 + } 1.1001 + } 1.1002 + }, this); 1.1003 + }, 1.1004 + 1.1005 + removeMessageListener: function(aMsgNames, aMm) { 1.1006 + if (aMsgNames.length === 1 && 1.1007 + aMsgNames[0] === "Webapps:Internal:AllMessages") { 1.1008 + for (let msgName in this.children) { 1.1009 + let msg = this.children[msgName]; 1.1010 + 1.1011 + for (let mmI = msg.length - 1; mmI >= 0; mmI -= 1) { 1.1012 + let mmRef = msg[mmI]; 1.1013 + if (mmRef.mm === aMm) { 1.1014 + msg.splice(mmI, 1); 1.1015 + } 1.1016 + } 1.1017 + 1.1018 + if (msg.length === 0) { 1.1019 + delete this.children[msgName]; 1.1020 + } 1.1021 + } 1.1022 + return; 1.1023 + } 1.1024 + 1.1025 + aMsgNames.forEach(function(aMsgName) { 1.1026 + if (!(aMsgName in this.children)) { 1.1027 + return; 1.1028 + } 1.1029 + 1.1030 + let removeIndex; 1.1031 + this.children[aMsgName].some(function(mmRef, index) { 1.1032 + if (mmRef.mm === aMm) { 1.1033 + mmRef.refCount--; 1.1034 + if (mmRef.refCount === 0) { 1.1035 + removeIndex = index; 1.1036 + } 1.1037 + return true; 1.1038 + } 1.1039 + return false; 1.1040 + }); 1.1041 + 1.1042 + if (removeIndex) { 1.1043 + this.children[aMsgName].splice(removeIndex, 1); 1.1044 + } 1.1045 + }, this); 1.1046 + }, 1.1047 + 1.1048 + receiveMessage: function(aMessage) { 1.1049 + // nsIPrefBranch throws if pref does not exist, faster to simply write 1.1050 + // the pref instead of first checking if it is false. 1.1051 + Services.prefs.setBoolPref("dom.mozApps.used", true); 1.1052 + 1.1053 + // We need to check permissions for calls coming from mozApps.mgmt. 1.1054 + // These are: getAll(), getNotInstalled(), applyDownload() and uninstall(). 1.1055 + if (["Webapps:GetAll", 1.1056 + "Webapps:GetNotInstalled", 1.1057 + "Webapps:ApplyDownload", 1.1058 + "Webapps:Uninstall"].indexOf(aMessage.name) != -1) { 1.1059 + if (!aMessage.target.assertPermission("webapps-manage")) { 1.1060 + debug("mozApps message " + aMessage.name + 1.1061 + " from a content process with no 'webapps-manage' privileges."); 1.1062 + return null; 1.1063 + } 1.1064 + } 1.1065 + 1.1066 + let msg = aMessage.data || {}; 1.1067 + let mm = aMessage.target; 1.1068 + msg.mm = mm; 1.1069 + 1.1070 + switch (aMessage.name) { 1.1071 + case "Webapps:Install": { 1.1072 +#ifdef MOZ_ANDROID_SYNTHAPKS 1.1073 + Services.obs.notifyObservers(mm, "webapps-runtime-install", JSON.stringify(msg)); 1.1074 +#else 1.1075 + this.doInstall(msg, mm); 1.1076 +#endif 1.1077 + break; 1.1078 + } 1.1079 + case "Webapps:GetSelf": 1.1080 + this.getSelf(msg, mm); 1.1081 + break; 1.1082 + case "Webapps:Uninstall": 1.1083 + this.doUninstall(msg, mm); 1.1084 + break; 1.1085 + case "Webapps:Launch": 1.1086 + this.doLaunch(msg, mm); 1.1087 + break; 1.1088 + case "Webapps:CheckInstalled": 1.1089 + this.checkInstalled(msg, mm); 1.1090 + break; 1.1091 + case "Webapps:GetInstalled": 1.1092 + this.getInstalled(msg, mm); 1.1093 + break; 1.1094 + case "Webapps:GetNotInstalled": 1.1095 + this.getNotInstalled(msg, mm); 1.1096 + break; 1.1097 + case "Webapps:GetAll": 1.1098 + this.doGetAll(msg, mm); 1.1099 + break; 1.1100 + case "Webapps:InstallPackage": { 1.1101 +#ifdef MOZ_ANDROID_SYNTHAPKS 1.1102 + Services.obs.notifyObservers(mm, "webapps-runtime-install-package", JSON.stringify(msg)); 1.1103 +#else 1.1104 + this.doInstallPackage(msg, mm); 1.1105 +#endif 1.1106 + break; 1.1107 + } 1.1108 + case "Webapps:RegisterForMessages": 1.1109 + this.addMessageListener(msg.messages, msg.app, mm); 1.1110 + break; 1.1111 + case "Webapps:UnregisterForMessages": 1.1112 + this.removeMessageListener(msg, mm); 1.1113 + break; 1.1114 + case "child-process-shutdown": 1.1115 + this.removeMessageListener(["Webapps:Internal:AllMessages"], mm); 1.1116 + break; 1.1117 + case "Webapps:GetList": 1.1118 + this.addMessageListener(["Webapps:AddApp", "Webapps:RemoveApp"], null, mm); 1.1119 + return this.webapps; 1.1120 + case "Webapps:Download": 1.1121 + this.startDownload(msg.manifestURL); 1.1122 + break; 1.1123 + case "Webapps:CancelDownload": 1.1124 + this.cancelDownload(msg.manifestURL); 1.1125 + break; 1.1126 + case "Webapps:CheckForUpdate": 1.1127 + this.checkForUpdate(msg, mm); 1.1128 + break; 1.1129 + case "Webapps:ApplyDownload": 1.1130 + this.applyDownload(msg.manifestURL); 1.1131 + break; 1.1132 + case "Activities:Register:OK": 1.1133 + this.notifyAppsRegistryReady(); 1.1134 + break; 1.1135 + case "Webapps:Install:Return:Ack": 1.1136 + this.onInstallSuccessAck(msg.manifestURL); 1.1137 + break; 1.1138 + case "Webapps:AddReceipt": 1.1139 + this.addReceipt(msg, mm); 1.1140 + break; 1.1141 + case "Webapps:RemoveReceipt": 1.1142 + this.removeReceipt(msg, mm); 1.1143 + break; 1.1144 + case "Webapps:ReplaceReceipt": 1.1145 + this.replaceReceipt(msg, mm); 1.1146 + break; 1.1147 + } 1.1148 + }, 1.1149 + 1.1150 + getAppInfo: function getAppInfo(aAppId) { 1.1151 + return AppsUtils.getAppInfo(this.webapps, aAppId); 1.1152 + }, 1.1153 + 1.1154 + // Some messages can be listened by several content processes: 1.1155 + // Webapps:AddApp 1.1156 + // Webapps:RemoveApp 1.1157 + // Webapps:Install:Return:OK 1.1158 + // Webapps:Uninstall:Return:OK 1.1159 + // Webapps:Uninstall:Broadcast:Return:OK 1.1160 + // Webapps:FireEvent 1.1161 + // Webapps:checkForUpdate:Return:OK 1.1162 + // Webapps:UpdateState 1.1163 + broadcastMessage: function broadcastMessage(aMsgName, aContent) { 1.1164 + if (!(aMsgName in this.children)) { 1.1165 + return; 1.1166 + } 1.1167 + this.children[aMsgName].forEach(function(mmRef) { 1.1168 + mmRef.mm.sendAsyncMessage(aMsgName, aContent); 1.1169 + }); 1.1170 + }, 1.1171 + 1.1172 + registerUpdateHandler: function(aHandler) { 1.1173 + this._updateHandlers.push(aHandler); 1.1174 + }, 1.1175 + 1.1176 + unregisterUpdateHandler: function(aHandler) { 1.1177 + let index = this._updateHandlers.indexOf(aHandler); 1.1178 + if (index != -1) { 1.1179 + this._updateHandlers.splice(index, 1); 1.1180 + } 1.1181 + }, 1.1182 + 1.1183 + notifyUpdateHandlers: function(aApp, aManifest, aZipPath) { 1.1184 + for (let updateHandler of this._updateHandlers) { 1.1185 + updateHandler(aApp, aManifest, aZipPath); 1.1186 + } 1.1187 + }, 1.1188 + 1.1189 + _getAppDir: function(aId) { 1.1190 + return FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true); 1.1191 + }, 1.1192 + 1.1193 + _writeFile: function(aPath, aData) { 1.1194 + debug("Saving " + aPath); 1.1195 + 1.1196 + let deferred = Promise.defer(); 1.1197 + 1.1198 + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); 1.1199 + file.initWithPath(aPath); 1.1200 + 1.1201 + // Initialize the file output stream 1.1202 + let ostream = FileUtils.openSafeFileOutputStream(file); 1.1203 + 1.1204 + // Obtain a converter to convert our data to a UTF-8 encoded input stream. 1.1205 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.1206 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.1207 + converter.charset = "UTF-8"; 1.1208 + 1.1209 + // Asynchronously copy the data to the file. 1.1210 + let istream = converter.convertToInputStream(aData); 1.1211 + NetUtil.asyncCopy(istream, ostream, function(aResult) { 1.1212 + if (!Components.isSuccessCode(aResult)) { 1.1213 + deferred.reject() 1.1214 + } else { 1.1215 + deferred.resolve(); 1.1216 + } 1.1217 + }); 1.1218 + 1.1219 + return deferred.promise; 1.1220 + }, 1.1221 + 1.1222 + doLaunch: function (aData, aMm) { 1.1223 + this.launch( 1.1224 + aData.manifestURL, 1.1225 + aData.startPoint, 1.1226 + aData.timestamp, 1.1227 + function onsuccess() { 1.1228 + aMm.sendAsyncMessage("Webapps:Launch:Return:OK", aData); 1.1229 + }, 1.1230 + function onfailure(reason) { 1.1231 + aMm.sendAsyncMessage("Webapps:Launch:Return:KO", aData); 1.1232 + } 1.1233 + ); 1.1234 + }, 1.1235 + 1.1236 + launch: function launch(aManifestURL, aStartPoint, aTimeStamp, aOnSuccess, aOnFailure) { 1.1237 + let app = this.getAppByManifestURL(aManifestURL); 1.1238 + if (!app) { 1.1239 + aOnFailure("NO_SUCH_APP"); 1.1240 + return; 1.1241 + } 1.1242 + 1.1243 + // Fire an error when trying to launch an app that is not 1.1244 + // yet fully installed. 1.1245 + if (app.installState == "pending") { 1.1246 + aOnFailure("PENDING_APP_NOT_LAUNCHABLE"); 1.1247 + return; 1.1248 + } 1.1249 + 1.1250 + // We have to clone the app object as nsIDOMApplication objects are 1.1251 + // stringified as an empty object. (see bug 830376) 1.1252 + let appClone = AppsUtils.cloneAppObject(app); 1.1253 + appClone.startPoint = aStartPoint; 1.1254 + appClone.timestamp = aTimeStamp; 1.1255 + Services.obs.notifyObservers(null, "webapps-launch", JSON.stringify(appClone)); 1.1256 + aOnSuccess(); 1.1257 + }, 1.1258 + 1.1259 + close: function close(aApp) { 1.1260 + debug("close"); 1.1261 + 1.1262 + // We have to clone the app object as nsIDOMApplication objects are 1.1263 + // stringified as an empty object. (see bug 830376) 1.1264 + let appClone = AppsUtils.cloneAppObject(aApp); 1.1265 + Services.obs.notifyObservers(null, "webapps-close", JSON.stringify(appClone)); 1.1266 + }, 1.1267 + 1.1268 + cancelDownload: function cancelDownload(aManifestURL, aError) { 1.1269 + debug("cancelDownload " + aManifestURL); 1.1270 + let error = aError || "DOWNLOAD_CANCELED"; 1.1271 + let download = AppDownloadManager.get(aManifestURL); 1.1272 + if (!download) { 1.1273 + debug("Could not find a download for " + aManifestURL); 1.1274 + return; 1.1275 + } 1.1276 + 1.1277 + let app = this.webapps[download.appId]; 1.1278 + 1.1279 + if (download.cacheUpdate) { 1.1280 + try { 1.1281 + download.cacheUpdate.cancel(); 1.1282 + } catch (e) { 1.1283 + debug (e); 1.1284 + } 1.1285 + } else if (download.channel) { 1.1286 + try { 1.1287 + download.channel.cancel(Cr.NS_BINDING_ABORTED); 1.1288 + } catch(e) { } 1.1289 + } else { 1.1290 + return; 1.1291 + } 1.1292 + 1.1293 + this._saveApps().then(() => { 1.1294 + this.broadcastMessage("Webapps:UpdateState", { 1.1295 + app: { 1.1296 + progress: 0, 1.1297 + installState: download.previousState, 1.1298 + downloading: false 1.1299 + }, 1.1300 + error: error, 1.1301 + manifestURL: app.manifestURL, 1.1302 + }) 1.1303 + this.broadcastMessage("Webapps:FireEvent", { 1.1304 + eventType: "downloaderror", 1.1305 + manifestURL: app.manifestURL 1.1306 + }); 1.1307 + }); 1.1308 + AppDownloadManager.remove(aManifestURL); 1.1309 + }, 1.1310 + 1.1311 + startDownload: Task.async(function*(aManifestURL) { 1.1312 + debug("startDownload for " + aManifestURL); 1.1313 + 1.1314 + let id = this._appIdForManifestURL(aManifestURL); 1.1315 + let app = this.webapps[id]; 1.1316 + if (!app) { 1.1317 + debug("startDownload: No app found for " + aManifestURL); 1.1318 + return; 1.1319 + } 1.1320 + 1.1321 + if (app.downloading) { 1.1322 + debug("app is already downloading. Ignoring."); 1.1323 + return; 1.1324 + } 1.1325 + 1.1326 + // If the caller is trying to start a download but we have nothing to 1.1327 + // download, send an error. 1.1328 + if (!app.downloadAvailable) { 1.1329 + this.broadcastMessage("Webapps:UpdateState", { 1.1330 + error: "NO_DOWNLOAD_AVAILABLE", 1.1331 + manifestURL: app.manifestURL 1.1332 + }); 1.1333 + this.broadcastMessage("Webapps:FireEvent", { 1.1334 + eventType: "downloaderror", 1.1335 + manifestURL: app.manifestURL 1.1336 + }); 1.1337 + return; 1.1338 + } 1.1339 + 1.1340 + // First of all, we check if the download is supposed to update an 1.1341 + // already installed application. 1.1342 + let isUpdate = (app.installState == "installed"); 1.1343 + 1.1344 + // An app download would only be triggered for two reasons: an app 1.1345 + // update or while retrying to download a previously failed or canceled 1.1346 + // instalation. 1.1347 + app.retryingDownload = !isUpdate; 1.1348 + 1.1349 + // We need to get the update manifest here, not the webapp manifest. 1.1350 + // If this is an update, the update manifest is staged. 1.1351 + let file = FileUtils.getFile(DIRECTORY_NAME, 1.1352 + ["webapps", id, 1.1353 + isUpdate ? "staged-update.webapp" 1.1354 + : "update.webapp"], 1.1355 + true); 1.1356 + 1.1357 + if (!file.exists()) { 1.1358 + // This is a hosted app, let's check if it has an appcache 1.1359 + // and download it. 1.1360 + let results = yield this._readManifests([{ id: id }]); 1.1361 + 1.1362 + let jsonManifest = results[0].manifest; 1.1363 + let manifest = new ManifestHelper(jsonManifest, app.origin); 1.1364 + 1.1365 + if (manifest.appcache_path) { 1.1366 + debug("appcache found"); 1.1367 + this.startOfflineCacheDownload(manifest, app, null, isUpdate); 1.1368 + } else { 1.1369 + // Hosted app with no appcache, nothing to do, but we fire a 1.1370 + // downloaded event. 1.1371 + debug("No appcache found, sending 'downloaded' for " + aManifestURL); 1.1372 + app.downloadAvailable = false; 1.1373 + 1.1374 + yield this._saveApps(); 1.1375 + 1.1376 + this.broadcastMessage("Webapps:UpdateState", { 1.1377 + app: app, 1.1378 + manifest: jsonManifest, 1.1379 + manifestURL: aManifestURL 1.1380 + }); 1.1381 + this.broadcastMessage("Webapps:FireEvent", { 1.1382 + eventType: "downloadsuccess", 1.1383 + manifestURL: aManifestURL 1.1384 + }); 1.1385 + } 1.1386 + 1.1387 + return; 1.1388 + } 1.1389 + 1.1390 + let json = yield AppsUtils.loadJSONAsync(file.path); 1.1391 + if (!json) { 1.1392 + debug("startDownload: No update manifest found at " + file.path + " " + 1.1393 + aManifestURL); 1.1394 + return; 1.1395 + } 1.1396 + 1.1397 + let manifest = new ManifestHelper(json, app.manifestURL); 1.1398 + let [aId, aManifest] = yield this.downloadPackage(manifest, { 1.1399 + manifestURL: aManifestURL, 1.1400 + origin: app.origin, 1.1401 + installOrigin: app.installOrigin, 1.1402 + downloadSize: app.downloadSize 1.1403 + }, isUpdate); 1.1404 + 1.1405 + // Success! Keep the zip in of TmpD, we'll move it out when 1.1406 + // applyDownload() will be called. 1.1407 + // Save the manifest in TmpD also 1.1408 + let manFile = OS.Path.join(OS.Constants.Path.tmpDir, "webapps", aId, 1.1409 + "manifest.webapp"); 1.1410 + yield this._writeFile(manFile, JSON.stringify(aManifest)); 1.1411 + 1.1412 + app = this.webapps[aId]; 1.1413 + // Set state and fire events. 1.1414 + app.downloading = false; 1.1415 + app.downloadAvailable = false; 1.1416 + app.readyToApplyDownload = true; 1.1417 + app.updateTime = Date.now(); 1.1418 + 1.1419 + yield this._saveApps(); 1.1420 + 1.1421 + this.broadcastMessage("Webapps:UpdateState", { 1.1422 + app: app, 1.1423 + manifestURL: aManifestURL 1.1424 + }); 1.1425 + this.broadcastMessage("Webapps:FireEvent", { 1.1426 + eventType: "downloadsuccess", 1.1427 + manifestURL: aManifestURL 1.1428 + }); 1.1429 + if (app.installState == "pending") { 1.1430 + // We restarted a failed download, apply it automatically. 1.1431 + this.applyDownload(aManifestURL); 1.1432 + } 1.1433 + }), 1.1434 + 1.1435 + applyDownload: function applyDownload(aManifestURL) { 1.1436 + debug("applyDownload for " + aManifestURL); 1.1437 + let id = this._appIdForManifestURL(aManifestURL); 1.1438 + let app = this.webapps[id]; 1.1439 + if (!app || (app && !app.readyToApplyDownload)) { 1.1440 + return; 1.1441 + } 1.1442 + 1.1443 + // We need to get the old manifest to unregister web activities. 1.1444 + this.getManifestFor(aManifestURL).then((aOldManifest) => { 1.1445 + // Move the application.zip and manifest.webapp files out of TmpD 1.1446 + let tmpDir = FileUtils.getDir("TmpD", ["webapps", id], true, true); 1.1447 + let manFile = tmpDir.clone(); 1.1448 + manFile.append("manifest.webapp"); 1.1449 + let appFile = tmpDir.clone(); 1.1450 + appFile.append("application.zip"); 1.1451 + 1.1452 + let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true); 1.1453 + appFile.moveTo(dir, "application.zip"); 1.1454 + manFile.moveTo(dir, "manifest.webapp"); 1.1455 + 1.1456 + // Move the staged update manifest to a non staged one. 1.1457 + let staged = dir.clone(); 1.1458 + staged.append("staged-update.webapp"); 1.1459 + 1.1460 + // If we are applying after a restarted download, we have no 1.1461 + // staged update manifest. 1.1462 + if (staged.exists()) { 1.1463 + staged.moveTo(dir, "update.webapp"); 1.1464 + } 1.1465 + 1.1466 + try { 1.1467 + tmpDir.remove(true); 1.1468 + } catch(e) { } 1.1469 + 1.1470 + // Clean up the deprecated manifest cache if needed. 1.1471 + if (id in this._manifestCache) { 1.1472 + delete this._manifestCache[id]; 1.1473 + } 1.1474 + 1.1475 + // Flush the zip reader cache to make sure we use the new application.zip 1.1476 + // when re-launching the application. 1.1477 + let zipFile = dir.clone(); 1.1478 + zipFile.append("application.zip"); 1.1479 + Services.obs.notifyObservers(zipFile, "flush-cache-entry", null); 1.1480 + 1.1481 + // Get the manifest, and set properties. 1.1482 + this.getManifestFor(aManifestURL).then((aData) => { 1.1483 + app.downloading = false; 1.1484 + app.downloadAvailable = false; 1.1485 + app.downloadSize = 0; 1.1486 + app.installState = "installed"; 1.1487 + app.readyToApplyDownload = false; 1.1488 + 1.1489 + // Update the staged properties. 1.1490 + if (app.staged) { 1.1491 + for (let prop in app.staged) { 1.1492 + app[prop] = app.staged[prop]; 1.1493 + } 1.1494 + delete app.staged; 1.1495 + } 1.1496 + 1.1497 + delete app.retryingDownload; 1.1498 + 1.1499 + // Update the asm.js scripts we need to compile. 1.1500 + ScriptPreloader.preload(app, aData) 1.1501 + .then(() => this._saveApps()).then(() => { 1.1502 + // Update the handlers and permissions for this app. 1.1503 + this.updateAppHandlers(aOldManifest, aData, app); 1.1504 + 1.1505 + AppsUtils.loadJSONAsync(staged.path).then((aUpdateManifest) => { 1.1506 + let appObject = AppsUtils.cloneAppObject(app); 1.1507 + appObject.updateManifest = aUpdateManifest; 1.1508 + this.notifyUpdateHandlers(appObject, aData, appFile.path); 1.1509 + }); 1.1510 + 1.1511 + if (supportUseCurrentProfile()) { 1.1512 + PermissionsInstaller.installPermissions( 1.1513 + { manifest: aData, 1.1514 + origin: app.origin, 1.1515 + manifestURL: app.manifestURL }, 1.1516 + true); 1.1517 + } 1.1518 + this.updateDataStore(this.webapps[id].localId, app.origin, 1.1519 + app.manifestURL, aData, app.appStatus); 1.1520 + this.broadcastMessage("Webapps:UpdateState", { 1.1521 + app: app, 1.1522 + manifest: aData, 1.1523 + manifestURL: app.manifestURL 1.1524 + }); 1.1525 + this.broadcastMessage("Webapps:FireEvent", { 1.1526 + eventType: "downloadapplied", 1.1527 + manifestURL: app.manifestURL 1.1528 + }); 1.1529 + }); 1.1530 + }); 1.1531 + }); 1.1532 + }, 1.1533 + 1.1534 + startOfflineCacheDownload: function(aManifest, aApp, aProfileDir, aIsUpdate) { 1.1535 + if (!aManifest.appcache_path) { 1.1536 + return; 1.1537 + } 1.1538 + 1.1539 + // If the manifest has an appcache_path property, use it to populate the 1.1540 + // appcache. 1.1541 + let appcacheURI = Services.io.newURI(aManifest.fullAppcachePath(), 1.1542 + null, null); 1.1543 + let docURI = Services.io.newURI(aManifest.fullLaunchPath(), null, null); 1.1544 + 1.1545 + // We determine the app's 'installState' according to its previous 1.1546 + // state. Cancelled downloads should remain as 'pending'. Successfully 1.1547 + // installed apps should morph to 'updating'. 1.1548 + if (aIsUpdate) { 1.1549 + aApp.installState = "updating"; 1.1550 + } 1.1551 + 1.1552 + // We set the 'downloading' flag and update the apps registry right before 1.1553 + // starting the app download/update. 1.1554 + aApp.downloading = true; 1.1555 + aApp.progress = 0; 1.1556 + DOMApplicationRegistry._saveApps().then(() => { 1.1557 + DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { 1.1558 + app: { 1.1559 + downloading: true, 1.1560 + installState: aApp.installState, 1.1561 + progress: 0 1.1562 + }, 1.1563 + manifestURL: aApp.manifestURL 1.1564 + }); 1.1565 + let cacheUpdate = updateSvc.scheduleAppUpdate( 1.1566 + appcacheURI, docURI, aApp.localId, false, aProfileDir); 1.1567 + 1.1568 + // We save the download details for potential further usage like 1.1569 + // cancelling it. 1.1570 + let download = { 1.1571 + cacheUpdate: cacheUpdate, 1.1572 + appId: this._appIdForManifestURL(aApp.manifestURL), 1.1573 + previousState: aIsUpdate ? "installed" : "pending" 1.1574 + }; 1.1575 + AppDownloadManager.add(aApp.manifestURL, download); 1.1576 + 1.1577 + cacheUpdate.addObserver(new AppcacheObserver(aApp), false); 1.1578 + 1.1579 + }); 1.1580 + }, 1.1581 + 1.1582 + // Returns the MD5 hash of the manifest. 1.1583 + computeManifestHash: function(aManifest) { 1.1584 + return AppsUtils.computeHash(JSON.stringify(aManifest)); 1.1585 + }, 1.1586 + 1.1587 + // Updates the redirect mapping, activities and system message handlers. 1.1588 + // aOldManifest can be null if we don't have any handler to unregister. 1.1589 + updateAppHandlers: function(aOldManifest, aNewManifest, aApp) { 1.1590 + debug("updateAppHandlers: old=" + aOldManifest + " new=" + aNewManifest); 1.1591 + this.notifyAppsRegistryStart(); 1.1592 + if (aApp.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED) { 1.1593 + aApp.redirects = this.sanitizeRedirects(aNewManifest.redirects); 1.1594 + } 1.1595 + 1.1596 + if (supportSystemMessages()) { 1.1597 + if (aOldManifest) { 1.1598 + this._unregisterActivities(aOldManifest, aApp); 1.1599 + } 1.1600 + this._registerSystemMessages(aNewManifest, aApp); 1.1601 + this._registerActivities(aNewManifest, aApp, true); 1.1602 + this._registerInterAppConnections(aNewManifest, aApp); 1.1603 + } else { 1.1604 + // Nothing else to do but notifying we're ready. 1.1605 + this.notifyAppsRegistryReady(); 1.1606 + } 1.1607 + }, 1.1608 + 1.1609 + checkForUpdate: function(aData, aMm) { 1.1610 + debug("checkForUpdate for " + aData.manifestURL); 1.1611 + 1.1612 + function sendError(aError) { 1.1613 + aData.error = aError; 1.1614 + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); 1.1615 + } 1.1616 + 1.1617 + let id = this._appIdForManifestURL(aData.manifestURL); 1.1618 + let app = this.webapps[id]; 1.1619 + 1.1620 + // We cannot update an app that does not exists. 1.1621 + if (!app) { 1.1622 + sendError("NO_SUCH_APP"); 1.1623 + return; 1.1624 + } 1.1625 + 1.1626 + // We cannot update an app that is not fully installed. 1.1627 + if (app.installState !== "installed") { 1.1628 + sendError("PENDING_APP_NOT_UPDATABLE"); 1.1629 + return; 1.1630 + } 1.1631 + 1.1632 + // We may be able to remove this when Bug 839071 is fixed. 1.1633 + if (app.downloading) { 1.1634 + sendError("APP_IS_DOWNLOADING"); 1.1635 + return; 1.1636 + } 1.1637 + 1.1638 + // If the app is packaged and its manifestURL has an app:// scheme, 1.1639 + // then we can't have an update. 1.1640 + if (app.origin.startsWith("app://") && 1.1641 + app.manifestURL.startsWith("app://")) { 1.1642 + aData.error = "NOT_UPDATABLE"; 1.1643 + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); 1.1644 + return; 1.1645 + } 1.1646 + 1.1647 + // For non-removable hosted apps that lives in the core apps dir we 1.1648 + // only check the appcache because we can't modify the manifest even 1.1649 + // if it has changed. 1.1650 + let onlyCheckAppCache = false; 1.1651 + 1.1652 +#ifdef MOZ_WIDGET_GONK 1.1653 + let appDir = FileUtils.getDir("coreAppsDir", ["webapps"], false); 1.1654 + onlyCheckAppCache = (app.basePath == appDir.path); 1.1655 +#endif 1.1656 + 1.1657 + if (onlyCheckAppCache) { 1.1658 + // Bail out for packaged apps. 1.1659 + if (app.origin.startsWith("app://")) { 1.1660 + aData.error = "NOT_UPDATABLE"; 1.1661 + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); 1.1662 + return; 1.1663 + } 1.1664 + 1.1665 + // We need the manifest to check if we have an appcache. 1.1666 + this._readManifests([{ id: id }]).then((aResult) => { 1.1667 + let manifest = aResult[0].manifest; 1.1668 + if (!manifest.appcache_path) { 1.1669 + aData.error = "NOT_UPDATABLE"; 1.1670 + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); 1.1671 + return; 1.1672 + } 1.1673 + 1.1674 + debug("Checking only appcache for " + aData.manifestURL); 1.1675 + // Check if the appcache is updatable, and send "downloadavailable" or 1.1676 + // "downloadapplied". 1.1677 + let updateObserver = { 1.1678 + observe: function(aSubject, aTopic, aObsData) { 1.1679 + debug("onlyCheckAppCache updateSvc.checkForUpdate return for " + 1.1680 + app.manifestURL + " - event is " + aTopic); 1.1681 + if (aTopic == "offline-cache-update-available") { 1.1682 + app.downloadAvailable = true; 1.1683 + this._saveApps().then(() => { 1.1684 + this.broadcastMessage("Webapps:UpdateState", { 1.1685 + app: app, 1.1686 + manifestURL: app.manifestURL 1.1687 + }); 1.1688 + this.broadcastMessage("Webapps:FireEvent", { 1.1689 + eventType: "downloadavailable", 1.1690 + manifestURL: app.manifestURL, 1.1691 + requestID: aData.requestID 1.1692 + }); 1.1693 + }); 1.1694 + } else { 1.1695 + aData.error = "NOT_UPDATABLE"; 1.1696 + aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData); 1.1697 + } 1.1698 + } 1.1699 + }; 1.1700 + let helper = new ManifestHelper(manifest, aData.manifestURL); 1.1701 + debug("onlyCheckAppCache - launch updateSvc.checkForUpdate for " + 1.1702 + helper.fullAppcachePath()); 1.1703 + updateSvc.checkForUpdate(Services.io.newURI(helper.fullAppcachePath(), null, null), 1.1704 + app.localId, false, updateObserver); 1.1705 + }); 1.1706 + return; 1.1707 + } 1.1708 + 1.1709 + // On xhr load request event 1.1710 + function onload(xhr, oldManifest) { 1.1711 + debug("Got http status=" + xhr.status + " for " + aData.manifestURL); 1.1712 + let oldHash = app.manifestHash; 1.1713 + let isPackage = app.origin.startsWith("app://"); 1.1714 + 1.1715 + if (xhr.status == 200) { 1.1716 + let manifest = xhr.response; 1.1717 + if (manifest == null) { 1.1718 + sendError("MANIFEST_PARSE_ERROR"); 1.1719 + return; 1.1720 + } 1.1721 + 1.1722 + if (!AppsUtils.checkManifest(manifest, app)) { 1.1723 + sendError("INVALID_MANIFEST"); 1.1724 + return; 1.1725 + } else if (!AppsUtils.checkInstallAllowed(manifest, app.installOrigin)) { 1.1726 + sendError("INSTALL_FROM_DENIED"); 1.1727 + return; 1.1728 + } else { 1.1729 + AppsUtils.ensureSameAppName(oldManifest, manifest, app); 1.1730 + 1.1731 + let hash = this.computeManifestHash(manifest); 1.1732 + debug("Manifest hash = " + hash); 1.1733 + if (isPackage) { 1.1734 + if (!app.staged) { 1.1735 + app.staged = { }; 1.1736 + } 1.1737 + app.staged.manifestHash = hash; 1.1738 + app.staged.etag = xhr.getResponseHeader("Etag"); 1.1739 + } else { 1.1740 + app.manifestHash = hash; 1.1741 + app.etag = xhr.getResponseHeader("Etag"); 1.1742 + } 1.1743 + 1.1744 + app.lastCheckedUpdate = Date.now(); 1.1745 + if (isPackage) { 1.1746 + if (oldHash != hash) { 1.1747 + this.updatePackagedApp(aData, id, app, manifest); 1.1748 + } else { 1.1749 + this._saveApps().then(() => { 1.1750 + // Like if we got a 304, just send a 'downloadapplied' 1.1751 + // or downloadavailable event. 1.1752 + let eventType = app.downloadAvailable ? "downloadavailable" 1.1753 + : "downloadapplied"; 1.1754 + aMm.sendAsyncMessage("Webapps:UpdateState", { 1.1755 + app: app, 1.1756 + manifestURL: app.manifestURL 1.1757 + }); 1.1758 + aMm.sendAsyncMessage("Webapps:FireEvent", { 1.1759 + eventType: eventType, 1.1760 + manifestURL: app.manifestURL, 1.1761 + requestID: aData.requestID 1.1762 + }); 1.1763 + }); 1.1764 + } 1.1765 + } else { 1.1766 + // Update only the appcache if the manifest has not changed 1.1767 + // based on the hash value. 1.1768 + this.updateHostedApp(aData, id, app, oldManifest, 1.1769 + oldHash == hash ? null : manifest); 1.1770 + } 1.1771 + } 1.1772 + } else if (xhr.status == 304) { 1.1773 + // The manifest has not changed. 1.1774 + if (isPackage) { 1.1775 + app.lastCheckedUpdate = Date.now(); 1.1776 + this._saveApps().then(() => { 1.1777 + // If the app is a packaged app, we just send a 'downloadapplied' 1.1778 + // or downloadavailable event. 1.1779 + let eventType = app.downloadAvailable ? "downloadavailable" 1.1780 + : "downloadapplied"; 1.1781 + aMm.sendAsyncMessage("Webapps:UpdateState", { 1.1782 + app: app, 1.1783 + manifestURL: app.manifestURL 1.1784 + }); 1.1785 + aMm.sendAsyncMessage("Webapps:FireEvent", { 1.1786 + eventType: eventType, 1.1787 + manifestURL: app.manifestURL, 1.1788 + requestID: aData.requestID 1.1789 + }); 1.1790 + }); 1.1791 + } else { 1.1792 + // For hosted apps, even if the manifest has not changed, we check 1.1793 + // for offline cache updates. 1.1794 + this.updateHostedApp(aData, id, app, oldManifest, null); 1.1795 + } 1.1796 + } else { 1.1797 + sendError("MANIFEST_URL_ERROR"); 1.1798 + } 1.1799 + } 1.1800 + 1.1801 + // Try to download a new manifest. 1.1802 + function doRequest(oldManifest, headers) { 1.1803 + headers = headers || []; 1.1804 + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] 1.1805 + .createInstance(Ci.nsIXMLHttpRequest); 1.1806 + xhr.open("GET", aData.manifestURL, true); 1.1807 + xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; 1.1808 + headers.forEach(function(aHeader) { 1.1809 + debug("Adding header: " + aHeader.name + ": " + aHeader.value); 1.1810 + xhr.setRequestHeader(aHeader.name, aHeader.value); 1.1811 + }); 1.1812 + xhr.responseType = "json"; 1.1813 + if (app.etag) { 1.1814 + debug("adding manifest etag:" + app.etag); 1.1815 + xhr.setRequestHeader("If-None-Match", app.etag); 1.1816 + } 1.1817 + xhr.channel.notificationCallbacks = 1.1818 + this.createLoadContext(app.installerAppId, app.installerIsBrowser); 1.1819 + 1.1820 + xhr.addEventListener("load", onload.bind(this, xhr, oldManifest), false); 1.1821 + xhr.addEventListener("error", (function() { 1.1822 + sendError("NETWORK_ERROR"); 1.1823 + }).bind(this), false); 1.1824 + 1.1825 + debug("Checking manifest at " + aData.manifestURL); 1.1826 + xhr.send(null); 1.1827 + } 1.1828 + 1.1829 + // Read the current app manifest file 1.1830 + this._readManifests([{ id: id }]).then((aResult) => { 1.1831 + let extraHeaders = []; 1.1832 +#ifdef MOZ_WIDGET_GONK 1.1833 + let pingManifestURL; 1.1834 + try { 1.1835 + pingManifestURL = Services.prefs.getCharPref("ping.manifestURL"); 1.1836 + } catch(e) { } 1.1837 + 1.1838 + if (pingManifestURL && pingManifestURL == aData.manifestURL) { 1.1839 + // Get the device info. 1.1840 + let device = libcutils.property_get("ro.product.model"); 1.1841 + extraHeaders.push({ name: "X-MOZ-B2G-DEVICE", 1.1842 + value: device || "unknown" }); 1.1843 + } 1.1844 +#endif 1.1845 + doRequest.call(this, aResult[0].manifest, extraHeaders); 1.1846 + }); 1.1847 + }, 1.1848 + 1.1849 + // Creates a nsILoadContext object with a given appId and isBrowser flag. 1.1850 + createLoadContext: function createLoadContext(aAppId, aIsBrowser) { 1.1851 + return { 1.1852 + associatedWindow: null, 1.1853 + topWindow : null, 1.1854 + appId: aAppId, 1.1855 + isInBrowserElement: aIsBrowser, 1.1856 + usePrivateBrowsing: false, 1.1857 + isContent: false, 1.1858 + 1.1859 + isAppOfType: function(appType) { 1.1860 + throw Cr.NS_ERROR_NOT_IMPLEMENTED; 1.1861 + }, 1.1862 + 1.1863 + QueryInterface: XPCOMUtils.generateQI([Ci.nsILoadContext, 1.1864 + Ci.nsIInterfaceRequestor, 1.1865 + Ci.nsISupports]), 1.1866 + getInterface: function(iid) { 1.1867 + if (iid.equals(Ci.nsILoadContext)) 1.1868 + return this; 1.1869 + throw Cr.NS_ERROR_NO_INTERFACE; 1.1870 + } 1.1871 + } 1.1872 + }, 1.1873 + 1.1874 + updatePackagedApp: Task.async(function*(aData, aId, aApp, aNewManifest) { 1.1875 + debug("updatePackagedApp"); 1.1876 + 1.1877 + // Store the new update manifest. 1.1878 + let dir = this._getAppDir(aId).path; 1.1879 + let manFile = OS.Path.join(dir, "staged-update.webapp"); 1.1880 + yield this._writeFile(manFile, JSON.stringify(aNewManifest)); 1.1881 + 1.1882 + let manifest = new ManifestHelper(aNewManifest, aApp.manifestURL); 1.1883 + // A package is available: set downloadAvailable to fire the matching 1.1884 + // event. 1.1885 + aApp.downloadAvailable = true; 1.1886 + aApp.downloadSize = manifest.size; 1.1887 + aApp.updateManifest = aNewManifest; 1.1888 + yield this._saveApps(); 1.1889 + 1.1890 + this.broadcastMessage("Webapps:UpdateState", { 1.1891 + app: aApp, 1.1892 + manifestURL: aApp.manifestURL 1.1893 + }); 1.1894 + this.broadcastMessage("Webapps:FireEvent", { 1.1895 + eventType: "downloadavailable", 1.1896 + manifestURL: aApp.manifestURL, 1.1897 + requestID: aData.requestID 1.1898 + }); 1.1899 + }), 1.1900 + 1.1901 + // A hosted app is updated if the app manifest or the appcache needs 1.1902 + // updating. Even if the app manifest has not changed, we still check 1.1903 + // for changes in the app cache. 1.1904 + // 'aNewManifest' would contain the updated app manifest if 1.1905 + // it has actually been updated, while 'aOldManifest' contains the 1.1906 + // stored app manifest. 1.1907 + updateHostedApp: Task.async(function*(aData, aId, aApp, aOldManifest, aNewManifest) { 1.1908 + debug("updateHostedApp " + aData.manifestURL); 1.1909 + 1.1910 + // Clean up the deprecated manifest cache if needed. 1.1911 + if (aId in this._manifestCache) { 1.1912 + delete this._manifestCache[aId]; 1.1913 + } 1.1914 + 1.1915 + aApp.manifest = aNewManifest || aOldManifest; 1.1916 + 1.1917 + let manifest; 1.1918 + if (aNewManifest) { 1.1919 + this.updateAppHandlers(aOldManifest, aNewManifest, aApp); 1.1920 + 1.1921 + this.notifyUpdateHandlers(AppsUtils.cloneAppObject(aApp), aNewManifest); 1.1922 + 1.1923 + // Store the new manifest. 1.1924 + let dir = this._getAppDir(aId).path; 1.1925 + let manFile = OS.Path.join(dir, "manifest.webapp"); 1.1926 + yield this._writeFile(manFile, JSON.stringify(aNewManifest)); 1.1927 + 1.1928 + manifest = new ManifestHelper(aNewManifest, aApp.origin); 1.1929 + 1.1930 + if (supportUseCurrentProfile()) { 1.1931 + // Update the permissions for this app. 1.1932 + PermissionsInstaller.installPermissions({ 1.1933 + manifest: aApp.manifest, 1.1934 + origin: aApp.origin, 1.1935 + manifestURL: aData.manifestURL 1.1936 + }, true); 1.1937 + } 1.1938 + 1.1939 + this.updateDataStore(this.webapps[aId].localId, aApp.origin, 1.1940 + aApp.manifestURL, aApp.manifest, aApp.appStatus); 1.1941 + 1.1942 + aApp.name = manifest.name; 1.1943 + aApp.csp = manifest.csp || ""; 1.1944 + aApp.role = manifest.role || ""; 1.1945 + aApp.updateTime = Date.now(); 1.1946 + } else { 1.1947 + manifest = new ManifestHelper(aOldManifest, aApp.origin); 1.1948 + } 1.1949 + 1.1950 + // Update the registry. 1.1951 + this.webapps[aId] = aApp; 1.1952 + yield this._saveApps(); 1.1953 + 1.1954 + if (!manifest.appcache_path) { 1.1955 + this.broadcastMessage("Webapps:UpdateState", { 1.1956 + app: aApp, 1.1957 + manifest: aApp.manifest, 1.1958 + manifestURL: aApp.manifestURL 1.1959 + }); 1.1960 + this.broadcastMessage("Webapps:FireEvent", { 1.1961 + eventType: "downloadapplied", 1.1962 + manifestURL: aApp.manifestURL, 1.1963 + requestID: aData.requestID 1.1964 + }); 1.1965 + } else { 1.1966 + // Check if the appcache is updatable, and send "downloadavailable" or 1.1967 + // "downloadapplied". 1.1968 + debug("updateHostedApp: updateSvc.checkForUpdate for " + 1.1969 + manifest.fullAppcachePath()); 1.1970 + 1.1971 + let updateDeferred = Promise.defer(); 1.1972 + 1.1973 + updateSvc.checkForUpdate(Services.io.newURI(manifest.fullAppcachePath(), null, null), 1.1974 + aApp.localId, false, 1.1975 + (aSubject, aTopic, aData) => updateDeferred.resolve(aTopic)); 1.1976 + 1.1977 + let topic = yield updateDeferred.promise; 1.1978 + 1.1979 + debug("updateHostedApp: updateSvc.checkForUpdate return for " + 1.1980 + aApp.manifestURL + " - event is " + topic); 1.1981 + 1.1982 + let eventType = 1.1983 + topic == "offline-cache-update-available" ? "downloadavailable" 1.1984 + : "downloadapplied"; 1.1985 + 1.1986 + aApp.downloadAvailable = (eventType == "downloadavailable"); 1.1987 + yield this._saveApps(); 1.1988 + 1.1989 + this.broadcastMessage("Webapps:UpdateState", { 1.1990 + app: aApp, 1.1991 + manifest: aApp.manifest, 1.1992 + manifestURL: aApp.manifestURL 1.1993 + }); 1.1994 + this.broadcastMessage("Webapps:FireEvent", { 1.1995 + eventType: eventType, 1.1996 + manifestURL: aApp.manifestURL, 1.1997 + requestID: aData.requestID 1.1998 + }); 1.1999 + } 1.2000 + 1.2001 + delete aApp.manifest; 1.2002 + }), 1.2003 + 1.2004 + // Downloads the manifest and run checks, then eventually triggers the 1.2005 + // installation UI. 1.2006 + doInstall: function doInstall(aData, aMm) { 1.2007 + let app = aData.app; 1.2008 + 1.2009 + let sendError = function sendError(aError) { 1.2010 + aData.error = aError; 1.2011 + aMm.sendAsyncMessage("Webapps:Install:Return:KO", aData); 1.2012 + Cu.reportError("Error installing app from: " + app.installOrigin + 1.2013 + ": " + aError); 1.2014 + }.bind(this); 1.2015 + 1.2016 + if (app.receipts.length > 0) { 1.2017 + for (let receipt of app.receipts) { 1.2018 + let error = this.isReceipt(receipt); 1.2019 + if (error) { 1.2020 + sendError(error); 1.2021 + return; 1.2022 + } 1.2023 + } 1.2024 + } 1.2025 + 1.2026 + // Hosted apps can't be trusted or certified, so just check that the 1.2027 + // manifest doesn't ask for those. 1.2028 + function checkAppStatus(aManifest) { 1.2029 + let manifestStatus = aManifest.type || "web"; 1.2030 + return manifestStatus === "web"; 1.2031 + } 1.2032 + 1.2033 + let checkManifest = (function() { 1.2034 + if (!app.manifest) { 1.2035 + sendError("MANIFEST_PARSE_ERROR"); 1.2036 + return false; 1.2037 + } 1.2038 + 1.2039 + // Disallow multiple hosted apps installations from the same origin for now. 1.2040 + // We will remove this code after multiple apps per origin are supported (bug 778277). 1.2041 + // This will also disallow reinstalls from the same origin for now. 1.2042 + for (let id in this.webapps) { 1.2043 + if (this.webapps[id].origin == app.origin && 1.2044 + !this.webapps[id].packageHash && 1.2045 + this._isLaunchable(this.webapps[id])) { 1.2046 + sendError("MULTIPLE_APPS_PER_ORIGIN_FORBIDDEN"); 1.2047 + return false; 1.2048 + } 1.2049 + } 1.2050 + 1.2051 + if (!AppsUtils.checkManifest(app.manifest, app)) { 1.2052 + sendError("INVALID_MANIFEST"); 1.2053 + return false; 1.2054 + } 1.2055 + 1.2056 + if (!AppsUtils.checkInstallAllowed(app.manifest, app.installOrigin)) { 1.2057 + sendError("INSTALL_FROM_DENIED"); 1.2058 + return false; 1.2059 + } 1.2060 + 1.2061 + if (!checkAppStatus(app.manifest)) { 1.2062 + sendError("INVALID_SECURITY_LEVEL"); 1.2063 + return false; 1.2064 + } 1.2065 + 1.2066 + return true; 1.2067 + }).bind(this); 1.2068 + 1.2069 + let installApp = (function() { 1.2070 + app.manifestHash = this.computeManifestHash(app.manifest); 1.2071 + // We allow bypassing the install confirmation process to facilitate 1.2072 + // automation. 1.2073 + let prefName = "dom.mozApps.auto_confirm_install"; 1.2074 + if (Services.prefs.prefHasUserValue(prefName) && 1.2075 + Services.prefs.getBoolPref(prefName)) { 1.2076 + this.confirmInstall(aData); 1.2077 + } else { 1.2078 + Services.obs.notifyObservers(aMm, "webapps-ask-install", 1.2079 + JSON.stringify(aData)); 1.2080 + } 1.2081 + }).bind(this); 1.2082 + 1.2083 + // We may already have the manifest (e.g. AutoInstall), 1.2084 + // in which case we don't need to load it. 1.2085 + if (app.manifest) { 1.2086 + if (checkManifest()) { 1.2087 + installApp(); 1.2088 + } 1.2089 + return; 1.2090 + } 1.2091 + 1.2092 + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] 1.2093 + .createInstance(Ci.nsIXMLHttpRequest); 1.2094 + xhr.open("GET", app.manifestURL, true); 1.2095 + xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; 1.2096 + xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId, 1.2097 + aData.isBrowser); 1.2098 + xhr.responseType = "json"; 1.2099 + 1.2100 + xhr.addEventListener("load", (function() { 1.2101 + if (xhr.status == 200) { 1.2102 + if (!AppsUtils.checkManifestContentType(app.installOrigin, app.origin, 1.2103 + xhr.getResponseHeader("content-type"))) { 1.2104 + sendError("INVALID_MANIFEST"); 1.2105 + return; 1.2106 + } 1.2107 + 1.2108 + app.manifest = xhr.response; 1.2109 + if (checkManifest()) { 1.2110 + app.etag = xhr.getResponseHeader("Etag"); 1.2111 + installApp(); 1.2112 + } 1.2113 + } else { 1.2114 + sendError("MANIFEST_URL_ERROR"); 1.2115 + } 1.2116 + }).bind(this), false); 1.2117 + 1.2118 + xhr.addEventListener("error", (function() { 1.2119 + sendError("NETWORK_ERROR"); 1.2120 + }).bind(this), false); 1.2121 + 1.2122 + xhr.send(null); 1.2123 + }, 1.2124 + 1.2125 + doInstallPackage: function doInstallPackage(aData, aMm) { 1.2126 + let app = aData.app; 1.2127 + 1.2128 + let sendError = function sendError(aError) { 1.2129 + aData.error = aError; 1.2130 + aMm.sendAsyncMessage("Webapps:Install:Return:KO", aData); 1.2131 + Cu.reportError("Error installing packaged app from: " + 1.2132 + app.installOrigin + ": " + aError); 1.2133 + }.bind(this); 1.2134 + 1.2135 + if (app.receipts.length > 0) { 1.2136 + for (let receipt of app.receipts) { 1.2137 + let error = this.isReceipt(receipt); 1.2138 + if (error) { 1.2139 + sendError(error); 1.2140 + return; 1.2141 + } 1.2142 + } 1.2143 + } 1.2144 + 1.2145 + let checkUpdateManifest = (function() { 1.2146 + let manifest = app.updateManifest; 1.2147 + 1.2148 + // Disallow reinstalls from the same manifest URL for now. 1.2149 + let id = this._appIdForManifestURL(app.manifestURL); 1.2150 + if (id !== null && this._isLaunchable(this.webapps[id])) { 1.2151 + sendError("REINSTALL_FORBIDDEN"); 1.2152 + return false; 1.2153 + } 1.2154 + 1.2155 + if (!(AppsUtils.checkManifest(manifest, app) && manifest.package_path)) { 1.2156 + sendError("INVALID_MANIFEST"); 1.2157 + return false; 1.2158 + } 1.2159 + 1.2160 + if (!AppsUtils.checkInstallAllowed(manifest, app.installOrigin)) { 1.2161 + sendError("INSTALL_FROM_DENIED"); 1.2162 + return false; 1.2163 + } 1.2164 + 1.2165 + return true; 1.2166 + }).bind(this); 1.2167 + 1.2168 + let installApp = (function() { 1.2169 + app.manifestHash = this.computeManifestHash(app.updateManifest); 1.2170 + 1.2171 + // We allow bypassing the install confirmation process to facilitate 1.2172 + // automation. 1.2173 + let prefName = "dom.mozApps.auto_confirm_install"; 1.2174 + if (Services.prefs.prefHasUserValue(prefName) && 1.2175 + Services.prefs.getBoolPref(prefName)) { 1.2176 + this.confirmInstall(aData); 1.2177 + } else { 1.2178 + Services.obs.notifyObservers(aMm, "webapps-ask-install", 1.2179 + JSON.stringify(aData)); 1.2180 + } 1.2181 + }).bind(this); 1.2182 + 1.2183 + // We may already have the manifest (e.g. AutoInstall), 1.2184 + // in which case we don't need to load it. 1.2185 + if (app.updateManifest) { 1.2186 + if (checkUpdateManifest()) { 1.2187 + installApp(); 1.2188 + } 1.2189 + return; 1.2190 + } 1.2191 + 1.2192 + let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] 1.2193 + .createInstance(Ci.nsIXMLHttpRequest); 1.2194 + xhr.open("GET", app.manifestURL, true); 1.2195 + xhr.channel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; 1.2196 + xhr.channel.notificationCallbacks = this.createLoadContext(aData.appId, 1.2197 + aData.isBrowser); 1.2198 + xhr.responseType = "json"; 1.2199 + 1.2200 + xhr.addEventListener("load", (function() { 1.2201 + if (xhr.status == 200) { 1.2202 + if (!AppsUtils.checkManifestContentType(app.installOrigin, app.origin, 1.2203 + xhr.getResponseHeader("content-type"))) { 1.2204 + sendError("INVALID_MANIFEST"); 1.2205 + return; 1.2206 + } 1.2207 + 1.2208 + app.updateManifest = xhr.response; 1.2209 + if (!app.updateManifest) { 1.2210 + sendError("MANIFEST_PARSE_ERROR"); 1.2211 + return; 1.2212 + } 1.2213 + if (checkUpdateManifest()) { 1.2214 + app.etag = xhr.getResponseHeader("Etag"); 1.2215 + debug("at install package got app etag=" + app.etag); 1.2216 + installApp(); 1.2217 + } 1.2218 + } 1.2219 + else { 1.2220 + sendError("MANIFEST_URL_ERROR"); 1.2221 + } 1.2222 + }).bind(this), false); 1.2223 + 1.2224 + xhr.addEventListener("error", (function() { 1.2225 + sendError("NETWORK_ERROR"); 1.2226 + }).bind(this), false); 1.2227 + 1.2228 + xhr.send(null); 1.2229 + }, 1.2230 + 1.2231 + denyInstall: function(aData) { 1.2232 + let packageId = aData.app.packageId; 1.2233 + if (packageId) { 1.2234 + let dir = FileUtils.getDir("TmpD", ["webapps", packageId], 1.2235 + true, true); 1.2236 + try { 1.2237 + dir.remove(true); 1.2238 + } catch(e) { 1.2239 + } 1.2240 + } 1.2241 + aData.mm.sendAsyncMessage("Webapps:Install:Return:KO", aData); 1.2242 + }, 1.2243 + 1.2244 + // This function is called after we called the onsuccess callback on the 1.2245 + // content side. This let the webpage the opportunity to set event handlers 1.2246 + // on the app before we start firing progress events. 1.2247 + queuedDownload: {}, 1.2248 + queuedPackageDownload: {}, 1.2249 + 1.2250 +onInstallSuccessAck: function onInstallSuccessAck(aManifestURL, 1.2251 + aDontNeedNetwork) { 1.2252 + // If we are offline, register to run when we'll be online. 1.2253 + if ((Services.io.offline) && !aDontNeedNetwork) { 1.2254 + let onlineWrapper = { 1.2255 + observe: function(aSubject, aTopic, aData) { 1.2256 + Services.obs.removeObserver(onlineWrapper, 1.2257 + "network:offline-status-changed"); 1.2258 + DOMApplicationRegistry.onInstallSuccessAck(aManifestURL); 1.2259 + } 1.2260 + }; 1.2261 + Services.obs.addObserver(onlineWrapper, 1.2262 + "network:offline-status-changed", false); 1.2263 + return; 1.2264 + } 1.2265 + 1.2266 + let cacheDownload = this.queuedDownload[aManifestURL]; 1.2267 + if (cacheDownload) { 1.2268 + this.startOfflineCacheDownload(cacheDownload.manifest, 1.2269 + cacheDownload.app, 1.2270 + cacheDownload.profileDir); 1.2271 + delete this.queuedDownload[aManifestURL]; 1.2272 + 1.2273 + return; 1.2274 + } 1.2275 + 1.2276 + let packageDownload = this.queuedPackageDownload[aManifestURL]; 1.2277 + if (packageDownload) { 1.2278 + let manifest = packageDownload.manifest; 1.2279 + let newApp = packageDownload.app; 1.2280 + let installSuccessCallback = packageDownload.callback; 1.2281 + 1.2282 + delete this.queuedPackageDownload[aManifestURL]; 1.2283 + 1.2284 + this.downloadPackage(manifest, newApp, false).then( 1.2285 + this._onDownloadPackage.bind(this, newApp, installSuccessCallback) 1.2286 + ); 1.2287 + } 1.2288 + }, 1.2289 + 1.2290 + _setupApp: function(aData, aId) { 1.2291 + let app = aData.app; 1.2292 + 1.2293 + // app can be uninstalled 1.2294 + app.removable = true; 1.2295 + 1.2296 + if (aData.isPackage) { 1.2297 + // Override the origin with the correct id. 1.2298 + app.origin = "app://" + aId; 1.2299 + } 1.2300 + 1.2301 + app.id = aId; 1.2302 + app.installTime = Date.now(); 1.2303 + app.lastUpdateCheck = Date.now(); 1.2304 + 1.2305 + return app; 1.2306 + }, 1.2307 + 1.2308 + _cloneApp: function(aData, aNewApp, aManifest, aId, aLocalId) { 1.2309 + let appObject = AppsUtils.cloneAppObject(aNewApp); 1.2310 + appObject.appStatus = 1.2311 + aNewApp.appStatus || Ci.nsIPrincipal.APP_STATUS_INSTALLED; 1.2312 + 1.2313 + if (aManifest.appcache_path) { 1.2314 + appObject.installState = "pending"; 1.2315 + appObject.downloadAvailable = true; 1.2316 + appObject.downloading = true; 1.2317 + appObject.downloadSize = 0; 1.2318 + appObject.readyToApplyDownload = false; 1.2319 + } else if (aManifest.package_path) { 1.2320 + appObject.installState = "pending"; 1.2321 + appObject.downloadAvailable = true; 1.2322 + appObject.downloading = true; 1.2323 + appObject.downloadSize = aManifest.size; 1.2324 + appObject.readyToApplyDownload = false; 1.2325 + } else { 1.2326 + appObject.installState = "installed"; 1.2327 + appObject.downloadAvailable = false; 1.2328 + appObject.downloading = false; 1.2329 + appObject.readyToApplyDownload = false; 1.2330 + } 1.2331 + 1.2332 + appObject.localId = aLocalId; 1.2333 + appObject.basePath = OS.Path.dirname(this.appsFile); 1.2334 + appObject.name = aManifest.name; 1.2335 + appObject.csp = aManifest.csp || ""; 1.2336 + appObject.role = aManifest.role || ""; 1.2337 + appObject.installerAppId = aData.appId; 1.2338 + appObject.installerIsBrowser = aData.isBrowser; 1.2339 + 1.2340 + return appObject; 1.2341 + }, 1.2342 + 1.2343 + _writeManifestFile: function(aId, aIsPackage, aJsonManifest) { 1.2344 + debug("_writeManifestFile"); 1.2345 + 1.2346 + // For packaged apps, keep the update manifest distinct from the app manifest. 1.2347 + let manifestName = aIsPackage ? "update.webapp" : "manifest.webapp"; 1.2348 + 1.2349 + let dir = this._getAppDir(aId).path; 1.2350 + let manFile = OS.Path.join(dir, manifestName); 1.2351 + this._writeFile(manFile, JSON.stringify(aJsonManifest)); 1.2352 + }, 1.2353 + 1.2354 + // Add an app that is already installed to the registry. 1.2355 + addInstalledApp: Task.async(function*(aApp, aManifest, aUpdateManifest) { 1.2356 + if (this.getAppLocalIdByManifestURL(aApp.manifestURL) != 1.2357 + Ci.nsIScriptSecurityManager.NO_APP_ID) { 1.2358 + return; 1.2359 + } 1.2360 + 1.2361 + let app = AppsUtils.cloneAppObject(aApp); 1.2362 + 1.2363 + if (!AppsUtils.checkManifest(aManifest, app) || 1.2364 + (aUpdateManifest && !AppsUtils.checkManifest(aUpdateManifest, app))) { 1.2365 + return; 1.2366 + } 1.2367 + 1.2368 + app.name = aManifest.name; 1.2369 + 1.2370 + app.csp = aManifest.csp || ""; 1.2371 + 1.2372 + app.appStatus = AppsUtils.getAppManifestStatus(aManifest); 1.2373 + 1.2374 + app.removable = true; 1.2375 + 1.2376 + // Reuse the app ID if the scheme is "app". 1.2377 + let uri = Services.io.newURI(app.origin, null, null); 1.2378 + if (uri.scheme == "app") { 1.2379 + app.id = uri.host; 1.2380 + } else { 1.2381 + app.id = this.makeAppId(); 1.2382 + } 1.2383 + 1.2384 + app.localId = this._nextLocalId(); 1.2385 + 1.2386 + app.basePath = OS.Path.dirname(this.appsFile); 1.2387 + 1.2388 + app.progress = 0.0; 1.2389 + app.installState = "installed"; 1.2390 + app.downloadAvailable = false; 1.2391 + app.downloading = false; 1.2392 + app.readyToApplyDownload = false; 1.2393 + 1.2394 + if (aUpdateManifest && aUpdateManifest.size) { 1.2395 + app.downloadSize = aUpdateManifest.size; 1.2396 + } 1.2397 + 1.2398 + app.manifestHash = AppsUtils.computeHash(JSON.stringify(aUpdateManifest || 1.2399 + aManifest)); 1.2400 + 1.2401 + let zipFile = WebappOSUtils.getPackagePath(app); 1.2402 + app.packageHash = yield this._computeFileHash(zipFile); 1.2403 + 1.2404 + app.role = aManifest.role || ""; 1.2405 + 1.2406 + app.redirects = this.sanitizeRedirects(aManifest.redirects); 1.2407 + 1.2408 + this.webapps[app.id] = app; 1.2409 + 1.2410 + // Store the manifest in the manifest cache, so we don't need to re-read it 1.2411 + this._manifestCache[app.id] = app.manifest; 1.2412 + 1.2413 + // Store the manifest and the updateManifest. 1.2414 + this._writeManifestFile(app.id, false, aManifest); 1.2415 + if (aUpdateManifest) { 1.2416 + this._writeManifestFile(app.id, true, aUpdateManifest); 1.2417 + } 1.2418 + 1.2419 + this._saveApps().then(() => { 1.2420 + this.broadcastMessage("Webapps:AddApp", { id: app.id, app: app }); 1.2421 + }); 1.2422 + }), 1.2423 + 1.2424 + confirmInstall: function(aData, aProfileDir, aInstallSuccessCallback) { 1.2425 + debug("confirmInstall"); 1.2426 + 1.2427 + let origin = Services.io.newURI(aData.app.origin, null, null); 1.2428 + let id = this._appIdForManifestURL(aData.app.manifestURL); 1.2429 + let manifestURL = origin.resolve(aData.app.manifestURL); 1.2430 + let localId = this.getAppLocalIdByManifestURL(manifestURL); 1.2431 + 1.2432 + let isReinstall = false; 1.2433 + 1.2434 + // Installing an application again is considered as an update. 1.2435 + if (id) { 1.2436 + isReinstall = true; 1.2437 + let dir = this._getAppDir(id); 1.2438 + try { 1.2439 + dir.remove(true); 1.2440 + } catch(e) { } 1.2441 + } else { 1.2442 + id = this.makeAppId(); 1.2443 + localId = this._nextLocalId(); 1.2444 + } 1.2445 + 1.2446 + let app = this._setupApp(aData, id); 1.2447 + 1.2448 + let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest; 1.2449 + this._writeManifestFile(id, aData.isPackage, jsonManifest); 1.2450 + 1.2451 + debug("app.origin: " + app.origin); 1.2452 + let manifest = new ManifestHelper(jsonManifest, app.origin); 1.2453 + 1.2454 + let appObject = this._cloneApp(aData, app, manifest, id, localId); 1.2455 + 1.2456 + this.webapps[id] = appObject; 1.2457 + 1.2458 + // For package apps, the permissions are not in the mini-manifest, so 1.2459 + // don't update the permissions yet. 1.2460 + if (!aData.isPackage) { 1.2461 + if (supportUseCurrentProfile()) { 1.2462 + PermissionsInstaller.installPermissions( 1.2463 + { 1.2464 + origin: appObject.origin, 1.2465 + manifestURL: appObject.manifestURL, 1.2466 + manifest: jsonManifest 1.2467 + }, 1.2468 + isReinstall, 1.2469 + this.uninstall.bind(this, aData, aData.mm) 1.2470 + ); 1.2471 + } 1.2472 + 1.2473 + this.updateDataStore(this.webapps[id].localId, this.webapps[id].origin, 1.2474 + this.webapps[id].manifestURL, jsonManifest, 1.2475 + this.webapps[id].appStatus); 1.2476 + } 1.2477 + 1.2478 + for each (let prop in ["installState", "downloadAvailable", "downloading", 1.2479 + "downloadSize", "readyToApplyDownload"]) { 1.2480 + aData.app[prop] = appObject[prop]; 1.2481 + } 1.2482 + 1.2483 + if (manifest.appcache_path) { 1.2484 + this.queuedDownload[app.manifestURL] = { 1.2485 + manifest: manifest, 1.2486 + app: appObject, 1.2487 + profileDir: aProfileDir 1.2488 + } 1.2489 + } 1.2490 + 1.2491 + // We notify about the successful installation via mgmt.oninstall and the 1.2492 + // corresponging DOMRequest.onsuccess event as soon as the app is properly 1.2493 + // saved in the registry. 1.2494 + this._saveApps().then(() => { 1.2495 + this.broadcastMessage("Webapps:AddApp", { id: id, app: appObject }); 1.2496 + if (aData.isPackage && aData.apkInstall && !aData.requestID) { 1.2497 + // Skip directly to onInstallSuccessAck, since there isn't 1.2498 + // a WebappsRegistry to receive Webapps:Install:Return:OK and respond 1.2499 + // Webapps:Install:Return:Ack when an app is being auto-installed. 1.2500 + this.onInstallSuccessAck(app.manifestURL); 1.2501 + } else { 1.2502 + // Broadcast Webapps:Install:Return:OK so the WebappsRegistry can notify 1.2503 + // the installing page about the successful install, after which it'll 1.2504 + // respond Webapps:Install:Return:Ack, which calls onInstallSuccessAck. 1.2505 + this.broadcastMessage("Webapps:Install:Return:OK", aData); 1.2506 + } 1.2507 + if (!aData.isPackage) { 1.2508 + this.updateAppHandlers(null, app.manifest, app); 1.2509 + if (aInstallSuccessCallback) { 1.2510 + aInstallSuccessCallback(app.manifest); 1.2511 + } 1.2512 + } 1.2513 + Services.obs.notifyObservers(null, "webapps-installed", 1.2514 + JSON.stringify({ manifestURL: app.manifestURL })); 1.2515 + }); 1.2516 + 1.2517 + let dontNeedNetwork = false; 1.2518 + if (manifest.package_path) { 1.2519 + // If it is a local app then it must been installed from a local file 1.2520 + // instead of web. 1.2521 +#ifdef MOZ_ANDROID_SYNTHAPKS 1.2522 + // In that case, we would already have the manifest, not just the update 1.2523 + // manifest. 1.2524 + dontNeedNetwork = !!aData.app.manifest; 1.2525 +#else 1.2526 + if (aData.app.localInstallPath) { 1.2527 + dontNeedNetwork = true; 1.2528 + jsonManifest.package_path = "file://" + aData.app.localInstallPath; 1.2529 + } 1.2530 +#endif 1.2531 + 1.2532 + // origin for install apps is meaningless here, since it's app:// and this 1.2533 + // can't be used to resolve package paths. 1.2534 + manifest = new ManifestHelper(jsonManifest, app.manifestURL); 1.2535 + 1.2536 + this.queuedPackageDownload[app.manifestURL] = { 1.2537 + manifest: manifest, 1.2538 + app: appObject, 1.2539 + callback: aInstallSuccessCallback 1.2540 + }; 1.2541 + } 1.2542 + 1.2543 + if (aData.forceSuccessAck) { 1.2544 + // If it's a local install, there's no content process so just 1.2545 + // ack the install. 1.2546 + this.onInstallSuccessAck(app.manifestURL, dontNeedNetwork); 1.2547 + } 1.2548 + }, 1.2549 + 1.2550 +/** 1.2551 + * Install the package after successfully downloading it 1.2552 + * 1.2553 + * Bound params: 1.2554 + * 1.2555 + * @param aNewApp {Object} the new app data 1.2556 + * @param aInstallSuccessCallback {Function} 1.2557 + * the callback to call on install success 1.2558 + * 1.2559 + * Passed params: 1.2560 + * 1.2561 + * @param aId {Integer} the unique ID of the application 1.2562 + * @param aManifest {Object} The manifest of the application 1.2563 + */ 1.2564 + _onDownloadPackage: Task.async(function*(aNewApp, aInstallSuccessCallback, 1.2565 + [aId, aManifest]) { 1.2566 + debug("_onDownloadPackage"); 1.2567 + // Success! Move the zip out of TmpD. 1.2568 + let app = this.webapps[aId]; 1.2569 + let zipFile = 1.2570 + FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true); 1.2571 + let dir = this._getAppDir(aId); 1.2572 + zipFile.moveTo(dir, "application.zip"); 1.2573 + let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true); 1.2574 + try { 1.2575 + tmpDir.remove(true); 1.2576 + } catch(e) { } 1.2577 + 1.2578 + // Save the manifest 1.2579 + let manFile = OS.Path.join(dir.path, "manifest.webapp"); 1.2580 + yield this._writeFile(manFile, JSON.stringify(aManifest)); 1.2581 + // Set state and fire events. 1.2582 + app.installState = "installed"; 1.2583 + app.downloading = false; 1.2584 + app.downloadAvailable = false; 1.2585 + 1.2586 + yield this._saveApps(); 1.2587 + 1.2588 + this.updateAppHandlers(null, aManifest, aNewApp); 1.2589 + // Clear the manifest cache in case it holds the update manifest. 1.2590 + if (aId in this._manifestCache) { 1.2591 + delete this._manifestCache[aId]; 1.2592 + } 1.2593 + 1.2594 + this.broadcastMessage("Webapps:AddApp", { id: aId, app: aNewApp }); 1.2595 + Services.obs.notifyObservers(null, "webapps-installed", 1.2596 + JSON.stringify({ manifestURL: aNewApp.manifestURL })); 1.2597 + 1.2598 + if (supportUseCurrentProfile()) { 1.2599 + // Update the permissions for this app. 1.2600 + PermissionsInstaller.installPermissions({ 1.2601 + manifest: aManifest, 1.2602 + origin: aNewApp.origin, 1.2603 + manifestURL: aNewApp.manifestURL 1.2604 + }, true); 1.2605 + } 1.2606 + 1.2607 + this.updateDataStore(this.webapps[aId].localId, aNewApp.origin, 1.2608 + aNewApp.manifestURL, aManifest, aNewApp.appStatus); 1.2609 + 1.2610 + this.broadcastMessage("Webapps:UpdateState", { 1.2611 + app: app, 1.2612 + manifest: aManifest, 1.2613 + manifestURL: aNewApp.manifestURL 1.2614 + }); 1.2615 + 1.2616 + // Check if we have asm.js code to preload for this application. 1.2617 + yield ScriptPreloader.preload(aNewApp, aManifest); 1.2618 + 1.2619 + this.broadcastMessage("Webapps:FireEvent", { 1.2620 + eventType: ["downloadsuccess", "downloadapplied"], 1.2621 + manifestURL: aNewApp.manifestURL 1.2622 + }); 1.2623 + 1.2624 + if (aInstallSuccessCallback) { 1.2625 + aInstallSuccessCallback(aManifest, zipFile.path); 1.2626 + } 1.2627 + }), 1.2628 + 1.2629 + _nextLocalId: function() { 1.2630 + let id = Services.prefs.getIntPref("dom.mozApps.maxLocalId") + 1; 1.2631 + 1.2632 + while (this.getManifestURLByLocalId(id)) { 1.2633 + id++; 1.2634 + } 1.2635 + 1.2636 + Services.prefs.setIntPref("dom.mozApps.maxLocalId", id); 1.2637 + Services.prefs.savePrefFile(null); 1.2638 + return id; 1.2639 + }, 1.2640 + 1.2641 + _appIdForManifestURL: function(aURI) { 1.2642 + for (let id in this.webapps) { 1.2643 + if (this.webapps[id].manifestURL == aURI) 1.2644 + return id; 1.2645 + } 1.2646 + return null; 1.2647 + }, 1.2648 + 1.2649 + makeAppId: function() { 1.2650 + let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator); 1.2651 + return uuidGenerator.generateUUID().toString(); 1.2652 + }, 1.2653 + 1.2654 + _saveApps: function() { 1.2655 + return this._writeFile(this.appsFile, JSON.stringify(this.webapps, null, 2)); 1.2656 + }, 1.2657 + 1.2658 + /** 1.2659 + * Asynchronously reads a list of manifests 1.2660 + */ 1.2661 + 1.2662 + _manifestCache: {}, 1.2663 + 1.2664 + _readManifests: function(aData) { 1.2665 + return Task.spawn(function*() { 1.2666 + for (let elem of aData) { 1.2667 + let id = elem.id; 1.2668 + 1.2669 + if (!this._manifestCache[id]) { 1.2670 + // the manifest file used to be named manifest.json, so fallback on this. 1.2671 + let baseDir = this.webapps[id].basePath == this.getCoreAppsBasePath() 1.2672 + ? "coreAppsDir" : DIRECTORY_NAME; 1.2673 + 1.2674 + let dir = FileUtils.getDir(baseDir, ["webapps", id], false, true); 1.2675 + 1.2676 + let fileNames = ["manifest.webapp", "update.webapp", "manifest.json"]; 1.2677 + for (let fileName of fileNames) { 1.2678 + this._manifestCache[id] = yield AppsUtils.loadJSONAsync(OS.Path.join(dir.path, fileName)); 1.2679 + if (this._manifestCache[id]) { 1.2680 + break; 1.2681 + } 1.2682 + } 1.2683 + } 1.2684 + 1.2685 + elem.manifest = this._manifestCache[id]; 1.2686 + } 1.2687 + 1.2688 + return aData; 1.2689 + }.bind(this)).then(null, Cu.reportError); 1.2690 + }, 1.2691 + 1.2692 + downloadPackage: function(aManifest, aNewApp, aIsUpdate, aOnSuccess) { 1.2693 + // Here are the steps when installing a package: 1.2694 + // - create a temp directory where to store the app. 1.2695 + // - download the zip in this directory. 1.2696 + // - check the signature on the zip. 1.2697 + // - extract the manifest from the zip and check it. 1.2698 + // - ask confirmation to the user. 1.2699 + // - add the new app to the registry. 1.2700 + // If we fail at any step, we revert the previous ones and return an error. 1.2701 + 1.2702 + // We define these outside the task to use them in its reject handler. 1.2703 + let id = this._appIdForManifestURL(aNewApp.manifestURL); 1.2704 + let oldApp = this.webapps[id]; 1.2705 + 1.2706 + return Task.spawn((function*() { 1.2707 + yield this._ensureSufficientStorage(aNewApp); 1.2708 + 1.2709 + let fullPackagePath = aManifest.fullPackagePath(); 1.2710 + 1.2711 + // Check if it's a local file install (we've downloaded/sideloaded the 1.2712 + // package already, it existed on the build, or it came with an APK). 1.2713 + // Note that this variable also controls whether files signed with expired 1.2714 + // certificates are accepted or not. If isLocalFileInstall is true and the 1.2715 + // device date is earlier than the build generation date, then the signature 1.2716 + // will be accepted even if the certificate is expired. 1.2717 + let isLocalFileInstall = 1.2718 + Services.io.extractScheme(fullPackagePath) === 'file'; 1.2719 + 1.2720 + debug("About to download " + fullPackagePath); 1.2721 + 1.2722 + let requestChannel = this._getRequestChannel(fullPackagePath, 1.2723 + isLocalFileInstall, 1.2724 + oldApp, 1.2725 + aNewApp); 1.2726 + 1.2727 + AppDownloadManager.add( 1.2728 + aNewApp.manifestURL, 1.2729 + { 1.2730 + channel: requestChannel, 1.2731 + appId: id, 1.2732 + previousState: aIsUpdate ? "installed" : "pending" 1.2733 + } 1.2734 + ); 1.2735 + 1.2736 + // We set the 'downloading' flag to true right before starting the fetch. 1.2737 + oldApp.downloading = true; 1.2738 + 1.2739 + // We determine the app's 'installState' according to its previous 1.2740 + // state. Cancelled download should remain as 'pending'. Successfully 1.2741 + // installed apps should morph to 'updating'. 1.2742 + oldApp.installState = aIsUpdate ? "updating" : "pending"; 1.2743 + 1.2744 + // initialize the progress to 0 right now 1.2745 + oldApp.progress = 0; 1.2746 + 1.2747 + let zipFile = yield this._getPackage(requestChannel, id, oldApp, aNewApp); 1.2748 + let hash = yield this._computeFileHash(zipFile.path); 1.2749 + 1.2750 + let responseStatus = requestChannel.responseStatus; 1.2751 + let oldPackage = (responseStatus == 304 || hash == oldApp.packageHash); 1.2752 + 1.2753 + if (oldPackage) { 1.2754 + debug("package's etag or hash unchanged; sending 'applied' event"); 1.2755 + // The package's Etag or hash has not changed. 1.2756 + // We send a "applied" event right away. 1.2757 + this._sendAppliedEvent(aNewApp, oldApp, id); 1.2758 + return; 1.2759 + } 1.2760 + 1.2761 + let newManifest = yield this._openAndReadPackage(zipFile, oldApp, aNewApp, 1.2762 + isLocalFileInstall, aIsUpdate, aManifest, requestChannel, hash); 1.2763 + 1.2764 + AppDownloadManager.remove(aNewApp.manifestURL); 1.2765 + 1.2766 + return [oldApp.id, newManifest]; 1.2767 + 1.2768 + }).bind(this)).then( 1.2769 + aOnSuccess, 1.2770 + this._revertDownloadPackage.bind(this, id, oldApp, aNewApp, aIsUpdate) 1.2771 + ); 1.2772 + }, 1.2773 + 1.2774 + _ensureSufficientStorage: function(aNewApp) { 1.2775 + let deferred = Promise.defer(); 1.2776 + 1.2777 + let navigator = Services.wm.getMostRecentWindow(chromeWindowType) 1.2778 + .navigator; 1.2779 + let deviceStorage = null; 1.2780 + 1.2781 + if (navigator.getDeviceStorage) { 1.2782 + deviceStorage = navigator.getDeviceStorage("apps"); 1.2783 + } 1.2784 + 1.2785 + if (deviceStorage) { 1.2786 + let req = deviceStorage.freeSpace(); 1.2787 + req.onsuccess = req.onerror = e => { 1.2788 + let freeBytes = e.target.result; 1.2789 + let sufficientStorage = this._checkDownloadSize(freeBytes, aNewApp); 1.2790 + if (sufficientStorage) { 1.2791 + deferred.resolve(); 1.2792 + } else { 1.2793 + deferred.reject("INSUFFICIENT_STORAGE"); 1.2794 + } 1.2795 + } 1.2796 + } else { 1.2797 + debug("No deviceStorage"); 1.2798 + // deviceStorage isn't available, so use FileUtils to find the size of 1.2799 + // available storage. 1.2800 + let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true); 1.2801 + try { 1.2802 + let sufficientStorage = this._checkDownloadSize(dir.diskSpaceAvailable, 1.2803 + aNewApp); 1.2804 + if (sufficientStorage) { 1.2805 + deferred.resolve(); 1.2806 + } else { 1.2807 + deferred.reject("INSUFFICIENT_STORAGE"); 1.2808 + } 1.2809 + } catch(ex) { 1.2810 + // If disk space information isn't available, we'll end up here. 1.2811 + // We should proceed anyway, otherwise devices that support neither 1.2812 + // deviceStorage nor diskSpaceAvailable will never be able to install 1.2813 + // packaged apps. 1.2814 + deferred.resolve(); 1.2815 + } 1.2816 + } 1.2817 + 1.2818 + return deferred.promise; 1.2819 + }, 1.2820 + 1.2821 + _checkDownloadSize: function(aFreeBytes, aNewApp) { 1.2822 + if (aFreeBytes) { 1.2823 + debug("Free storage: " + aFreeBytes + ". Download size: " + 1.2824 + aNewApp.downloadSize); 1.2825 + if (aFreeBytes <= 1.2826 + aNewApp.downloadSize + AppDownloadManager.MIN_REMAINING_FREESPACE) { 1.2827 + return false; 1.2828 + } 1.2829 + } 1.2830 + return true; 1.2831 + }, 1.2832 + 1.2833 + _getRequestChannel: function(aFullPackagePath, aIsLocalFileInstall, aOldApp, 1.2834 + aNewApp) { 1.2835 + let requestChannel; 1.2836 + 1.2837 + if (aIsLocalFileInstall) { 1.2838 + requestChannel = NetUtil.newChannel(aFullPackagePath) 1.2839 + .QueryInterface(Ci.nsIFileChannel); 1.2840 + } else { 1.2841 + requestChannel = NetUtil.newChannel(aFullPackagePath) 1.2842 + .QueryInterface(Ci.nsIHttpChannel); 1.2843 + requestChannel.loadFlags |= Ci.nsIRequest.INHIBIT_CACHING; 1.2844 + } 1.2845 + 1.2846 + if (aOldApp.packageEtag && !aIsLocalFileInstall) { 1.2847 + debug("Add If-None-Match header: " + aOldApp.packageEtag); 1.2848 + requestChannel.setRequestHeader("If-None-Match", aOldApp.packageEtag, 1.2849 + false); 1.2850 + } 1.2851 + 1.2852 + let lastProgressTime = 0; 1.2853 + 1.2854 + requestChannel.notificationCallbacks = { 1.2855 + QueryInterface: function(aIID) { 1.2856 + if (aIID.equals(Ci.nsISupports) || 1.2857 + aIID.equals(Ci.nsIProgressEventSink) || 1.2858 + aIID.equals(Ci.nsILoadContext)) 1.2859 + return this; 1.2860 + throw Cr.NS_ERROR_NO_INTERFACE; 1.2861 + }, 1.2862 + getInterface: function(aIID) { 1.2863 + return this.QueryInterface(aIID); 1.2864 + }, 1.2865 + onProgress: (function(aRequest, aContext, aProgress, aProgressMax) { 1.2866 + aOldApp.progress = aProgress; 1.2867 + let now = Date.now(); 1.2868 + if (now - lastProgressTime > MIN_PROGRESS_EVENT_DELAY) { 1.2869 + debug("onProgress: " + aProgress + "/" + aProgressMax); 1.2870 + this._sendDownloadProgressEvent(aNewApp, aProgress); 1.2871 + lastProgressTime = now; 1.2872 + this._saveApps(); 1.2873 + } 1.2874 + }).bind(this), 1.2875 + onStatus: function(aRequest, aContext, aStatus, aStatusArg) { }, 1.2876 + 1.2877 + // nsILoadContext 1.2878 + appId: aOldApp.installerAppId, 1.2879 + isInBrowserElement: aOldApp.installerIsBrowser, 1.2880 + usePrivateBrowsing: false, 1.2881 + isContent: false, 1.2882 + associatedWindow: null, 1.2883 + topWindow : null, 1.2884 + isAppOfType: function(appType) { 1.2885 + throw Cr.NS_ERROR_NOT_IMPLEMENTED; 1.2886 + } 1.2887 + }; 1.2888 + 1.2889 + return requestChannel; 1.2890 + }, 1.2891 + 1.2892 + _sendDownloadProgressEvent: function(aNewApp, aProgress) { 1.2893 + this.broadcastMessage("Webapps:UpdateState", { 1.2894 + app: { 1.2895 + progress: aProgress 1.2896 + }, 1.2897 + manifestURL: aNewApp.manifestURL 1.2898 + }); 1.2899 + this.broadcastMessage("Webapps:FireEvent", { 1.2900 + eventType: "progress", 1.2901 + manifestURL: aNewApp.manifestURL 1.2902 + }); 1.2903 + }, 1.2904 + 1.2905 + _getPackage: function(aRequestChannel, aId, aOldApp, aNewApp) { 1.2906 + let deferred = Promise.defer(); 1.2907 + 1.2908 + // Staging the zip in TmpD until all the checks are done. 1.2909 + let zipFile = 1.2910 + FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true); 1.2911 + 1.2912 + // We need an output stream to write the channel content to the zip file. 1.2913 + let outputStream = Cc["@mozilla.org/network/file-output-stream;1"] 1.2914 + .createInstance(Ci.nsIFileOutputStream); 1.2915 + // write, create, truncate 1.2916 + outputStream.init(zipFile, 0x02 | 0x08 | 0x20, parseInt("0664", 8), 0); 1.2917 + let bufferedOutputStream = 1.2918 + Cc['@mozilla.org/network/buffered-output-stream;1'] 1.2919 + .createInstance(Ci.nsIBufferedOutputStream); 1.2920 + bufferedOutputStream.init(outputStream, 1024); 1.2921 + 1.2922 + // Create a listener that will give data to the file output stream. 1.2923 + let listener = Cc["@mozilla.org/network/simple-stream-listener;1"] 1.2924 + .createInstance(Ci.nsISimpleStreamListener); 1.2925 + 1.2926 + listener.init(bufferedOutputStream, { 1.2927 + onStartRequest: function(aRequest, aContext) { 1.2928 + // Nothing to do there anymore. 1.2929 + }, 1.2930 + 1.2931 + onStopRequest: function(aRequest, aContext, aStatusCode) { 1.2932 + bufferedOutputStream.close(); 1.2933 + outputStream.close(); 1.2934 + 1.2935 + if (!Components.isSuccessCode(aStatusCode)) { 1.2936 + deferred.reject("NETWORK_ERROR"); 1.2937 + return; 1.2938 + } 1.2939 + 1.2940 + // If we get a 4XX or a 5XX http status, bail out like if we had a 1.2941 + // network error. 1.2942 + let responseStatus = aRequestChannel.responseStatus; 1.2943 + if (responseStatus >= 400 && responseStatus <= 599) { 1.2944 + // unrecoverable error, don't bug the user 1.2945 + aOldApp.downloadAvailable = false; 1.2946 + deferred.reject("NETWORK_ERROR"); 1.2947 + return; 1.2948 + } 1.2949 + 1.2950 + deferred.resolve(zipFile); 1.2951 + } 1.2952 + }); 1.2953 + aRequestChannel.asyncOpen(listener, null); 1.2954 + 1.2955 + // send a first progress event to correctly set the DOM object's properties 1.2956 + this._sendDownloadProgressEvent(aNewApp, 0); 1.2957 + 1.2958 + return deferred.promise; 1.2959 + }, 1.2960 + 1.2961 + /** 1.2962 + * Compute the MD5 hash of a file, doing async IO off the main thread. 1.2963 + * 1.2964 + * @param {String} aFilePath 1.2965 + * the path of the file to hash 1.2966 + * @returns {String} the MD5 hash of the file 1.2967 + */ 1.2968 + _computeFileHash: function(aFilePath) { 1.2969 + let deferred = Promise.defer(); 1.2970 + 1.2971 + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); 1.2972 + file.initWithPath(aFilePath); 1.2973 + 1.2974 + NetUtil.asyncFetch(file, function(inputStream, status) { 1.2975 + if (!Components.isSuccessCode(status)) { 1.2976 + debug("Error reading " + aFilePath + ": " + e); 1.2977 + deferred.reject(); 1.2978 + return; 1.2979 + } 1.2980 + 1.2981 + let hasher = Cc["@mozilla.org/security/hash;1"] 1.2982 + .createInstance(Ci.nsICryptoHash); 1.2983 + // We want to use the MD5 algorithm. 1.2984 + hasher.init(hasher.MD5); 1.2985 + 1.2986 + const PR_UINT32_MAX = 0xffffffff; 1.2987 + hasher.updateFromStream(inputStream, PR_UINT32_MAX); 1.2988 + 1.2989 + // Return the two-digit hexadecimal code for a byte. 1.2990 + function toHexString(charCode) { 1.2991 + return ("0" + charCode.toString(16)).slice(-2); 1.2992 + } 1.2993 + 1.2994 + // We're passing false to get the binary hash and not base64. 1.2995 + let data = hasher.finish(false); 1.2996 + // Convert the binary hash data to a hex string. 1.2997 + let hash = [toHexString(data.charCodeAt(i)) for (i in data)].join(""); 1.2998 + debug("File hash computed: " + hash); 1.2999 + 1.3000 + deferred.resolve(hash); 1.3001 + }); 1.3002 + 1.3003 + return deferred.promise; 1.3004 + }, 1.3005 + 1.3006 + /** 1.3007 + * Send an "applied" event right away for the package being installed. 1.3008 + * 1.3009 + * XXX We use this to exit the app update process early when the downloaded 1.3010 + * package is identical to the last one we installed. Presumably we do 1.3011 + * something similar after updating the app, and we could refactor both cases 1.3012 + * to use the same code to send the "applied" event. 1.3013 + * 1.3014 + * @param aNewApp {Object} the new app data 1.3015 + * @param aOldApp {Object} the currently stored app data 1.3016 + * @param aId {String} the unique id of the app 1.3017 + */ 1.3018 + _sendAppliedEvent: function(aNewApp, aOldApp, aId) { 1.3019 + aOldApp.downloading = false; 1.3020 + aOldApp.downloadAvailable = false; 1.3021 + aOldApp.downloadSize = 0; 1.3022 + aOldApp.installState = "installed"; 1.3023 + aOldApp.readyToApplyDownload = false; 1.3024 + if (aOldApp.staged && aOldApp.staged.manifestHash) { 1.3025 + // If we're here then the manifest has changed but the package 1.3026 + // hasn't. Let's clear this, so we don't keep offering 1.3027 + // a bogus update to the user 1.3028 + aOldApp.manifestHash = aOldApp.staged.manifestHash; 1.3029 + aOldApp.etag = aOldApp.staged.etag || aOldApp.etag; 1.3030 + aOldApp.staged = {}; 1.3031 + 1.3032 + // Move the staged update manifest to a non staged one. 1.3033 + try { 1.3034 + let staged = this._getAppDir(aId); 1.3035 + staged.append("staged-update.webapp"); 1.3036 + staged.moveTo(staged.parent, "update.webapp"); 1.3037 + } catch (ex) { 1.3038 + // We don't really mind much if this fails. 1.3039 + } 1.3040 + } 1.3041 + 1.3042 + // Save the updated registry, and cleanup the tmp directory. 1.3043 + this._saveApps().then(() => { 1.3044 + this.broadcastMessage("Webapps:UpdateState", { 1.3045 + app: aOldApp, 1.3046 + manifestURL: aNewApp.manifestURL 1.3047 + }); 1.3048 + this.broadcastMessage("Webapps:FireEvent", { 1.3049 + manifestURL: aNewApp.manifestURL, 1.3050 + eventType: ["downloadsuccess", "downloadapplied"] 1.3051 + }); 1.3052 + }); 1.3053 + let file = FileUtils.getFile("TmpD", ["webapps", aId], false); 1.3054 + if (file && file.exists()) { 1.3055 + file.remove(true); 1.3056 + } 1.3057 + }, 1.3058 + 1.3059 + _openAndReadPackage: function(aZipFile, aOldApp, aNewApp, aIsLocalFileInstall, 1.3060 + aIsUpdate, aManifest, aRequestChannel, aHash) { 1.3061 + return Task.spawn((function*() { 1.3062 + let zipReader, isSigned, newManifest; 1.3063 + 1.3064 + try { 1.3065 + [zipReader, isSigned] = yield this._openPackage(aZipFile, aOldApp, 1.3066 + aIsLocalFileInstall); 1.3067 + newManifest = yield this._readPackage(aOldApp, aNewApp, 1.3068 + aIsLocalFileInstall, aIsUpdate, aManifest, aRequestChannel, 1.3069 + aHash, zipReader, isSigned); 1.3070 + } catch (e) { 1.3071 + debug("package open/read error: " + e); 1.3072 + // Something bad happened when opening/reading the package. 1.3073 + // Unrecoverable error, don't bug the user. 1.3074 + // Apps with installState 'pending' does not produce any 1.3075 + // notification, so we are safe with its current 1.3076 + // downloadAvailable state. 1.3077 + if (aOldApp.installState !== "pending") { 1.3078 + aOldApp.downloadAvailable = false; 1.3079 + } 1.3080 + if (typeof e == 'object') { 1.3081 + Cu.reportError("Error while reading package:" + e); 1.3082 + throw "INVALID_PACKAGE"; 1.3083 + } else { 1.3084 + throw e; 1.3085 + } 1.3086 + } finally { 1.3087 + if (zipReader) { 1.3088 + zipReader.close(); 1.3089 + } 1.3090 + } 1.3091 + 1.3092 + return newManifest; 1.3093 + 1.3094 + }).bind(this)); 1.3095 + }, 1.3096 + 1.3097 + _openPackage: function(aZipFile, aApp, aIsLocalFileInstall) { 1.3098 + return Task.spawn((function*() { 1.3099 + let certDb; 1.3100 + try { 1.3101 + certDb = Cc["@mozilla.org/security/x509certdb;1"] 1.3102 + .getService(Ci.nsIX509CertDB); 1.3103 + } catch (e) { 1.3104 + debug("nsIX509CertDB error: " + e); 1.3105 + // unrecoverable error, don't bug the user 1.3106 + aApp.downloadAvailable = false; 1.3107 + throw "CERTDB_ERROR"; 1.3108 + } 1.3109 + 1.3110 + let [result, zipReader] = yield this._openSignedPackage(aApp.installOrigin, 1.3111 + aApp.manifestURL, 1.3112 + aZipFile, 1.3113 + certDb); 1.3114 + 1.3115 + // We cannot really know if the system date is correct or 1.3116 + // not. What we can know is if it's after the build date or not, 1.3117 + // and assume the build date is correct (which we cannot 1.3118 + // really know either). 1.3119 + let isLaterThanBuildTime = Date.now() > PLATFORM_BUILD_ID_TIME; 1.3120 + 1.3121 + let isSigned; 1.3122 + 1.3123 + if (Components.isSuccessCode(result)) { 1.3124 + isSigned = true; 1.3125 + } else if (result == Cr.NS_ERROR_SIGNED_JAR_MODIFIED_ENTRY || 1.3126 + result == Cr.NS_ERROR_SIGNED_JAR_UNSIGNED_ENTRY || 1.3127 + result == Cr.NS_ERROR_SIGNED_JAR_ENTRY_MISSING) { 1.3128 + throw "APP_PACKAGE_CORRUPTED"; 1.3129 + } else if (result == Cr.NS_ERROR_FILE_CORRUPTED || 1.3130 + result == Cr.NS_ERROR_SIGNED_JAR_ENTRY_TOO_LARGE || 1.3131 + result == Cr.NS_ERROR_SIGNED_JAR_ENTRY_INVALID || 1.3132 + result == Cr.NS_ERROR_SIGNED_JAR_MANIFEST_INVALID) { 1.3133 + throw "APP_PACKAGE_INVALID"; 1.3134 + } else if ((!aIsLocalFileInstall || isLaterThanBuildTime) && 1.3135 + (result != Cr.NS_ERROR_SIGNED_JAR_NOT_SIGNED)) { 1.3136 + throw "INVALID_SIGNATURE"; 1.3137 + } else { 1.3138 + // If it's a localFileInstall and the validation failed 1.3139 + // because of a expired certificate, just assume it was valid 1.3140 + // and that the error occurred because the system time has not 1.3141 + // been set yet. 1.3142 + isSigned = (aIsLocalFileInstall && 1.3143 + (getNSPRErrorCode(result) == 1.3144 + SEC_ERROR_EXPIRED_CERTIFICATE)); 1.3145 + 1.3146 + zipReader = Cc["@mozilla.org/libjar/zip-reader;1"] 1.3147 + .createInstance(Ci.nsIZipReader); 1.3148 + zipReader.open(aZipFile); 1.3149 + } 1.3150 + 1.3151 + return [zipReader, isSigned]; 1.3152 + 1.3153 + }).bind(this)); 1.3154 + }, 1.3155 + 1.3156 + _openSignedPackage: function(aInstallOrigin, aManifestURL, aZipFile, aCertDb) { 1.3157 + let deferred = Promise.defer(); 1.3158 + 1.3159 + let root = TrustedRootCertificate.index; 1.3160 + 1.3161 + let useReviewerCerts = false; 1.3162 + try { 1.3163 + useReviewerCerts = Services.prefs. 1.3164 + getBoolPref("dom.mozApps.use_reviewer_certs"); 1.3165 + } catch (ex) { } 1.3166 + 1.3167 + // We'll use the reviewer and dev certificates only if the pref is set to 1.3168 + // true. 1.3169 + if (useReviewerCerts) { 1.3170 + let manifestPath = Services.io.newURI(aManifestURL, null, null).path; 1.3171 + 1.3172 + switch (aInstallOrigin) { 1.3173 + case "https://marketplace.firefox.com": 1.3174 + root = manifestPath.startsWith("/reviewers/") 1.3175 + ? Ci.nsIX509CertDB.AppMarketplaceProdReviewersRoot 1.3176 + : Ci.nsIX509CertDB.AppMarketplaceProdPublicRoot; 1.3177 + break; 1.3178 + 1.3179 + case "https://marketplace-dev.allizom.org": 1.3180 + root = manifestPath.startsWith("/reviewers/") 1.3181 + ? Ci.nsIX509CertDB.AppMarketplaceDevReviewersRoot 1.3182 + : Ci.nsIX509CertDB.AppMarketplaceDevPublicRoot; 1.3183 + break; 1.3184 + } 1.3185 + } 1.3186 + 1.3187 + aCertDb.openSignedAppFileAsync( 1.3188 + root, aZipFile, 1.3189 + function(aRv, aZipReader) { 1.3190 + deferred.resolve([aRv, aZipReader]); 1.3191 + } 1.3192 + ); 1.3193 + 1.3194 + return deferred.promise; 1.3195 + }, 1.3196 + 1.3197 + _readPackage: function(aOldApp, aNewApp, aIsLocalFileInstall, aIsUpdate, 1.3198 + aManifest, aRequestChannel, aHash, aZipReader, 1.3199 + aIsSigned) { 1.3200 + this._checkSignature(aNewApp, aIsSigned, aIsLocalFileInstall); 1.3201 + 1.3202 + if (!aZipReader.hasEntry("manifest.webapp")) { 1.3203 + throw "MISSING_MANIFEST"; 1.3204 + } 1.3205 + 1.3206 + let istream = aZipReader.getInputStream("manifest.webapp"); 1.3207 + 1.3208 + // Obtain a converter to read from a UTF-8 encoded input stream. 1.3209 + let converter = Cc["@mozilla.org/intl/scriptableunicodeconverter"] 1.3210 + .createInstance(Ci.nsIScriptableUnicodeConverter); 1.3211 + converter.charset = "UTF-8"; 1.3212 + 1.3213 + let newManifest = JSON.parse(converter.ConvertToUnicode( 1.3214 + NetUtil.readInputStreamToString(istream, istream.available()) || "")); 1.3215 + 1.3216 + if (!AppsUtils.checkManifest(newManifest, aOldApp)) { 1.3217 + throw "INVALID_MANIFEST"; 1.3218 + } 1.3219 + 1.3220 + // For app updates we don't forbid apps to rename themselves but 1.3221 + // we still retain the old name of the app. In the future we 1.3222 + // will use UI to allow updates to rename an app after we check 1.3223 + // with the user that the rename is ok. 1.3224 + if (aIsUpdate) { 1.3225 + // Call ensureSameAppName before compareManifests as `manifest` 1.3226 + // has been normalized to avoid app rename. 1.3227 + AppsUtils.ensureSameAppName(aManifest._manifest, newManifest, aOldApp); 1.3228 + } 1.3229 + 1.3230 + if (!AppsUtils.compareManifests(newManifest, aManifest._manifest)) { 1.3231 + throw "MANIFEST_MISMATCH"; 1.3232 + } 1.3233 + 1.3234 + if (!AppsUtils.checkInstallAllowed(newManifest, aNewApp.installOrigin)) { 1.3235 + throw "INSTALL_FROM_DENIED"; 1.3236 + } 1.3237 + 1.3238 + // Local file installs can be privileged even without the signature. 1.3239 + let maxStatus = aIsSigned || aIsLocalFileInstall 1.3240 + ? Ci.nsIPrincipal.APP_STATUS_PRIVILEGED 1.3241 + : Ci.nsIPrincipal.APP_STATUS_INSTALLED; 1.3242 + 1.3243 + if (AppsUtils.getAppManifestStatus(newManifest) > maxStatus) { 1.3244 + throw "INVALID_SECURITY_LEVEL"; 1.3245 + } 1.3246 + 1.3247 + aOldApp.appStatus = AppsUtils.getAppManifestStatus(newManifest); 1.3248 + 1.3249 + this._saveEtag(aIsUpdate, aOldApp, aRequestChannel, aHash, newManifest); 1.3250 + this._checkOrigin(aIsSigned || aIsLocalFileInstall, aOldApp, newManifest, 1.3251 + aIsUpdate); 1.3252 + this._getIds(aIsSigned, aZipReader, converter, aNewApp, aOldApp, aIsUpdate); 1.3253 + 1.3254 + return newManifest; 1.3255 + }, 1.3256 + 1.3257 + _checkSignature: function(aApp, aIsSigned, aIsLocalFileInstall) { 1.3258 + // XXX Security: You CANNOT safely add a new app store for 1.3259 + // installing privileged apps just by modifying this pref and 1.3260 + // adding the signing cert for that store to the cert trust 1.3261 + // database. *Any* origin listed can install apps signed with 1.3262 + // *any* certificate trusted; we don't try to maintain a strong 1.3263 + // association between certificate with installOrign. The 1.3264 + // expectation here is that in production builds the pref will 1.3265 + // contain exactly one origin. However, in custom development 1.3266 + // builds it may contain more than one origin so we can test 1.3267 + // different stages (dev, staging, prod) of the same app store. 1.3268 + // 1.3269 + // Only allow signed apps to be installed from a whitelist of 1.3270 + // domains, and require all packages installed from any of the 1.3271 + // domains on the whitelist to be signed. This is a stopgap until 1.3272 + // we have a real story for handling multiple app stores signing 1.3273 + // apps. 1.3274 + let signedAppOriginsStr = 1.3275 + Services.prefs.getCharPref("dom.mozApps.signed_apps_installable_from"); 1.3276 + // If it's a local install and it's signed then we assume 1.3277 + // the app origin is a valid signer. 1.3278 + let isSignedAppOrigin = (aIsSigned && aIsLocalFileInstall) || 1.3279 + signedAppOriginsStr.split(","). 1.3280 + indexOf(aApp.installOrigin) > -1; 1.3281 + if (!aIsSigned && isSignedAppOrigin) { 1.3282 + // Packaged apps installed from these origins must be signed; 1.3283 + // if not, assume somebody stripped the signature. 1.3284 + throw "INVALID_SIGNATURE"; 1.3285 + } else if (aIsSigned && !isSignedAppOrigin) { 1.3286 + // Other origins are *prohibited* from installing signed apps. 1.3287 + // One reason is that our app revocation mechanism requires 1.3288 + // strong cooperation from the host of the mini-manifest, which 1.3289 + // we assume to be under the control of the install origin, 1.3290 + // even if it has a different origin. 1.3291 + throw "INSTALL_FROM_DENIED"; 1.3292 + } 1.3293 + }, 1.3294 + 1.3295 + _saveEtag: function(aIsUpdate, aOldApp, aRequestChannel, aHash, aManifest) { 1.3296 + // Save the new Etag for the package. 1.3297 + if (aIsUpdate) { 1.3298 + if (!aOldApp.staged) { 1.3299 + aOldApp.staged = { }; 1.3300 + } 1.3301 + try { 1.3302 + aOldApp.staged.packageEtag = aRequestChannel.getResponseHeader("Etag"); 1.3303 + } catch(e) { } 1.3304 + aOldApp.staged.packageHash = aHash; 1.3305 + aOldApp.staged.appStatus = AppsUtils.getAppManifestStatus(aManifest); 1.3306 + } else { 1.3307 + try { 1.3308 + aOldApp.packageEtag = aRequestChannel.getResponseHeader("Etag"); 1.3309 + } catch(e) { } 1.3310 + aOldApp.packageHash = aHash; 1.3311 + aOldApp.appStatus = AppsUtils.getAppManifestStatus(aManifest); 1.3312 + } 1.3313 + }, 1.3314 + 1.3315 + _checkOrigin: function(aIsSigned, aOldApp, aManifest, aIsUpdate) { 1.3316 + // Check if the app declares which origin it will use. 1.3317 + if (aIsSigned && 1.3318 + aOldApp.appStatus >= Ci.nsIPrincipal.APP_STATUS_PRIVILEGED && 1.3319 + aManifest.origin !== undefined) { 1.3320 + let uri; 1.3321 + try { 1.3322 + uri = Services.io.newURI(aManifest.origin, null, null); 1.3323 + } catch(e) { 1.3324 + throw "INVALID_ORIGIN"; 1.3325 + } 1.3326 + if (uri.scheme != "app") { 1.3327 + throw "INVALID_ORIGIN"; 1.3328 + } 1.3329 + 1.3330 + if (aIsUpdate) { 1.3331 + // Changing the origin during an update is not allowed. 1.3332 + if (uri.prePath != aOldApp.origin) { 1.3333 + throw "INVALID_ORIGIN_CHANGE"; 1.3334 + } 1.3335 + // Nothing else to do for an update... since the 1.3336 + // origin can't change we don't need to move the 1.3337 + // app nor can we have a duplicated origin 1.3338 + } else { 1.3339 + debug("Setting origin to " + uri.prePath + 1.3340 + " for " + aOldApp.manifestURL); 1.3341 + let newId = uri.prePath.substring(6); // "app://".length 1.3342 + if (newId in this.webapps) { 1.3343 + throw "DUPLICATE_ORIGIN"; 1.3344 + } 1.3345 + aOldApp.origin = uri.prePath; 1.3346 + // Update the registry. 1.3347 + let oldId = aOldApp.id; 1.3348 + aOldApp.id = newId; 1.3349 + this.webapps[newId] = aOldApp; 1.3350 + delete this.webapps[oldId]; 1.3351 + // Rename the directories where the files are installed. 1.3352 + [DIRECTORY_NAME, "TmpD"].forEach(function(aDir) { 1.3353 + let parent = FileUtils.getDir(aDir, ["webapps"], true, true); 1.3354 + let dir = FileUtils.getDir(aDir, ["webapps", oldId], true, true); 1.3355 + dir.moveTo(parent, newId); 1.3356 + }); 1.3357 + // Signals that we need to swap the old id with the new app. 1.3358 + this.broadcastMessage("Webapps:RemoveApp", { id: oldId }); 1.3359 + this.broadcastMessage("Webapps:AddApp", { id: newId, 1.3360 + app: aOldApp }); 1.3361 + } 1.3362 + } 1.3363 + }, 1.3364 + 1.3365 + _getIds: function(aIsSigned, aZipReader, aConverter, aNewApp, aOldApp, 1.3366 + aIsUpdate) { 1.3367 + // Get ids.json if the file is signed 1.3368 + if (aIsSigned) { 1.3369 + let idsStream; 1.3370 + try { 1.3371 + idsStream = aZipReader.getInputStream("META-INF/ids.json"); 1.3372 + } catch (e) { 1.3373 + throw aZipReader.hasEntry("META-INF/ids.json") 1.3374 + ? e 1.3375 + : "MISSING_IDS_JSON"; 1.3376 + } 1.3377 + 1.3378 + let ids = JSON.parse(aConverter.ConvertToUnicode(NetUtil. 1.3379 + readInputStreamToString( idsStream, idsStream.available()) || "")); 1.3380 + if ((!ids.id) || !Number.isInteger(ids.version) || 1.3381 + (ids.version <= 0)) { 1.3382 + throw "INVALID_IDS_JSON"; 1.3383 + } 1.3384 + let storeId = aNewApp.installOrigin + "#" + ids.id; 1.3385 + this._checkForStoreIdMatch(aIsUpdate, aOldApp, storeId, ids.version); 1.3386 + aOldApp.storeId = storeId; 1.3387 + aOldApp.storeVersion = ids.version; 1.3388 + } 1.3389 + }, 1.3390 + 1.3391 + // aStoreId must be a string of the form 1.3392 + // <installOrigin>#<storeId from ids.json> 1.3393 + // aStoreVersion must be a positive integer. 1.3394 + _checkForStoreIdMatch: function(aIsUpdate, aNewApp, aStoreId, aStoreVersion) { 1.3395 + // Things to check: 1.3396 + // 1. if it's a update: 1.3397 + // a. We should already have this storeId, or the original storeId must 1.3398 + // start with STORE_ID_PENDING_PREFIX 1.3399 + // b. The manifestURL for the stored app should be the same one we're 1.3400 + // updating 1.3401 + // c. And finally the version of the update should be higher than the one 1.3402 + // on the already installed package 1.3403 + // 2. else 1.3404 + // a. We should not have this storeId on the list 1.3405 + // We're currently launching WRONG_APP_STORE_ID for all the mismatch kind of 1.3406 + // errors, and APP_STORE_VERSION_ROLLBACK for the version error. 1.3407 + 1.3408 + // Does an app with this storeID exist already? 1.3409 + let appId = this.getAppLocalIdByStoreId(aStoreId); 1.3410 + let isInstalled = appId != Ci.nsIScriptSecurityManager.NO_APP_ID; 1.3411 + if (aIsUpdate) { 1.3412 + let isDifferent = aNewApp.localId !== appId; 1.3413 + let isPending = aNewApp.storeId.indexOf(STORE_ID_PENDING_PREFIX) == 0; 1.3414 + 1.3415 + if ((!isInstalled && !isPending) || (isInstalled && isDifferent)) { 1.3416 + throw "WRONG_APP_STORE_ID"; 1.3417 + } 1.3418 + 1.3419 + if (!isPending && (aNewApp.storeVersion >= aStoreVersion)) { 1.3420 + throw "APP_STORE_VERSION_ROLLBACK"; 1.3421 + } 1.3422 + 1.3423 + } else if (isInstalled) { 1.3424 + throw "WRONG_APP_STORE_ID"; 1.3425 + } 1.3426 + }, 1.3427 + 1.3428 + // Removes the directory we created, and sends an error to the DOM side. 1.3429 + _revertDownloadPackage: function(aId, aOldApp, aNewApp, aIsUpdate, aError) { 1.3430 + debug("Cleanup: " + aError + "\n" + aError.stack); 1.3431 + let dir = FileUtils.getDir("TmpD", ["webapps", aId], true, true); 1.3432 + try { 1.3433 + dir.remove(true); 1.3434 + } catch (e) { } 1.3435 + 1.3436 + // We avoid notifying the error to the DOM side if the app download 1.3437 + // was cancelled via cancelDownload, which already sends its own 1.3438 + // notification. 1.3439 + if (aOldApp.isCanceling) { 1.3440 + delete aOldApp.isCanceling; 1.3441 + return; 1.3442 + } 1.3443 + 1.3444 + let download = AppDownloadManager.get(aNewApp.manifestURL); 1.3445 + aOldApp.downloading = false; 1.3446 + 1.3447 + // If there were not enough storage to download the package we 1.3448 + // won't have a record of the download details, so we just set the 1.3449 + // installState to 'pending' at first download and to 'installed' when 1.3450 + // updating. 1.3451 + aOldApp.installState = download ? download.previousState 1.3452 + : aIsUpdate ? "installed" 1.3453 + : "pending"; 1.3454 + 1.3455 + if (aOldApp.staged) { 1.3456 + delete aOldApp.staged; 1.3457 + } 1.3458 + 1.3459 + this._saveApps().then(() => { 1.3460 + this.broadcastMessage("Webapps:UpdateState", { 1.3461 + app: aOldApp, 1.3462 + error: aError, 1.3463 + manifestURL: aNewApp.manifestURL 1.3464 + }); 1.3465 + this.broadcastMessage("Webapps:FireEvent", { 1.3466 + eventType: "downloaderror", 1.3467 + manifestURL: aNewApp.manifestURL 1.3468 + }); 1.3469 + }); 1.3470 + AppDownloadManager.remove(aNewApp.manifestURL); 1.3471 + }, 1.3472 + 1.3473 + doUninstall: function(aData, aMm) { 1.3474 + this.uninstall(aData.manifestURL, 1.3475 + function onsuccess() { 1.3476 + aMm.sendAsyncMessage("Webapps:Uninstall:Return:OK", aData); 1.3477 + }, 1.3478 + function onfailure() { 1.3479 + // Fall-through, fails to uninstall the desired app because: 1.3480 + // - we cannot find the app to be uninstalled. 1.3481 + // - the app to be uninstalled is not removable. 1.3482 + aMm.sendAsyncMessage("Webapps:Uninstall:Return:KO", aData); 1.3483 + } 1.3484 + ); 1.3485 + }, 1.3486 + 1.3487 + uninstall: function(aManifestURL, aOnSuccess, aOnFailure) { 1.3488 + debug("uninstall " + aManifestURL); 1.3489 + 1.3490 + let app = this.getAppByManifestURL(aManifestURL); 1.3491 + if (!app) { 1.3492 + aOnFailure("NO_SUCH_APP"); 1.3493 + return; 1.3494 + } 1.3495 + let id = app.id; 1.3496 + 1.3497 + if (!app.removable) { 1.3498 + debug("Error: cannot uninstall a non-removable app."); 1.3499 + aOnFailure("NON_REMOVABLE_APP"); 1.3500 + return; 1.3501 + } 1.3502 + 1.3503 + // Check if we are downloading something for this app, and cancel the 1.3504 + // download if needed. 1.3505 + this.cancelDownload(app.manifestURL); 1.3506 + 1.3507 + // Clean up the deprecated manifest cache if needed. 1.3508 + if (id in this._manifestCache) { 1.3509 + delete this._manifestCache[id]; 1.3510 + } 1.3511 + 1.3512 + // Clear private data first. 1.3513 + this._clearPrivateData(app.localId, false); 1.3514 + 1.3515 + // Then notify observers. 1.3516 + // We have to clone the app object as nsIDOMApplication objects are 1.3517 + // stringified as an empty object. (see bug 830376) 1.3518 + let appClone = AppsUtils.cloneAppObject(app); 1.3519 + Services.obs.notifyObservers(null, "webapps-uninstall", JSON.stringify(appClone)); 1.3520 + 1.3521 + if (supportSystemMessages()) { 1.3522 + this._readManifests([{ id: id }]).then((aResult) => { 1.3523 + this._unregisterActivities(aResult[0].manifest, app); 1.3524 + }); 1.3525 + } 1.3526 + 1.3527 + let dir = this._getAppDir(id); 1.3528 + try { 1.3529 + dir.remove(true); 1.3530 + } catch (e) {} 1.3531 + 1.3532 + delete this.webapps[id]; 1.3533 + 1.3534 + this._saveApps().then(() => { 1.3535 + this.broadcastMessage("Webapps:Uninstall:Broadcast:Return:OK", appClone); 1.3536 + // Catch exception on callback call to ensure notifying observers after 1.3537 + try { 1.3538 + if (aOnSuccess) { 1.3539 + aOnSuccess(); 1.3540 + } 1.3541 + } catch(ex) { 1.3542 + Cu.reportError("DOMApplicationRegistry: Exception on app uninstall: " + 1.3543 + ex + "\n" + ex.stack); 1.3544 + } 1.3545 + this.broadcastMessage("Webapps:RemoveApp", { id: id }); 1.3546 + }); 1.3547 + }, 1.3548 + 1.3549 + getSelf: function(aData, aMm) { 1.3550 + aData.apps = []; 1.3551 + 1.3552 + if (aData.appId == Ci.nsIScriptSecurityManager.NO_APP_ID || 1.3553 + aData.appId == Ci.nsIScriptSecurityManager.UNKNOWN_APP_ID) { 1.3554 + aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); 1.3555 + return; 1.3556 + } 1.3557 + 1.3558 + let tmp = []; 1.3559 + 1.3560 + for (let id in this.webapps) { 1.3561 + if (this.webapps[id].origin == aData.origin && 1.3562 + this.webapps[id].localId == aData.appId && 1.3563 + this._isLaunchable(this.webapps[id])) { 1.3564 + let app = AppsUtils.cloneAppObject(this.webapps[id]); 1.3565 + aData.apps.push(app); 1.3566 + tmp.push({ id: id }); 1.3567 + break; 1.3568 + } 1.3569 + } 1.3570 + 1.3571 + if (!aData.apps.length) { 1.3572 + aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); 1.3573 + return; 1.3574 + } 1.3575 + 1.3576 + this._readManifests(tmp).then((aResult) => { 1.3577 + for (let i = 0; i < aResult.length; i++) 1.3578 + aData.apps[i].manifest = aResult[i].manifest; 1.3579 + aMm.sendAsyncMessage("Webapps:GetSelf:Return:OK", aData); 1.3580 + }); 1.3581 + }, 1.3582 + 1.3583 + checkInstalled: function(aData, aMm) { 1.3584 + aData.app = null; 1.3585 + let tmp = []; 1.3586 + 1.3587 + for (let appId in this.webapps) { 1.3588 + if (this.webapps[appId].manifestURL == aData.manifestURL && 1.3589 + this._isLaunchable(this.webapps[appId])) { 1.3590 + aData.app = AppsUtils.cloneAppObject(this.webapps[appId]); 1.3591 + tmp.push({ id: appId }); 1.3592 + break; 1.3593 + } 1.3594 + } 1.3595 + 1.3596 + this._readManifests(tmp).then((aResult) => { 1.3597 + for (let i = 0; i < aResult.length; i++) { 1.3598 + aData.app.manifest = aResult[i].manifest; 1.3599 + break; 1.3600 + } 1.3601 + aMm.sendAsyncMessage("Webapps:CheckInstalled:Return:OK", aData); 1.3602 + }); 1.3603 + }, 1.3604 + 1.3605 + getInstalled: function(aData, aMm) { 1.3606 + aData.apps = []; 1.3607 + let tmp = []; 1.3608 + 1.3609 + for (let id in this.webapps) { 1.3610 + if (this.webapps[id].installOrigin == aData.origin && 1.3611 + this._isLaunchable(this.webapps[id])) { 1.3612 + aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id])); 1.3613 + tmp.push({ id: id }); 1.3614 + } 1.3615 + } 1.3616 + 1.3617 + this._readManifests(tmp).then((aResult) => { 1.3618 + for (let i = 0; i < aResult.length; i++) 1.3619 + aData.apps[i].manifest = aResult[i].manifest; 1.3620 + aMm.sendAsyncMessage("Webapps:GetInstalled:Return:OK", aData); 1.3621 + }); 1.3622 + }, 1.3623 + 1.3624 + getNotInstalled: function(aData, aMm) { 1.3625 + aData.apps = []; 1.3626 + let tmp = []; 1.3627 + 1.3628 + for (let id in this.webapps) { 1.3629 + if (!this._isLaunchable(this.webapps[id])) { 1.3630 + aData.apps.push(AppsUtils.cloneAppObject(this.webapps[id])); 1.3631 + tmp.push({ id: id }); 1.3632 + } 1.3633 + } 1.3634 + 1.3635 + this._readManifests(tmp).then((aResult) => { 1.3636 + for (let i = 0; i < aResult.length; i++) 1.3637 + aData.apps[i].manifest = aResult[i].manifest; 1.3638 + aMm.sendAsyncMessage("Webapps:GetNotInstalled:Return:OK", aData); 1.3639 + }); 1.3640 + }, 1.3641 + 1.3642 + doGetAll: function(aData, aMm) { 1.3643 + this.getAll(function (apps) { 1.3644 + aData.apps = apps; 1.3645 + aMm.sendAsyncMessage("Webapps:GetAll:Return:OK", aData); 1.3646 + }); 1.3647 + }, 1.3648 + 1.3649 + getAll: function(aCallback) { 1.3650 + debug("getAll"); 1.3651 + let apps = []; 1.3652 + let tmp = []; 1.3653 + 1.3654 + for (let id in this.webapps) { 1.3655 + let app = AppsUtils.cloneAppObject(this.webapps[id]); 1.3656 + if (!this._isLaunchable(app)) 1.3657 + continue; 1.3658 + 1.3659 + apps.push(app); 1.3660 + tmp.push({ id: id }); 1.3661 + } 1.3662 + 1.3663 + this._readManifests(tmp).then((aResult) => { 1.3664 + for (let i = 0; i < aResult.length; i++) 1.3665 + apps[i].manifest = aResult[i].manifest; 1.3666 + aCallback(apps); 1.3667 + }); 1.3668 + }, 1.3669 + 1.3670 + /* Check if |data| is actually a receipt */ 1.3671 + isReceipt: function(data) { 1.3672 + try { 1.3673 + // The receipt data shouldn't be too big (allow up to 1 MiB of data) 1.3674 + const MAX_RECEIPT_SIZE = 1048576; 1.3675 + 1.3676 + if (data.length > MAX_RECEIPT_SIZE) { 1.3677 + return "RECEIPT_TOO_BIG"; 1.3678 + } 1.3679 + 1.3680 + // Marketplace receipts are JWK + "~" + JWT 1.3681 + // Other receipts may contain only the JWT 1.3682 + let receiptParts = data.split('~'); 1.3683 + let jwtData = null; 1.3684 + if (receiptParts.length == 2) { 1.3685 + jwtData = receiptParts[1]; 1.3686 + } else { 1.3687 + jwtData = receiptParts[0]; 1.3688 + } 1.3689 + 1.3690 + let segments = jwtData.split('.'); 1.3691 + if (segments.length != 3) { 1.3692 + return "INVALID_SEGMENTS_NUMBER"; 1.3693 + } 1.3694 + 1.3695 + // We need to translate the base64 alphabet used in JWT to our base64 alphabet 1.3696 + // before calling atob. 1.3697 + let decodedReceipt = JSON.parse(atob(segments[1].replace(/-/g, '+') 1.3698 + .replace(/_/g, '/'))); 1.3699 + if (!decodedReceipt) { 1.3700 + return "INVALID_RECEIPT_ENCODING"; 1.3701 + } 1.3702 + 1.3703 + // Required values for a receipt 1.3704 + if (!decodedReceipt.typ) { 1.3705 + return "RECEIPT_TYPE_REQUIRED"; 1.3706 + } 1.3707 + if (!decodedReceipt.product) { 1.3708 + return "RECEIPT_PRODUCT_REQUIRED"; 1.3709 + } 1.3710 + if (!decodedReceipt.user) { 1.3711 + return "RECEIPT_USER_REQUIRED"; 1.3712 + } 1.3713 + if (!decodedReceipt.iss) { 1.3714 + return "RECEIPT_ISS_REQUIRED"; 1.3715 + } 1.3716 + if (!decodedReceipt.nbf) { 1.3717 + return "RECEIPT_NBF_REQUIRED"; 1.3718 + } 1.3719 + if (!decodedReceipt.iat) { 1.3720 + return "RECEIPT_IAT_REQUIRED"; 1.3721 + } 1.3722 + 1.3723 + let allowedTypes = [ "purchase-receipt", "developer-receipt", 1.3724 + "reviewer-receipt", "test-receipt" ]; 1.3725 + if (allowedTypes.indexOf(decodedReceipt.typ) < 0) { 1.3726 + return "RECEIPT_TYPE_UNSUPPORTED"; 1.3727 + } 1.3728 + } catch (e) { 1.3729 + return "RECEIPT_ERROR"; 1.3730 + } 1.3731 + 1.3732 + return null; 1.3733 + }, 1.3734 + 1.3735 + addReceipt: function(aData, aMm) { 1.3736 + debug("addReceipt " + aData.manifestURL); 1.3737 + 1.3738 + let receipt = aData.receipt; 1.3739 + 1.3740 + if (!receipt) { 1.3741 + aData.error = "INVALID_PARAMETERS"; 1.3742 + aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData); 1.3743 + return; 1.3744 + } 1.3745 + 1.3746 + let error = this.isReceipt(receipt); 1.3747 + if (error) { 1.3748 + aData.error = error; 1.3749 + aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData); 1.3750 + return; 1.3751 + } 1.3752 + 1.3753 + let id = this._appIdForManifestURL(aData.manifestURL); 1.3754 + let app = this.webapps[id]; 1.3755 + 1.3756 + if (!app.receipts) { 1.3757 + app.receipts = []; 1.3758 + } else if (app.receipts.length > 500) { 1.3759 + aData.error = "TOO_MANY_RECEIPTS"; 1.3760 + aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData); 1.3761 + return; 1.3762 + } 1.3763 + 1.3764 + let index = app.receipts.indexOf(receipt); 1.3765 + if (index >= 0) { 1.3766 + aData.error = "RECEIPT_ALREADY_EXISTS"; 1.3767 + aMm.sendAsyncMessage("Webapps:AddReceipt:Return:KO", aData); 1.3768 + return; 1.3769 + } 1.3770 + 1.3771 + app.receipts.push(receipt); 1.3772 + 1.3773 + this._saveApps().then(() => { 1.3774 + aData.receipts = app.receipts; 1.3775 + aMm.sendAsyncMessage("Webapps:AddReceipt:Return:OK", aData); 1.3776 + }); 1.3777 + }, 1.3778 + 1.3779 + removeReceipt: function(aData, aMm) { 1.3780 + debug("removeReceipt " + aData.manifestURL); 1.3781 + 1.3782 + let receipt = aData.receipt; 1.3783 + 1.3784 + if (!receipt) { 1.3785 + aData.error = "INVALID_PARAMETERS"; 1.3786 + aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData); 1.3787 + return; 1.3788 + } 1.3789 + 1.3790 + let id = this._appIdForManifestURL(aData.manifestURL); 1.3791 + let app = this.webapps[id]; 1.3792 + 1.3793 + if (!app.receipts) { 1.3794 + aData.error = "NO_SUCH_RECEIPT"; 1.3795 + aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData); 1.3796 + return; 1.3797 + } 1.3798 + 1.3799 + let index = app.receipts.indexOf(receipt); 1.3800 + if (index == -1) { 1.3801 + aData.error = "NO_SUCH_RECEIPT"; 1.3802 + aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData); 1.3803 + return; 1.3804 + } 1.3805 + 1.3806 + app.receipts.splice(index, 1); 1.3807 + 1.3808 + this._saveApps().then(() => { 1.3809 + aData.receipts = app.receipts; 1.3810 + aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:OK", aData); 1.3811 + }); 1.3812 + }, 1.3813 + 1.3814 + replaceReceipt: function(aData, aMm) { 1.3815 + debug("replaceReceipt " + aData.manifestURL); 1.3816 + 1.3817 + let oldReceipt = aData.oldReceipt; 1.3818 + let newReceipt = aData.newReceipt; 1.3819 + 1.3820 + if (!oldReceipt || !newReceipt) { 1.3821 + aData.error = "INVALID_PARAMETERS"; 1.3822 + aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:KO", aData); 1.3823 + return; 1.3824 + } 1.3825 + 1.3826 + let error = this.isReceipt(newReceipt); 1.3827 + if (error) { 1.3828 + aData.error = error; 1.3829 + aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:KO", aData); 1.3830 + return; 1.3831 + } 1.3832 + 1.3833 + let id = this._appIdForManifestURL(aData.manifestURL); 1.3834 + let app = this.webapps[id]; 1.3835 + 1.3836 + if (!app.receipts) { 1.3837 + aData.error = "NO_SUCH_RECEIPT"; 1.3838 + aMm.sendAsyncMessage("Webapps:RemoveReceipt:Return:KO", aData); 1.3839 + return; 1.3840 + } 1.3841 + 1.3842 + let oldIndex = app.receipts.indexOf(oldReceipt); 1.3843 + if (oldIndex == -1) { 1.3844 + aData.error = "NO_SUCH_RECEIPT"; 1.3845 + aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:KO", aData); 1.3846 + return; 1.3847 + } 1.3848 + 1.3849 + app.receipts[oldIndex] = newReceipt; 1.3850 + 1.3851 + this._saveApps().then(() => { 1.3852 + aData.receipts = app.receipts; 1.3853 + aMm.sendAsyncMessage("Webapps:ReplaceReceipt:Return:OK", aData); 1.3854 + }); 1.3855 + }, 1.3856 + 1.3857 + getManifestFor: function(aManifestURL) { 1.3858 + let id = this._appIdForManifestURL(aManifestURL); 1.3859 + let app = this.webapps[id]; 1.3860 + if (!id || (app.installState == "pending" && !app.retryingDownload)) { 1.3861 + return Promise.resolve(null); 1.3862 + } 1.3863 + 1.3864 + return this._readManifests([{ id: id }]).then((aResult) => { 1.3865 + return aResult[0].manifest; 1.3866 + }); 1.3867 + }, 1.3868 + 1.3869 + getAppByManifestURL: function(aManifestURL) { 1.3870 + return AppsUtils.getAppByManifestURL(this.webapps, aManifestURL); 1.3871 + }, 1.3872 + 1.3873 + getCSPByLocalId: function(aLocalId) { 1.3874 + debug("getCSPByLocalId:" + aLocalId); 1.3875 + return AppsUtils.getCSPByLocalId(this.webapps, aLocalId); 1.3876 + }, 1.3877 + 1.3878 + getAppLocalIdByStoreId: function(aStoreId) { 1.3879 + debug("getAppLocalIdByStoreId:" + aStoreId); 1.3880 + return AppsUtils.getAppLocalIdByStoreId(this.webapps, aStoreId); 1.3881 + }, 1.3882 + 1.3883 + getAppByLocalId: function(aLocalId) { 1.3884 + return AppsUtils.getAppByLocalId(this.webapps, aLocalId); 1.3885 + }, 1.3886 + 1.3887 + getManifestURLByLocalId: function(aLocalId) { 1.3888 + return AppsUtils.getManifestURLByLocalId(this.webapps, aLocalId); 1.3889 + }, 1.3890 + 1.3891 + getAppLocalIdByManifestURL: function(aManifestURL) { 1.3892 + return AppsUtils.getAppLocalIdByManifestURL(this.webapps, aManifestURL); 1.3893 + }, 1.3894 + 1.3895 + getCoreAppsBasePath: function() { 1.3896 + return AppsUtils.getCoreAppsBasePath(); 1.3897 + }, 1.3898 + 1.3899 + getWebAppsBasePath: function() { 1.3900 + return OS.Path.dirname(this.appsFile); 1.3901 + }, 1.3902 + 1.3903 + _isLaunchable: function(aApp) { 1.3904 + if (this.allAppsLaunchable) 1.3905 + return true; 1.3906 + 1.3907 + return WebappOSUtils.isLaunchable(aApp); 1.3908 + }, 1.3909 + 1.3910 + _notifyCategoryAndObservers: function(subject, topic, data, msg) { 1.3911 + const serviceMarker = "service,"; 1.3912 + 1.3913 + // First create observers from the category manager. 1.3914 + let cm = 1.3915 + Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager); 1.3916 + let enumerator = cm.enumerateCategory(topic); 1.3917 + 1.3918 + let observers = []; 1.3919 + 1.3920 + while (enumerator.hasMoreElements()) { 1.3921 + let entry = 1.3922 + enumerator.getNext().QueryInterface(Ci.nsISupportsCString).data; 1.3923 + let contractID = cm.getCategoryEntry(topic, entry); 1.3924 + 1.3925 + let factoryFunction; 1.3926 + if (contractID.substring(0, serviceMarker.length) == serviceMarker) { 1.3927 + contractID = contractID.substring(serviceMarker.length); 1.3928 + factoryFunction = "getService"; 1.3929 + } 1.3930 + else { 1.3931 + factoryFunction = "createInstance"; 1.3932 + } 1.3933 + 1.3934 + try { 1.3935 + let handler = Cc[contractID][factoryFunction](); 1.3936 + if (handler) { 1.3937 + let observer = handler.QueryInterface(Ci.nsIObserver); 1.3938 + observers.push(observer); 1.3939 + } 1.3940 + } catch(e) { } 1.3941 + } 1.3942 + 1.3943 + // Next enumerate the registered observers. 1.3944 + enumerator = Services.obs.enumerateObservers(topic); 1.3945 + while (enumerator.hasMoreElements()) { 1.3946 + try { 1.3947 + let observer = enumerator.getNext().QueryInterface(Ci.nsIObserver); 1.3948 + if (observers.indexOf(observer) == -1) { 1.3949 + observers.push(observer); 1.3950 + } 1.3951 + } catch (e) { } 1.3952 + } 1.3953 + 1.3954 + observers.forEach(function (observer) { 1.3955 + try { 1.3956 + observer.observe(subject, topic, data); 1.3957 + } catch(e) { } 1.3958 + }); 1.3959 + // Send back an answer to the child. 1.3960 + if (msg) { 1.3961 + ppmm.broadcastAsyncMessage("Webapps:ClearBrowserData:Return", msg); 1.3962 + } 1.3963 + }, 1.3964 + 1.3965 + registerBrowserElementParentForApp: function(bep, appId) { 1.3966 + let mm = bep._mm; 1.3967 + 1.3968 + // Make a listener function that holds on to this appId. 1.3969 + let listener = this.receiveAppMessage.bind(this, appId); 1.3970 + 1.3971 + this.frameMessages.forEach(function(msgName) { 1.3972 + mm.addMessageListener(msgName, listener); 1.3973 + }); 1.3974 + }, 1.3975 + 1.3976 + receiveAppMessage: function(appId, message) { 1.3977 + switch (message.name) { 1.3978 + case "Webapps:ClearBrowserData": 1.3979 + this._clearPrivateData(appId, true, message.data); 1.3980 + break; 1.3981 + } 1.3982 + }, 1.3983 + 1.3984 + _clearPrivateData: function(appId, browserOnly, msg) { 1.3985 + let subject = { 1.3986 + appId: appId, 1.3987 + browserOnly: browserOnly, 1.3988 + QueryInterface: XPCOMUtils.generateQI([Ci.mozIApplicationClearPrivateDataParams]) 1.3989 + }; 1.3990 + this._notifyCategoryAndObservers(subject, "webapps-clear-data", null, msg); 1.3991 + } 1.3992 +}; 1.3993 + 1.3994 +/** 1.3995 + * Appcache download observer 1.3996 + */ 1.3997 +let AppcacheObserver = function(aApp) { 1.3998 + debug("Creating AppcacheObserver for " + aApp.origin + 1.3999 + " - " + aApp.installState); 1.4000 + this.app = aApp; 1.4001 + this.startStatus = aApp.installState; 1.4002 + this.lastProgressTime = 0; 1.4003 + // Send a first progress event to correctly set the DOM object's properties. 1.4004 + this._sendProgressEvent(); 1.4005 +}; 1.4006 + 1.4007 +AppcacheObserver.prototype = { 1.4008 + // nsIOfflineCacheUpdateObserver implementation 1.4009 + _sendProgressEvent: function() { 1.4010 + let app = this.app; 1.4011 + DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { 1.4012 + app: app, 1.4013 + manifestURL: app.manifestURL 1.4014 + }); 1.4015 + DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { 1.4016 + eventType: "progress", 1.4017 + manifestURL: app.manifestURL 1.4018 + }); 1.4019 + }, 1.4020 + 1.4021 + updateStateChanged: function appObs_Update(aUpdate, aState) { 1.4022 + let mustSave = false; 1.4023 + let app = this.app; 1.4024 + 1.4025 + debug("Offline cache state change for " + app.origin + " : " + aState); 1.4026 + 1.4027 + var self = this; 1.4028 + let setStatus = function appObs_setStatus(aStatus, aProgress) { 1.4029 + debug("Offlinecache setStatus to " + aStatus + " with progress " + 1.4030 + aProgress + " for " + app.origin); 1.4031 + mustSave = (app.installState != aStatus); 1.4032 + 1.4033 + app.installState = aStatus; 1.4034 + app.progress = aProgress; 1.4035 + if (aStatus != "installed") { 1.4036 + self._sendProgressEvent(); 1.4037 + return; 1.4038 + } 1.4039 + 1.4040 + app.updateTime = Date.now(); 1.4041 + app.downloading = false; 1.4042 + app.downloadAvailable = false; 1.4043 + DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { 1.4044 + app: app, 1.4045 + manifestURL: app.manifestURL 1.4046 + }); 1.4047 + DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { 1.4048 + eventType: ["downloadsuccess", "downloadapplied"], 1.4049 + manifestURL: app.manifestURL 1.4050 + }); 1.4051 + } 1.4052 + 1.4053 + let setError = function appObs_setError(aError) { 1.4054 + debug("Offlinecache setError to " + aError); 1.4055 + app.downloading = false; 1.4056 + DOMApplicationRegistry.broadcastMessage("Webapps:UpdateState", { 1.4057 + app: app, 1.4058 + manifestURL: app.manifestURL 1.4059 + }); 1.4060 + DOMApplicationRegistry.broadcastMessage("Webapps:FireEvent", { 1.4061 + error: aError, 1.4062 + eventType: "downloaderror", 1.4063 + manifestURL: app.manifestURL 1.4064 + }); 1.4065 + mustSave = true; 1.4066 + } 1.4067 + 1.4068 + switch (aState) { 1.4069 + case Ci.nsIOfflineCacheUpdateObserver.STATE_ERROR: 1.4070 + aUpdate.removeObserver(this); 1.4071 + AppDownloadManager.remove(app.manifestURL); 1.4072 + setError("APP_CACHE_DOWNLOAD_ERROR"); 1.4073 + break; 1.4074 + case Ci.nsIOfflineCacheUpdateObserver.STATE_NOUPDATE: 1.4075 + case Ci.nsIOfflineCacheUpdateObserver.STATE_FINISHED: 1.4076 + aUpdate.removeObserver(this); 1.4077 + AppDownloadManager.remove(app.manifestURL); 1.4078 + setStatus("installed", aUpdate.byteProgress); 1.4079 + break; 1.4080 + case Ci.nsIOfflineCacheUpdateObserver.STATE_DOWNLOADING: 1.4081 + setStatus(this.startStatus, aUpdate.byteProgress); 1.4082 + break; 1.4083 + case Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMSTARTED: 1.4084 + case Ci.nsIOfflineCacheUpdateObserver.STATE_ITEMPROGRESS: 1.4085 + let now = Date.now(); 1.4086 + if (now - this.lastProgressTime > MIN_PROGRESS_EVENT_DELAY) { 1.4087 + setStatus(this.startStatus, aUpdate.byteProgress); 1.4088 + this.lastProgressTime = now; 1.4089 + } 1.4090 + break; 1.4091 + } 1.4092 + 1.4093 + // Status changed, update the stored version. 1.4094 + if (mustSave) { 1.4095 + DOMApplicationRegistry._saveApps(); 1.4096 + } 1.4097 + }, 1.4098 + 1.4099 + applicationCacheAvailable: function appObs_CacheAvail(aApplicationCache) { 1.4100 + // Nothing to do. 1.4101 + } 1.4102 +}; 1.4103 + 1.4104 +DOMApplicationRegistry.init();