Skip to content
This repository
Browse code

Merge pull request #134 from anantn/develop

Alpha version of native app generation for Mac & Windows
  • Loading branch information...
commit 10a4a41b666a7116bc607653aaac9dab1cbda23b 2 parents 6b34cce + 49073f9
Anant Narayanan authored

Showing 31 changed files with 2,446 additions and 26 deletions. Show diff stats Hide diff stats

  1. 2  .gitignore
  2. 10  Makefile
  3. 1  addons/jetpack/data/native-install/XUL/chrome.manifest
  4. 35  addons/jetpack/data/native-install/XUL/content/main.js
  5. 5  addons/jetpack/data/native-install/XUL/content/main.xul
  6. 315  addons/jetpack/data/native-install/XUL/content/window.js
  7. 17  addons/jetpack/data/native-install/XUL/content/window.xul
  8. 9  addons/jetpack/data/native-install/XUL/defaults/preferences/prefs.js
  9. 10  addons/jetpack/data/native-install/XUL/newapp_template.ini
  10. 10  addons/jetpack/data/native-install/XUL/specialfiles.json
  11. 74  addons/jetpack/data/native-install/mac/Contents/Info.plist
  12. BIN  addons/jetpack/data/native-install/mac/Contents/MacOS/foxlauncher
  13. 0  addons/jetpack/data/native-install/mac/Contents/Resources/empty
  14. 13  addons/jetpack/data/native-install/mac/specialfiles.json
  15. 6  addons/jetpack/data/native-install/windows/app/specialfiles.json
  16. 2  addons/jetpack/data/native-install/windows/app/uninstall.ini
  17. 10  addons/jetpack/data/native-install/windows/installer/install.ini
  18. 12  addons/jetpack/data/native-install/windows/installer/specialfiles.json
  19. 2  addons/jetpack/data/widget.html
  20. 26  addons/jetpack/lib/api.js
  21. 28  addons/jetpack/lib/injector.js
  22. 4  addons/jetpack/lib/main.js
  23. 844  addons/jetpack/lib/nativeshell.js
  24. 10  addons/jetpack/lib/repo.js
  25. 178  addons/jetpack/lib/socketserver.js
  26. 41  addons/jetpack/mac/Makefile
  27. 260  addons/jetpack/mac/foxlauncher.m
  28. 208  addons/jetpack/windows/installer/webapp-installer.nsi
  29. 77  examples/demopaid/index.html
  30. 13  examples/demopaid/manifest.webapp
  31. 250  jslibs/app.js
2  .gitignore
@@ -4,3 +4,5 @@ site/tools/yuicompressor-2.4.2.jar
4 4
 site/tools/yuicompressor-2.4.2.zip
5 5
 site/tools/build-compressor
6 6
 deps/
  7
+addons/jetpack/mac/foxlauncher.o
  8
+openwebapps.xpi
10  Makefile
@@ -2,6 +2,12 @@ APPNAME = fx-share-addon
2 2
 DEPS = github:addon-sdk,github:oauthorizer
3 3
 PYTHON = python
4 4
 
  5
+MAKELAUNCHER =
  6
+SYS := $(shell uname -s)
  7
+ifeq ($(SYS), Darwin)
  8
+	MAKELAUNCHER := ${MAKE} -C addons/jetpack/mac/
  9
+endif
  10
+
5 11
 ifeq ($(TOPSRCDIR),)
6 12
   export TOPSRCDIR = $(shell pwd)
7 13
 endif
@@ -43,7 +49,8 @@ endif
43 49
 
44 50
 all: xpi
45 51
 
46  
-xpi:    pull
  52
+xpi: pull
  53
+	$(MAKELAUNCHER)
47 54
 	$(addon_sdk)/cfx xpi $(cfx_args)
48 55
 
49 56
 pull:
@@ -53,6 +60,7 @@ test:
53 60
 	$(addon_sdk)/cfx test $(cfx_args) $(test_args)
54 61
 
55 62
 run:
  63
+	$(MAKELAUNCHER)
56 64
 	$(addon_sdk)/cfx run $(cfx_args)	
57 65
 
58 66
 .PHONY: xpi clean pull test run
1  addons/jetpack/data/native-install/XUL/chrome.manifest
... ...
@@ -0,0 +1 @@
  1
+content webapp content/
35  addons/jetpack/data/native-install/XUL/content/main.js
... ...
@@ -0,0 +1,35 @@
  1
+
  2
+var Cu = Components.utils;
  3
+var Cc = Components.classes;
  4
+var Ci = Components.interfaces;
  5
+
  6
+var os = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULRuntime).OS;
  7
+    
  8
+// only make the tiny window that handles window closing events (and quits the app) for Mac OS
  9
+if ("Darwin" === os) {
  10
+  var observer = {
  11
+    observe: function(contentWindow, aTopic, aData) {
  12
+      if (aTopic == 'xul-window-destroyed') {
  13
+        // If there is nothing left but the main (invisible) window, quit
  14
+        var wm = Cc["@mozilla.org/appshell/window-mediator;1"]  
  15
+          .getService(Ci.nsIWindowMediator);  
  16
+        var enumerator = wm.getEnumerator("app");
  17
+        if (enumerator.hasMoreElements()) return;
  18
+
  19
+        var appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
  20
+          appStartup.quit(appStartup.eAttemptQuit);
  21
+        }
  22
+    }
  23
+  }
  24
+
  25
+  // Register our observer:
  26
+  var observerService = Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
  27
+  observerService.addObserver(observer, "xul-window-destroyed", false);
  28
+};
  29
+
  30
+// Create the first window
  31
+var appName = "$APPNAME";
  32
+var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"]
  33
+                   .getService(Ci.nsIWindowWatcher);
  34
+var win = ww.openWindow(null, "chrome://webapp/content/window.xul",
  35
+                        appName, "chrome,centerscreen,resizable", null);
5  addons/jetpack/data/native-install/XUL/content/main.xul
... ...
@@ -0,0 +1,5 @@
  1
+<?xml version="1.0"?>
  2
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
  3
