Bug 920478 - Print errors when something is wrong with app launch path. r=paul
☠☠ backed out by c31df2ff673f ☠ ☠
authorAlexandre Poirot <poirot.alex@gmail.com>
Thu, 17 Oct 2013 14:27:04 -0400
changeset 151123 7063d9f5c1fdd312bdc8403fc6cd34e30d76a782
parent 151122 2601d36dc1f12d8c5c42e183b2d26aeca55cab1a
child 151124 62732da6ae3d5addd4e4522610d042cf1d8804f6
push id3096
push userryanvm@gmail.com
push dateThu, 17 Oct 2013 18:27:38 +0000
treeherderfx-team@62732da6ae3d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerspaul
bugs920478
milestone27.0a1
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
+