Permalink
Browse files

Alpha version of native app generation for Mac & Windows

  • Loading branch information...
1 parent 6b34cce commit b53a1cc2453a2fa704207d9e26dabcdfac1aa156 @anantn committed Nov 22, 2011
Showing with 2,485 additions and 23 deletions.
  1. +2 −0 Makefile
  2. +1 −0 addons/jetpack/data/native-install/XUL/chrome.manifest
  3. +35 −0 addons/jetpack/data/native-install/XUL/content/main.js
  4. +5 −0 addons/jetpack/data/native-install/XUL/content/main.xul
  5. +343 −0 addons/jetpack/data/native-install/XUL/content/window.js
  6. +17 −0 addons/jetpack/data/native-install/XUL/content/window.xul
  7. +9 −0 addons/jetpack/data/native-install/XUL/defaults/preferences/prefs.js
  8. +10 −0 addons/jetpack/data/native-install/XUL/newapp_template.ini
  9. +10 −0 addons/jetpack/data/native-install/XUL/specialfiles.json
  10. +74 −0 addons/jetpack/data/native-install/mac/Contents/Info.plist
  11. +13 −0 addons/jetpack/data/native-install/mac/specialfiles.json
  12. +6 −0 addons/jetpack/data/native-install/windows/app/specialfiles.json
  13. +2 −0 addons/jetpack/data/native-install/windows/app/uninstall.ini
  14. +10 −0 addons/jetpack/data/native-install/windows/installer/install.ini
  15. +12 −0 addons/jetpack/data/native-install/windows/installer/specialfiles.json
  16. +1 −1 addons/jetpack/data/widget.html
  17. +17 −10 addons/jetpack/lib/api.js
  18. +21 −7 addons/jetpack/lib/injector.js
  19. +3 −0 addons/jetpack/lib/main.js
  20. +902 −0 addons/jetpack/lib/nativeshell.js
  21. +5 −5 addons/jetpack/lib/repo.js
  22. +138 −0 addons/jetpack/lib/socketserver.js
  23. +41 −0 addons/jetpack/mac/Makefile
  24. +260 −0 addons/jetpack/mac/foxlauncher.m
  25. +208 −0 addons/jetpack/windows/installer/webapp-installer.nsi
  26. +77 −0 examples/demopaid/index.html
  27. +13 −0 examples/demopaid/manifest.webapp
  28. +250 −0 jslibs/app.js
