From 1953135dda9521617558d4c26b67be493db75cf3 Mon Sep 17 00:00:00 2001 From: "bsmedberg%covad.net" Date: Fri, 7 Oct 2005 15:41:05 +0000 Subject: [PATCH] Bug 300139 - API and command-line flag to install a XUL application r=robstrong (xulrunner-only) git-svn-id: svn://10.0.0.236/branches/MOZILLA_1_8_BRANCH@181775 18797224-902f-48f8-a5cc-f745e15eee43 --- mozilla/xulrunner/Makefile.in | 1 + mozilla/xulrunner/app/Makefile.in | 1 + mozilla/xulrunner/app/nsXULRunnerApp.cpp | 81 ++++ .../xulrunner/examples/simple/application.ini | 2 +- mozilla/xulrunner/setup/Makefile.in | 54 +++ mozilla/xulrunner/setup/nsIXULAppInstall.idl | 68 +++ mozilla/xulrunner/setup/nsXULAppInstall.js | 412 ++++++++++++++++++ mozilla/xulrunner/stub/nsXULStubOSX.cpp | 7 +- 8 files changed, 624 insertions(+), 2 deletions(-) create mode 100644 mozilla/xulrunner/setup/Makefile.in create mode 100644 mozilla/xulrunner/setup/nsIXULAppInstall.idl create mode 100644 mozilla/xulrunner/setup/nsXULAppInstall.js diff --git a/mozilla/xulrunner/Makefile.in b/mozilla/xulrunner/Makefile.in index bc8d9591801..a3505f102ee 100644 --- a/mozilla/xulrunner/Makefile.in +++ b/mozilla/xulrunner/Makefile.in @@ -44,6 +44,7 @@ VPATH = @srcdir@ include $(DEPTH)/config/autoconf.mk DIRS = \ + setup \ stub \ util \ app \ diff --git a/mozilla/xulrunner/app/Makefile.in b/mozilla/xulrunner/app/Makefile.in index 74b8e5a29fe..5be50a1c631 100644 --- a/mozilla/xulrunner/app/Makefile.in +++ b/mozilla/xulrunner/app/Makefile.in @@ -68,6 +68,7 @@ REQUIRES = \ string \ appshell \ xulapp \ + xulrunner \ $(NULL) CPPSRCS = nsXULRunnerApp.cpp diff --git a/mozilla/xulrunner/app/nsXULRunnerApp.cpp b/mozilla/xulrunner/app/nsXULRunnerApp.cpp index ddd583b2857..e754e3a6b5f 100644 --- a/mozilla/xulrunner/app/nsXULRunnerApp.cpp +++ b/mozilla/xulrunner/app/nsXULRunnerApp.cpp @@ -47,8 +47,10 @@ #include "nsAppRunner.h" #include "nsINIParser.h" #include "nsILocalFile.h" +#include "nsIXULAppInstall.h" #include "nsCOMPtr.h" #include "nsMemory.h" +#include "nsNativeCharsetUtils.h" #include "nsBuildID.h" #include "plstr.h" #include "prprf.h" @@ -270,6 +272,8 @@ static void Usage() " --register-user\n" " --find-gre Find a GRE with version and print\n" " the path on stdout\n" + " --install-app [ []]\n" + " Install a XUL application.\n" "\n" "APP-FILE\n" " Application initialization file.\n" @@ -298,6 +302,53 @@ GetXULRunnerDir(const char *argv0, nsIFile* *aResult) return rv; } +static int +InstallXULApp(nsIFile* aXULRunnerDir, + const char *aAppLocation, + const char *aInstallTo, + const char *aLeafName) +{ + nsCOMPtr appLocation; + nsCOMPtr installTo; + nsAutoString leafName; + + nsresult rv = XRE_GetFileFromPath(aAppLocation, getter_AddRefs(appLocation)); + if (NS_FAILED(rv)) + return 2; + + if (aInstallTo) { + rv = XRE_GetFileFromPath(aInstallTo, getter_AddRefs(installTo)); + if (NS_FAILED(rv)) + return 2; + } + + if (aLeafName) + NS_CopyNativeToUnicode(nsDependentCString(aLeafName), leafName); + + rv = NS_InitXPCOM2(nsnull, aXULRunnerDir, nsnull); + if (NS_FAILED(rv)) + return 3; + + { + // Scope our COMPtr to avoid holding XPCOM refs beyond xpcom shutdown + nsCOMPtr install + (do_GetService("@mozilla.org/xulrunner/app-install-service;1")); + if (!install) { + rv = NS_ERROR_FAILURE; + } + else { + rv = install->InstallApplication(appLocation, installTo, leafName); + } + } + + NS_ShutdownXPCOM(nsnull); + + if (NS_FAILED(rv)) + return 3; + + return 0; +} + int main(int argc, char* argv[]) { if (argc > 1 && (IsArg(argv[1], "h") || @@ -376,6 +427,36 @@ int main(int argc, char* argv[]) printf("%s\n", GRE_BUILD_ID); return 0; } + + if (IsArg(argv[1], "install-app")) { + if (argc < 3 || argc > 5) { + Usage(); + return 1; + } + + char *appLocation = argv[2]; + + char *installTo = nsnull; + if (argc > 3) { + installTo = argv[3]; + if (!*installTo) // left blank? + installTo = nsnull; + } + + char *leafName = nsnull; + if (argc > 4) { + leafName = argv[4]; + if (!*leafName) + leafName = nsnull; + } + + nsCOMPtr regDir; + nsresult rv = GetXULRunnerDir(argv[0], getter_AddRefs(regDir)); + if (NS_FAILED(rv)) + return 2; + + return InstallXULApp(regDir, appLocation, installTo, leafName); + } } geckoVersion = ParseVersion(GRE_BUILD_ID); diff --git a/mozilla/xulrunner/examples/simple/application.ini b/mozilla/xulrunner/examples/simple/application.ini index fa2cd67966a..dcb631af5d4 100644 --- a/mozilla/xulrunner/examples/simple/application.ini +++ b/mozilla/xulrunner/examples/simple/application.ini @@ -8,7 +8,7 @@ Vendor=MozillaTest ; This field specifies your application's name. This field is required. Name=Simple ; -; This field specifies your application's version. This field is optional. +; This field specifies your application's version. This field is required. Version=0.1 ; ; This field specifies your application's build ID (timestamp). This field is diff --git a/mozilla/xulrunner/setup/Makefile.in b/mozilla/xulrunner/setup/Makefile.in new file mode 100644 index 00000000000..984a5949c80 --- /dev/null +++ b/mozilla/xulrunner/setup/Makefile.in @@ -0,0 +1,54 @@ +# ***** BEGIN LICENSE BLOCK ***** +# Version: MPL 1.1/GPL 2.0/LGPL 2.1 +# +# The contents of this file are subject to the Mozilla Public License Version +# 1.1 (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" basis, +# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License +# for the specific language governing rights and limitations under the +# License. +# +# The Original Code is Mozilla XULRunner. +# +# The Initial Developer of the Original Code is +# Benjamin Smedberg . +# +# Portions created by the Initial Developer are Copyright (C) 2005 +# the Mozilla Foundation . All Rights Reserved. +# +# Contributor(s): +# +# Alternatively, the contents of this file may be used under the terms of +# either the GNU General Public License Version 2 or later (the "GPL"), or +# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), +# in which case the provisions of the GPL or the LGPL are applicable instead +# of those above. If you wish to allow use of your version of this file only +# under the terms of either the GPL or the LGPL, and not to allow others to +# use your version of this file under the terms of the MPL, indicate your +# decision by deleting the provisions above and replace them with the notice +# and other provisions required by the GPL or the LGPL. If you do not delete +# the provisions above, a recipient may use your version of this file under +# the terms of any one of the MPL, the GPL or the LGPL. +# +# ***** END LICENSE BLOCK ***** + +DEPTH = ../.. +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ +VPATH = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +MODULE = xulrunner +XPIDL_MODULE = xulapp_setup + +XPIDLSRCS = \ + nsIXULAppInstall.idl \ + $(NULL) + +EXTRA_PP_COMPONENTS = nsXULAppInstall.js + +include $(topsrcdir)/config/rules.mk diff --git a/mozilla/xulrunner/setup/nsIXULAppInstall.idl b/mozilla/xulrunner/setup/nsIXULAppInstall.idl new file mode 100644 index 00000000000..94730e5db57 --- /dev/null +++ b/mozilla/xulrunner/setup/nsIXULAppInstall.idl @@ -0,0 +1,68 @@ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla XULRunner. + * + * The Initial Developer of the Original Code is + * Benjamin Smedberg + * + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Mozilla Foundation . All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +#include "nsISupports.idl" + +interface nsIFile; + +/** + * Installation and management of XUL applications. + * + * @status IN_FLUX This interface is not stable and will change in the + * future. + */ +[scriptable, uuid(800ace15-6b38-48c4-b057-7928378f6cd2)] +interface nsIXULAppInstall : nsISupports +{ + /** + * Install a XUL application into a form that can be run by the native + * operating system. + * + * @param aAppFile Directory or a zip file containing a + * XULRunner package (with the required application.ini + * file in the root). + * @param aDirectory Path specifying the location to install the + * application. If null, an appropriate default install + * location will be used. e.g. "C:\Program Files\" + * on Windows. + * @param aLeafName The leaf name of the application directory. If empty + * an appropriate default will be chosen. e.g. "Simple.app" + * on Mac. + */ + void installApplication(in nsIFile aAppFile, in nsIFile aDirectory, + in AString aLeafName); +}; diff --git a/mozilla/xulrunner/setup/nsXULAppInstall.js b/mozilla/xulrunner/setup/nsXULAppInstall.js new file mode 100644 index 00000000000..7e8c5dfe42e --- /dev/null +++ b/mozilla/xulrunner/setup/nsXULAppInstall.js @@ -0,0 +1,412 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is Mozilla XULRunner. + * + * The Initial Developer of the Original Code is + * Benjamin Smedberg + * + * Portions created by the Initial Developer are Copyright (C) 2005 + * the Mozilla Foundation . All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ + +const nsIFile = Components.interfaces.nsIFile; +const nsIINIParser = Components.interfaces.nsIINIParser; +const nsIINIParserFactory = Components.interfaces.nsIINIParserFactory; +const nsILocalFile = Components.interfaces.nsILocalFile; +const nsISupports = Components.interfaces.nsISupports; +const nsIXULAppInstall = Components.interfaces.nsIXULAppInstall; +const nsIZipEntry = Components.interfaces.nsIZipEntry; +const nsIZipReader = Components.interfaces.nsIZipReader; + +function getDirectoryKey(aKey) { + try { + return Components.classes["@mozilla.org/file/directory_service;1"]. + getService(Components.interfaces.nsIProperties). + get(aKey, nsIFile); + } + catch (e) { + throw "Couln't get directory service key: " + aKey; + } +} + +function createINIParser(aFile) { + return Components.manager. + getClassObjectByContractID("@mozilla.org/xpcom/ini-parser-factory;1", + nsIINIParserFactory). + createINIParser(aFile); +} + +function copy_recurse(aSource, aDest) { + var e = aSource.directoryEntries; + + while (e.hasMoreElements()) { + var f = e.getNext().QueryInterface(nsIFile); + var leaf = f.leafName; + + var ddest = aDest.clone(); + ddest.append(leaf); + + if (f.isDirectory()) { + copy_recurse(f, ddest); + } + else { + if (ddest.exists()) + ddest.remove(false); + + f.copyTo(aDest, leaf); + } + } +} + +const PR_WRONLY = 0x02; +const PR_CREATE_FILE = 0x08; +const PR_TRUNCATE = 0x20; + +function openFileOutputStream(aFile) { + var s = Components.classes["@mozilla.org/network/file-output-stream;1"]. + createInstance(Components.interfaces.nsIFileOutputStream); + s.init(aFile, PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, 0644, 0); + return s; +} + +/** + * An extractor implements the following prototype: + * readonly attribute nsIINIPaser iniParser; + * void copyTo(in nsILocalFile root); + */ + +function directoryExtractor(aFile) { + this.mDirectory = aFile; +} + +directoryExtractor.prototype = { + mINIParser : null, + + get iniParser() { + if (!this.mINIParser) { + var iniFile = this.mDirectory.clone(); + iniFile.append("application.ini"); + + this.mINIParser = createINIParser(iniFile); + } + return this.mINIParser; + }, + + copyTo : function de_copyTo(aDest) { + // Assume the root already exists + copy_recurse(this.mDirectory, aDest); + } +}; + +function zipExtractor(aFile) { + this.mZipReader = Components.classes["@mozilla.org/libjar/zip-reader;1"]. + createInstance(nsIZipReader); + this.mZipReader.init(aFile); + this.mZipReader.open(); + this.mZipReader.test(null); +} + +zipExtractor.prototype = { + mINIParser : null, + + get iniParser() { + if (!this.mINIParser) { + // XXXbsmedberg: this is not very unique, guessing could be a problem + var f = getDirectoryKey("TmpD"); + f.append("application.ini"); + f.createUnique(nsIFile.NORMAL_FILE_TYPE, 0600); + + try { + this.mZipReader.extract("application.ini", f); + this.mINIParser = createINIParser(f); + } + catch (e) { + try { + f.remove(); + } + catch (ee) { } + + throw e; + } + try { + f.remove(); + } + catch (e) { } + } + return this.mINIParser; + }, + + copyTo : function ze_CopyTo(aDest) { + var entries = this.mZipReader.findEntries("*"); + while (entries.hasMoreElements()) { + var entry = entries.getNext().QueryInterface(nsIZipEntry); + + this._installZipEntry(this.mZipReader, entry, aDest); + } + }, + + _installZipEntry : function ze_installZipEntry(aZipReader, aZipEntry, + aDestination) { + var file = aDestination.clone(); + + var path = aZipEntry.name; + var dirs = path.split(/\//); + var isDirectory = path.match(/\/$/) != null; + + var end = dirs.length; + if (!isDirectory) + --end; + + for (var i = 0; i < end; ++i) { + file.append(dirs[i]); + if (!file.exists()) { + file.create(nsIFile.DIRECTORY_TYPE, 0755); + } + } + + if (!isDirectory) { + file.append(dirs[end]); + aZipReader.extract(path, file); + } + } +}; + +function createExtractor(aFile) { + if (aFile.isDirectory()) + return new directoryExtractor(aFile); + + return new zipExtractor(aFile); +} + +const AppInstall = { + + /* nsISupports */ + QueryInterface : function ai_QI(iid) { + if (iid.equals(nsIXULAppInstall) || + iid.equals(nsISupports)) + return this; + + throw Components.result.NS_ERROR_NO_INTERFACE; + }, + + /* nsIXULAppInstall */ + installApplication : function ai_IA(aAppFile, aDirectory, aLeafName) { + var extractor = createExtractor(aAppFile); + var iniParser = extractor.iniParser; + + var appName = iniParser.getString("App", "Name"); + + // vendor is optional + var vendor; + try { + vendor = iniParser.getString("App", "Vendor"); + } + catch (e) { } + + if (aDirectory == null) { +#ifdef XP_WIN + aDirectory = getDirectoryKey("ProgF"); + if (vendor) + aDirectory.append(vendor); +#else +#ifdef XP_MACOSX + aDirectory = getDirectoryKey("LocApp"); + if (vendor) + aDirectory.append(vendor); +#else + aDirectory = Components.classes["@mozilla.org/file/local;1"]. + createInstance(nsILocalFile); + aDirectory.initWithPath("/usr/lib"); + aDirectory.append(vendor.toLowerCase()); +#endif +#endif + } + else { + aDirectory = aDirectory.clone(); + } + + if (!aDirectory.exists()) { + aDirectory.create(nsIFile.DIRECTORY_TYPE, 0755); + } + + if (aLeafName == "") { +#ifdef XP_MACOSX + aLeafName = appName + ".app"; +#else +#ifdef XP_WIN + aLeafName = appName; +#else + aLeafName = appName.toLowerCase(); +#endif +#endif + } + + aDirectory.append(aLeafName); + if (!aDirectory.exists()) { + aDirectory.create(nsIFile.DIRECTORY_TYPE, 0755); + } + +#ifdef XP_MACOSX + aDirectory.append("Contents"); + if (!aDirectory.exists()) { + aDirectory.create(nsIFile.DIRECTORY_TYPE, 0755); + } + + var version = iniParser.getString("App", "Version"); + var buildID = iniParser.getString("App", "BuildID"); + + var infoString = ""; + if (vendor) { + infoString = vendor + " "; + } + infoString += appName + " " + version; + + var plistFile = aDirectory.clone(); + plistFile.append("Info.plist"); + var ostream = openFileOutputStream(plistFile); + + var contents = + "\n" + + "\n" + + "\n" + + "\n" + + "CFBundleInfoDictionaryVersion\n" + + "6.0\n" + + "CFBundlePackageType\n" + + "APPL\n" + + "CFBundleExecutable\n" + + "xulrunner\n" + + "NSAppleScriptEnabled\n" + + "\n" + + "CFBundleGetInfoString\n" + + "" + infoString + "\n" + + "CFBundleName\n" + + "" + appName + "\n" + + "CFBundleShortVersionString\n" + + "" + version + "\n" + + "CFBundleVersion\n" + + "" + version + "." + buildID + "\n" + + "\n" + + ""; + + // "CFBundleIdentifier\n" + + // "org.%s.%s\n" + + // "CFBundleSignature\n" + + // "MOZB\n" + + // "CFBundleIconFile\n" + + // "document.icns\n" + + + ostream.write(contents, contents.length); + ostream.close(); + + var contentsDir = aDirectory.clone(); + contentsDir.append("MacOS"); + + var xulrunnerBinary = getDirectoryKey("XCurProcD"); + xulrunnerBinary.append("xulrunner"); + + xulrunnerBinary.copyTo(contentsDir, "xulrunner"); + + aDirectory.append("Resources"); + extractor.copyTo(aDirectory); +#else + extractor.copyTo(aDirectory); + // Need to copy the XULRunner stub (which doesn't exist yet) +#endif + } +}; + +const AppInstallFactory = { + /* nsISupports */ + QueryInterface : function aif_QI(iid) { + if (iid.equals(Components.interfaces.nsIFactory) || + iid.equals(nsISupports)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + /* nsIFactory */ + createInstance : function aif_CI(aOuter, aIID) { + if (aOuter) + throw Components.results.NS_ERROR_NO_AGGREGATION; + + return AppInstall.QueryInterface(aIID); + }, + + lockFactory : function aif_lock(aLock) { } +}; + +const AppInstallContractID = "@mozilla.org/xulrunner/app-install-service;1"; +const AppInstallCID = Components.ID("{00790a19-27e2-4d9a-bef0-244080feabfd}"); + +const AppInstallModule = { + /* nsISupports */ + QueryInterface : function mod_QI(iid) { + if (iid.equals(Components.interfaces.nsIModule) || + iid.equals(nsISupports)) + return this; + + throw Components.results.NS_ERROR_NO_INTERFACE; + }, + + /* nsIModule */ + getClassObject : function mod_gco(aCompMgr, aClass, aIID) { + if (aClass.equals(AppInstallCID)) + return AppInstallFactory.QueryInterface(aIID); + + return Components.results.NS_ERROR_FACTORY_NOT_REGISTERED; + }, + + registerSelf : function mod_regself(aCompMgr, aLocation, + aLoaderStr, aType) { + var reg = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); + reg.registerFactoryLocation(AppInstallCID, + "nsXULAppInstall", + AppInstallContractID, + aLocation, + aLoaderStr, + aType); + }, + + unregisterSelf : function mod_unreg(aCompMgr, aLocation, aLoaderStr) { + var reg = aCompMgr.QueryInterface(Components.interfaces.nsIComponentRegistrar); + reg.unregisterFactoryLocation(AppInstallCID, + aLocation); + }, + + canUnload : function mod_unload(aCompMgr) { + return true; + } +}; + +function NSGetModule(compMgr, fileSpec) { + return AppInstallModule; +} diff --git a/mozilla/xulrunner/stub/nsXULStubOSX.cpp b/mozilla/xulrunner/stub/nsXULStubOSX.cpp index 9ff76f86432..f2acdb8861a 100644 --- a/mozilla/xulrunner/stub/nsXULStubOSX.cpp +++ b/mozilla/xulrunner/stub/nsXULStubOSX.cpp @@ -70,7 +70,7 @@ main(int argc, char **argv) absResourcesURL, CFSTR("application.ini"), false); - CFRelease(resourcesURL); + CFRelease(absResourcesURL); if (!iniFileURL) return 1; @@ -131,6 +131,11 @@ main(int argc, char **argv) return 1; } + char *lastSlash = strrchr(greDir, '/'); + if (lastSlash) { + *lastSlash = '\0'; + } + char **argv2 = (char**) alloca(sizeof(char*) * (argc + 2)); char xulBin[PATH_MAX];