+<window id="main" title="main" width="0" height="0" xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"> 
  4
+  <script type="application/javascript" src="chrome://webapp/content/main.js"/>
  5
+</window>
315  addons/jetpack/data/native-install/XUL/content/window.js
... ...
@@ -0,0 +1,315 @@
  1
+
  2
+var Cu = Components.utils;
  3
+var Cc = Components.classes;
  4
+var Cm = Components.manager;
  5
+var Ci = Components.interfaces;
  6
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
  7
+
  8
+function doHandleMenuBar(toCall)
  9
+{
  10
+    // We need to pageMod in from the faker
  11
+    // TODO pass this into content code somehow
  12
+    window.alert("Menu bar item " + toCall + " was clicked!");
  13
+    return;
  14
+}
  15
+
  16
+window.addEventListener("click", function(e) {
  17
+    // Make sure clicks remain in our context
  18
+    // TODO check to see if we are in same origin?
  19
+    if (e.target.nodeName == "A") {
  20
+        e.preventDefault();
  21
+        window.location = e.target.href;
  22
+    }
  23
+}, false);
  24
+
  25
+// Commands:
  26
+var appName = "$APPNAME";
  27
+function newWindow()
  28
+{
  29
+    var ww = Components.classes["@mozilla.org/embedcomp/window-watcher;1"]
  30
+                       .getService(Components.interfaces.nsIWindowWatcher);
  31
+    var win = ww.openWindow(null, "chrome://webapp/content/window.xul",
  32
+                            null, "chrome,centerscreen,resizable", null);
  33
+}
  34
+
  35
+// Inject APIs
  36
+//----- navigator.mozApps api implementation
  37
+// FIXME: Fallback doesn't actually work on Fx<9, debug why
  38
+var injector = {};
  39
+Cu.import("chrome://webapp/content/injector.js", injector);
  40
+injector.init();
  41
+
  42
+function NavigatorAPI() {};
  43
+NavigatorAPI.prototype = {
  44
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer]),
  45
+  
  46
+  init: function API_init(aWindow) {
  47
+    let chromeObject = this._getObject(aWindow);
  48
+
  49
+    // We need to return an actual content object here, instead of a wrapped
  50
+    // chrome object. This allows things like console.log.bind() to work.
  51
+    let contentObj = Cu.createObjectIn(aWindow);
  52
+    function genPropDesc(fun) {
  53
+      return { enumerable: true, configurable: true, writable: true,
  54
+               value: chromeObject[fun].bind(chromeObject) };
  55
+    }
  56
+    let properties = {};
  57
+  
  58
+    for (var fn in chromeObject.__exposedProps__) {
  59
+      properties[fn] = genPropDesc(fn);
  60
+    }
  61
+
  62
+    Object.defineProperties(contentObj, properties);
  63
+    Cu.makeObjectPropsNormal(contentObj);
  64
+
  65
+    return contentObj;
  66
+  }
  67
+};
  68
+
  69
+var MozAppsAPIContract = "@mozilla.org/openwebapps/mozApps;1";
  70
+var MozAppsAPIClassID = Components.ID("{19c6a16b-18d1-f749-a2c7-fa23e70daf2b}");
  71
+function MozAppsAPI() {}
  72
+MozAppsAPI.prototype = {
  73
+  __proto__: NavigatorAPI.prototype,
  74
+  classID: MozAppsAPIClassID,
  75
+  _getObject: function(aWindow) {
  76
+    return {
  77
+      amInstalled: function(callback) {
  78
+        getInstallRecord(callback);
  79
+      },
  80
+
  81
+      verifyReceipt: function(callback, options, cb, verifyOnly) {
  82
+        doVerifyReceipt(aWindow, callback, options, cb, verifyOnly);
  83
+      },
  84
+      
  85
+      __exposedProps__: {
  86
+        amInstalled: "r",
  87
+        verifyReceipt: "r"
  88
+      }
  89
+    };
  90
+  }
  91
+};
  92
+
  93
+let MozAppsAPIFactory = {
  94
+  createInstance: function(outer, iid) {
  95
+    if (outer != null) throw Cr.NS_ERROR_NO_AGGREGATION;
  96
+    return new MozAppsAPI().QueryInterface(iid);
  97
+  }
  98
+};
  99
+
  100
+Cm.QueryInterface(Ci.nsIComponentRegistrar).registerFactory(
  101
+  MozAppsAPIClassID, "MozAppsAPI", MozAppsAPIContract, MozAppsAPIFactory
  102
+);
  103
+Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager)
  104
+  .addCategoryEntry("JavaScript-navigator-property", "mozApps", MozAppsAPIContract, false, true);
  105
+
  106
+/* TODO: Unload injector
  107
+unloaders.push(function() {
  108
+  Cm.QueryInterface(Ci.nsIComponentRegistrar).unregisterFactory(
  109
+    MozAppsAPIClassID, MozAppsAPIFactory
  110
+  );
  111
+  Cc["@mozilla.org/categorymanager;1"].getService(Ci.nsICategoryManager).
  112
+              deleteCategoryEntry("JavaScript-navigator-property", "mozApps", false);
  113
+});
  114
+*/
  115
+
  116
+function getInstallRecord(cb) {
  117
+  var appDirectory = Cc["@mozilla.org/file/directory_service;1"].
  118
+    getService(Ci.nsIProperties).  
  119
+    get("CurProcD", Ci.nsIFile);
  120
+            
  121
+  var aNsLocalFile = Cc['@mozilla.org/file/local;1'].createInstance(Ci.nsILocalFile);
  122
+  aNsLocalFile.initWithFile(appDirectory);
  123
+  aNsLocalFile.appendRelativePath("installrecord.json");
  124
+
  125
+  Cu.import("resource://gre/modules/NetUtil.jsm");
  126
+  Cu.import("resource://gre/modules/FileUtils.jsm");
  127
+          
  128
+  dump(aNsLocalFile.path + "\n");
  129
+  NetUtil.asyncFetch(aNsLocalFile, function(inputStream, status) {  
  130
+      if (!Components.isSuccessCode(status)) {  
  131
+          // Handle error!  
  132
+          console.log("ERROR: " + status + " failed to read file: " + inFile);
  133
+          return;  
  134
+      }  
  135
+      var data = NetUtil.readInputStreamToString(inputStream, inputStream.available());  
  136
+      var parsed = JSON.parse(data);
  137
+      inputStream.close();
  138
+      cb(parsed);
  139
+  });
  140
+}
  141
