Bug 997886 - Test installing and updating apps with asm.js pre-compiling. r=fabrice
authorMarco Castelluccio <mar.castelluccio@studenti.unina.it>
Sat, 26 Apr 2014 03:37:00 -0400
changeset 198990 b50bcf913c9b249b3aa8fcd82d70be65176f9bef
parent 198989 9d04bde9fcd105ca4a5d676b7e7ce60e12d44f86
child 198991 fefcd48d83131da9be48603fc1e5eecfe8f65bce
push id3624
push userasasaki@mozilla.com
push dateMon, 09 Jun 2014 21:49:01 +0000
treeherdermozilla-beta@b1a5da15899a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersfabrice
bugs997886
milestone31.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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
@@ -1386,17 +1386,17 @@ this.DOMApplicationRegistry = {
 
     let json = yield AppsUtils.loadJSONAsync(file.path);
     if (!json) {
       debug("startDownload: No update manifest found at " + file.path + " " +
             aManifestURL);
       return;
     }
 
-    let manifest = new ManifestHelper(json, app.installOrigin);
+    let manifest = new ManifestHelper(json, app.manifestURL);
     let [aId, aManifest] = yield this.downloadPackage(manifest, {
         manifestURL: aManifestURL,
         origin: app.origin,
         installOrigin: app.installOrigin,
         downloadSize: app.downloadSize
       }, isUpdate);
 
     // Success! Keep the zip in of TmpD, we'll move it out when
@@ -1689,17 +1689,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>