Bug 997886 - Test installing and updating apps with asm.js pre-compiling. r=fabrice
☠☠ backed out by 78b0aaa91320 ☠ ☠
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Mon, 21 Apr 2014 10:58:19 -0400
changeset 179805 d525f195556dedaaa0572e84995047def5461cbf
parent 179804 994095bfc2e8ef759ef6ae63e82f216ea02f3a44
child 179806 aaf8037b9e0f2ffdec6ae5ecbbe49be06cdc7ca0
push id272
push userpvanderbeken@mozilla.com
push dateMon, 05 May 2014 16:31:18 +0000
reviewersfabrice
bugs997886
milestone31.0a1
Bug 997886 - Test installing and updating apps with asm.js pre-compiling. r=fabrice
dom/apps/src/Webapps.jsm
dom/apps/tests/asmjs/asmjs_app.sjs
dom/apps/tests/asmjs/index.html
dom/apps/tests/asmjs/manifest.webapp
dom/apps/tests/chrome.ini
dom/apps/tests/test_packaged_app_asmjs.html
--- a/dom/apps/src/Webapps.jsm
+++ b/dom/apps/src/Webapps.jsm
@@ -1384,17 +1384,17 @@ this.DOMApplicationRegistry = {
 
     AppsUtils.loadJSONAsync(file.path).then((aJSON) => {
       if (!aJSON) {
         debug("startDownload: No update manifest found at " + file.path + " " +
               aManifestURL);
         return;
       }
 
-      let manifest = new ManifestHelper(aJSON, app.installOrigin);
+      let manifest = new ManifestHelper(aJSON, app.manifestURL);
       this.downloadPackage(manifest, {
           manifestURL: aManifestURL,
           origin: app.origin,
           installOrigin: app.installOrigin,
           downloadSize: app.downloadSize
         }, isUpdate).then(function([aId, aManifest]) {
           // Success! Keep the zip in of TmpD, we'll move it out when
           // applyDownload() will be called.
@@ -1687,17 +1687,17 @@ this.DOMApplicationRegistry = {
                 });
               });
             } else {
               aData.error = "NOT_UPDATABLE";
               aMm.sendAsyncMessage("Webapps:CheckForUpdate:Return:KO", aData);
             }
           }
         };