+
  142
+function doVerifyReceipt(contentWindowRef, options, cb, verifyOnly) {
  143
+  getInstallRecord(function(record) {
  144
+    if (!record) {
  145
+      cb({"error": "Invalid Receipt"});
  146
+      return;
  147
+    }
  148
+
  149
+    function base64urldecode(arg) {
  150
+      var s = arg;
  151
+      s = s.replace(/-/g, '+'); // 62nd char of encoding
  152
+      s = s.replace(/_/g, '/'); // 63rd char of encoding
  153
+      switch (s.length % 4) // Pad with trailing '='s
  154
+      {
  155
+        case 0: break; // No pad chars in this case
  156
+        case 2: s += "=="; break; // Two pad chars
  157
+        case 3: s += "="; break; // One pad char
  158
+        default: throw new InputException("Illegal base64url string!");
  159
+      }
  160
+      return window.atob(s); // Standard base64 decoder
  161
+    }
  162
+
  163
+    function parseReceipt(rcptData) {
  164
+      // rcptData is a JWT.  We should use a JWT library.
  165
+      var data = rcptData.split(".");
  166
+      if (data.length != 3)
  167
+        return null;
  168
+
  169
+      // convert base64url to base64
  170
+      var payload = base64urldecode(data[1]);
  171
+      var parsed = JSON.parse(payload);
  172
+
  173
+      return parsed;
  174
+    }
  175
+      
  176
+    var receipt = parseReceipt(record.install_data.receipt);
  177
+    if (!receipt) {
  178
+      cb({"error": "Invalid Receipt"});
  179
+      return;
  180
+    }
  181
+      
  182
+    // Two status "flags", one for verify XHR other for BrowserID XHR
  183
+    // These two XHRs run in parallel, and the last one to finish invokes cb()
  184
+    var assertion;
  185
+    var verifyStatus = false;
  186
+    var assertStatus = false;
  187
+
  188
+    var verifyURL = receipt.verify;
  189
+    var verifyReq = new XMLHttpRequest();  
  190
+    verifyReq.open('GET', verifyURL, true);  
  191
+    verifyReq.onreadystatechange = function (aEvt) {  
  192
+      if (verifyReq.readyState == 4) {
  193
+        // FIXME: 404? Yeah, because that's what we get now
  194
+        // Hook up to real verification when it's done on AMO
  195
+        // and change this to 200 !!!
  196
+        verifyStatus = true;
  197
+        if (verifyReq.status == 404) {
  198
+          if (verifyOnly && typeof verifyOnly == "function") {
  199
+            verifyOnly(receipt);
  200
+          }
  201
+          if (verifyStatus && assertStatus) {
  202
+            cb({"success": {"receipt": receipt, "assertion": assertion}});
  203
+          }
  204
+        } else {
  205
+          if (verifyStatus && assertStatus) {
  206
+            cb({"error": "Invalid Receipt: " + req.responseText});
  207
+          }
  208
+        }
  209
+      }
  210
+    };
  211
+    verifyReq.send(null);
  212
+
  213
+    // Start BrowserID verification
  214
+    var options = {"silent": true, "requiredEmail": receipt.user.value};
  215
+    checkNativeIdentityDaemon(contentWindowRef.location, options, function(ast) {
  216
+      assertion = ast;
  217
+      if (!assertion) {
  218
+        cb({"error": "Invalid Identity"});
  219
+        return;
  220
+      }
  221
+
  222
+      var assertReq = new XMLHttpRequest();
  223
+      assertReq.open('POST', 'https://browserid.org/verify', true);
  224
+      assertReq.onreadystatechange = function(aEvt) {
  225
+        if (assertReq.readyState == 4) {
  226
+          assertStatus = true;
  227
+
  228
+          // FIXME: a 200 status code doesn't mean OK, check
  229
+          // the responseText
  230
+          if (assertReq.status == 200) {
  231
+            if (verifyStatus && assertStatus) {
  232
+              cb({"success": {"receipt": receipt, "assertion": assertion}});
  233
+            }
  234
+          } else {
  235
+            if (verifyStatus && assertStatus) {
  236
+              cb({"error": "Invalid Identity: " + assertReq.responseText});
  237
+            }
  238
+          }
  239
+        }
  240
+      };
  241
+
  242
+      var body = "assertion=" + encodeURIComponent(assertion) + "&audience=" +
  243
+        contentWindowRef.location.protocol + "//" + contentWindowRef.location.host;
  244
+      assertReq.send(body);
  245
+    });
  246
+  });
  247
+}
  248
+
  249
+function checkNativeIdentityDaemon(callingLocation, options, success, failure)
  250
