Bug 920478 - Print errors when something is wrong with app launch path. r=paul
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 17 Oct 2013 14:27:04 -0400
changeset 165212 894025edbb7ba4e478e0386612d639102e619837
parent 165211 5a315b0b917dc9412fb8754752793c0b62f71b17
child 165213 f0208c342805e4b88aabf142bd97c6d8a6696596
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaul
bugs920478
milestone27.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 920478 - Print errors when something is wrong with app launch path. r=paul
browser/devtools/app-manager/app-validator.js
browser/devtools/app-manager/test/chrome.ini
browser/devtools/app-manager/test/test_app_validator.html
browser/devtools/app-manager/test/validator/non-absolute-path/manifest.webapp
browser/devtools/app-manager/test/validator/valid/home.html
browser/devtools/app-manager/test/validator/valid/icon.png
browser/devtools/app-manager/test/validator/valid/manifest.webapp
browser/devtools/app-manager/test/validator/wrong-launch-path/icon.png
browser/devtools/app-manager/test/validator/wrong-launch-path/manifest.webapp
browser/locales/en-US/chrome/browser/devtools/app-manager.properties
--- a/browser/devtools/app-manager/app-validator.js
+++ b/browser/devtools/app-manager/app-validator.js
@@ -47,16 +47,17 @@ AppValidator.prototype._getPackagedManif
   if (!manifestFile) {
     return null;
   }
   return Services.io.newFileURI(manifestFile).spec;
 };
 
 AppValidator.prototype._fetchManifest = function (manifestURL) {
   let deferred = promise.defer();
+  this.manifestURL = manifestURL;
 
   let req = new XMLHttpRequest();
   try {
     req.open("GET", manifestURL, true);
   } catch(e) {
     this.error(strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1));
     deferred.resolve(null);
     return deferred.promise;
@@ -115,16 +116,78 @@ AppValidator.prototype.validateManifest 
 
   if (!manifest.icons || Object.keys(manifest.icons).length == 0) {
     this.warning(strings.GetStringFromName("validator.missIconsManifestProperty"));
   } else if (!manifest.icons["128"]) {
     this.warning(strings.GetStringFromName("validator.missIconMarketplace"));
   }
 }
 
+AppValidator.prototype._getOriginURL = function (manifest) {
+  if (this.project.type == "packaged") {
+    let manifestURL = Services.io.newURI(this.manifestURL, null, null);
+    return Services.io.newURI(".", null, manifestURL).spec;
+  } else if (this.project.type == "hosted") {
+    return Services.io.newURI(this.project.location, null, null).prePath;
+  }
+}
+
+AppValidator.prototype.validateLaunchPath = function (manifest) {
+  let deferred = promise.defer();
+  // The launch_path field has to start with a `/`
+  if (manifest.launch_path && manifest.launch_path[0] !== "/") {
+    this.error(strings.formatStringFromName("validator.nonAbsoluteLaunchPath", [manifest.launch_path], 1));
+    deferred.resolve();
+    return deferred.promise;
+  }
+  let origin = this._getOriginURL();
+  let path;
+  if (this.project.type == "packaged") {
+    path = "." + ( manifest.launch_path || "/index.html" );
+  } else if (this.project.type == "hosted") {
+    path = manifest.launch_path || "/";
+  }
+  let indexURL;
+  try {
+    indexURL = Services.io.newURI(path, null, Services.io.newURI(origin, null, null)).spec;
+  } catch(e) {
+    this.error(strings.formatStringFromName("validator.invalidLaunchPath", [origin + path], 1));
+    deferred.resolve();
+    return deferred.promise;
+  }
+
+  let req = new XMLHttpRequest();
+  try {
+    req.open("HEAD", indexURL, true);
+  } catch(e) {
+    this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1));
+    deferred.resolve();
+    return deferred.promise;
+  }
+  req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
+  req.onload = () => {
+    if (req.status >= 400)
+      this.error(strings.formatStringFromName("validator.invalidLaunchPathBadHttpCode", [indexURL, req.status], 2));
+    deferred.resolve();
+  };
+  req.onerror = () => {
+    this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1));
+    deferred.resolve();
+  };
+
+  try {
+    req.send(null);
+  } catch(e) {
+    this.error(strings.formatStringFromName("validator.invalidLaunchPath", [indexURL], 1));
+    deferred.resolve();
+  }
+
+  return deferred.promise;
+}
+
 AppValidator.prototype.validateType = function (manifest) {
   let appType = manifest.type || "web";
   if (["web", "privileged", "certified"].indexOf(appType) === -1) {
     this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1));
   } else if (this.project.type == "hosted" &&
              ["certified", "privileged"].indexOf(appType) !== -1) {
     this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1));
   }