-        let helper = new ManifestHelper(manifest);
+        let helper = new ManifestHelper(manifest, aData.manifestURL);
         debug("onlyCheckAppCache - launch updateSvc.checkForUpdate for " +
               helper.fullAppcachePath());
         updateSvc.checkForUpdate(Services.io.newURI(helper.fullAppcachePath(), null, null),
                                  app.localId, false, updateObserver);
       });
       return;
     }
 
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/asmjs/asmjs_app.sjs
@@ -0,0 +1,143 @@
+var Cc = Components.classes;
+var Ci = Components.interfaces;
+var Cu = Components.utils;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/NetUtil.jsm");
+
+function handleRequest(aRequest, aResponse) {
+  aResponse.setHeader("Access-Control-Allow-Origin", "*", false);
+
+  var query = getQuery(aRequest);
+
+  if ("setVersion" in query) {
+    setState("version", query.setVersion);
+    aResponse.write("OK");
+    return;
+  }
+  var version = Number(getState("version"));
+
+  // setPrecompile sets the "precompile" field in the app manifest
+  if ("setPrecompile" in query) {
+    setState("precompile", query.setPrecompile);
+    aResponse.write("OK");
+    return;
+  }
+  var precompile = getState("precompile");
+
+  if ("getPackage" in query) {
+    aResponse.setHeader("Content-Type", "application/zip", false);
+    aResponse.write(buildAppPackage(version, precompile));
+    return;
+  }
+
+  if ("getManifest" in query) {
+    aResponse.setHeader("Content-Type", "application/x-web-app-manifest+json", false);
+    aResponse.write(getManifest(version, precompile));
+    return;
+  }
+}
+
+function getQuery(aRequest) {
+  var query = {};
+  aRequest.queryString.split('&').forEach(function(val) {
+    var [name, value] = val.split('=');
+    query[decodeURIComponent(name)] = decodeURIComponent(value);
+  });
+  return query;
+}
+
+function getTestFile(aName) {
+  var file = Services.dirsvc.get("CurWorkD", Ci.nsIFile);
+
+  var path = "chrome/dom/apps/tests/asmjs/" + aName;
+
+  path.split("/").forEach(function(component) {
+    file.append(component);
+  });
+
+  return file;
+}
+
+function readFile(aFile) {
+  var fstream = Cc["@mozilla.org/network/file-input-stream;1"].
+                createInstance(Ci.nsIFileInputStream);
+  fstream.init(aFile, -1, 0, 0);
+  var data = NetUtil.readInputStreamToString(fstream, fstream.available());
+  fstream.close();
+  return data;
+}
+
+function getManifest(aVersion, aPrecompile) {
+  return readFile(getTestFile("manifest.webapp")).
+           replace(/VERSIONTOKEN/g, aVersion).
+           replace(/PRECOMPILETOKEN/g, aPrecompile);
+}
+
+function buildAppPackage(aVersion, aPrecompile) {
+  const PR_RDWR        = 0x04;
+  const PR_CREATE_FILE = 0x08;
+  const PR_TRUNCATE    = 0x20;
+
+  let zipFile = Services.dirsvc.get("TmpD", Ci.nsIFile);
+  zipFile.append("application.zip");
+
+  let zipWriter = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter);
+  zipWriter.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+
+  // Add index.html file to the zip file
+  zipWriter.addEntryFile("index.html",
+                         Ci.nsIZipWriter.COMPRESSION_NONE,
+                         getTestFile("index.html"),
+                         false);
+
+  // Add manifest to the zip file
+  var manStream = Cc["@mozilla.org/io/string-input-stream;1"].
+                  createInstance(Ci.nsIStringInputStream);
+  var manifest = getManifest(aVersion, aPrecompile);
+  manStream.setData(manifest, manifest.length);
+  zipWriter.addEntryStream("manifest.webapp", Date.now(),
+                           Ci.nsIZipWriter.COMPRESSION_NONE,
+                           manStream, false);
+
+  // Add an asm.js module to the zip file
+  // The module also contains some code to perform tests
+
+  // Creates a module big enough to be cached
+  // The module is different according to the app version, this way
+  // we can test that on update the new module is compiled
+  var code = "function f() { 'use asm';\n";
+  for (var i = 0; i < 5000; i++) {
+    code += "function g" + i + "() { return " + i + "}\n";
+  }
+  code += "return g" + aVersion + " }\n\
+var jsFuns = SpecialPowers.Cu.getJSTestingFunctions();\n\
+ok(jsFuns.isAsmJSCompilationAvailable(), 'asm.js compilation is available');\n\
+ok(jsFuns.isAsmJSModule(f), 'f is an asm.js module');\n\
+var g" + aVersion + " = f();\n\
+ok(jsFuns.isAsmJSFunction(g" + aVersion + "), 'g" + aVersion + " is an asm.js function');\n\
+ok(g" + aVersion + "() === " + aVersion + ", 'g" + aVersion + " returns the correct result');";
+
+  // If the "precompile" field contains this module, we test that it is loaded
+  // from the cache.
+  // If the "precompile" field doesn't contain the module or is incorrect, we
+  // test that the module is not loaded from the cache.
+  if (aPrecompile == '["asmjsmodule.js"]') {
+    code += "ok(jsFuns.isAsmJSModuleLoadedFromCache(f), 'module loaded from cache');\n";
+  } else {
+    code += "ok(!jsFuns.isAsmJSModuleLoadedFromCache(f), 'module not loaded from cache');\n";
+  }
+
+  code += "alert('DONE');";
+
+  var jsStream = Cc["@mozilla.org/io/string-input-stream;1"].
+                 createInstance(Ci.nsIStringInputStream);
+  jsStream.setData(code, code.length);
+  zipWriter.addEntryStream("asmjsmodule.js", Date.now(),
+                           Ci.nsIZipWriter.COMPRESSION_NONE,
+                           jsStream, false);
+
+  zipWriter.close();
+
+  return readFile(zipFile);
+}
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/asmjs/index.html
@@ -0,0 +1,21 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <meta charset="utf-8">
+  <title>Asm.js test helper</title>
+</head>
+<body>
+<script src="asmjsmodule.js" async></script>
+<script>
+
+function ok(aCondition, aMessage) {
+  if (aCondition) {
+    alert("OK: " + aMessage);
+  } else {
+    alert("ERROR: " + aMessage);
+  }
+}
+
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/asmjs/manifest.webapp
@@ -0,0 +1,13 @@
+{
+  "name" : "Asm.js test app",
+  "version" : "VERSIONTOKEN",
+  "package_path": "asmjs_app.sjs?getPackage=true",
+  "description": "Asm.js test app",
+  "launch_path": "/index.html",
+  "developer": {
+    "name": "marco",
+    "url": "http://www.example.com/"
+  },
+  "default_locale": "en-US",
+  "precompile": PRECOMPILETOKEN
+}
--- a/dom/apps/tests/chrome.ini
+++ b/dom/apps/tests/chrome.ini
@@ -1,7 +1,10 @@
 [DEFAULT]