+{
  251
+  dump("CheckNativeIdentityDaemon " + callingLocation + " " + JSON.stringify(options) + "\n");
  252
+  if (typeof failure != "function") {
  253
+      function failure(e) {
  254
+          dump("Failure callback not defined, shimmed " + e);
  255
+      }
  256
+  }
  257
+
  258
+  // XXX what do we do if we are not passed a requiredEmail?
  259
+  // could fail immediately, or could ask Firefox for a default somehow
  260
+  if (!options || !options.requiredEmail) failure();
  261
+
  262
+  var port = 7350;
  263
+  var output, input, scriptableStream;
  264
+  var sockTransportService = Cc["@mozilla.org/network/socket-transport-service;1"]
  265
+    .getService(Ci.nsISocketTransportService);
  266
+  
  267
+  var domain = callingLocation.protocol + "//" + callingLocation.host;
  268
+  if (callingLocation.port && callingLocation.port.length > 0) callingLocation += ":" + callingLocation.port;
  269
+  var buf = "IDCHECK " + options.requiredEmail + " " + domain + "\r\n\r\n";
  270
+
  271
+  var eventSink = {
  272
+      onTransportStatus: function(aTransport, aStatus, aProgress, aProgressMax) {
  273
+          if (aStatus == aTransport.STATUS_CONNECTED_TO) {
  274
+              output.write(buf, buf.length);
  275
+          } else if (aStatus == aTransport.STATUS_RECEIVING_FROM) {
  276
+              var chunk = scriptableStream.read(8192);
  277
+              if (chunk.length> 1) {
  278
+                  success(chunk);
  279
+                  return;
  280
+              } else {
  281
+                  failure();
  282
+                  return;
  283
+              }
  284
+          } else if (false /* connection refused */) {
  285
+              port++;
  286
+              attemptConnection();
  287
+          }
  288
+      }
  289
+  };
  290
+
  291
+  var threadMgr = Cc["@mozilla.org/thread-manager;1"].getService();
  292
+  function attemptConnection() {
  293
+      if (port > 7550) {
  294
+          failure();
  295
+          return;
  296
+      }
  297
+      var transport = sockTransportService.createTransport(null, 0, "127.0.0.1", port, null);
  298
+      transport.setEventSink(eventSink, threadMgr.currentThread);
  299
+      try {
  300
+          output = transport.openOutputStream(transport.OPEN_BLOCKING, 0, 0);
  301
+          output.write(buf, buf.length);
  302
+          input = transport.openInputStream(transport.OPEN_BLOCKING, 0, 0);
  303
+          scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]  
  304
+                               .createInstance(Ci.nsIScriptableInputStream);  
  305
+          scriptableStream.init(input);
  306
+      } catch (e) {
  307
+          alert(e);
  308
+          port++;
  309
+          attemptConnection();
  310
+      }
  311
+  }
  312
+  attemptConnection();
  313
+}
  314
+
  315
+/* TODO: Add navigator.id.getVerifiedEmail */
17  addons/jetpack/data/native-install/XUL/content/window.xul
... ...
@@ -0,0 +1,17 @@
  1
+<?xml version="1.0"?>
  2
+<?xml-stylesheet href="chrome://global/skin/" type="text/css"?>
  3
+<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! -->
  4
+  <script type="application/javascript" src="chrome://webapp/content/window.js"/>
  5
+  <commandset>
  6
+  	<command id="new_window" oncommand="newWindow()"/>
  7
+  </commandset>
  8
+
  9
+  <menubar>
  10
+      <menu label="File">
  11
+      <menupopup>
  12
+        <menuitem label="New Window" command="new_window"/>
  13
+      </menupopup>
  14
+      </menu>
  15
+  </menubar>
  16
+  <browser id="content_browser" type="content" src="$LAUNCHPATH" flex="1"/>
  17
+</window>
9  addons/jetpack/data/native-install/XUL/defaults/preferences/prefs.js
... ...
@@ -0,0 +1,9 @@
  1
+pref("toolkit.defaultChromeURI", "chrome://webapp/content/main.xul");
  2
+pref("toolkit.singletonWindowType", true);
  3
+pref("extensions.logging.enabled", "true");
  4
+pref("dom.report_all_js_exceptions", "true");
  5
+pref("browser.dom.window.dump.enabled", true);
  6
+pref("javascript.options.showInConsole", true);
  7
+pref("javascript.options.strict", true);
  8
+pref("nglayout.debug.disable_xul_cache", true);
  9
+pref("nglayout.debug.disable_xul_fastload", true);
10  addons/jetpack/data/native-install/XUL/newapp_template.ini
... ...
@@ -0,0 +1,10 @@
  1
+[App]
  2
+Vendor=Mozilla
  3
+Name=$APPNAME
  4
+Version=1.0
  5
+BuildID=20110713
  6
+ID=webapp@$APPDOMAIN
  7
+
  8
+[Gecko]
  9
+MinVersion=2.0
  10
+MaxVersion=11.*
10  addons/jetpack/data/native-install/XUL/specialfiles.json
... ...
@@ -0,0 +1,10 @@
  1
+{
  2
+  "newapp_template.ini":
  3
+  {
  4
+    "rename": "application.ini"
  5
+  },
  6
+  "specialfiles.json":
  7
+  {
  8
+    "ignore": true
  9
+  }
  10
+}
74  addons/jetpack/data/native-install/mac/Contents/Info.plist
... ...
@@ -0,0 +1,74 @@
  1
+<?xml version="1.0" encoding="UTF-8"?>
  2
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
  3
+<plist version="1.0">
  4
+<dict>
  5
+	<key>CFBundleDisplayName</key>
  6
+	<string>$APPNAME</string>
  7
+	<key>CFBundleDevelopmentRegion</key>
  8
+	<string>English</string>
  9
+	<key>CFBundleExecutable</key>
  10
+	<string>$APPNAME</string>
  11
+	<key>CFBundleGetInfoString</key>
  12
+	<string>$APPNAME</string>
  13
+	<key>CFBundleIconFile</key>
  14
+	<string>appicon</string>
  15
+	<key>CFBundleIdentifier</key>
  16
+	<string>$REVERSED_APPDOMAIN</string>
  17
+	<key>CFBundleInfoDictionaryVersion</key>
  18
+	<string>6.0</string>
  19
+	<key>CFBundleName</key>
  20
+	<string>$APPNAME</string>
  21
+	<key>CFBundlePackageType</key>
  22
+	<string>APPL</string>
  23
+	<key>CFBundleShortVersionString</key>
  24
+	<string>1.0</string>
  25
+	<key>CFBundleSignature</key>
  26
+	<string>MOZB</string>
  27
+	<key>CFBundleURLTypes</key>
  28
+	<array>
  29
+		<dict>
  30
+			<key>CFBundleURLIconFile</key>
  31
+			<string>document.icns</string>
  32
+			<key>CFBundleURLName</key>
  33
+			<string>http URL</string>
  34
