From 19539a707131f0d81426ca22ecca0ee50048d7e1 Mon Sep 17 00:00:00 2001 From: Marco Castelluccio Date: Thu, 1 Aug 2013 17:00:39 -0700 Subject: [PATCH] Bug 777402 - Implement support for packaged apps via the installPackage function in the mozapps DOM API in desktop web runtime. r=myk,wesj,fabrice --- browser/modules/webappsUI.jsm | 15 +- dom/apps/src/Webapps.js | 101 +++---- dom/apps/src/Webapps.jsm | 22 +- dom/apps/src/moz.build | 5 +- .../apps/nsIDOMApplicationRegistry.idl | 15 +- .../apps/nsIDOMApplicationRegistry2.idl | 15 +- .../mochitest/webapps/test_install_errors.xul | 7 - dom/tests/mochitest/webapps/test_list_api.xul | 1 + mobile/android/chrome/content/browser.js | 15 +- .../mozbase/mozprofile/mozprofile/webapps.py | 1 + toolkit/webapps/WebappOSUtils.jsm | 9 +- toolkit/webapps/WebappsInstaller.jsm | 258 +++++++++++------- webapprt/CommandLineHandler.js | 2 +- webapprt/WebappRT.jsm | 14 +- webapprt/WebappsHandler.jsm | 27 +- webapprt/test/chrome/head.js | 2 +- 16 files changed, 284 insertions(+), 225 deletions(-) diff --git a/browser/modules/webappsUI.jsm b/browser/modules/webappsUI.jsm index 503bed39544f..395fb4dc9064 100644 --- a/browser/modules/webappsUI.jsm +++ b/browser/modules/webappsUI.jsm @@ -106,15 +106,21 @@ this.webappsUI = { label: bundle.getString("webapps.install"), accessKey: bundle.getString("webapps.install.accesskey"), callback: function() { - let app = WebappsInstaller.install(aData); + let app = WebappsInstaller.init(aData); + if (app) { let localDir = null; if (app.appProfile) { localDir = app.appProfile.localDir; } - DOMApplicationRegistry.confirmInstall(aData, false, localDir); - installationSuccessNotification(aData, app, aWindow); + DOMApplicationRegistry.confirmInstall(aData, false, localDir, null, + function (aManifest) { + if (WebappsInstaller.install(aData, aManifest)) { + installationSuccessNotification(aData, app, aWindow); + } + } + ); } else { DOMApplicationRegistry.denyInstall(aData); } @@ -122,7 +128,8 @@ this.webappsUI = { }; let requestingURI = aWindow.makeURI(aData.from); - let manifest = new ManifestHelper(aData.app.manifest, aData.app.origin); + let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest; + let manifest = new ManifestHelper(jsonManifest, aData.app.origin); let host; try { diff --git a/dom/apps/src/Webapps.js b/dom/apps/src/Webapps.js index 4d9c68a1b743..56233bee2da5 100644 --- a/dom/apps/src/Webapps.js +++ b/dom/apps/src/Webapps.js @@ -118,19 +118,9 @@ WebappsRegistry.prototype = { return false; }, - // mozIDOMApplicationRegistry implementation - - install: function(aURL, aParams) { - let uri = this._validateURL(aURL); - - let request = this.createRequest(); - - if (!this._ensureForeground(request)) { - return request; - } - + _prepareInstall: function(aURL, aRequest, aParams, isPackage) { let installURL = this._window.location.href; - let requestID = this.getRequestId(request); + let requestID = this.getRequestId(aRequest); let receipts = (aParams && aParams.receipts && Array.isArray(aParams.receipts)) ? aParams.receipts : []; @@ -139,20 +129,36 @@ WebappsRegistry.prototype = { : []; let principal = this._window.document.nodePrincipal; - cpmm.sendAsyncMessage("Webapps:Install", - { app: { - installOrigin: this._getOrigin(installURL), - origin: this._getOrigin(uri), - manifestURL: uri, - receipts: receipts, - categories: categories - }, - from: installURL, - oid: this._id, - requestID: requestID, - appId: principal.appId, - isBrowser: principal.isInBrowserElement - }); + + return { app: { + installOrigin: this._getOrigin(installURL), + origin: this._getOrigin(aURL), + manifestURL: aURL, + receipts: receipts, + categories: categories + }, + + from: installURL, + oid: this._id, + requestID: requestID, + appId: principal.appId, + isBrowser: principal.isInBrowserElement, + isPackage: isPackage + }; + }, + + // mozIDOMApplicationRegistry implementation + + install: function(aURL, aParams) { + let uri = this._validateURL(aURL); + + let request = this.createRequest(); + + if (this._ensureForeground(request)) { + cpmm.sendAsyncMessage("Webapps:Install", + this._prepareInstall(uri, request, aParams, false)); + } + return request; }, @@ -201,42 +207,16 @@ WebappsRegistry.prototype = { ["Webapps:Install:Return:OK"]); }, - // mozIDOMApplicationRegistry2 implementation - installPackage: function(aURL, aParams) { let uri = this._validateURL(aURL); let request = this.createRequest(); - if (!this._ensureForeground(request)) { - return request; + if (this._ensureForeground(request)) { + cpmm.sendAsyncMessage("Webapps:InstallPackage", + this._prepareInstall(uri, request, aParams, true)); } - let installURL = this._window.location.href; - let requestID = this.getRequestId(request); - let receipts = (aParams && aParams.receipts && - Array.isArray(aParams.receipts)) ? aParams.receipts - : []; - let categories = (aParams && aParams.categories && - Array.isArray(aParams.categories)) ? aParams.categories - : []; - - let principal = this._window.document.nodePrincipal; - cpmm.sendAsyncMessage("Webapps:InstallPackage", - { app: { - installOrigin: this._getOrigin(installURL), - origin: this._getOrigin(uri), - manifestURL: uri, - receipts: receipts, - categories: categories - }, - from: installURL, - oid: this._id, - requestID: requestID, - isPackage: true, - appId: principal.appId, - isBrowser: principal.isInBrowserElement - }); return request; }, @@ -266,22 +246,13 @@ WebappsRegistry.prototype = { QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference, Ci.mozIDOMApplicationRegistry, -#ifdef MOZ_B2G Ci.mozIDOMApplicationRegistry2, -#elifdef MOZ_WIDGET_ANDROID - Ci.mozIDOMApplicationRegistry2, -#endif Ci.nsIDOMGlobalPropertyInitializer]), classInfo: XPCOMUtils.generateCI({classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"), contractID: "@mozilla.org/webapps;1", interfaces: [Ci.mozIDOMApplicationRegistry, -#ifdef MOZ_B2G - Ci.mozIDOMApplicationRegistry2, -#elifdef MOZ_WIDGET_ANDROID - Ci.mozIDOMApplicationRegistry2, -#endif - ], + Ci.mozIDOMApplicationRegistry2], flags: Ci.nsIClassInfo.DOM_OBJECT, classDescription: "Webapps Registry"}) } diff --git a/dom/apps/src/Webapps.jsm b/dom/apps/src/Webapps.jsm index e153f5f4dce6..1e3be61435d9 100644 --- a/dom/apps/src/Webapps.jsm +++ b/dom/apps/src/Webapps.jsm @@ -1841,15 +1841,6 @@ this.DOMApplicationRegistry = { app.installOrigin + ": " + aError); }.bind(this); - // Disallow reinstalls from the same manifest URL for now. - // See https://bugzilla.mozilla.org/show_bug.cgi?id=821288 - if (this.getAppLocalIdByManifestURL(app.manifestURL) !== - Ci.nsIScriptSecurityManager.NO_APP_ID || - this._appId(app.origin) !== null) { - sendError("REINSTALL_FORBIDDEN"); - return; - } - let xhr = Cc["@mozilla.org/xmlextras/xmlhttprequest;1"] .createInstance(Ci.nsIXMLHttpRequest); xhr.open("GET", app.manifestURL, true); @@ -1872,6 +1863,13 @@ this.DOMApplicationRegistry = { return; } + // Disallow reinstalls from the same manifest URL for now. + if (this._appIdForManifestURL(app.manifestURL) !== null && + this._isLaunchable(app)) { + sendError("REINSTALL_FORBIDDEN"); + return; + } + if (!(AppsUtils.checkManifest(manifest, app) && manifest.package_path)) { sendError("INVALID_MANIFEST"); @@ -1988,7 +1986,7 @@ this.DOMApplicationRegistry = { appObject.id = id; appObject.localId = localId; appObject.basePath = FileUtils.getDir(DIRECTORY_NAME, ["webapps"], true, true).path; - let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", id], true, true); + let dir = this._getAppDir(id); let manFile = dir.clone(); manFile.append(manifestName); let jsonManifest = aData.isPackage ? app.updateManifest : app.manifest; @@ -2062,7 +2060,7 @@ this.DOMApplicationRegistry = { if (!aData.isPackage) { this.updateAppHandlers(null, app.manifest, app); if (aInstallSuccessCallback) { - aInstallSuccessCallback(manifest); + aInstallSuccessCallback(app.manifest); } } @@ -2074,7 +2072,7 @@ this.DOMApplicationRegistry = { // Success! Move the zip out of TmpD. let app = DOMApplicationRegistry.webapps[aId]; let zipFile = FileUtils.getFile("TmpD", ["webapps", aId, "application.zip"], true); - let dir = FileUtils.getDir(DIRECTORY_NAME, ["webapps", aId], true, true); + let dir = this._getAppDir(id); zipFile.moveTo(dir, "application.zip"); let tmpDir = FileUtils.getDir("TmpD", ["webapps", aId], true, true); try { diff --git a/dom/apps/src/moz.build b/dom/apps/src/moz.build index c8b90539372e..b3eb71e349ca 100644 --- a/dom/apps/src/moz.build +++ b/dom/apps/src/moz.build @@ -7,11 +7,8 @@ EXTRA_COMPONENTS += [ 'AppsService.js', 'AppsService.manifest', - 'Webapps.manifest', -] - -EXTRA_PP_COMPONENTS += [ 'Webapps.js', + 'Webapps.manifest', ] EXTRA_JS_MODULES += [ diff --git a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl index 0852c46ab96f..d1ef258087e1 100644 --- a/dom/interfaces/apps/nsIDOMApplicationRegistry.idl +++ b/dom/interfaces/apps/nsIDOMApplicationRegistry.idl @@ -138,7 +138,7 @@ interface mozIDOMApplicationMgmt : nsISupports nsIDOMDOMRequest uninstall(in mozIDOMApplication app); }; -[scriptable, uuid(abfc6c15-8b92-4b9a-b892-52e6ae76f379)] +[scriptable, uuid(52710c5f-b2a2-4b27-b5b9-f679a1bcc79b)] interface mozIDOMApplicationRegistry : nsISupports { /** @@ -169,5 +169,18 @@ interface mozIDOMApplicationRegistry : nsISupports */ nsIDOMDOMRequest getInstalled(); + /** + * Install a packaged web app. + * + * @param packageUrl : the URL of the webapps manifest. + * @param parameters : A structure with optional information. + * { + * receipts: ... Will be used to specify the payment receipts for this installation. + * categories: ... Will be used to specify the categories of the webapp. + * } + * @returns : A DOMRequest object, returning the app object in |result| if install succeeds. + */ + nsIDOMDOMRequest installPackage(in DOMString packageUrl, [optional] in jsval parameters); + readonly attribute mozIDOMApplicationMgmt mgmt; }; diff --git a/dom/interfaces/apps/nsIDOMApplicationRegistry2.idl b/dom/interfaces/apps/nsIDOMApplicationRegistry2.idl index 621b2636b037..71e2200aa28a 100644 --- a/dom/interfaces/apps/nsIDOMApplicationRegistry2.idl +++ b/dom/interfaces/apps/nsIDOMApplicationRegistry2.idl @@ -6,19 +6,8 @@ interface nsIDOMDOMRequest; -[scriptable, uuid(34498a66-3aee-4b80-8b8b-a9c5d5ba32b6)] +// This interface is still here for backwards compatibility. +[scriptable, uuid(5bd838b2-cf3d-406e-bbef-f633cf9e68de)] interface mozIDOMApplicationRegistry2 : mozIDOMApplicationRegistry { - /** - * Install a packaged web app. - * - * @param packageUrl : the URL of the webapps manifest. - * @param parameters : A structure with optional information. - * { - * receipts: ... Will be used to specify the payment receipts for this installation. - * categories: ... Will be used to specify the categories of the webapp. - * } - * @returns : A DOMRequest object, returning the app object in |result| if install succeeds. - */ - nsIDOMDOMRequest installPackage(in DOMString packageUrl, [optional] in jsval parameters); }; diff --git a/dom/tests/mochitest/webapps/test_install_errors.xul b/dom/tests/mochitest/webapps/test_install_errors.xul index 8a88b3818034..4f0d1f234658 100644 --- a/dom/tests/mochitest/webapps/test_install_errors.xul +++ b/dom/tests/mochitest/webapps/test_install_errors.xul @@ -24,7 +24,6 @@ var steps = [ invalidManifest, permissionDenied, invalidContent, - installPackageNotImplemented, invalidLaunchPath, invalidLaunchPath2, invalidEntryPoint, @@ -167,12 +166,6 @@ function invalidMessage(next) { }; } -function installPackageNotImplemented(next) { - ok(!("installPackage" in navigator.mozApps), - "installPackage not in navigator.mozApps"); - next(); -} - function fileURL(next) { try { navigator.mozApps.install("file:///nonexistent"); diff --git a/dom/tests/mochitest/webapps/test_list_api.xul b/dom/tests/mochitest/webapps/test_list_api.xul index f2836273a3c8..dad70db422f8 100644 --- a/dom/tests/mochitest/webapps/test_list_api.xul +++ b/dom/tests/mochitest/webapps/test_list_api.xul @@ -24,6 +24,7 @@ var props = { getInstalled: "function", getSelf: "function", install: "function", + installPackage: "function", mgmt: "object", }; diff --git a/mobile/android/chrome/content/browser.js b/mobile/android/chrome/content/browser.js index 689784ff7321..5b1c779af293 100644 --- a/mobile/android/chrome/content/browser.js +++ b/mobile/android/chrome/content/browser.js @@ -6722,10 +6722,9 @@ var WebappsUI = { doInstall: function doInstall(aData) { let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest; let manifest = new ManifestHelper(jsonManifest, aData.app.origin); - let name = manifest.name ? manifest.name : manifest.fullLaunchPath(); let showPrompt = true; - if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), name + "\n" + aData.app.origin)) { + if (!showPrompt || Services.prompt.confirm(null, Strings.browser.GetStringFromName("webapps.installTitle"), manifest.name + "\n" + aData.app.origin)) { // Get a profile for the app to be installed in. We'll download everything before creating the icons. let origin = aData.app.origin; let profilePath = sendMessageToJava({ @@ -6737,10 +6736,12 @@ var WebappsUI = { if (profilePath) { let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsILocalFile); file.initWithPath(profilePath); - + let self = this; DOMApplicationRegistry.confirmInstall(aData, false, file, null, - function (manifest) { + function (aManifest) { + let localeManifest = new ManifestHelper(aManifest, aData.app.origin); + // the manifest argument is the manifest from within the zip file, // TODO so now would be a good time to ask about permissions. self.makeBase64Icon(self.getBiggestIcon(manifest.icons, Services.io.newURI(aData.app.origin, null, null)), @@ -6760,7 +6761,7 @@ var WebappsUI = { // aData.app.origin may now point to the app: url that hosts this app sendMessageToJava({ type: "WebApps:PostInstall", - name: manifest.name, + name: localeManifest.name, manifestURL: aData.app.manifestURL, originalOrigin: origin, origin: aData.app.origin, @@ -6770,7 +6771,7 @@ var WebappsUI = { // For packaged apps, put a notification in the notification bar. let message = Strings.browser.GetStringFromName("webapps.alertSuccess"); let alerts = Cc["@mozilla.org/alerts-service;1"].getService(Ci.nsIAlertsService); - alerts.showAlertNotification("drawable://alert_app", manifest.name, message, true, "", { + alerts.showAlertNotification("drawable://alert_app", localeManifest.name, message, true, "", { observe: function () { self.openURL(aData.app.manifestURL, aData.app.origin); } @@ -6779,7 +6780,7 @@ var WebappsUI = { } catch(ex) { console.log(ex); } - self.writeDefaultPrefs(file, manifest); + self.writeDefaultPrefs(file, localeManifest); } ); } diff --git a/testing/mozbase/mozprofile/mozprofile/webapps.py b/testing/mozbase/mozprofile/mozprofile/webapps.py index a5d94f0d63a4..17c2bd4c8752 100644 --- a/testing/mozbase/mozprofile/mozprofile/webapps.py +++ b/testing/mozbase/mozprofile/mozprofile/webapps.py @@ -66,6 +66,7 @@ class WebappCollection(object): """A list-like object that collects webapps and updates the webapp manifests""" json_template = Template(""""$name": { + "name": "$name", "origin": "$origin", "installOrigin": "$origin", "receipt": null, diff --git a/toolkit/webapps/WebappOSUtils.jsm b/toolkit/webapps/WebappOSUtils.jsm index b9397a414606..c48e9c0bd696 100644 --- a/toolkit/webapps/WebappOSUtils.jsm +++ b/toolkit/webapps/WebappOSUtils.jsm @@ -21,13 +21,18 @@ this.WebappOSUtils = { // doesn't have a name property. We then need to use the manifest. // For some mozApps calls, the aApp object doesn't have a manifest // associated, and so we need to use the name property. + // They're guaranteed to be always identical to the application + // name in the user locale. if (aApp.name) { name = aApp.name; } else { - name = aApp.manifest.name; + let manifest = + new ManifestHelper(aApp.updateManifest || aApp.manifest, aApp.origin); + name = manifest.name; } - return this.sanitizeStringForFilename(name).toLowerCase() + "-" + AppsUtils.computeHash(aApp.manifestURL); + return this.sanitizeStringForFilename(name).toLowerCase() + "-" + + AppsUtils.computeHash(aApp.manifestURL); }, /** diff --git a/toolkit/webapps/WebappsInstaller.jsm b/toolkit/webapps/WebappsInstaller.jsm index 0fe3d8989a76..778883499bcf 100644 --- a/toolkit/webapps/WebappsInstaller.jsm +++ b/toolkit/webapps/WebappsInstaller.jsm @@ -17,113 +17,174 @@ Cu.import("resource://gre/modules/WebappOSUtils.jsm"); Cu.import("resource://gre/modules/AppsUtils.jsm"); this.WebappsInstaller = { + shell: null, + + /** + * Initializes the app object that takes care of the installation + * and creates the profile directory for an application + * + * @param aData the data provided to the install function + * + * @returns NativeApp on success, null on error + */ + init: function(aData) { +#ifdef XP_WIN + this.shell = new WinNativeApp(aData); +#elifdef XP_MACOSX + this.shell = new MacNativeApp(aData); +#elifdef XP_UNIX + this.shell = new LinuxNativeApp(aData); +#else + return null; +#endif + + try { + if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) { + return this.shell; + } + } catch (ex) {} + + try { + this.shell.createAppProfile(); + } catch (ex) { + Cu.reportError("Error installing app: " + ex); + return null; + } + + return this.shell; + }, + /** * Creates a native installation of the web app in the OS * - * @param aData the manifest data provided by the web app + * @param aData the data provided to the install function + * @param aManifest the manifest data provided by the web app * - * @returns bool true on success, false if an error was thrown + * @returns true on success, false if an error was thrown */ - install: function(aData) { - + install: function(aData, aManifest) { try { if (Services.prefs.getBoolPref("browser.mozApps.installer.dry_run")) { return true; } } catch (ex) {} -#ifdef XP_WIN - let shell = new WinNativeApp(aData); -#elifdef XP_MACOSX - let shell = new MacNativeApp(aData); -#elifdef XP_UNIX - let shell = new LinuxNativeApp(aData); -#else - return false; -#endif + this.shell.init(aData, aManifest); try { - shell.install(); + this.shell.install(); } catch (ex) { Cu.reportError("Error installing app: " + ex); - return null; + return false; } let data = { - "installDir": shell.installDir.path, - "app": aData.app + "installDir": this.shell.installDir.path, + "app": { + "manifest": aManifest, + "origin": aData.app.origin + } }; Services.obs.notifyObservers(null, "webapp-installed", JSON.stringify(data)); - return shell; + return true; } } /** * This function implements the common constructor for - * the Windows, Mac and Linux native app shells. It reads and parses - * the data from the app manifest and stores it in the NativeApp - * object. It's meant to be called as NativeApp.call(this, aData) - * from the platform-specific constructor. + * the Windows, Mac and Linux native app shells. It sets + * the app unique name. It's meant to be called as + * NativeApp.call(this, aData) from the platform-specific + * constructor. * - * @param aData the data object provided by the web app with - * all the app settings and specifications. + * @param aData the data object provided to the install function * */ function NativeApp(aData) { - let app = this.app = aData.app; + this.uniqueName = WebappOSUtils.getUniqueName(aData.app); - this.uniqueName = WebappOSUtils.getUniqueName(app); + let jsonManifest = aData.isPackage ? aData.app.updateManifest : aData.app.manifest; + let manifest = new ManifestHelper(jsonManifest, aData.app.origin); - let origin = Services.io.newURI(app.origin, null, null); - - let biggestIcon = getBiggestIconURL(app.manifest.icons); - try { - let iconURI = Services.io.newURI(biggestIcon, null, null); - if (iconURI.scheme == "data") { - this.iconURI = iconURI; - } - } catch (ex) {} - - if (!this.iconURI) { - try { - this.iconURI = Services.io.newURI(origin.resolve(biggestIcon), null, null); - } - catch (ex) {} - } - - this.appName = sanitize(app.manifest.name); + this.appName = sanitize(manifest.name); this.appNameAsFilename = stripStringForFilename(this.appName); - - if(app.manifest.developer && app.manifest.developer.name) { - let devName = app.manifest.developer.name.substr(0, 128); - devName = sanitize(devName); - if (devName) { - this.developerName = devName; - } - } - - let shortDesc = this.appName; - if (app.manifest.description) { - let firstLine = app.manifest.description.split("\n")[0]; - shortDesc = firstLine.length <= 256 - ? firstLine - : firstLine.substr(0, 253) + "..."; - } - this.shortDescription = sanitize(shortDesc); - - // The app registry is the Firefox profile from which the app - // was installed. - this.registryFolder = Services.dirsvc.get("ProfD", Ci.nsIFile); - - this.webappJson = { - "registryDir": this.registryFolder.path, - "app": app - }; - - this.runtimeFolder = Services.dirsvc.get("GreD", Ci.nsIFile); } +NativeApp.prototype = { + uniqueName: null, + appName: null, + appNameAsFilename: null, + iconURI: null, + developerName: null, + shortDescription: null, + categories: null, + webappJson: null, + runtimeFolder: null, + + /** + * This function reads and parses the data from the app + * manifest and stores it in the NativeApp object. + * + * @param aData the data object provided to the install function + * @param aManifest the manifest data provided by the web app + * + */ + init: function(aData, aManifest) { + let manifest = new ManifestHelper(aManifest, aData.app.origin); + + let origin = Services.io.newURI(aData.app.origin, null, null); + + let biggestIcon = getBiggestIconURL(manifest.icons); + try { + let iconURI = Services.io.newURI(biggestIcon, null, null); + if (iconURI.scheme == "data") { + this.iconURI = iconURI; + } + } catch (ex) {} + + if (!this.iconURI) { + try { + this.iconURI = Services.io.newURI(origin.resolve(biggestIcon), null, null); + } + catch (ex) {} + } + + if (manifest.developer && manifest.developer.name) { + let devName = sanitize(manifest.developer.name.substr(0, 128)); + if (devName) { + this.developerName = devName; + } + } + + if (manifest.description) { + let firstLine = manifest.description.split("\n")[0]; + let shortDesc = firstLine.length <= 256 + ? firstLine + : firstLine.substr(0, 253) + "…"; + this.shortDescription = sanitize(shortDesc); + } else { + this.shortDescription = this.appName; + } + + this.categories = aData.app.categories.slice(0); + + // The app registry is the Firefox profile from which the app + // was installed. + let registryFolder = Services.dirsvc.get("ProfD", Ci.nsIFile); + + this.webappJson = { + "registryDir": registryFolder.path, + "app": { + "manifest": aManifest, + "origin": aData.app.origin + } + }; + + this.runtimeFolder = Services.dirsvc.get("GreD", Ci.nsIFile); + } +}; + #ifdef XP_WIN /************************************* * Windows app installer @@ -151,8 +212,7 @@ function NativeApp(aData) { /** * Constructor for the Windows native app shell * - * @param aData the data object provided by the web app with - * all the app settings and specifications. + * @param aData the data object provided to the install function */ function WinNativeApp(aData) { NativeApp.call(this, aData); @@ -160,21 +220,18 @@ function WinNativeApp(aData) { } WinNativeApp.prototype = { + __proto__: NativeApp.prototype, + /** - * Install the app in the system by creating the folder structure, + * Install the app in the system * */ install: function() { - // Remove previously installed app (for update purposes) - this._removeInstallation(true); - try { - this._createDirectoryStructure(); this._copyPrebuiltFiles(); this._createConfigFiles(); this._createShortcutFiles(); this._writeSystemKeys(); - this._createAppProfile(); } catch (ex) { this._removeInstallation(false); throw(ex); @@ -221,6 +278,11 @@ WinNativeApp.prototype = { this.iconFile.append("default.ico"); this.uninstallSubkeyStr = this.uniqueName; + + // Remove previously installed app (for update purposes) + this._removeInstallation(true); + + this._createDirectoryStructure(); }, /** @@ -269,15 +331,17 @@ WinNativeApp.prototype = { * Creates the main directory structure. */ _createDirectoryStructure: function() { - if (!this.installDir.exists()) + if (!this.installDir.exists()) { this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); + } + this.uninstallDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); }, /** * Creates the profile to be used for this app. */ - _createAppProfile: function() { + createAppProfile: function() { let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"] .getService(Ci.nsIToolkitProfileService); @@ -453,6 +517,8 @@ function MacNativeApp(aData) { } MacNativeApp.prototype = { + __proto__: NativeApp.prototype, + _init: function() { this.appSupportDir = Services.dirsvc.get("ULibDir", Ci.nsILocalFile); this.appSupportDir.append("Application Support"); @@ -482,15 +548,17 @@ MacNativeApp.prototype = { this.iconFile = this.resourcesDir.clone(); this.iconFile.append("appicon.icns"); + + // Remove previously installed app (for update purposes) + this._removeInstallation(true); + + this._createDirectoryStructure(); }, install: function() { - this._removeInstallation(true); try { - this._createDirectoryStructure(); this._copyPrebuiltFiles(); this._createConfigFiles(); - this._createAppProfile(); } catch (ex) { this._removeInstallation(false); throw(ex); @@ -510,15 +578,16 @@ MacNativeApp.prototype = { }, _createDirectoryStructure: function() { - if (!this.appProfileDir.exists()) + if (!this.appProfileDir.exists()) { this.appProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); + } this.contentsDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); this.macOSDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); this.resourcesDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0755); }, - _createAppProfile: function() { + createAppProfile: function() { let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"] .getService(Ci.nsIToolkitProfileService); @@ -650,6 +719,8 @@ function LinuxNativeApp(aData) { } LinuxNativeApp.prototype = { + __proto__: NativeApp.prototype, + _init: function() { // The ${InstallDir} and desktop entry filename are: sanitized app name + // "-" + manifest url hash @@ -685,16 +756,17 @@ LinuxNativeApp.prototype = { this.desktopINI.append("applications"); this.desktopINI.append("owa-" + this.uniqueName + ".desktop"); + + // Remove previously installed app (for update purposes) + this._removeInstallation(true); + + this._createDirectoryStructure(); }, install: function() { - this._removeInstallation(true); - try { - this._createDirectoryStructure(); this._copyPrebuiltFiles(); this._createConfigFiles(); - this._createAppProfile(); } catch (ex) { this._removeInstallation(false); throw(ex); @@ -729,7 +801,7 @@ LinuxNativeApp.prototype = { webapprtPre.copyTo(this.installDir, this.webapprt.leafName); }, - _createAppProfile: function() { + createAppProfile: function() { let profSvc = Cc["@mozilla.org/toolkit/profile-service;1"] .getService(Ci.nsIToolkitProfileService); @@ -770,7 +842,7 @@ LinuxNativeApp.prototype = { // The trailing semicolon is needed as written in the freedesktop specification let categories = ""; - for (let category of this.app.categories) { + for (let category of this.categories) { let catLower = category.toLowerCase(); if (catLower in translations) { categories += translations[catLower] + ";"; diff --git a/webapprt/CommandLineHandler.js b/webapprt/CommandLineHandler.js index e61183541700..c0e43c24c5f8 100644 --- a/webapprt/CommandLineHandler.js +++ b/webapprt/CommandLineHandler.js @@ -30,7 +30,7 @@ CommandLineHandler.prototype = { "chrome,dialog=no", args); } else { - args.setProperty("url", WebappRT.launchURI.spec); + args.setProperty("url", WebappRT.launchURI); Services.ww.openWindow(null, "chrome://webapprt/content/webapp.xul", "_blank", diff --git a/webapprt/WebappRT.jsm b/webapprt/WebappRT.jsm index 3a920908eeee..acd8f2017158 100644 --- a/webapprt/WebappRT.jsm +++ b/webapprt/WebappRT.jsm @@ -10,17 +10,13 @@ const Cu = Components.utils; Cu.import("resource://gre/modules/XPCOMUtils.jsm"); Cu.import("resource://gre/modules/Services.jsm"); +Cu.import("resource://gre/modules/AppsUtils.jsm"); XPCOMUtils.defineLazyGetter(this, "FileUtils", function() { Cu.import("resource://gre/modules/FileUtils.jsm"); return FileUtils; }); -XPCOMUtils.defineLazyGetter(this, "DOMApplicationRegistry", function() { - Cu.import("resource://gre/modules/Webapps.jsm"); - return DOMApplicationRegistry; -}); - this.WebappRT = { _config: null, @@ -51,10 +47,8 @@ this.WebappRT = { }, get launchURI() { - let url = Services.io.newURI(this.config.app.origin, null, null); - if (this.config.app.manifest.launch_path) { - url = Services.io.newURI(this.config.app.manifest.launch_path, null, url); - } - return url; + let manifest = new ManifestHelper(this.config.app.manifest, + this.config.app.origin); + return manifest.fullLaunchPath(); } }; diff --git a/webapprt/WebappsHandler.jsm b/webapprt/WebappsHandler.jsm index b4b8ec0d25d1..888dae75c45b 100644 --- a/webapprt/WebappsHandler.jsm +++ b/webapprt/WebappsHandler.jsm @@ -12,6 +12,7 @@ let Cu = Components.utils; Cu.import("resource://gre/modules/Services.jsm"); Cu.import("resource://gre/modules/Webapps.jsm"); +Cu.import("resource://gre/modules/AppsUtils.jsm"); Cu.import("resource://gre/modules/WebappsInstaller.jsm"); Cu.import("resource://gre/modules/WebappOSUtils.jsm"); @@ -42,7 +43,9 @@ this.WebappsHandler = { }, doInstall: function(data, window) { - let {name} = data.app.manifest; + let jsonManifest = data.isPackage ? data.app.updateManifest : data.app.manifest; + let manifest = new ManifestHelper(jsonManifest, data.app.origin); + let name = manifest.name; let bundle = Services.strings.createBundle("chrome://webapprt/locale/webapp.properties"); let choice = Services.prompt.confirmEx( @@ -60,10 +63,24 @@ this.WebappsHandler = { {}); // Perform the install if the user allows it - if (choice == 0 && WebappsInstaller.install(data)) { - DOMApplicationRegistry.confirmInstall(data); - } - else { + if (choice == 0) { + let shell = WebappsInstaller.init(data); + + if (shell) { + let localDir = null; + if (shell.appProfile) { + localDir = shell.appProfile.localDir; + } + + DOMApplicationRegistry.confirmInstall(data, false, localDir, null, + function (aManifest) { + WebappsInstaller.install(data, aManifest); + } + ); + } else { + DOMApplicationRegistry.denyInstall(data); + } + } else { DOMApplicationRegistry.denyInstall(data); } } diff --git a/webapprt/test/chrome/head.js b/webapprt/test/chrome/head.js index ae3eba9f7d58..bd2107f066c6 100644 --- a/webapprt/test/chrome/head.js +++ b/webapprt/test/chrome/head.js @@ -31,7 +31,7 @@ function loadWebapp(manifest, parameters, onLoad) { onLoad(); } gAppBrowser.addEventListener("load", onLoadApp, true); - gAppBrowser.setAttribute("src", WebappRT.launchURI.spec); + gAppBrowser.setAttribute("src", WebappRT.launchURI); }); registerCleanupFunction(function() {