From 6c2d0b1566e03904df11a5edfe4655108a3494a7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fabrice=20Desr=C3=A9?= Date: Wed, 29 Aug 2012 14:20:03 -0700 Subject: [PATCH] Bug 778079 - Support loading app packages from multiple locations [r=vingtetun] --- b2g/components/DirectoryProvider.js | 11 ++- dom/apps/src/AppsUtils.jsm | 1 + dom/apps/src/Webapps.js | 38 ++++----- dom/apps/src/Webapps.jsm | 82 +++++++++++++------ .../apps/nsIDOMApplicationRegistry.idl | 3 +- netwerk/protocol/app/AppProtocolHandler.js | 14 ++-- 6 files changed, 95 insertions(+), 54 deletions(-) diff --git a/b2g/components/DirectoryProvider.js b/b2g/components/DirectoryProvider.js index 208c355c0821..d6db58922b59 100644 --- a/b2g/components/DirectoryProvider.js +++ b/b2g/components/DirectoryProvider.js @@ -23,14 +23,19 @@ DirectoryProvider.prototype = { getFile: function dp_getFile(prop, persistent) { #ifdef MOZ_WIDGET_GONK let localProps = ["cachePDir", "webappsDir", "PrefD", "indexedDBPDir", - "permissionDBPDir", "UpdRootD"]; + "permissionDBPDir", "UpdRootD"]; if (localProps.indexOf(prop) != -1) { - prop.persistent = true; let file = Cc["@mozilla.org/file/local;1"] .createInstance(Ci.nsILocalFile) file.initWithPath(LOCAL_DIR); + persistent.value = true; return file; - } + } else if (prop == "coreAppsDir") { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile) + file.initWithPath("/system/b2g"); + persistent.value = true; + return file; + } #endif return null; diff --git a/dom/apps/src/AppsUtils.jsm b/dom/apps/src/AppsUtils.jsm index fbc49a2c0ea0..eab48946f9c2 100644 --- a/dom/apps/src/AppsUtils.jsm +++ b/dom/apps/src/AppsUtils.jsm @@ -30,6 +30,7 @@ let AppsUtils = { installTime: aApp.installTime, manifestURL: aApp.manifestURL, appStatus: aApp.appStatus, + removable: aApp.removable, localId: aApp.localId, progress: aApp.progress || 0.0, status: aApp.status || "installed" diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index 66b7810ea09f..2292b6bacbd9 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -20,8 +20,7 @@ function convertAppsArray(aApps, aWindow) { let apps = Cu.createArrayIn(aWindow); for (let i = 0; i < aApps.length; i++) { let app = aApps[i]; - apps.push(createApplicationObject(aWindow, app.origin, app.manifest, app.manifestURL, - app.receipts, app.installOrigin, app.installTime)); + apps.push(createApplicationObject(aWindow, app)); } return apps; @@ -73,8 +72,7 @@ WebappsRegistry.prototype = { let app = msg.app; switch (aMessage.name) { case "Webapps:Install:Return:OK": - Services.DOMRequest.fireSuccess(req, createApplicationObject(this._window, app.origin, app.manifest, app.manifestURL, app.receipts, - app.installOrigin, app.installTime)); + Services.DOMRequest.fireSuccess(req, createApplicationObject(this._window, app)); break; case "Webapps:Install:Return:KO": Services.DOMRequest.fireError(req, msg.error || "DENIED"); @@ -82,8 +80,7 @@ WebappsRegistry.prototype = { case "Webapps:GetSelf:Return:OK": if (msg.apps.length) { app = msg.apps[0]; - Services.DOMRequest.fireSuccess(req, createApplicationObject(this._window, app.origin, app.manifest, app.manifestURL, app.receipts, - app.installOrigin, app.installTime)); + Services.DOMRequest.fireSuccess(req, createApplicationObject(this._window, app)); } else { Services.DOMRequest.fireSuccess(req, null); } @@ -223,9 +220,9 @@ WebappsRegistry.prototype = { * mozIDOMApplication object */ -function createApplicationObject(aWindow, aOrigin, aManifest, aManifestURL, aReceipts, aInstallOrigin, aInstallTime) { +function createApplicationObject(aWindow, aApp) { let app = Cc["@mozilla.org/webapps/application;1"].createInstance(Ci.mozIDOMApplication); - app.wrappedJSObject.init(aWindow, aOrigin, aManifest, aManifestURL, aReceipts, aInstallOrigin, aInstallTime); + app.wrappedJSObject.init(aWindow, aApp); return app; } @@ -246,20 +243,24 @@ WebappsApplication.prototype = { onprogress: 'rw', launch: 'r', receipts: 'r', + removable: 'r', uninstall: 'r' }, - init: function(aWindow, aOrigin, aManifest, aManifestURL, aReceipts, aInstallOrigin, aInstallTime) { - this.origin = aOrigin; - this.manifest = ObjectWrapper.wrap(aManifest, aWindow); - this.manifestURL = aManifestURL; - this.receipts = aReceipts; - this.installOrigin = aInstallOrigin; - this.installTime = aInstallTime; + init: function(aWindow, aApp) { + this.origin = aApp.origin; + this.manifest = ObjectWrapper.wrap(aApp.manifest, aWindow); + this.manifestURL = aApp.manifestURL; + this.receipts = aApp.receipts; + this.installOrigin = aApp.installOrigin; + this.installTime = aApp.installTime; this.status = "installed"; + this.removable = aApp.removable; this.progress = NaN; this._onprogress = null; - this.initHelper(aWindow, ["Webapps:Uninstall:Return:OK", "Webapps:Uninstall:Return:KO", "Webapps:OfflineCache"]); + this.initHelper(aWindow, ["Webapps:Uninstall:Return:OK", + "Webapps:Uninstall:Return:KO", + "Webapps:OfflineCache"]); }, set onprogress(aCallback) { @@ -422,15 +423,14 @@ WebappsApplicationMgmt.prototype = { if (this._oninstall) { let app = msg.app; let event = new this._window.MozApplicationEvent("applicationinstall", - { application : createApplicationObject(this._window, app.origin, app.manifest, app.manifestURL, app.receipts, - app.installOrigin, app.installTime) }); + { application : createApplicationObject(this._window, app) }); this._oninstall.handleEvent(event); } break; case "Webapps:Uninstall:Return:OK": if (this._onuninstall) { let event = new this._window.MozApplicationEvent("applicationuninstall", - { application : createApplicationObject(this._window, msg.origin, null, null, null, null, 0) }); + { application : createApplicationObject(this._window, { origin: msg.origin }) }); this._onuninstall.handleEvent(event); } break; diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index 7a344a6a804c..02794f704ffa 100644 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -71,24 +71,41 @@ let DOMApplicationRegistry = { this.appsFile = FileUtils.getFile(DIRECTORY_NAME, ["webapps", "webapps.json"], true); - if (this.appsFile.exists()) { - this._loadJSONAsync(this.appsFile, (function(aData) { - this.webapps = aData; - for (let id in this.webapps) { -#ifdef MOZ_SYS_MSG - this._processManifestForId(id); -#endif - if (!this.webapps[id].localId) { - this.webapps[id].localId = this._nextLocalId(); - } + let dirList = [DIRECTORY_NAME]; - // Default to a non privileged status. - if (this.webapps[id].appStatus === undefined) { - this.webapps[id].appStatus = Ci.nsIPrincipal.APP_STATUS_INSTALLED; +#ifdef MOZ_WIDGET_GONK + dirList.push("coreAppsDir"); +#endif + let currentId = 1; + dirList.forEach((function(dir) { + let curFile = FileUtils.getFile(dir, ["webapps", "webapps.json"], true); + if (curFile.exists()) { + let appDir = FileUtils.getDir(dir, ["webapps"]); + this._loadJSONAsync(curFile, (function(aData) { + if (!aData) { + return; } - }; - }).bind(this)); - } + // Add new apps to the merged list. + for (let id in aData) { + this.webapps[id] = aData[id]; + this.webapps[id].basePath = appDir.path; + this.webapps[id].removable = (dir == DIRECTORY_NAME); +#ifdef MOZ_SYS_MSG + this._processManifestForId(id); +#endif + // local ids must be stable between restarts. + // We partition the ids in two buckets: + // - 1 to 1000 for the core apps. + // - 1001 to Inf for installed apps. + // This way, a gecko update with new core apps will not lead to + // changes for installed apps ids. + if (!this.webapps[id].removable) { + this.webapps[id].localId = currentId++; + } + }; + }).bind(this)); + } + }).bind(this)); try { let hosts = Services.prefs.getCharPref("dom.mozApps.whitelist"); @@ -254,7 +271,7 @@ let DOMApplicationRegistry = { this.installPackage(msg); break; case "Webapps:GetBasePath": - return FileUtils.getFile(DIRECTORY_NAME, ["webapps"], true).path; + return this.webapps[msg.id].basePath; break; case "Webapps:GetList": this.children.push(aMessage.target); @@ -262,6 +279,11 @@ let DOMApplicationRegistry = { } }, + _getAppDir: function(aId) { + FileUtils.getDir(this.webapps[aId].removable ? DIRECTORY_NAME : "coreAppsDir", + ["webapps", aId], true, true); + }, + _writeFile: function ss_writeFile(aFile, aData, aCallbak) { // Initialize the file output stream. let ostream = FileUtils.openSafeFileOutputStream(aFile); @@ -294,12 +316,13 @@ let DOMApplicationRegistry = { confirmInstall: function(aData, aFromSync, aProfileDir, aOfflineCacheObserver) { let app = aData.app; + app.removable = true; let id = app.syncId || this._appId(app.origin); let localId = this.getAppLocalIdByManifestURL(app.manifestURL); // Installing an application again is considered as an update. if (id) { - let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true); + let dir = this._getAppDir(id); try { dir.remove(true); } catch(e) { @@ -375,7 +398,8 @@ let DOMApplicationRegistry = { }, _nextLocalId: function() { - let maxLocalId = Ci.nsIScriptSecurityManager.NO_APP_ID; + // All installed apps have a localId > 1000. + let maxLocalId = 1000; for (let id in this.webapps) { if (this.webapps[id].localId > maxLocalId) { @@ -419,9 +443,10 @@ let DOMApplicationRegistry = { let id = aData[index].id; // the manifest file used to be named manifest.json, so fallback on this. - let file = FileUtils.getFile(DIRECTORY_NAME, ["webapps", id, "manifest.webapp"], true); + let baseDir = (this.webapps[id].removable ? DIRECTORY_NAME : "coreAppsDir"); + let file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.webapp"], true); if (!file.exists()) { - file = FileUtils.getFile(DIRECTORY_NAME, ["webapps", id, "manifest.json"], true); + file = FileUtils.getFile(baseDir, ["webapps", id, "manifest.json"], true); } this._loadJSONAsync(file, (function(aJSON) { @@ -598,6 +623,9 @@ let DOMApplicationRegistry = { continue; } + if (!this.webapps[id].removable) + return; + found = true; let appNote = JSON.stringify(AppsUtils.cloneAppObject(app)); appNote.id = id; @@ -608,7 +636,7 @@ let DOMApplicationRegistry = { #endif }).bind(this)); - let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true); + let dir = this._getAppDir(id); try { dir.remove(true); } catch (e) {} @@ -761,11 +789,11 @@ let DOMApplicationRegistry = { for (let i = 0; i < aRecords.length; i++) { let record = aRecords[i]; if (record.hidden) { - if (!this.webapps[record.id]) + if (!this.webapps[record.id] || !this.webapps[record.id].removable) continue; let origin = this.webapps[record.id].origin; delete this.webapps[record.id]; - let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", record.id], true, true); + let dir = this._getAppDir(record.id); try { dir.remove(true); } catch (e) { @@ -798,8 +826,12 @@ let DOMApplicationRegistry = { wipe: function(aCallback) { let ids = this.getAllIDs(); for (let id in ids) { + if (!this.webapps[id].removable) { + continue; + } + delete this.webapps[id]; - let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true); + let dir = this._getAppDir(id); try { dir.remove(true); } catch (e) { diff --git a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl index aac5e6b77aa8..b63def5712ef 100644 --- a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl +++ b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl @@ -8,7 +8,7 @@ interface nsIDOMDOMRequest; interface nsIArray; -[scriptable, uuid(9583b825-46b1-4e8f-bb48-9fed660a95e6)] +[scriptable, uuid(e3649c1d-c950-495e-b0ed-6ce40be9743b)] interface mozIDOMApplication : nsISupports { readonly attribute jsval manifest; @@ -17,6 +17,7 @@ interface mozIDOMApplication : nsISupports readonly attribute DOMString origin; readonly attribute DOMString installOrigin; readonly attribute unsigned long long installTime; + readonly attribute boolean removable; /* * The current progress when downloading an offline cache. diff --git a/netwerk/protocol/app/AppProtocolHandler.js b/netwerk/protocol/app/AppProtocolHandler.js index fe6cbe15dd44..28050c5c1abc 100644 --- a/netwerk/protocol/app/AppProtocolHandler.js +++ b/netwerk/protocol/app/AppProtocolHandler.js @@ -16,7 +16,7 @@ XPCOMUtils.defineLazyServiceGetter(this, "cpmm", "nsISyncMessageSender"); function AppProtocolHandler() { - this._basePath = null; + this._basePath = []; } AppProtocolHandler.prototype = { @@ -30,12 +30,14 @@ AppProtocolHandler.prototype = { Ci.nsIProtocolHandler.URI_NOAUTH | Ci.nsIProtocolHandler.URI_LOADABLE_BY_ANYONE, - get basePath() { - if (!this._basePath) { - this._basePath = cpmm.sendSyncMessage("Webapps:GetBasePath", { })[0] + "/"; + getBasePath: function app_phGetBasePath(aId) { + + if (!this._basePath[aId]) { + this._basePath[aId] = cpmm.sendSyncMessage("Webapps:GetBasePath", + { id: aId })[0] + "/"; } - return this._basePath; + return this._basePath[aId]; }, newURI: function app_phNewURI(aSpec, aOriginCharset, aBaseURI) { @@ -60,7 +62,7 @@ AppProtocolHandler.prototype = { } // Build a jar channel and masquerade as an app:// URI. - let uri = "jar:file://" + this.basePath + appId + "/application.zip!" + fileSpec; + let uri = "jar:file://" + this.getBasePath(appId) + appId + "/application.zip!" + fileSpec; let channel = Services.io.newChannel(uri, null, null); channel.QueryInterface(Ci.nsIJARChannel).setAppURI(aURI); channel.QueryInterface(Ci.nsIChannel).originalURI = aURI;