+			<key>CFBundleURLSchemes</key>
  35
+			<array>
  36
+				<string>http</string>
  37
+			</array>
  38
+		</dict>
  39
+		<dict>
  40
+			<key>CFBundleURLIconFile</key>
  41
+			<string>document.icns</string>
  42
+			<key>CFBundleURLName</key>
  43
+			<string>https URL</string>
  44
+			<key>CFBundleURLSchemes</key>
  45
+			<array>
  46
+				<string>https</string>
  47
+			</array>
  48
+		</dict>
  49
+		<dict>
  50
+			<key>CFBundleURLName</key>
  51
+			<string>file URL</string>
  52
+			<key>CFBundleURLSchemes</key>
  53
+			<array>
  54
+				<string>file</string>
  55
+			</array>
  56
+		</dict>
  57
+	</array>
  58
+	<key>CFBundleVersion</key>
  59
+	<string>1.0</string>
  60
+	<key>CGDisableCoalescedUpdates</key>
  61
+	<true/>
  62
+	<key>LSMinimumSystemVersion</key>
  63
+	<string>10.5</string>
  64
+	<key>LSMinimumSystemVersionByArchitecture</key>
  65
+	<dict>
  66
+		<key>i386</key>
  67
+		<string>10.5.0</string>
  68
+		<key>x86_64</key>
  69
+		<string>10.6.0</string>
  70
+	</dict>
  71
+	<key>NSAppleScriptEnabled</key>
  72
+	<true/>
  73
+</dict>
  74
+</plist>
BIN  addons/jetpack/data/native-install/mac/Contents/MacOS/foxlauncher
Binary file not shown
0  addons/jetpack/data/native-install/mac/Contents/Resources/empty
No changes.
13  addons/jetpack/data/native-install/mac/specialfiles.json
... ...
@@ -0,0 +1,13 @@
  1
+{
  2
+  "foxlauncher":
  3
+  {
  4
+    "rename": "$APPNAME",
  5
+    "mode": "b",
  6
+    "isExecutable": true,
  7
+    "substituteStrings": false
  8
+  },
  9
+  "specialfiles.json":
  10
+  {
  11
+    "ignore": true
  12
+  }
  13
+}
6  addons/jetpack/data/native-install/windows/app/specialfiles.json
... ...
@@ -0,0 +1,6 @@
  1
+{
  2
+  "specialfiles.json":
  3
+  {
  4
+    "ignore": true
  5
+  }
  6
+}
2  addons/jetpack/data/native-install/windows/app/uninstall.ini
... ...
@@ -0,0 +1,2 @@
  1
+[required]
  2
+appName=$APPNAME
10  addons/jetpack/data/native-install/windows/installer/install.ini
... ...
@@ -0,0 +1,10 @@
  1
+[required]
  2
+appName=$APPNAME
  3
+FFPath=$FFPATH
  4
+
  5
+[optional]
  6
+appURL=$LAUNCHPATH
  7
+appDesc=$APPDESC
  8
+iconPath=$ICONPATH
  9
+createDesktopShortcut=$DESKTOP_SHORTCUT
  10
+createStartMenuShortcut=$SM_SHORTCUT
12  addons/jetpack/data/native-install/windows/installer/specialfiles.json
... ...
@@ -0,0 +1,12 @@
  1
+{
  2
+  "install.exe":
  3
+  {
  4
+    "mode": "b",
  5
+    "isExecutable": true,
  6
+    "substituteStrings": false
  7
+  },
  8
+  "specialfiles.json":
  9
+  {
  10
+    "ignore": true
  11
+  }
  12
+}
2  addons/jetpack/data/widget.html
@@ -5,7 +5,7 @@
5 5
 </head>
6 6
 
7 7
 <body>
8  
-  <div style="width: 100%; font-family: arial; font-size: 14; cursor: pointer">
  8
+  <div style="width: 100%; font-family: arial; font-weight:bold; font-size: small; cursor: pointer">
9 9
     <img src="skin/toolbar-button.png" height="16" width="16" border="0" align="left" />
10 10
     <div style="-moz-user-select: none">
11 11
       Apps
26  addons/jetpack/lib/api.js
@@ -39,8 +39,6 @@
39 39
 const { Cc, Ci, Cu, Cr, components } = require("chrome");
40 40
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
41 41
 