View
2 Makefile
@@ -44,6 +44,7 @@ endif
all: xpi
xpi: pull
+ ${MAKE} -C addons/jetpack/mac/
$(addon_sdk)/cfx xpi $(cfx_args)
pull:
@@ -53,6 +54,7 @@ test:
$(addon_sdk)/cfx test $(cfx_args) $(test_args)
run:
+ ${MAKE} -C addons/jetpack/mac/
$(addon_sdk)/cfx run $(cfx_args)
.PHONY: xpi clean pull test run
View
1 addons/jetpack/data/native-install/XUL/chrome.manifest
@@ -0,0 +1 @@
+content webapp content/
View
35 addons/jetpack/data/native-install/XUL/content/main.js
@@ -0,0 +1,35 @@
+
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+
+var os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
+
+// only make the tiny window that handles window closing events (and quits the app) for Mac OS
+if ("Darwin" === os) {
+ var observer = {
+ observe: function(contentWindow, aTopic, aData) {
+ if (aTopic == 'xul-window-destroyed') {
+ // If there is nothing left but the main (invisible) window, quit
+ var wm = Cc["@mozilla.org/appshell/window-mediator;1"]
+ .getService(Ci.nsIWindowMediator);
+ var enumerator = wm.getEnumerator("app");
+ if (enumerator.hasMoreElements()) return;
+
+ var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
+ appStartup.quit(appStartup.eAttemptQuit);
+ }
+ }
+ }
+
+ // Register our observer:
+ var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
+ observerService.addObserver(observer, "xul-window-destroyed", false);
+};
+
+// Create the first window
+var appName = "$APPNAME";
+var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Ci.nsIWindowWatcher);
+var win = ww.openWindow(null, "chrome://webapp/content/window.xul",
+ appName, "chrome,centerscreen,resizable", null);
View
5 addons/jetpack/data/native-install/XUL/content/main.xul
@@ -0,0 +1,5 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window id="main" title="main" width="0" height="0" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
+ <script type="application/javascript" src="chrome://webapp/content/main.js"/>
+</window>
View
343 addons/jetpack/data/native-install/XUL/content/window.js
@@ -0,0 +1,343 @@
+
+var Cu = Components.utils;
+var Cc = Components.classes;
+var Cm = Components.manager;
+var Ci = Components.interfaces;
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+function doHandleMenuBar(toCall)
+{
+ // We need to pageMod in from the faker
+ // TODO pass this into content code somehow
+ window.alert("Menu bar item " + toCall + " was clicked!");
+ return;
+}
+
+window.addEventListener("click", function(e) {
+ // Make sure clicks remain in our context
+ // TODO check to see if we are in same origin?
+ if (e.target.nodeName == "A") {
+ e.preventDefault();
+ window.location = e.target.href;
+ }
+}, false);
+
+// Commands:
+var appName = "$APPNAME";
+function newWindow()
+{
+ var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
+ .getService(Components.interfaces.nsIWindowWatcher);
+ var win = ww.openWindow(null, "chrome://webapp/content/window.xul",
+ null, "chrome,centerscreen,resizable", null);
+}
+
+// Inject APIs
+//----- navigator.mozApps api implementation
+// FIXME: Fallback doesn't actually work on Fx<9, debug why
+var injector = {};
+Cu.import("chrome://webapp/content/injector.js", injector);
+injector.init();
+
+function NavigatorAPI() {};
+NavigatorAPI.prototype = {
+ QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]),
+
+ init: function API_init(aWindow) {
+ let chromeObject = this._getObject(aWindow);
+
+ // We need to return an actual content object here, instead of a wrapped
+ // chrome object. This allows things like console.log.bind() to work.
+ let contentObj = Cu.createObjectIn(aWindow);
+ function genPropDesc(fun) {
+ return { enumerable: true, configurable: true, writable: true,
+ value: chromeObject[fun].bind(chromeObject) };
+ }
+ let properties = {};
+
+ for (var fn in chromeObject.__exposedProps__) {
+ properties[fn] = genPropDesc(fn);
+ }
+
+ Object.defineProperties(contentObj, properties);
+ Cu.makeObjectPropsNormal(contentObj);
+
+ return contentObj;
+ }
+};
+
+var MozAppsAPIContract = "@mozilla.org/openwebapps/mozApps;1";
+var MozAppsAPIClassID = Components.ID("{19c6a16b-18d1-f749-a2c7-fa23e70daf2b}");
+function MozAppsAPI() {}
+MozAppsAPI.prototype = {
+ __proto__: NavigatorAPI.prototype,
+ classID: MozAppsAPIClassID,
+ _getObject: function(aWindow) {
+ return {
+ amInstalled: function(callback) {
+ getInstallRecord(callback);
+ },
+
+ verifyReceipt: function(callback, options, cb, verifyOnly) {
+ doVerifyReceipt(aWindow, callback, options, cb, verifyOnly);
+ },
+
+ __exposedProps__: {
+ amInstalled: "r",
+ verifyReceipt: "r"
+ }
+ };
+ }
+};
+
+let MozAppsAPIFactory = {
+ createInstance: function(outer, iid) {
+ if (outer != null) throw Cr.NS_ERROR_NO_AGGREGATION;
+ return new MozAppsAPI().QueryInterface(iid);
+ }
+};
+
+Cm.QueryInterface(Ci.nsIComponentRegistrar).registerFactory(
+ MozAppsAPIClassID, "MozAppsAPI", MozAppsAPIContract, MozAppsAPIFactory
+);
+Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager)
+ .addCategoryEntry("JavaScript-navigator-property", "mozApps", MozAppsAPIContract, false, true);
+
+/* TODO: Unload injector
+unloaders.push(function() {
+ Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(
+ MozAppsAPIClassID, MozAppsAPIFactory
+ );
+ Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager).
+ deleteCategoryEntry("JavaScript-navigator-property", "mozApps", false);
+});
+*/
+
+function getInstallRecord(cb) {
+ var appDirectory = Cc["@mozilla.org/file/directory_service;1"].
+ getService(Ci.nsIProperties).
+ get("CurProcD", Ci.nsIFile);
+
+ var aNsLocalFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ aNsLocalFile.initWithFile(appDirectory);
+ aNsLocalFile.appendRelativePath("installrecord.json");
+
+ Cu.import("resource://gre/modules/NetUtil.jsm");
+ Cu.import("resource://gre/modules/FileUtils.jsm");
+
+ dump(aNsLocalFile.path + "\n");
+ NetUtil.asyncFetch(aNsLocalFile, function(inputStream, status) {
+ if (!Components.isSuccessCode(status)) {
+ // Handle error!
+ console.log("ERROR: " + status + " failed to read file: " + inFile);
+ return;
+ }
+ var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+ var parsed = JSON.parse(data);
+ inputStream.close();
+ cb(parsed);
+ });
+}
+
+function doVerifyReceipt(contentWindowRef, options, cb, verifyOnly) {
+ getInstallRecord(function(record) {
+ if (!record) {
+ cb({"error": "Invalid Receipt"});
+ return;
+ }
+
+ function base64urldecode(arg) {
+ var s = arg;
+ s = s.replace(/-/g, '+'); // 62nd char of encoding
+ s = s.replace(/_/g, '/'); // 63rd char of encoding
+ switch (s.length % 4) // Pad with trailing '='s
+ {
+ case 0: break; // No pad chars in this case
+ case 2: s += "=="; break; // Two pad chars
+ case 3: s += "="; break; // One pad char
+ default: throw new InputException("Illegal base64url string!");
+ }
+ return window.atob(s); // Standard base64 decoder
+ }
+
+ function parseReceipt(rcptData) {
+ // rcptData is a JWT. We should use a JWT library.
+ var data = rcptData.split(".");
+ if (data.length != 3)
+ return null;
+
+ // convert base64url to base64
+ var payload = base64urldecode(data[1]);
+ var parsed = JSON.parse(payload);
+
+ return parsed;
+ }
+
+ var receipt = parseReceipt(record.install_data.receipt);
+ if (!receipt) {
+ cb({"error": "Invalid Receipt"});
+ return;
+ }
+
+ // Two status "flags", one for verify XHR other for BrowserID XHR
+ // These two XHRs run in parallel, and the last one to finish invokes cb()
+ var assertion;
+ var verifyStatus = false;
+ var assertStatus = false;
+
+ var verifyURL = receipt.verify;
+ var verifyReq = new XMLHttpRequest();
+ verifyReq.open('GET', verifyURL, true);
+ verifyReq.onreadystatechange = function (aEvt) {
+ if (verifyReq.readyState == 4) {
+ // FIXME: 404? Yeah, because that's what we get now
+ // Hook up to real verification when it's done on AMO
+ // and change this to 200 !!!
+ verifyStatus = true;
+ if (verifyReq.status == 404) {
+ if (verifyOnly && typeof verifyOnly == "function") {
+ verifyOnly(receipt);
+ }
+ if (verifyStatus && assertStatus) {
+ cb({"success": {"receipt": receipt, "assertion": assertion}});
+ }
+ } else {
+ if (verifyStatus && assertStatus) {
+ cb({"error": "Invalid Receipt: " + req.responseText});
+ }
+ }
+ }
+ };
+ verifyReq.send(null);
+
+ // Start BrowserID verification
+ var options = {"silent": true, "requiredEmail": receipt.user.value};
+ checkNativeIdentityDaemon(contentWindowRef.location, options, function(ast) {
+ assertion = ast;
+ if (!assertion) {
+ cb({"error": "Invalid Identity"});
+ return;
+ }
+
+ var assertReq = new XMLHttpRequest();
+ assertReq.open('POST', 'https://browserid.org/verify', true);
+ assertReq.onreadystatechange = function(aEvt) {
+ if (assertReq.readyState == 4) {
+ assertStatus = true;
+
+ // FIXME: a 200 status code doesn't mean OK, check
+ // the responseText
+ if (assertReq.status == 200) {
+ if (verifyStatus && assertStatus) {
+ cb({"success": {"receipt": receipt, "assertion": assertion}});
+ }
+ } else {
+ if (verifyStatus && assertStatus) {
+ cb({"error": "Invalid Identity: " + assertReq.responseText});
+ }
+ }
+ }
+ };
+
+ var body = "assertion=" + encodeURIComponent(assertion) + "&audience=" +
+ contentWindowRef.location.protocol + "//" + contentWindowRef.location.host;
+ assertReq.send(body);
+ });
+ });
+}
+
+function checkNativeIdentityDaemon(callingLocation, options, success, failure)
+{
+ dump("CheckNativeIdentityDaemon " + callingLocation + " " + JSON.stringify(options) + "\n");
+ if (typeof failure != "function") {
+ function failure(e) {
+ dump("Failure callback not defined, shimmed " + e);
+ }
+ }
+
+ // XXX what do we do if we are not passed a requiredEmail?
+ // could fail immediately, or could ask Firefox for a default somehow
+ if (!options || !options.requiredEmail) failure();
+
+ var port = 7350;
+ var output, input, scriptableStream;
+ var sockTransportService = Cc["@mozilla.org/network/socket-transport-service;1"]
+ .getService(Ci.nsISocketTransportService);
+
+ var domain = callingLocation.protocol + "//" + callingLocation.host;
+ if (callingLocation.port && callingLocation.port.length > 0) callingLocation += ":" + callingLocation.port;
+ var buf = "IDCHECK " + options.requiredEmail + " " + domain + "\r\n\r\n";
+
+ var eventSink = {
+ onTransportStatus: function(aTransport, aStatus, aProgress, aProgressMax) {
+ if (aStatus == aTransport.STATUS_CONNECTED_TO) {
+ output.write(buf, buf.length);
+ } else if (aStatus == aTransport.STATUS_RECEIVING_FROM) {
+ var chunk = scriptableStream.read(8192);
+ if (chunk.length> 1) {
+ success(chunk);
+ return;
+ } else {
+ failure();
+ return;
+ }
+ } else if (false /* connection refused */) {
+ port++;
+ attemptConnection();
+ }
+ }
+ };
+
+ var threadMgr = Cc["@mozilla.org/thread-manager;1"].getService();
+ function attemptConnection() {
+ if (port > 7550) {
+ failure();
+ return;
+ }
+ var transport = sockTransportService.createTransport(null, 0, "127.0.0.1", port, null);
+ transport.setEventSink(eventSink, threadMgr.currentThread);
+ try {
+ output = transport.openOutputStream(transport.OPEN_BLOCKING, 0, 0);
+ output.write(buf, buf.length);
+ input = transport.openInputStream(transport.OPEN_BLOCKING, 0, 0);
+ scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+ .createInstance(Ci.nsIScriptableInputStream);
+ scriptableStream.init(input);
+ } catch (e) {
+ alert(e);
+ port++;
+ attemptConnection();
+ }
+ }
+ attemptConnection();
+}
+
+/*
+window.appinjector.register({
+ apibase: "navigator.id",
+ name: "getVerifiedEmail",
+ script: null,
+ getapi: function(contentWindowRef) {
+ return function(callback, options) { // XXX what is the options API going to be?
+ checkNativeIdentityDaemon(contentWindowRef.location, options, function(assertion) {
+ // success: return to caller
+ var assert = JSON.parse(assertion);
+ if (assert.status == "ok" && assert.assertion) {
+ callback(assert.assertion);
+ } else {
+ // failure
+ callback(null);
+ }
+ }, function() {
+ // failure: need to present BrowserID dialog
+ if (!options || !options.silent) {
+ dump("OpenBrowserIDDialog\n");
+ openBrowserIDDialog(callback, options);
+ } else {
+ callback(null);
+ }
+ });
+ }
+ }
+});
+*/
View
17 addons/jetpack/data/native-install/XUL/content/window.xul
@@ -0,0 +1,17 @@
+<?xml version="1.0"?>
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
+<window id="$APPNAME" windowtype="app" title="$APPNAME" width="1024" height="800" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> <!-- TODO Escape quotes! -->
+ <script type="application/javascript" src="chrome://webapp/content/window.js"/>
+ <commandset>
+ <command id="new_window" oncommand="newWindow()"/>
+ </commandset>
+
+ <menubar>
+ <menu label="File">
+ <menupopup>
+ <menuitem label="New Window" command="new_window"/>
+ </menupopup>
+ </menu>
+ </menubar>
+ <browser id="content_browser" type="content" src="$LAUNCHPATH" flex="1"/>
+</window>
View
9 addons/jetpack/data/native-install/XUL/defaults/preferences/prefs.js
@@ -0,0 +1,9 @@
+pref("toolkit.defaultChromeURI", "chrome://webapp/content/main.xul");
+pref("toolkit.singletonWindowType", true);
+pref("extensions.logging.enabled", "true");
+pref("dom.report_all_js_exceptions", "true");
+pref("browser.dom.window.dump.enabled", true);
+pref("javascript.options.showInConsole", true);
+pref("javascript.options.strict", true);
+pref("nglayout.debug.disable_xul_cache", true);
+pref("nglayout.debug.disable_xul_fastload", true);
View
10 addons/jetpack/data/native-install/XUL/newapp_template.ini
@@ -0,0 +1,10 @@
+[App]
+Vendor=Mozilla
+Name=$APPNAME
+Version=1.0
+BuildID=20110713
+ID=webapp@$APPDOMAIN
+
+[Gecko]
+MinVersion=2.0
+MaxVersion=11.*
View
10 addons/jetpack/data/native-install/XUL/specialfiles.json
@@ -0,0 +1,10 @@
+{
+ "newapp_template.ini":
+ {
+ "rename": "application.ini"
+ },
+ "specialfiles.json":
+ {
+ "ignore": true
+ }
+}
View
74 addons/jetpack/data/native-install/mac/Contents/Info.plist
@@ -0,0 +1,74 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>CFBundleDisplayName</key>
+ <string>$APPNAME</string>
+ <key>CFBundleDevelopmentRegion</key>
+ <string>English</string>
+ <key>CFBundleExecutable</key>
+ <string>$APPNAME</string>
+ <key>CFBundleGetInfoString</key>
+ <string>$APPNAME</string>
+ <key>CFBundleIconFile</key>
+ <string>appicon</string>
+ <key>CFBundleIdentifier</key>
+ <string>$REVERSED_APPDOMAIN</string>
+ <key>CFBundleInfoDictionaryVersion</key>
+ <string>6.0</string>
+ <key>CFBundleName</key>
+ <string>$APPNAME</string>
+ <key>CFBundlePackageType</key>
+ <string>APPL</string>
+ <key>CFBundleShortVersionString</key>
+ <string>1.0</string>
+ <key>CFBundleSignature</key>
+ <string>MOZB</string>
+ <key>CFBundleURLTypes</key>
+ <array>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>http URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>http</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLIconFile</key>
+ <string>document.icns</string>
+ <key>CFBundleURLName</key>
+ <string>https URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>https</string>
+ </array>
+ </dict>
+ <dict>
+ <key>CFBundleURLName</key>
+ <string>file URL</string>
+ <key>CFBundleURLSchemes</key>
+ <array>
+ <string>file</string>
+ </array>
+ </dict>
+ </array>
+ <key>CFBundleVersion</key>
+ <string>1.0</string>
+ <key>CGDisableCoalescedUpdates</key>
+ <true/>
+ <key>LSMinimumSystemVersion</key>
+ <string>10.5</string>
+ <key>LSMinimumSystemVersionByArchitecture</key>
+ <dict>
+ <key>i386</key>
+ <string>10.5.0</string>
+ <key>x86_64</key>
+ <string>10.6.0</string>
+ </dict>
+ <key>NSAppleScriptEnabled</key>
+ <true/>
+</dict>
+</plist>
View
13 addons/jetpack/data/native-install/mac/specialfiles.json
@@ -0,0 +1,13 @@
+{
+ "foxlauncher":
+ {
+ "rename": "$APPNAME",
+ "mode": "b",
+ "isExecutable": true,
+ "substituteStrings": false
+ },
+ "specialfiles.json":
+ {
+ "ignore": true
+ }
+}
View
6 addons/jetpack/data/native-install/windows/app/specialfiles.json
@@ -0,0 +1,6 @@
+{
+ "specialfiles.json":
+ {
+ "ignore": true
+ }
+}
View
2 addons/jetpack/data/native-install/windows/app/uninstall.ini
@@ -0,0 +1,2 @@
+[required]
+appName=$APPNAME
View
10 addons/jetpack/data/native-install/windows/installer/install.ini
@@ -0,0 +1,10 @@
+[required]
+appName=$APPNAME
+FFPath=$FFPATH
+
+[optional]
+appURL=$LAUNCHPATH
+appDesc=$APPDESC
+iconPath=$ICONPATH
+createDesktopShortcut=$DESKTOP_SHORTCUT
+createStartMenuShortcut=$SM_SHORTCUT
View
12 addons/jetpack/data/native-install/windows/installer/specialfiles.json
@@ -0,0 +1,12 @@
+{
+ "install.exe":
+ {
+ "mode": "b",
+ "isExecutable": true,
+ "substituteStrings": false
+ },
+ "specialfiles.json":
+ {
+ "ignore": true
+ }
+}
View
2 addons/jetpack/data/widget.html
@@ -5,7 +5,7 @@
</head>
<body>
- <div style="width: 100%; font-family: arial; font-size: 14; cursor: pointer">
+ <div style="width: 100%; font-family: arial; font-weight:bold; font-size: small; cursor: pointer">
<img src="skin/toolbar-button.png" height="16" width="16" border="0" align="left" />
<div style="-moz-user-select: none">
Apps
View
27 addons/jetpack/lib/api.js
@@ -41,6 +41,10 @@ Cu.import("resource://gre/modules/XPCOMUtils.jsm");
var { TypedStorage } = require("typed_storage");
+//don't remove this please!
+var { NativeShell } = require("./nativeshell");
+
+
if (!console || !console.log) {
var console = {
log: function(s) {
@@ -51,6 +55,7 @@ if (!console || !console.log) {
var { Manifest } = require("./manifest");
var { URLParse } = require("./urlmatch");
+var { NativeShell } = require("./nativeshell");
// We want to use as much from the cross-platform repo implementation
// as possible, but we do need to override a few methods.
@@ -286,16 +291,18 @@ FFRepoImpl.prototype = {
self._callWatchers("add", [app]);
});
// create OS-local application
-/*
- dump("APPS | jetpack.install | Getting app by URL now\n");
- Repo.getAppById(origin, function(app) {
- dump("APPS | jetpack.install | getAppByUrl returned " + app + "\n");
- if (app) {
- dump("APPS | jetpack.install | Calling NativeShell.CreateNativeShell\n");
- NativeShell.CreateNativeShell(app);
- }
- });
- */
+ console.log("APPS | jetpack.install | Getting app by URL now\n");
+ Repo.getAppById(origin, function(app) {
+ console.log("APPS | jetpack.install | getAppByUrl returned " + app + "\n");
+ if (app) {
+ console.log("APPS | jetpack.install | Calling NativeShell.CreateNativeShell\n");
+ try {
+ NativeShell.CreateNativeShell(app);
+ } catch (e) {
+ console.log("APPS | NativeShell | Aborted: " + e);
+ }
+ }
+ });
if (args.onsuccess) {
(1, args.onsuccess)();
View
28 addons/jetpack/lib/injector.js
@@ -41,12 +41,22 @@
/* Inject the People content API into window.navigator objects. */
/* Partly based on code in the Geode extension. */
-const { Cc, Ci, Cu } = require("chrome");
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
-const xulApp = require("api-utils/xul-app");
+var HAS_NAVIGATOR_INJECTOR;
+if (typeof require !== "undefined") {
+ var { Cc, Ci, Cu } = require("chrome");
+ const xulApp = require("api-utils/xul-app");
+ HAS_NAVIGATOR_INJECTOR = xulApp.versionInRange(xulApp.version, "9.0a2", "*");
+} else {
+ var Cc = Components.classes;
+ var Ci = Components.interfaces;
+ var Cu = Components.utils;
+
+ var xulAppInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
+ var comparator = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
+ HAS_NAVIGATOR_INJECTOR = comparator.compare(xulAppInfo.version, "9.0a2");
+}
-const HAS_NAVIGATOR_INJECTOR =
- xulApp.versionInRange(xulApp.version, "9.0a2", "*");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
/**
* NavigatorInjector
@@ -170,9 +180,13 @@ NavigatorInjector.prototype = {
}
var gInjector;
-exports.init = function() {
+function init() {
if (!HAS_NAVIGATOR_INJECTOR)
gInjector = new NavigatorInjector();
}
-
+if (typeof exports !== "undefined") {
+ exports.init = init;
+} else {
+ EXPORTED_SYMBOLS = ["init"];
+}
View
3 addons/jetpack/lib/main.js
@@ -493,5 +493,8 @@ function shutdown(why) {
// Let's go!
startup(addon.data.url);
+const socketserver = require("socketserver");
+socketserver.startServer();
+
// Hook up unloaders
unload.when(shutdown);
View
902 addons/jetpack/lib/nativeshell.js
@@ -0,0 +1,902 @@
+/* -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* ***** 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 Open Web Apps for Firefox.
+ *
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2011
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Michael Hanson <mhanson@mozilla.com>
+ * Anant Narayanan <anant@kix.in>
+ * Tim Abraldes <tabraldes@mozilla.com>
+ * Dan Walkowski <dwalkowski@mozilla.com>
+ *
+ * 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 {components, Cc, Cu, Ci} = require("chrome");
+const file = require("file");
+const self = require("self");
+const url = require("url");
+
+//used for several things
+Components.utils.import("resource://gre/modules/NetUtil.jsm");
+Components.utils.import("resource://gre/modules/FileUtils.jsm");
+
+NativeShell = (function() {
+ function CreateNativeShell(app)
+ {
+ let os = Cc["@mozilla.org/xre/app-info;1"]
+ .getService(Ci.nsIXULRuntime).OS;
+ let nativeShell;
+ if("WINNT" === os) {
+ nativeShell = new WinNativeShell();
+ } else if("Darwin" === os) {
+ nativeShell = new MacNativeShell();
+ }
+ if(nativeShell) {
+ nativeShell.createAppNativeLauncher(app);
+ } else {
+ console.log("APPS | CreateNativeShell | "
+ + "No OS-specific native shell could be created");
+ }
+ }
+
+ return {
+ CreateNativeShell: CreateNativeShell
+ }
+})();
+
+function substituteStrings(inputString, substituteStrings)
+{
+ try {
+ let working = inputString;
+ for (let key in substituteStrings) {
+ if(substituteStrings.hasOwnProperty(key)) {
+ re = new RegExp(key, "gi");
+ working = working.replace(re, substituteStrings[key]);
+ }
+ }
+ return working;
+ } catch(e) {
+ throw("Failure in substituteStrings (" + e + ")");
+ }
+}
+
+function reverseDNS(domain)
+{
+ var d = domain.split(".");
+ var s = "";
+ for (var i=d.length-1;i--;i>=0)
+ {
+ if (s.length > 0) s += ".";
+ s += d[i];
+ }
+ return s;
+}
+
+function getBiggestIcon(app, callback) {
+ let icon = 0;
+ if (app.manifest.icons) {
+ for (z in app.manifest.icons) {
+ let size = parseInt(z, 10);
+ if (size > icon) {
+ icon = size;
+ }
+ }
+ }
+ if (icon === 0) {
+ return null;
+ } else {
+ icon = app.manifest.icons[icon];
+ }
+
+ if (icon.indexOf("data:") === 0) {
+ let tIndex = icon.indexOf(";");
+ mimeType = icon.substring(5, tIndex);
+
+ let base64 = icon.indexOf("base64,");
+ if (base64 < 0) {
+ throw("getBiggestIcon - "
+ + "Found a data URL but it appears not to be base64 encoded");
+ }
+
+ let binaryStream;
+ try {
+ let base64Data = icon.substring(base64 + 7);
+
+ const AppShellService =
+ Cc["@mozilla.org/appshell/appShellService;1"]
+ .getService(Ci.nsIAppShellService);
+ let binaryData =
+ AppShellService.hiddenDOMWindow.atob(String(base64Data));
+ let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
+ .createInstance(Ci.nsIStringInputStream);
+ stringStream.setData(binaryData, binaryData.length);
+ binaryStream = Cc["@mozilla.org/binaryinputstream;1"]
+ .createInstance(Ci.nsIObjectInputStream);
+ binaryStream.setInputStream(stringStream);
+ } catch(e) {
+ throw("getBiggestIcon - "
+ + "Failure converting base64 data "
+ + "(" + e + ")");
+ }
+
+ try {
+ callback(0, mimeType, binaryStream);
+ } catch(e) {
+ throw("getBiggestIcon - "
+ + "Failure in callback "
+ + "(" + e + ")");
+ }
+ } else {
+ // TODO: Come up with a smarter way to determine MIME type
+ try {
+ if (icon.indexOf(".png") > 0) {
+ mimeType = "image/png";
+ } else if ((icon.indexOf(".jpeg") > 0)
+ || (icon.indexOf(".jpg") > 0)) {
+ mimeType = "image/jpeg";
+ }
+ } catch(e) {
+ throw("getBiggestIcon - "
+ + "Failure determining MIME type of " + icon
+ + " (" + e + ")");
+ }
+
+ let iconPath = app.origin + icon;
+ NetUtil.asyncFetch(iconPath,
+ function(inputStream, resultCode, request) {
+ try {
+ callback(resultCode, mimeType, inputStream);
+ } catch (e) {
+ console.log("getBiggestIcon - "
+ + "Failure in callback function"
+ + " (" + e + ")");
+ }
+ });
+ }
+}
+
+function embedInstallRecord(app, destination) {
+ //write the contents of the app (install record), json-ified, into
+ //the specified file.
+ let theDestination = destination.clone();
+ theDestination.append("installrecord.json");
+ try {
+ let installRecString = JSON.stringify(app);
+ writeFile(installRecString, theDestination.path);
+ } catch (e) {
+ console.log("error writing installrecord : " + e + "\n");
+ }
+}
+
+
+//used to copy in the necessary js files to include so we can call the
+//MozApps api to do browserID stuff.
+// turns out that we only really need injector.js for now
+//FUTURE: might it be possible to get a nice reference to /lib/injector.js
+//using the same scheme as self.data?
+function embedMozAppsAPIFiles(destDir)
+{
+ //find where the jetpack addon is, and where it is keeping the necessary
+ //js files we need to copy into the native app
+ var mozappsD = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("ProfD", Ci.nsIFile);
+ mozappsD.append("extensions");
+ mozappsD.append(self.id);
+ mozappsD.append("resources");
+ mozappsD.append("openwebapps-at-mozillalabs-dot-com-openwebapps-lib");
+
+ var injectorSrc = mozappsD.clone();
+ injectorSrc.append("injector.js");
+ var injectorDest = destDir.clone();
+ injectorDest.append("injector.js");
+
+ copyFile(injectorSrc.path, injectorDest.path);
+}
+
+function copyFile(srcFile, destFile, fileProperties, substitutions) {
+ try {
+ //open the source file and read in the contents
+ var openProps = fileProperties?fileProperties["mode"]:"";
+ let inputStream = file.open(srcFile, openProps);
+ let fileContents = inputStream.read();
+ inputStream.close();
+
+ writeFile(fileContents, destFile, fileProperties, substitutions);
+
+ } catch(e) {
+ throw("copyFile - "
+ + "Failed copying file from "
+ + srcFile
+ + " to "
+ + destFile
+ + " (" + e + ")");
+ }
+}
+
+function writeFile(fileContents, destFile, fileProperties, substitutions) {
+ try {
+ var openProps = fileProperties?fileProperties["mode"]:"";
+ //do string substitutions if necessary
+ let finalContents;
+ if(fileProperties && fileProperties["substituteStrings"]) {
+ finalContents = substituteStrings(fileContents, substitutions);
+ } else {
+ finalContents = fileContents;
+ }
+ //write out the (possibly altered) file to the new location
+ let outputStream = file.open(destFile, "w" + openProps);
+ outputStream.write(finalContents);
+ outputStream.close();
+
+ } catch(e) {
+ throw("writeFile - "
+ + "Failed writing file to "
+ + destFile
+ + " (" + e + ")");
+ }
+}
+
+//ASYNC file reading/writing/copying code. unable to evaluate the issues that might occur during a file
+// tree copy, so putting on hold for now.
+// function asyncReadFile(inFile, callback) {
+// //passes the string contents of the file to the callback for you to do with as you like.
+
+// NetUtil.asyncFetch(inFile, function(inputStream, status) {
+// if (!Components.isSuccessCode(status)) {
+// // should probably throw instead
+// console.log("ERROR: " + status + " failed to read file: " + inFile);
+// return;
+// }
+// var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());
+// inputStream.close();
+// callback(data);
+// });
+// }
+
+// //make it into an inputstream and then send it to copy
+// function asyncWriteFile(strData, outFile) {
+// var outStream = FileUtils.openSafeFileOutputStream(outFile);
+// var converter = Components.classes["@mozilla.org/intl/scriptableunicodeconverter"].createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
+// converter.charset = "UTF-8";
+// var inStream = converter.convertToInputStream(strData);
+
+// // The last argument (the callback) is optional.
+// NetUtil.asyncCopy(inStream, outStream, function(status) {
+// if (!Components.isSuccessCode(status)) {
+// // should probably throw instead
+// console.log("ERROR: " + status + " failed to write file: " + outFile.path);
+// return;
+// }
+// });
+// }
+
+// // NOTE: both inFile and outFile are nsIFile objects
+// // NOTE: this code should probably throw, and get caught up at the top, where we can cancel the creation of the native app
+// function asyncCopyFile(inFile, outFile, options) {
+
+// NetUtil.asyncFetch(inFile, function(inputStream, status) {
+// if (!Components.isSuccessCode(status)) {
+// // should probably throw instead
+// console.log("ERROR: " + status + " failed to read file: " + inFile.path);
+// return;
+// }
+
+// var outputStream = FileUtils.openSafeFileOutputStream(outFile);
+
+// NetUtil.asyncCopy(inputStream, outputStream, function(status) {
+// if (!Components.isSuccessCode(status)) {
+// // should probably throw instead
+// console.log("ERROR: " + status + " failed to write file: " + outFile.path);
+// return;
+// }
+// });
+// });
+// }
+
+function recursiveFileCopy(srcDir,
+ leaf,
+ dstDir,
+ separator,
+ substitutions,
+ specialFiles)
+{
+ if(!specialFiles) {
+ specialFiles = {};
+ }
+ try {
+ let srcCompletePath = srcDir;
+ var dest = dstDir;
+ if(leaf) {
+ srcCompletePath += "/" + leaf;
+ dest += separator + leaf;
+ }
+ let srcURL = self.data.url(srcCompletePath);
+ var srcFile = url.toFilename(srcURL);
+ //console.log(srcFile);
+ } catch(e) {
+ throw("recursiveFileCopy - "
+ + "Failure while setting up paths (" + e +")");
+ }
+
+ try {
+ var fileExists = file.exists(srcFile);
+ if(fileExists) {
+ // file doesn't expose an isDirectory function yet
+ // so we negate file.isFile
+ var isDir = !file.isFile(srcFile);
+ }
+ } catch(e) {
+ throw("recursiveFileCopy - "
+ + "Failure obtaining information about file "
+ + srcFile
+ + " (" + e + ")");
+ }
+
+ if(!fileExists) {
+ throw("recursiveFileCopy - "
+ + "Tried to copy file but source file doesn't exist ("
+ + srcFile + ")");
+ }
+
+ if (isDir)
+ {
+ let newSpecialFiles = Object.create(specialFiles);
+ try {
+ var dirContents = file.list(srcFile);
+ file.mkpath(dest);
+ } catch(e) {
+ throw("recursiveFileCopy - "
+ + "Failure setting up directory copy from "
+ + srcFile
+ + " to "
+ + dest
+ + " (" + e + ")");
+ }
+
+ let manifestPath = srcFile + separator + "specialfiles.json";
+ try {
+ if(file.exists(manifestPath)) {
+ let manifestStream = file.open(manifestPath);
+ let fileContents = manifestStream.read();
+ manifestStream.close();
+
+ let parsedManifest = JSON.parse(fileContents);
+ for(let specialFile in parsedManifest) {
+ if(parsedManifest.hasOwnProperty(specialFile)) {
+ if(!(specialFile in newSpecialFiles)) {
+ newSpecialFiles[specialFile] = {};
+ }
+ for(let property in parsedManifest[specialFile]) {
+ if(parsedManifest[specialFile].hasOwnProperty(property)) {
+ newSpecialFiles[specialFile][property] =
+ parsedManifest[specialFile][property];
+ }
+ }
+ }
+ }
+ }
+ } catch(e) {
+ throw("Failure reading/parsing specialFiles manifest "
+ + manifestPath
+ + " (" + e + ")");
+ }
+
+ for (let i=0; i < dirContents.length; i++)
+ {
+ recursiveFileCopy(
+ // Use "/" instead of separator; this is a URI
+ srcDir + "/" + leaf,
+ dirContents[i],
+ dest,
+ separator,
+ substitutions,
+ newSpecialFiles);
+ }
+ } else {
+ let fileProperties = {"ignore": false,
+ "rename": leaf,
+ "isExecutable": false,
+ "mode": "",
+ "substituteStrings": true};
+ if(leaf in specialFiles) {
+ for(let property in fileProperties) {
+ if(fileProperties.hasOwnProperty(property)) {
+ if(property in specialFiles[leaf]) {
+ fileProperties[property] = specialFiles[leaf][property];
+ }
+ }
+ }
+ }
+
+ if(!(fileProperties["ignore"])) {
+ for(let property in fileProperties) {
+ if(fileProperties.hasOwnProperty(property)
+ && (typeof(fileProperties[property]) === "string")) {
+ fileProperties[property] =
+ substituteStrings(fileProperties[property],
+ substitutions);
+ }
+ }
+
+ dest = dstDir + separator + fileProperties["rename"];
+
+ if(fileProperties["isExecutable"])
+ {
+ try {
+ // Some shenanigans here to set the executable bit:
+ let aNsLocalFile = Cc['@mozilla.org/file/local;1']
+ .createInstance(Ci.nsILocalFile);
+ aNsLocalFile.initWithPath(dest);
+ aNsLocalFile.create(aNsLocalFile.NORMAL_FILE_TYPE,
+ 0x1ed); // octal 755
+ } catch(e) {
+ throw("recursiveFileCopy - "
+ + "Failed creating executable file "
+ + dest
+ + " (" + e + ")");
+ }
+ }
+
+ //actually do the copy
+ copyFile(srcFile, dest, fileProperties, substitutions);
+ }
+ }
+}
+
+// Windows implementation
+function WinNativeShell() {
+}
+
+WinNativeShell.prototype = {
+ createAppNativeLauncher : function(app)
+ {
+ this.createExecutable(app);
+ },
+
+ setUpPaths : function(app) {
+ this.appName = app.manifest.name;
+
+ let directoryService = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties);
+ this.installDir = directoryService.get("AppData", Ci.nsIFile);
+ this.installDir.append(this.appName);
+
+ this.launchPath = app.origin;
+ if (app.manifest.launch_path) {
+ this.launchPath += app.manifest.launch_path;
+ }
+
+ this.iconFile = this.installDir.clone();
+ this.iconFile.append("chrome");
+ this.iconFile.append("icons");
+ this.iconFile.append("default");
+ this.iconFile.append(this.appName + ".ico");
+
+ this.installerDir = directoryService.get("TmpD", Ci.nsIFile);
+ this.installerDir.append(this.appName);
+ this.installerDir.createUnique(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+
+ this.firefoxFile = directoryService.get("CurProcD", Ci.nsIFile);
+ // TODO: What if FF has been renamed?
+ this.firefoxFile.append("firefox.exe");
+ },
+
+ // TODO: Ask user whether to create desktop/start menu shortcuts
+ // TODO: Support App descriptions
+ createExecutable : function(app)
+ {
+ try {
+ this.setUpPaths(app);
+ } catch(e) {
+ throw("createExecutable - Failure setting up paths (" + e + ")");
+ }
+
+ let substitutions;
+ try {
+ substitutions = {
+ "\\$APPNAME": this.appName,
+ "\\$APPDOMAIN": app.origin,
+ "\\$APPDESC": "App descriptions are not yet supported",
+ "\\$FFPATH": this.firefoxFile.path,
+ "\\$LAUNCHPATH": this.launchPath,
+ "\\$INSTALLDIR": this.installDir.path,
+ "\\$ICONPATH": this.iconFile.path,
+ "\\$DESKTOP_SHORTCUT": "y",
+ "\\$SM_SHORTCUT": "y"
+ }
+ } catch(e) {
+ throw("createExecutable - "
+ + "Failure setting up substitutions");
+ }
+
+ try {
+ recursiveFileCopy("native-install/windows/installer",
+ "",
+ this.installerDir.path,
+ "\\",
+ substitutions,
+ undefined);
+ } catch (e) {
+ throw("createExecutable - "
+ + "Failure copying installer to temporary location "
+ + this.installerDir.path
+ + " (" + e + ")");
+ }
+
+ try {
+ this.removeInstallation();
+ this.installDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+ this.iconFile.parent.create(Ci.nsIFile.DIRECTORY_TYPE, 0777);
+ } catch(e) {
+ throw("createExecutable - "
+ + "Failure setting up target location (" + e + ")");
+ }
+
+ try {
+ this.synthesizeIcon(app);
+ } catch(e) {
+ // Don't fail the installation on icon failures
+ console.log("createExecutable - "
+ + "Error synthesizing icon: " + e);
+ }
+
+ try {
+ let process = Cc["@mozilla.org/process/util;1"]
+ .createInstance(Ci.nsIProcess);
+
+ let installerFile = this.installerDir.clone();
+ installerFile.append("install.exe");
+
+ process.init(installerFile);
+ // TODO: Run this asynchronously
+ process.run(true, ["/S"], 1);
+ if(0 !== process.exitValue) {
+ throw("Installer returned " + process.exitValue);
+ }
+ } catch (e) {
+ throw("createExecutable - "
+ + "Failure running installer (" + e + ")");
+ }
+
+ try {
+ recursiveFileCopy("native-install/windows/app",
+ "",
+ this.installDir.path,
+ "\\",
+ substitutions,
+ undefined);
+
+ recursiveFileCopy("native-install/XUL",
+ "",
+ this.installDir.path,
+ "\\",
+ substitutions,
+ undefined);
+
+ //add the install record to the native app bundle
+ embedInstallRecord(app, this.installDir);
+
+ //add injector.js, which we need to inject some apis
+ //into the webapp content page
+ let contentDir = this.installDir.clone();
+ contentDir.append("content");
+ embedMozAppsAPIFiles(contentDir);
+ } catch(e) {
+ this.removeInstallation();
+ throw("createExecutable - "
+ + "Failure copying files (" + e + ")");
+ }
+ },
+
+ removeInstallation : function() {
+ try {
+ if(this.installDir.exists()) {
+ let uninstallerFile = this.installDir.clone();
+ uninstallerFile.append("uninstall.exe");
+
+ if (uninstallerFile.exists()) {
+ let process = Cc["@mozilla.org/process/util;1"]
+ .createInstance(Ci.nsIProcess);
+ process.init(uninstallerFile);
+ // TODO: Run this asynchronously
+ process.run(true, ["/S"], 1);
+ }
+ }
+ } catch(e) {
+
+ }
+
+ try {
+ if(this.installDir.exists()) {
+ this.installDir.remove(true);
+ }
+ } catch(e) {
+
+ }
+ },
+
+ synthesizeIcon : function(app)
+ {
+ try {
+ getBiggestIcon(app,
+ this.onIconRetrieved.bind(this));
+ } catch(e) {
+ throw("synthesizeIcon - Failure reading icon information (" + e + ")");
+ }
+ },
+
+ onIconRetrieved : function(resultCode,
+ mimeType,
+ imageStream)
+ {
+ if (!components.isSuccessCode(resultCode)) {
+ console.log("APPS | nativeshell.win | synthesizeIcon - "
+ + "Attempt to retrieve icon returned result code "
+ + resultCode);
+ return;
+ }
+
+ let iconStream;
+ try {
+ let imgTools = Cc["@mozilla.org/image/tools;1"]
+ .createInstance(Ci.imgITools);
+ let imgContainer = { value: null };
+
+ imgTools.decodeImageData(imageStream, mimeType, imgContainer);
+
+ iconStream =
+ imgTools.encodeImage(imgContainer.value,
+ "image/vnd.microsoft.icon");
+ } catch (e) {
+ console.log("APPS | nativeshell.win | synthesizeIcon - "
+ + "Failure converting icon"
+ + " (" + e + ")");
+ return;
+ }
+
+ try {
+ let outputStream = FileUtils.openSafeFileOutputStream(this.iconFile);
+ NetUtil.asyncCopy(iconStream,
+ outputStream,
+ function(result) {
+ if (!Components.isSuccessCode(result)) {
+ console.log("APPS | nativeshell.win | synthesizeIcon - "
+ + "Failure writing icon file "
+ + " (" + result + ")");
+ }
+ });
+ } catch (e) {
+ console.log("APPS | nativeshell.win | synthesizeIcon - "
+ + "Failure writing icon file "
+ + " (" + e + ")");
+ return;
+ }
+ }
+};
+
+// Mac implementation
+//
+// Our Mac strategy for now is to create a .webloc file and
+// to put the app icon on it. We also create a "Web Apps"
+// subfolder in the Applications folder.
+//
+// This does _not_ give us document opening (boo) but it will
+// interact reasonably with the Finder and the Dock
+
+function MacNativeShell() {
+
+}
+
+function sanitizeMacFileName(path)
+{
+ return path.replace(":", "-").replace("/", "-");
+}
+
+MacNativeShell.prototype = {
+
+ createAppNativeLauncher : function(app)
+ {
+ dump("APPS | nativeshell.mac | Creating app native launcher\n");
+ this.createExecutable(app);
+ },
+
+ setUpPaths : function(app) {
+
+ let installDirPath = "~/Applications";
+
+ this.appName = sanitizeMacFileName(app.manifest.name) + ".app";
+
+ this.installDir = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ this.installDir.initWithPath(installDirPath);
+ this.installDir.append(this.appName);
+
+ let webRTPath = self.data.url("native-install/mac/xulrunner");
+ this.webRTDir = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ this.webRTDir.initWithPath(url.toFilename(webRTPath));
+
+ this.webRTConfigFile = this.installDir.clone();
+ this.webRTConfigFile.append("webRT.config");
+ console.log(this.webRTConfigFile.path);
+
+ this.iconFile = this.installDir.clone();
+ this.iconFile.append("Contents");
+ this.iconFile.append("Resources");
+ this.iconFile.append("appicon.icns");
+ },
+
+
+ createExecutable : function(app)
+ {
+ try {
+ this.setUpPaths(app);
+ } catch(e) {
+ throw("createExecutable - Failure setting up paths (" + e + ")");
+ }
+
+ if (file.exists(this.installDir.path))
+ {
+ // recursive delete
+ this.installDir.remove(true);
+ }
+
+ // Now we synthesize a .app by copying the mac-app-template directory from our internal state
+ var launchPath = app.origin;
+ if (app.manifest.launch_path) {
+ launchPath += app.manifest.launch_path;
+ }
+
+ let substitutions = {
+ "\\$APPNAME": app.manifest.name,
+ "\\$APPDOMAIN": app.origin,
+ "\\$REVERSED_APPDOMAIN": /*reverseDNS(*/app.origin/*)*/,
+ "\\$LAUNCHPATH": launchPath
+ }
+ file.mkpath(this.installDir.path);
+
+ recursiveFileCopy("native-install/mac",
+ "",
+ this.installDir.path,
+ "/",
+ substitutions);
+
+ recursiveFileCopy("native-install/XUL",
+ "",
+ this.installDir.path + "/XUL",
+ "/",
+ substitutions);
+
+ //////////////////////////////////////////////
+ //this code should be cross-platform
+ var XULDir = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
+ XULDir.initWithPath(this.installDir.path);
+ XULDir.append("XUL");
+ var contentDir = XULDir.clone();
+ contentDir.append("content");
+
+ //add the install record to the native app bundle
+ embedInstallRecord(app, XULDir);
+ //add injector.js, which we need to inject some apis into the webapp content page
+ embedMozAppsAPIFiles(contentDir);
+ /////////////////////////////////////////////
+
+ this.synthesizeIcon(app);
+ },
+
+ synthesizeIcon : function(app)
+ {
+ getBiggestIcon(app, this.onIconRetrieved.bind(this));
+ },
+
+ onIconRetrieved : function(resultCode, mimeType, iconStream) {
+ if (!components.isSuccessCode(resultCode)) {
+ console.log("APPS | nativeshell.mac | synthesizeIcon - "
+ + "Attempt to retrieve icon returned result code "
+ + resultCode);
+ return;
+ }
+
+ try {
+ var filePath = Cc["@mozilla.org/file/directory_service;1"]
+ .getService(Ci.nsIProperties)
+ .get("TmpD", Ci.nsIFile);
+
+
+ var tSuffix;
+ if(mimeType === "image/jpeg") {
+ tSuffix = ".jpeg";
+ } else if(mimeType === "image/png") {
+ tSuffix = ".png";
+ } else {
+ // log error
+ }
+
+ filePath.append("tmpicon" + tSuffix);
+ filePath.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
+
+ var outputStream = Cc["@mozilla.org/network/safe-file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
+
+ // readwrite, create, truncate
+ outputStream.init(filePath, 0x04 | 0x08 | 0x20, 0600, 0);
+
+ var self = this;
+ console.log("filePath.path: " + filePath.path);
+ NetUtil.asyncCopy(iconStream,
+ outputStream,
+ function(result) {
+ if (!Components.isSuccessCode(result)) {
+ console.log("APPS | nativeshell.mac | synthesizeIcon - "
+ + "Failure writing temporary icon file "
+ + " (" + result + ")");
+ return;
+ }
+
+ self.onTmpIconWritten(filePath);
+ });
+ //this.onTmpIconWritten.bind(this, filePath));
+ } catch(e) {
+ console.log("APPS | nativeshell.mac | synthesizeIcon - "
+ + "Failure creating temp icon"
+ + " (" + e + ")");
+ }
+ },
+
+ onTmpIconWritten : function(filePath) {
+ console.log("onTmpIconWritten: " + filePath.path);
+
+ try {
+ var process = Cc["@mozilla.org/process/util;1"]
+ .createInstance(Ci.nsIProcess);
+ var sipsFile = Cc["@mozilla.org/file/local;1"]
+ .createInstance(Ci.nsILocalFile);
+ sipsFile.initWithPath("/usr/bin/sips");
+
+ process.init(sipsFile);
+ process.runAsync(["-s",
+ "format", "icns",
+ filePath.path,
+ "--out", this.iconFile.path,
+ "-z", "128", "128"],
+ 9);
+ } catch(e) {
+ console.log("APPS | nativeshell.mac | synthesizeIcon - "
+ + "Failure writing icon file"
+ + " (" + e + ")");
+ return;
+ }
+ }
+}
+
+
+
+/* Jetpack specific export */
+if (typeof exports !== "undefined")
+ exports.NativeShell = NativeShell;
View
10 addons/jetpack/lib/repo.js
@@ -71,11 +71,11 @@ if (typeof require !== "undefined") {
function App(app_obj) {
- this._app_obj = app_obj;
- this.origin = this._app_obj.origin;
- this.install_origin = this._app_obj.install_origin;
- this.install_time = this._app_obj.install_time;
- this.manifest = this._app_obj.manifest;
+ this.origin = app_obj.origin;
+ this.install_origin = app_obj.install_origin;
+ this.install_time = app_obj.install_time;
+ this.install_data = app_obj.install_data;
+ this.manifest = app_obj.manifest;
if ("services" in this.manifest) {
this.services = this.manifest.services;
View
138 addons/jetpack/lib/socketserver.js
@@ -0,0 +1,138 @@
+const {Cc, Ci} = require("chrome");
+var pageWorkers = require("page-worker");
+
+var gIdServer;
+const PORT_RANGE_START = 7350;
+const PORT_RANGE_END = 7400;
+
+function IdentityServer() {
+
+ return this;
+}
+
+IdentityServer.prototype = {
+ start: function() {
+ if (this.serverSocket) return;
+
+ this.serverSocket = Cc["@mozilla.org/network/server-socket;1"].createInstance(Ci.nsIServerSocket);
+
+ var port = PORT_RANGE_START;
+ while (port < PORT_RANGE_END)
+ {
+ try {
+ this.serverSocket.init(port, true, 5);
+ console.log("Started identity server on port " + port);
+ break;
+ } catch (e) {
+ port++;
+ if (port == PORT_RANGE_END)
+ {
+ console.log("Unable to start identity server on port " + port + "; checking next available port");
+ }
+ else
+ {
+ console.log("Unable to start identity server on any port. Terminating.");
+ this.serverSocket = null;
+ return;
+ }
+ break;
+ }
+ }
+
+ this.serverSocket.asyncListen(this);
+ },
+
+ stop: function() {
+ if (this.serverSocket) this.serverSocket.stop();
+ this.serverSocket = null;
+ },
+
+ onSocketAccepted: function(aSocket, aTransport)
+ {
+ try {
+ console.log("ID server accepted connection from " + aTransport.port);
+ var inStreamNative = aTransport.openInputStream(aTransport.OPEN_BLOCKING, 0, 0); // XXX blocking, blech.
+ var inStream = Cc["@mozilla.org/scriptableinputstream;1"].createInstance(Ci.nsIScriptableInputStream);
+ inStream.init(inStreamNative);
+
+ var buffer = "";
+
+ try {
+ while (true)
+ {
+ var chunk = inStream.read(1024);
+ buffer = buffer + chunk;
+ console.log("GOT DATA: " + chunk + "\n");
+ if (chunk == null || buffer.indexOf("\r\n\r\n") >= 0) {
+ console.log("Found end of buffer\n");
+ this.handleRequest(buffer, aTransport);
+ break;
+ } else {
+ console.log("Looking for more data\n");
+ }
+ }
+ } catch (e) {
+ console.log("ID server I/O error while reading request:" + e);
+ }
+ inStream.close();
+ } catch (e) {
+ console.log(e);
+ }
+ },
+
+ onStopListening: function(aSocket, aStatus)
+ {
+ console.log("ID server stop listening; " + aStatus);
+ },
+
+ handleRequest: function(request, aTransport)
+ {
+ var outStream = aTransport.openOutputStream(aTransport.OPEN_BLOCKING, 0, 0); // XXX blocking, blech.
+
+ var idLine = request.split("\r")[0];
+ var lineParts = idLine.split(" ");
+ if (lineParts[0] != "IDCHECK" || lineParts.length != 3) {
+ console.log("Incorrect request on ID server port");
+ var err = '{"status":"error"}';
+ outStream.write(err, err.length);
+ outStream.close();
+ return;
+ }
+ var id = lineParts[1];
+ var audience = lineParts[2];
+
+ // TODO sanitize ID through regex
+ console.log("Passing identity " + id + " to browserid\n");
+ try {
+ pageWorkers.Page({
+ contentURL: "https://browserid.org",
+ contentScript: "unsafeWindow.BrowserID.User.setOrigin(\"" + audience + "\");" +
+ "unsafeWindow.BrowserID.User.getAssertion(\"" + id + "\", " +
+ "function(res) {self.postMessage({status:\"ok\", assertion:res});}, " +
+ "function(err) {self.postMessage({status:\"error\"});});",
+ contentScriptWhen: "end",
+ onMessage: function(message) {
+ var out = JSON.stringify(message) + "\r\n\r\n";
+ console.log("BrowserID returned: " + out);
+ outStream.write(out, out.length);
+ outStream.close();
+ }
+ });
+ } catch (e) {
+ var out = "error: " + e;
+ outStream.write(out, out.length);
+ outStream.close();
+ }
+ }
+}
+
+function startServer() {
+ gIdServer = new IdentityServer();
+ gIdServer.start();
+}
+function stopServer() {
+
+}
+
+exports.startServer = startServer;
+exports.stopServer = stopServer;
View
41 addons/jetpack/mac/Makefile
@@ -0,0 +1,41 @@
+LD=gcc
+LIBHEADERS =-I/System/Library/Frameworks/AppKit.framework/Headers -I/System/Library/Frameworks/Foundation.framework/Headers
+
+CFLAGS= -c $(LIBHEADERS) -fPIC -m64
+# -isysroot /Developer/SDKs/MacOSX10.6.sdk
+# /usr/libexec/gcc/i686-apple-darwin10/4.2.1/cc1 -quiet -v -imultilib x86_64 -D__DYNAMIC__ testmain.c -fPIC -quiet -dumpbase testmain.c
+#-mmacosx-version-min=10.6.7 -m64 -mtune=core2 -auxbase testmain -version -o /var/folders/0A/0A78iplZFxChIh+g37kvY++++TI/-Tmp-//cc2ptuB5.s
+
+LDFLAGS= -dynamic -arch x86_64 -framework CoreFoundation -framework AppKit
+
+# /usr/libexec/gcc/i686-apple-darwin10/4.2.1/collect2 -dynamic -arch x86_64 -macosx_version_min 10.6.7 -weak_reference_mismatches non-weak -o a.out
+# -lcrt1.10.6.o -L/usr/lib/gcc/i686-apple-darwin10/4.2.1/x86_64 -L/usr/lib/gcc/i686-apple-darwin10/4.2.1/x86_64 -L/usr/lib/i686-apple-darwin10/4.2.1 -L/usr/lib/gcc/i686-apple-darwin10/4.2.1 -L/usr/lib/gcc/i686-apple-darwin10/4.2.1 -L/usr/lib/gcc/i686-apple-darwin10/4.2.1/../../../i686-apple-darwin10/4.2.1 -L/usr/lib/gcc/i686-apple-darwin10/4.2.1/../../.. /var/folders/0A/0A78iplZFxChIh+g37kvY++++TI/-Tmp-//ccgQ1PXt.o -lSystem
+#-lgcc -lSystem
+
+LIBS = -lc -lobjc -lSystem
+
+OBJS = foxlauncher.o
+
+all: build
+
+clean:
+ rm $(OBJS)
+
+build: foxlauncher.o foxlauncher
+
+.m.o:
+ gcc $(CFLAGS) $(@:.o=.m)
+#-o $@
+.c.o:
+ gcc $(CFLAGS) $(@:.o=.c)
+# -o $@
+
+foxlauncher: $(OBJS)
+ gcc $(LDFLAGS) $(LIBS) -L. foxlauncher.o -o ../data/native-install/mac/Contents/MacOS/foxlauncher
+
+.o.a:
+ ar rcs $@ $(@:.a=.o)
+
+#iconsetter.dylib:
+# gcc -shared $(LDFLAGS) -o iconsetter.dylib iconsetter.o
+
View
260 addons/jetpack/mac/foxlauncher.m
@@ -0,0 +1,260 @@
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <dirent.h>
+#include <sys/stat.h>
+
+#include <Cocoa/Cocoa.h>
+#include <Foundation/Foundation.h>
+
+const char *ENVIRONMENT_DIR = "env";
+const char *FIREFOX_EXECUTABLE = "firefox-bin";
+const char *VERSION_FILE = "ffx.version";
+
+NSString *pathToCurrentFirefox();
+int updateApplicationEnvironment(NSString *firefoxPath, char *appEnvDirPath);
+int deleteApplicationEnvironment(char *appEnvDirPath);
+int buildApplicationEnvironment(NSString *firefoxPath, char *appEnvDirPath);
+void launchApplication();
+char *readAppEnvVersion(char *appEnvDirPath);
+
+int gVerbose = 0;
+
+int main(int argc, char **argv)
+{
+ if (argc > 1 && !strcmp(argv[1], "-v")) {
+ gVerbose = 1;
+ }
+
+ NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
+ NSString *firefoxPath = pathToCurrentFirefox(); // XXX developer feature to specify other firefox here
+ if (!firefoxPath) {
+ // Launch a dialog to explain to the user that there's no compatible web runtime
+ return 0;
+ }
+
+ char appEnvDirPath[4096];
+ snprintf(appEnvDirPath, 4096, "%s/Contents/MacOS/%s", [[[NSBundle mainBundle] bundlePath] UTF8String], ENVIRONMENT_DIR);
+
+ int rc = updateApplicationEnvironment(firefoxPath, appEnvDirPath);
+ if (rc) {
+ exit(-1);
+ }
+ launchApplication();
+ [pool drain];
+ exit(-1);
+}
+
+NSString *pathToCurrentFirefox()
+{
+ NSString *firefoxRoot = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"org.mozilla.firefox"];
+ if (firefoxRoot) {
+ return firefoxRoot;
+ } else {
+ return NULL;
+ }
+}
+
+// const char *appdir = [[[NSBundle mainBundle] bundlePath] UTF8String];
+// NSString *FirefoxRoot = [[NSWorkspace sharedWorkspace] absolutePathForAppBundleWithIdentifier:@"org.mozilla.firefox"];
+
+char *currentAppVersion()
+{
+ char appVersionPath[4096];
+ snprintf(appVersionPath, 4096, "%s/Contents/MacOS/%s/%s", [[[NSBundle mainBundle] bundlePath] UTF8String], ENVIRONMENT_DIR, VERSION_FILE);
+ FILE *fp = fopen(appVersionPath, "r");
+ if (fp) {
+ char buf[512];
+ size_t read = fread(buf, 1, 511, fp);
+ if (read > 0) {
+ buf[read] = 0;
+ fclose(fp);
+ return strdup(buf);
+ }
+ fclose(fp);
+ return NULL;
+ }
+ return NULL;
+}
+
+int updateApplicationEnvironment(NSString *firefoxPath, char *appEnvDirPath)
+{
+ int rc;
+ struct stat my_stat;
+ rc = stat(appEnvDirPath, &my_stat);
+
+ if (rc < 0 && errno == ENOENT) {
+ // doesn't exist - that's okay
+ } else if (rc == 0) {
+ // exists - check it
+ if ((my_stat.st_mode & S_IFDIR) == 0) {
+ fprintf(stderr, "Error while updating application environment: env is not a directory");
+ return -1;
+ }
+
+ if (applicationVersionsMatch(appEnvDirPath)) {
+ if (gVerbose) printf("Application environment version match\n");
+ // To be super-careful we could check whether all the symlinks are
+ // still the same...
+ return 0;
+ }
+
+ if (gVerbose) printf("Deleting application environment\n");
+ rc = deleteApplicationEnvironment(appEnvDirPath);
+ if (rc) return rc;
+ // carry on and create the new one...
+
+ } else {
+ // anything else is bad.
+ fprintf(stderr, "Error while updating application environment: %s (%d)", strerror(errno), rc);
+ return rc;
+ }
+ rc = buildApplicationEnvironment(firefoxPath, appEnvDirPath);
+ return rc;
+}
+
+int applicationVersionsMatch(char *appEnvDirPath)
+{
+ char *curVer = currentAppVersion();
+ char *envVer = readAppEnvVersion(appEnvDirPath);
+ if (curVer && envVer && strcmp(curVer, envVer) == 0) return 1;
+ return 0;
+}
+
+char *readAppEnvVersion(char *appEnvDirPath)
+{
+ char appIniPath[4096];
+ snprintf(appIniPath, 4096, "%s/application.ini", appEnvDirPath);
+ FILE *fp = fopen(appIniPath, "r");
+ if (fp) {
+ while (1) {
+ char lineBuf[1024];
+ char *line = fgets(lineBuf, 1024, fp);
+ if (!line) break;
+ if (strncmp(line, "Version=", 8) == 0) {
+ fclose(fp);
+ return strdup(line+8);
+ }
+ }
+ fclose(fp);
+ }
+ return NULL;
+}
+
+int deleteApplicationEnvironment(char *appEnvDirPath)
+{
+ struct dirent *dp = NULL;
+ int rc;
+
+ DIR *dirp = opendir(appEnvDirPath);
+ if (dirp == NULL) return;
+
+ while ((dp = readdir(dirp)) != NULL)
+ {
+ // Skip '.' and '..'
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue;
+
+ // Unlink the rest
+ char delPath[4096];
+ snprintf(delPath, 4096, "%s/%s", appEnvDirPath, dp->d_name);
+ rc = unlink(delPath);
+ if (rc) {
+ fprintf(stderr, "Error while deleting application environment: %s (%d)", strerror(errno), errno);
+ (void)closedir(dirp);
+ return rc;
+ }
+ }
+
+ // And delete the directory:
+ (void)closedir(dirp);
+ rc = rmdir(appEnvDirPath);
+ if (rc) {
+ fprintf(stderr, "Error while deleting application environment: %s (%d)", strerror(errno), errno);
+ return rc;
+ }
+ return 0;
+}
+
+int buildApplicationEnvironment(NSString *firefoxPath, char *appEnvDirPath)
+{
+ struct dirent *dp = NULL;
+ if (gVerbose) printf("Building new application environment\n");
+
+ int rc = mkdir(appEnvDirPath, 0755); // rwxr_xr_x
+ if (rc) {
+ fprintf(stderr, "Error while creating application environment: %s (%d)", strerror(errno), errno);
+ return rc;
+ }
+
+ char firefoxBundlePath[4096];
+ snprintf(firefoxBundlePath, 4096, "%s/Contents/MacOS", [firefoxPath UTF8String]);
+ DIR *dirp = opendir(firefoxBundlePath);
+ if (dirp == NULL) {
+ fprintf(stderr, "Error while creating application environment: can't open Firefox bundle");
+ return -1;
+ }
+
+ while ((dp = readdir(dirp)) != NULL)
+ {
+ char sourcePath[4096], destPath[4096];
+ // Skip '.' and '..'
+ if (strcmp(dp->d_name, ".") == 0 || strcmp(dp->d_name, "..") == 0)
+ continue;
+
+ snprintf(sourcePath, 4096, "%s/%s", firefoxBundlePath, dp->d_name);
+ snprintf(destPath, 4096, "%s/%s", appEnvDirPath, dp->d_name);
+ int rc = symlink(sourcePath, destPath);
+ if (rc) {
+ fprintf(stderr, "Error while constructing application environment: %s (%d)", strerror(errno), rc);
+ (void)closedir(dirp);
+ return rc;
+ }
+ }
+ (void)closedir(dirp);
+
+ // Write the version:
+ char versionPath[4096];
+ snprintf(versionPath, 4096, "%s/%s", appEnvDirPath, VERSION_FILE);
+ FILE *fp = fopen(versionPath, "w");
+ if (fp) {
+ fprintf(fp, "%s", readAppEnvVersion(appEnvDirPath));
+ fclose(fp);
+ }
+ return 0;
+}
+
+void launchApplication()
+{
+ const char *appdir = [[[NSBundle mainBundle] bundlePath] UTF8String];
+
+ char launchPath[1024];
+ snprintf(launchPath, 1024, "%s/Contents/MacOS/%s/%s", appdir, ENVIRONMENT_DIR, FIREFOX_EXECUTABLE);
+
+ char xulPath[1024];
+ snprintf(xulPath, 1024, "%s/XUL/application.ini", appdir);
+
+ char *newargv[4];
+ newargv[0] = "firefox-bin";
+ newargv[1] = "-app";
+ newargv[2] = xulPath;
+ newargv[3] = NULL;
+
+ execv(launchPath, (char **)newargv);
+}
+
+
+/*
+
+ * What is the current newest non-beta Firefox
+ * (FUTURE: Way for developer to specify beta, alpha, nightly)
+ * Do we have a symlink directory?
+ * If yes, does it point to the directory of Firefox that I found above
+ * If yes, is it the same version as when I made the symlinks? (XX: Need to stash a version code)
+ * If yes, happy days, execute
+ * If no on ANYTHING:
+ * Delete symlink directory
+ * Make new one (iterate all contents of Firefox directly making links)
+ * Execute
+
+*/
View
208 addons/jetpack/windows/installer/webapp-installer.nsi
@@ -0,0 +1,208 @@
+# 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 an NSIS installer/uninstaller for Open Web Apps
+#
+# The Initial Developer of the Original Code is
+# Mozilla Foundation.
+# Portions created by the Initial Developer are Copyright (C) 2011
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Tim Abraldes <tabraldes@mozilla.com>
+#
+# 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 "FileFunc.nsh"
+
+SetCompressor /SOLID /FINAL lzma
+CRCCheck on
+RequestExecutionLevel user
+
+Var appName
+Var appURL
+Var appDesc
+Var iconPath
+Var createDesktopShortcut
+Var createStartMenuShortcut
+Var FFPath
+
+Name $appName
+OutFile ..\..\data\native-install\windows\installer\install.exe
+
+Function .onInit
+ ClearErrors
+ readAppName:
+ ReadINIStr $appName $EXEDIR\install.ini required appName
+ IfErrors 0 setOutDir
+ SetErrors
+ Goto cleanup
+ setOutDir:
+ StrCpy $INSTDIR $APPDATA\$appName
+ SetOutPath $INSTDIR
+ readFFPath:
+ ReadINIStr $FFPath $EXEDIR\install.ini required FFPath
+ IfErrors 0 readOptionalInfo
+ SetErrors
+ Goto cleanup
+ readOptionalInfo:
+ ReadINIStr $appURL $EXEDIR\install.ini optional appURL
+ ReadINIStr $appDesc $EXEDIR\install.ini optional appDesc
+ ReadINIStr $iconPath $EXEDIR\install.ini optional iconPath
+ ReadINIStr $createDesktopShortcut $EXEDIR\install.ini optional createDesktopShortcut
+ ReadINIStr $createStartMenuShortcut $EXEDIR\install.ini optional createStartMenuShortcut
+ ClearErrors
+ cleanup:
+ IfErrors 0 done
+ Abort
+ done:
+FunctionEnd
+
+Function WriteRegKeys
+ DetailPrint "Writing registry keys"
+ WriteRegStr HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "DisplayName" \
+ $appName
+ WriteRegStr HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "UninstallString" \
+ "$OUTDIR\uninstall.exe"
+ WriteRegStr HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "InstallLocation" \
+ "$OUTDIR"
+ WriteRegStr HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "DisplayIcon" \
+ "$iconPath"
+ WriteRegStr HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "HelpLink" \
+ "https://apps.mozillalabs.com/"
+ WriteRegStr HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "URLUpdateInfo" \
+ "https://apps.mozillalabs.com/"
+ WriteRegStr HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "URLInfoAbout" \
+ "$appURL"
+ WriteRegDWORD HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "NoModify" \
+ 0x1
+ WriteRegDWORD HKCU \
+ "Software\Microsoft\Windows\CurrentVersion\Uninstall\$appName" \
+ "NoRepair" \
+ 0x1
+ DetailPrint "Done"
+FunctionEnd
+
+Function CreateShortcuts
+ ClearErrors
+ CreateShortcut $OUTDIR\$appName.lnk \
+ $FFPath \
+ '-app "$OUTDIR\application.ini"' \
+ $iconPath \
+ 0 \
+ "" \
+ "" \
+ $appDesc
+ maybeCreateDesktopShortcut:
+ StrCmp $createDesktopShortcut "y" 0 maybeCreateStartMenuShortcut
+ CreateShortcut $DESKTOP\$appName.lnk \
+ $OUTDIR\$appName.lnk \
+ "" \
+ $iconPath \
+ 0 \
+ "" \
+ "" \
+ $appDesc
+ maybeCreateStartMenuShortcut:
+ StrCmp $createStartMenuShortcut "y" 0 cleanup
+ CreateShortcut $SMPROGRAMS\$appName.lnk \
+ $OUTDIR\$appName.lnk \
+ "" \
+ $iconPath \
+ 0 \
+ "" \
+ "" \
+ $appDesc
+ cleanup:
+FunctionEnd
+
+Section Install
+ Call WriteRegKeys
+ Call CreateShortcuts
+ WriteUninstaller $OUTDIR\uninstall.exe
+SectionEnd
+
+Function un.onInit
+ ClearErrors
+ readAppName:
+ ReadINIStr $appName $INSTDIR\uninstall.ini required appName
+ IfErrors 0 setUpPaths
+ SetErrors
+ Goto cleanup
+ setUpPaths:
+ ReadRegStr $INSTDIR \
+ HKCU \