@@ -139,14 +202,15 @@ AppValidator.prototype.validate = functi
   this.errors = [];
   this.warnings = [];
   return this._getManifest().
     then((function (manifest) {
       if (manifest) {
         this.manifest = manifest;
         this.validateManifest(manifest);
         this.validateType(manifest);
+        return this.validateLaunchPath(manifest);
       }
     }).bind(this));
 }
 
 exports.AppValidator = AppValidator;
 
--- a/browser/devtools/app-manager/test/chrome.ini
+++ b/browser/devtools/app-manager/test/chrome.ini
@@ -1,8 +1,11 @@
 [DEFAULT]
-support-files = hosted_app.manifest
+support-files =
+  hosted_app.manifest
+  validator/*
 
 [test_connection_store.html]
 [test_device_store.html]
 [test_projects_store.html]
 [test_remain_connected.html]
 [test_template.html]
+[test_app_validator.html]
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/test_app_validator.html
@@ -0,0 +1,148 @@
+<!DOCTYPE html>
+
+<html>
+
+  <head>
+    <meta charset="utf8">
+    <title></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">
+  </head>
+
+  <body>
+
+    <script type="application/javascript;version=1.8">
+    const Cu = Components.utils;
+    const Cc = Components.classes;
+    const Ci = Components.interfaces;
+    Cu.import("resource://testing-common/httpd.js");
+    const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
+    const {require} = devtools;
+
+    const {AppValidator} = require("devtools/app-manager/app-validator");
+    const {Services} = Cu.import("resource://gre/modules/Services.jsm");
+    const nsFile = Components.Constructor("@mozilla.org/file/local;1",
+                                           "nsILocalFile", "initWithPath");
+    const cr = Cc["@mozilla.org/chrome/chrome-registry;1"]
+                 .getService(Ci.nsIChromeRegistry);
+    const strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
+    let httpserver, origin;
+
+    window.onload = function() {
+      SimpleTest.waitForExplicitFinish();
+
+      httpserver = new HttpServer();
+      httpserver.start(-1);
+      origin = "http://localhost:" + httpserver.identity.primaryPort + "/";
+
+      next();
+    }
+
+    function createHosted(path) {
+      let dirPath = getTestFilePath("validator/" + path);
+      httpserver.registerDirectory("/", nsFile(dirPath));
+      return new AppValidator({
+        type: "hosted",
+        location: origin + "/manifest.webapp"
+      });
+    }
+
+    function createPackaged(path) {
+      let dirPath = getTestFilePath("validator/" + path);
+      return new AppValidator({
+        type: "packaged",
+        location: dirPath
+      });
+    }
+
+    function next() {
+      let test = tests.shift();
+      if (test) {
+        try {
+          test();
+        } catch(e) {
+          console.error("exception", String(e), e, e.stack);
+        }
+      } else {
+        httpserver.stop(function() {
+          SimpleTest.finish();
+        });
+      }
+    }
+
+    let tests =  [
+      // Test a 100% valid example
+      function () {
+        let validator = createHosted("valid");
+        validator.validate().then(() => {
+            is(validator.errors.length, 0, "valid app got no error");
+            is(validator.warnings.length, 0, "valid app got no warning");
+
+            next();
+          });
+      },
+
+      function () {
+        let validator = createPackaged("valid");
+        validator.validate().then(() => {
+            is(validator.errors.length, 0, "valid packaged app got no error");
+            is(validator.warnings.length, 0, "valid packaged app got no warning");
+
+            next();
+          });
+      },
+
+      // Test a launch path that returns a 404
+      function () {
+        let validator = createHosted("wrong-launch-path");
+        validator.validate().then(() => {
+            is(validator.errors.length, 1, "app with non-existant launch path got an error");
+            is(validator.errors[0], strings.formatStringFromName("validator.invalidLaunchPathBadHttpCode", [origin + "wrong-path.html", 404], 2),
+               "with the right error message");
+            is(validator.warnings.length, 0, "but no warning");
+            next();
+          });
+      },
+      function () {
+        let validator = createPackaged("wrong-launch-path");
+        validator.validate().then(() => {
+            is(validator.errors.length, 1, "app with wrong path got an error");
+            let file = nsFile(validator.project.location);
+            file.append("wrong-path.html");
+            let url = Services.io.newFileURI(file);
+            is(validator.errors[0], strings.formatStringFromName("validator.invalidLaunchPath", [url.spec], 1),
+               "with the expected message");
+            is(validator.warnings.length, 0, "but no warning");
+
+            next();
+          });
+      },
+
+      // Test when using a non-absolute path for launch_path
+      function () {
+        let validator = createHosted("non-absolute-path");
+        validator.validate().then(() => {
+            is(validator.errors.length, 1, "app with non absolute path got an error");
+            is(validator.errors[0], strings.formatStringFromName("validator.nonAbsoluteLaunchPath", ["non-absolute.html"], 1),
+               "with expected message");
+            is(validator.warnings.length, 0, "but no warning");
+            next();
+          });
+      },
+      function () {
+        let validator = createPackaged("non-absolute-path");
+        validator.validate().then(() => {
+            is(validator.errors.length, 1, "app with non absolute path got an error");
+            is(validator.errors[0], strings.formatStringFromName("validator.nonAbsoluteLaunchPath", ["non-absolute.html"], 1),
+               "with expected message");
+            is(validator.warnings.length, 0, "but no warning");
+            next();
+          });
+      },
+    ];
+
+    </script>
+  </body>
+</html>
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/validator/non-absolute-path/manifest.webapp
@@ -0,0 +1,7 @@
+{
+  "name": "non-absolute path",
+  "icons": {
+    "128": "/icon.png"
+  },
+  "launch_path": "non-absolute.html"
+}
new file mode 100644
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/validator/valid/manifest.webapp
@@ -0,0 +1,7 @@
+{
+  "name": "valid",
+  "launch_path": "/home.html",
+  "icons": {
+    "128": "/icon.png"
+  }
+}
new file mode 100644
new file mode 100644
--- /dev/null
+++ b/browser/devtools/app-manager/test/validator/wrong-launch-path/manifest.webapp
@@ -0,0 +1,7 @@
+{
+  "name": "valid",
+  "launch_path": "/wrong-path.html",
+  "icons": {
+    "128": "/icon.png"
+  }
+}
--- a/browser/locales/en-US/chrome/browser/devtools/app-manager.properties
+++ b/browser/locales/en-US/chrome/browser/devtools/app-manager.properties
@@ -28,8 +28,14 @@ validator.invalidProjectType=Unknown pro
 # LOCALIZATION NOTE (validator.missNameManifestProperty, validator.missIconsManifestProperty):
 # don't translate 'icons' and 'name'.
 validator.missNameManifestProperty=Missing mandatory 'name' in Manifest.
 validator.missIconsManifestProperty=Missing 'icons' in Manifest.
 validator.missIconMarketplace=app submission to the Marketplace needs at least a 128px icon
 validator.invalidAppType=Unknown app type: '%S'.
 validator.invalidHostedPriviledges=Hosted App can't be type '%S'.
 validator.noCertifiedSupport='certified' apps are not fully supported on the App manager.
+validator.nonAbsoluteLaunchPath=Launch path has to be an absolute path starting with '/': '%S'
+validator.invalidLaunchPath=Unable to access to app starting document '%S'
+# LOCALIZATION NOTE (validator.invalidLaunchPathBadHttpCode): %1$S is the URI of
+# the launch document, %2$S is the http error code.
+validator.invalidLaunchPathBadHttpCode=Unable to access to app starting document '%1$S', got HTTP code %2$S
+