42  
-var { TypedStorage } = require("typed_storage");
43  
-
44 42
 if (!console || !console.log) {
45 43
   var console = {
46 44
     log: function(s) {
@@ -51,6 +49,8 @@ if (!console || !console.log) {
51 49
 
52 50
 var { Manifest } = require("./manifest");
53 51
 var { URLParse } = require("./urlmatch");
  52
+var { NativeShell } = require("./nativeshell");
  53
+var { TypedStorage } = require("typed_storage");
54 54
 
55 55
 // We want to use as much from the cross-platform repo implementation
56 56
 // as possible, but we do need to override a few methods.
@@ -286,16 +286,18 @@ FFRepoImpl.prototype = {
286 286
           self._callWatchers("add", [app]);
287 287
         });
288 288
         // create OS-local application
289  
-/*
290  
-                    dump("APPS | jetpack.install | Getting app by URL now\n");
291  
-                    Repo.getAppById(origin, function(app) {
292  
-                        dump("APPS | jetpack.install | getAppByUrl returned " + app + "\n");
293  
-                        if (app) {
294  
-                          dump("APPS | jetpack.install | Calling NativeShell.CreateNativeShell\n");
295  
-                          NativeShell.CreateNativeShell(app);
296  
-                        }
297  
-                    });
298  
-                    */
  289
+        console.log("APPS | jetpack.install | Getting app by URL now\n");
  290
+        Repo.getAppById(origin, function(app) {
  291
+          console.log("APPS | jetpack.install | getAppByUrl returned " + app + "\n");
  292
+          if (app) {
  293
+            console.log("APPS | jetpack.install | Calling NativeShell.CreateNativeShell\n");
  294
+            try {
  295
+              NativeShell.CreateNativeShell(app);
  296
+            } catch (e) {
  297
+              console.log("APPS | NativeShell | Aborted: " + e);
  298
+            }
  299
+          }
  300
+        });
299 301
 
300 302
         if (args.onsuccess) {
301 303
           (1, args.onsuccess)();
28  addons/jetpack/lib/injector.js
@@ -41,12 +41,22 @@
41 41
 /* Inject the People content API into window.navigator objects. */
42 42
 /* Partly based on code in the Geode extension. */
43 43
 
44  
-const { Cc, Ci, Cu } = require("chrome");
45  
-Cu.import("resource://gre/modules/XPCOMUtils.jsm");
46  
-const xulApp = require("api-utils/xul-app");
  44
+var HAS_NAVIGATOR_INJECTOR;
  45
+if (typeof require !== "undefined") {
  46
+  var { Cc, Ci, Cu } = require("chrome");
  47
+  const xulApp = require("api-utils/xul-app");
  48
+  HAS_NAVIGATOR_INJECTOR = xulApp.versionInRange(xulApp.version, "9.0a2", "*");
  49
+} else {
  50
+  var Cc = Components.classes;
  51
+  var Ci = Components.interfaces;
  52
+  var Cu = Components.utils;
  53
+
  54
+  var xulAppInfo = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
  55
+  var comparator = Cc["@mozilla.org/xpcom/version-comparator;1"].getService(Ci.nsIVersionComparator);
  56
+  HAS_NAVIGATOR_INJECTOR = comparator.compare(xulAppInfo.version, "9.0a2");
  57
+}
47 58
 
48  
-const HAS_NAVIGATOR_INJECTOR =
49  
-        xulApp.versionInRange(xulApp.version, "9.0a2", "*");
  59
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
50 60
 
51 61
 /**
52 62
  * NavigatorInjector
@@ -170,9 +180,13 @@ NavigatorInjector.prototype = {
170 180
 }
171 181
 
172 182
 var gInjector;
173  
-exports.init = function() {
  183
+function init() {
174 184
   if (!HAS_NAVIGATOR_INJECTOR)
175 185
     gInjector = new NavigatorInjector();
176 186
 }
177 187
 
178  
-
  188
+if (typeof exports !== "undefined") {
  189
+  exports.init = init;
  190
+} else {
  191
+  EXPORTED_SYMBOLS = ["init"];
  192
+}
4  addons/jetpack/lib/main.js
@@ -477,6 +477,10 @@ function startup(getUrlCB) { /* Initialize simple storage */
477 477
   // We don't have an assertion from BrowserID, so let's ask the user to login
478 478
   setupLogin(service);
479 479
 
  480
+  // Setup socket server
  481
+  // TODO: Add stopServer method to unloaders
  482
+  require("socketserver").startServer();
  483
+
480 484
   // Broadcast that we're done, in case anybody is listening
481 485
   Services.obs.notifyObservers(tmp.FFRepoImplService, "openwebapps-startup-complete", "");
482 486
 
844  addons/jetpack/lib/nativeshell.js
... ...
@@ -0,0 +1,844 @@
  1
+/* -*- Mode: JavaScript; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
  2
+/* vim: set ts=2 et sw=2 tw=80: */
  3
+/* ***** BEGIN LICENSE BLOCK *****
  4
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
  5
+ *
  6
+ * The contents of this file are subject to the Mozilla Public License Version
  7
+ * 1.1 (the "License"); you may not use this file except in compliance with
  8
+ * the License. You may obtain a copy of the License at
  9
+ * http://www.mozilla.org/MPL/
  10
+ *
  11
+ * Software distributed under the License is distributed on an "AS IS" basis,
  12
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
  13
+ * for the specific language governing rights and limitations under the
  14
+ * License.
  15
+ *
  16
+ * The Original Code is Open Web Apps for Firefox.
  17
+ *
  18
+ * The Initial Developer of the Original Code is The Mozilla Foundation.
  19
+ * Portions created by the Initial Developer are Copyright (C) 2011
  20
+ * the Initial Developer. All Rights Reserved.
  21
+ *
  22
+ * Contributor(s):
  23
+ *     Michael Hanson <mhanson@mozilla.com>
  24
+ *     Anant Narayanan <anant@kix.in>
  25
+ *     Tim Abraldes <tabraldes@mozilla.com>
  26
+ *     Dan Walkowski <dwalkowski@mozilla.com>
  27
+ *
  28
+ * Alternatively, the contents of this file may be used under the terms of
  29
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
  30
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
  31
+ * in which case the provisions of the GPL or the LGPL are applicable instead
  32
+ * of those above. If you wish to allow use of your version of this file only
  33
+ * under the terms of either the GPL or the LGPL, and not to allow others to
  34
+ * use your version of this file under the terms of the MPL, indicate your
  35
+ * decision by deleting the provisions above and replace them with the notice
  36
+ * and other provisions required by the GPL or the LGPL. If you do not delete
  37
+ * the provisions above, a recipient may use your version of this file under
  38
+ * the terms of any one of the MPL, the GPL or the LGPL.
  39
+ *
  40
+ * ***** END LICENSE BLOCK ***** */
  41
+
  42
+const {components, Cc, Cu, Ci} = require("chrome");
  43
+const file = require("file");
  44
+const self = require("self");
  45
+const url = require("url");
  46
+
  47
+//used for several things
  48
+Components.utils.import("resource://gre/modules/NetUtil.jsm");  
  49
+Components.utils.import("resource://gre/modules/FileUtils.jsm");  
  50
+
  51
+NativeShell = (function() {
  52
+  function CreateNativeShell(app)
  53
+  {
  54
+    let os = Cc["@mozilla.org/xre/app-info;1"]
  55
+             .getService(Ci.nsIXULRuntime).OS;
  56
+    let nativeShell;
  57
+    if("WINNT" === os) {
  58
+      nativeShell = new WinNativeShell();
  59
+    } else if("Darwin" === os) {
  60
+      nativeShell = new MacNativeShell();
  61
+    }
  62
+    if(nativeShell) {
  63
+      nativeShell.createAppNativeLauncher(app);
  64
+    } else {
  65
+      console.log("APPS | CreateNativeShell | "
  66
+                  + "No OS-specific native shell could be created");
  67
+    }
  68
+  }
  69
+
  70
+  return {
  71
+    CreateNativeShell: CreateNativeShell
  72
+  }
  73
+})();
  74
+
  75
+function substituteStrings(inputString, substituteStrings)
  76
+{
  77
+  try {
  78
+    let working = inputString;
  79
+    for (let key in substituteStrings) {
  80
+      if(substituteStrings.hasOwnProperty(key)) {
  81
+        re = new RegExp(key, "gi");
  82
+        working = working.replace(re, substituteStrings[key]);
  83
+      }
  84
+    }
  85
+    return working;
  86
+  } catch(e) {
  87
+    throw("Failure in substituteStrings (" + e + ")");
  88
+  }
  89
+}
  90
+
  91
+function reverseDNS(domain)
  92
+{
  93
+  var d = domain.split(".");
  94
+  var s = "";
  95
+  for (var i=d.length-1;i--;i>=0)
  96
+  {
  97
+    if (s.length > 0) s += ".";
  98
+    s += d[i];
  99
+  }
  100
+  return s;
  101
+}
  102
+
  103
+function getBiggestIcon(app, callback) {
  104
+  let icon = 0;
  105
+  if (app.manifest.icons) {
  106
+    for (z in app.manifest.icons) {
  107
+      let size = parseInt(z, 10);
  108
+      if (size > icon) {
  109
+        icon = size;
  110
+      }
  111
+    }
  112
+  }
  113
+  if (icon === 0) {
  114
+    return null;
  115
+  } else {
  116
+    icon = app.manifest.icons[icon];
  117
+  }
  118
+
  119
+  if (icon.indexOf("data:") === 0) {
  120
+    let tIndex = icon.indexOf(";");
  121
+    mimeType = icon.substring(5, tIndex);
  122
+
  123
+    let base64 = icon.indexOf("base64,");
  124
+    if (base64 < 0) {
  125
+      throw("getBiggestIcon - "
  126
+            + "Found a data URL but it appears not to be base64 encoded");
  127
+    }
  128
+
  129
+    let binaryStream;
  130
+    try {
  131
+      let base64Data = icon.substring(base64 + 7);
  132
+
  133
+      const AppShellService =
  134
+            Cc["@mozilla.org/appshell/appShellService;1"]
  135
+            .getService(Ci.nsIAppShellService);
  136
+      let binaryData =
  137
+            AppShellService.hiddenDOMWindow.atob(String(base64Data));
  138
+      let stringStream = Cc["@mozilla.org/io/string-input-stream;1"]
  139
+                         .createInstance(Ci.nsIStringInputStream);
  140
+      stringStream.setData(binaryData, binaryData.length);
  141
+      binaryStream = Cc["@mozilla.org/binaryinputstream;1"]
  142
+                     .createInstance(Ci.nsIObjectInputStream);
  143
+      binaryStream.setInputStream(stringStream);
  144
+    } catch(e) {
  145
+      throw("getBiggestIcon - "
  146
+            + "Failure converting base64 data "
  147
+            + "(" + e + ")");
  148
+    }
  149
+
  150
+    try {
  151
+      callback(0, mimeType, binaryStream);
  152
+    } catch(e) {
  153
+      throw("getBiggestIcon - "
  154
+            + "Failure in callback "
  155
+            + "(" + e + ")");
  156
+    }
  157
+  } else {
  158
+    // TODO: Come up with a smarter way to determine MIME type
  159
+    try {
  160
+      if (icon.indexOf(".png") > 0) {
  161
+        mimeType = "image/png";
  162
+      } else if ((icon.indexOf(".jpeg") > 0)
  163
+                || (icon.indexOf(".jpg") > 0)) {
  164
+        mimeType = "image/jpeg";
  165
+      }
  166
+    } catch(e) {
  167
+      throw("getBiggestIcon - "
  168
+            + "Failure determining MIME type of " + icon
  169
+            + " (" + e + ")");
  170
+    }
  171
+
  172
+    let iconPath = app.origin + icon;
  173
+    NetUtil.asyncFetch(iconPath,
  174
+                       function(inputStream, resultCode, request) {
  175
+        try {
  176
+          callback(resultCode, mimeType, inputStream);
  177
+        } catch (e) {
  178
+          console.log("getBiggestIcon - "
  179
+            + "Failure in callback function"
  180
+            + " (" + e + ")");
  181
+        }
  182
+    });
  183
+  }
  184
+}
  185
+
  186
+function embedInstallRecord(app, destination) {
  187
+  //write the contents of the app (install record), json-ified, into
  188
+  //the specified file.
  189
+  let theDestination = destination.clone();
  190
+  theDestination.append("installrecord.json");
  191
+  try {
  192
+    let installRecString = JSON.stringify(app);
  193
+    writeFile(installRecString, theDestination.path);
  194
+  } catch (e) {
  195
+    console.log("error writing installrecord : " + e + "\n");
  196
+  }
  197
+}
  198
+
  199
+
  200
+//used to copy in the necessary js files to include so we can call the
  201
+//MozApps api to do browserID stuff.
  202
+// turns out that we only really need injector.js for now
  203
+//FUTURE: might it be possible to get a nice reference to /lib/injector.js
  204
+//using the same scheme as self.data?
  205
+function embedMozAppsAPIFiles(destDir)
  206
+{
  207
+  //find where the jetpack addon is, and where it is keeping the necessary
  208
+  //js files we need to copy into the native app
  209
+  var mozappsD = Cc["@mozilla.org/file/directory_service;1"]
  210
+                 .getService(Ci.nsIProperties)
  211
+                 .get("ProfD", Ci.nsIFile);
  212
+  mozappsD.append("extensions");
  213
+  mozappsD.append(self.id);
  214
+  mozappsD.append("resources");
  215
+  mozappsD.append("openwebapps-at-mozillalabs-dot-com-openwebapps-lib");
  216
+
  217
+  var injectorSrc = mozappsD.clone();
  218
+  injectorSrc.append("injector.js");
  219
+  var injectorDest = destDir.clone();
  220
+  injectorDest.append("injector.js");
  221
+
  222
+  copyFile(injectorSrc.path, injectorDest.path);
  223
+}
  224
+
  225
+function copyFile(srcFile, destFile, fileProperties, substitutions) {
  226
+  try {
  227
+    //open the source file and read in the contents
  228
+    var openProps = fileProperties?fileProperties["mode"]:"";
  229
+    let inputStream = file.open(srcFile, openProps);
  230
+    let fileContents = inputStream.read();
  231
+    inputStream.close();
  232
+
  233
+    writeFile(fileContents, destFile, fileProperties, substitutions);
  234
+
  235
+  } catch(e) {
  236
+    throw("copyFile - "
  237
+        + "Failed copying file from "
  238
+        + srcFile
  239
+        + " to "
  240
+        + destFile
  241
+        + " (" + e + ")");
  242
+  }
  243
+}
  244
+
  245
+/* TODO: Convert all file operations to be async */
  246
+function writeFile(fileContents, destFile, fileProperties, substitutions) {
  247
+  try {
  248
+    var openProps = fileProperties?fileProperties["mode"]:"";
  249
+    //do string substitutions if necessary
  250
+    let finalContents;
  251
+    if(fileProperties && fileProperties["substituteStrings"]) {
  252
+      finalContents = substituteStrings(fileContents, substitutions);
  253
+    } else {
  254
+      finalContents = fileContents;
  255
+    }
  256
+    //write out the (possibly altered) file to the new location
  257
+    let outputStream = file.open(destFile, "w" + openProps);
  258
+    outputStream.write(finalContents);
  259
+    outputStream.close();
  260
+
  261
+  } catch(e) {
  262
+    throw("writeFile - "
  263
+        + "Failed writing file to "
  264
+        + destFile
  265
+        + " (" + e + ")");
  266
+  }
  267
+}
  268
+
  269
+function recursiveFileCopy(srcDir,
  270
+                           leaf,
  271
+                           dstDir,
  272
+                           separator,
  273
+                           substitutions,
  274
+                           specialFiles)
  275
+{
  276
+  if(!specialFiles) {
  277
+    specialFiles = {};
  278
+  }
  279
+  try {
  280
+    let srcCompletePath = srcDir;
  281
+    var dest = dstDir;
  282
+    if(leaf) {
  283
+      srcCompletePath += "/" + leaf;
  284
+      dest += separator + leaf;
  285
+    }
  286
+    let srcURL = self.data.url(srcCompletePath);
  287
+    var srcFile = url.toFilename(srcURL);
  288
+    //console.log(srcFile);
  289
+  } catch(e) {
  290
+    throw("recursiveFileCopy - "
  291
+          + "Failure while setting up paths (" + e +")");
  292
+  }
  293
+
  294
+  try {
  295
+    var fileExists = file.exists(srcFile);
  296
+    if(fileExists) {
  297
+      // file doesn't expose an isDirectory function yet
  298
+      // so we negate file.isFile
  299
+      var isDir = !file.isFile(srcFile);
  300
+    }
  301
+  } catch(e) {
  302
+    throw("recursiveFileCopy - "
  303
+          + "Failure obtaining information about file "
  304
+          + srcFile
  305
+          + " (" + e + ")");
  306
+  }
  307
+
  308
+  if(!fileExists) {
  309
+    throw("recursiveFileCopy - "
  310
+          + "Tried to copy file but source file doesn't exist ("
  311
+          + srcFile + ")");
  312
+  }
  313
+
  314
+  if (isDir)
  315
+  {
  316
+    let newSpecialFiles = Object.create(specialFiles);
  317
+    try {
  318
+      var dirContents = file.list(srcFile);
  319
+      file.mkpath(dest);
  320
+    } catch(e) {
  321
+      throw("recursiveFileCopy - "
  322
+            + "Failure setting up directory copy from "
  323
+            + srcFile
  324
+            + " to "
  325
+            + dest
  326
+            + " (" + e + ")");
  327
+    }
  328
+
  329
+    let manifestPath = srcFile + separator + "specialfiles.json";
  330
+    try {
  331
+      if(file.exists(manifestPath)) {
  332
+        let manifestStream = file.open(manifestPath);
  333
+        let fileContents = manifestStream.read();
  334
+        manifestStream.close();
  335
+
  336
+        let parsedManifest = JSON.parse(fileContents);
  337
+        for(let specialFile in parsedManifest) {
  338
+          if(parsedManifest.hasOwnProperty(specialFile)) {
  339
+            if(!(specialFile in newSpecialFiles)) {
  340
+              newSpecialFiles[specialFile] = {};
  341
+            }
  342
+            for(let property in parsedManifest[specialFile]) {
  343
+              if(parsedManifest[specialFile].hasOwnProperty(property)) {
  344
+                newSpecialFiles[specialFile][property] =
  345
+                                    parsedManifest[specialFile][property];
  346
+              }
  347
+            }
  348
+          }
  349
+        }
  350
+      }
  351
+    } catch(e) {
  352
+      throw("Failure reading/parsing specialFiles manifest "
  353
+            + manifestPath
  354
+            + " (" + e + ")");
  355
+    }
  356
+
  357
+    for (let i=0; i < dirContents.length; i++)
  358
+    {
  359
+      recursiveFileCopy(
  360
+                        // Use "/" instead of separator; this is a URI
  361
+                        srcDir + "/" + leaf,
  362
+                        dirContents[i],
  363
+                        dest,
  364
+                        separator,
  365
+                        substitutions,
  366
+                        newSpecialFiles);
  367
+    }
  368
+  } else {
  369
+    let fileProperties = {"ignore": false,
  370
+                          "rename": leaf,
  371
+                          "isExecutable": false,
  372
+                          "mode": "",
  373
+                          "substituteStrings": true};