+support-files =
+  asmjs/*
 
 [test_apps_service.xul]
 [test_operator_app_install.js]
 [test_operator_app_install.xul]
 # bug 928262
  skip-if = os == "win"
+[test_packaged_app_asmjs.html]
new file mode 100644
--- /dev/null
+++ b/dom/apps/tests/test_packaged_app_asmjs.html
@@ -0,0 +1,237 @@
+<!DOCTYPE HTML>
+<html>
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=997886
+-->
+<head>
+  <meta charset="utf-8">
+  <title>Test for Bug 997886 - Test installing and updating apps with asm.js pre-compiling</title>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
+  <script type="application/javascript"
+          src="chrome://mochikit/content/chrome-harness.js"></script>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
+  <script type="application/javascript;version=1.7">
+
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+SimpleTest.waitForExplicitFinish();
+
+const gBaseURL = 'http://test/chrome/dom/apps/tests/asmjs/';
+const gSJS = gBaseURL + 'asmjs_app.sjs';
+const gManifestURL = gSJS + '?getManifest=true';
+let gGenerator = runTest();
+
+// Mock WebappOSUtils
+Cu.import("resource://gre/modules/WebappOSUtils.jsm");
+let oldWebappOSUtils = WebappOSUtils;
+WebappOSUtils.getPackagePath = function(aApp) {
+  return aApp.basePath + "/" + aApp.id;
+}
+
+// Enable the ScriptPreloader module
+Cu.import("resource://gre/modules/ScriptPreloader.jsm");
+let oldScriptPreloaderEnabled = ScriptPreloader._enabled;
+ScriptPreloader._enabled = true;
+
+SimpleTest.registerCleanupFunction(() => {
+  WebappOSUtils = oldWebappOSUtils;
+  ScriptPreloader._enabled = oldScriptPreloaderEnabled;
+});
+
+function go() {
+  gGenerator.next();
+}
+
+function continueTest() {
+  try {
+    gGenerator.next();
+  } catch (e if e instanceof StopIteration) {
+    SimpleTest.finish();
+  }
+}
+
+function mozAppsError() {
+  ok(false, "mozApps error: " + this.error.name);
+  SimpleTest.finish();
+}
+
+function xhrError(aEvent, aURL) {
+  var xhr = aEvent.target;
+  ok(false, "XHR error loading " + aURL + ": " + xhr.status + " - " +
+            xhr.statusText);
+  SimpleTest.finish();
+}
+
+function xhrAbort(aURL) {
+  ok(false, "XHR abort loading " + aURL);
+  SimpleTest.finish();
+}
+
+function setState(aQuery, aVersion, aCallback) {
+  var xhr = new XMLHttpRequest();
+  var url = gSJS + '?' + aQuery + '=' + aVersion;
+  xhr.addEventListener("load", function() {
+    is(xhr.responseText, "OK", aQuery + " OK");
+    aCallback();
+  });
+  xhr.addEventListener("error", event => xhrError(event, url));
+  xhr.addEventListener("abort", event => xhrAbort(url));
+  xhr.open('GET', url, true);
+  xhr.send();
+}
+
+function runApp(aApp, aCallback) {
+  let domParent = document.getElementById('container');
+
+  let ifr = document.createElement('iframe');
+  ifr.setAttribute('mozbrowser', 'true');
+  ifr.setAttribute('mozapp', gManifestURL);
+  ifr.src = aApp.origin + aApp.manifest.launch_path;
+
+  ifr.addEventListener('mozbrowsershowmodalprompt', function onAlert(e) {
+    var message = e.detail.message;
+
+    if (message.startsWith("OK: ")) {
+      ok(true, message.substring(4, message.length));
+    } else if (message.startsWith("ERROR: ")) {
+      ok(false, message.substring(7, message.length));
+    } else if (message == "DONE") {
+      ifr.removeEventListener('mozbrowsershowmodalprompt', onAlert, false);
+      domParent.removeChild(ifr);
+      aCallback();
+    }
+  }, false);
+
+  domParent.appendChild(ifr);
+}
+
+function testNoPrecompile(aPrecompile, aCallback) {
+  setState("setPrecompile", aPrecompile, function() {
+    let request = navigator.mozApps.installPackage(gManifestURL);
+    request.onerror = mozAppsError;
+    request.onsuccess = function() {
+      let app = request.result;
+      ok(app, "App is non-null");
+
+      app.ondownloaderror = mozAppsError;
+      app.ondownloadapplied = function() {
+        runApp(app, function() {
+          request = navigator.mozApps.mgmt.uninstall(app);
+          request.onerror = mozAppsError;
+          request.onsuccess = function() {
+            app.ondownloadapplied = null;
+            aCallback();
+          };
+        });
+      };
+    };
+  });
+}
+
+function runTest() {
+  // Set up.
+
+  SpecialPowers.setAllAppsLaunchable(true);
+  SpecialPowers.pushPrefEnv({'set': [["dom.mozBrowserFramesEnabled", true]]},
+                            continueTest);
+  yield undefined;
+
+  SpecialPowers.autoConfirmAppInstall(continueTest);
+  yield undefined;
+
+  setState("setVersion", 1, continueTest);
+  yield undefined;
+
+  // Test apps with wrong precompile fields
+  info("Test with an empty array");
+  testNoPrecompile('[]', continueTest);
+  yield undefined;
+  info("Test with an object");
+  testNoPrecompile('{}', continueTest);
+  yield undefined;
+  info("Test with a string");
+  testNoPrecompile('"asmjsmodule"', continueTest);
+  yield undefined;
+  info("Test with a file that doesn't exist");
+  testNoPrecompile('["file.js"]', continueTest);
+  yield undefined;
+
+  setState("setPrecompile", '["asmjsmodule.js"]', continueTest);
+  yield undefined;
+
+  let request = navigator.mozApps.installPackage(gManifestURL);
+  request.onerror = mozAppsError;
+  request.onsuccess = continueTest;
+  yield undefined;
+  let app = request.result;
+  ok(app, "App is non-null");
+  app.ondownloaderror = mozAppsError;
+  app.ondownloadsuccess = continueTest;
+  app.ondownloadapplied = continueTest;
+  yield undefined;
+  yield undefined;
+
+  runApp(app, continueTest);
+  yield undefined;
+
+  // Update app version
+  setState("setVersion", 2, continueTest);
+  yield undefined;
+
+  // Check for updates
+  lastCheck = app.lastUpdateCheck;
+  app.ondownloadavailable = function() {
+    ok(true, "downloadavailable fired");
+    continueTest();
+  }
+  request = app.checkForUpdate();
+  request.onerror = mozAppsError;
+  request.onsuccess = function() {
+    ok(true, "checkForUpdate success");
+    continueTest();
+  }
+  yield undefined;
+  yield undefined;
+
+  // Check that there's a download available and download it
+  ok(app.downloadAvailable, "Download available");
+  app.ondownloaderror = mozAppsError;
+  app.ondownloadsuccess = function() {
+    ok(true, "downloadsuccess fired");
+    continueTest();
+  }
+  app.download();
+  yield undefined;
+
+  // Apply downloaded update
+  ok(app.readyToApplyDownload, "App ready to apply download");
+  app.ondownloaderror = mozAppsError;
+  app.ondownloadapplied = function() {
+    ok(true, "downloadapplied fired");
+    continueTest();
+  }
+  navigator.mozApps.mgmt.applyDownload(app);
+  yield undefined;
+
+  runApp(app, continueTest);
+  yield undefined;
+
+  // Uninstall the app.
+  request = navigator.mozApps.mgmt.uninstall(app);
+  request.onerror = mozAppsError;
+  request.onsuccess = continueTest;
+  yield undefined;
+}
+
+  </script>
+</head>
+<body onload="go()">
+<p id="display"></p>
+<div id="content" style="display: none">
+</div>
+<pre id="test">
+</pre>
+<div id="container"></div>
+</body>
+</html>