summaryrefslogtreecommitdiffstats
path: root/devtools/client/webide/modules/app-validator.js
diff options
context:
space:
mode:
Diffstat (limited to 'devtools/client/webide/modules/app-validator.js')
-rw-r--r--devtools/client/webide/modules/app-validator.js292
1 files changed, 292 insertions, 0 deletions
diff --git a/devtools/client/webide/modules/app-validator.js b/devtools/client/webide/modules/app-validator.js
new file mode 100644
index 000000000..750720110
--- /dev/null
+++ b/devtools/client/webide/modules/app-validator.js
@@ -0,0 +1,292 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+"use strict";
+
+var {Ci, Cu, CC} = require("chrome");
+const promise = require("promise");
+
+const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm", {});
+const Services = require("Services");
+const {Task} = require("devtools/shared/task");
+var XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
+var strings = Services.strings.createBundle("chrome://devtools/locale/app-manager.properties");
+
+function AppValidator({ type, location }) {
+ this.type = type;
+ this.location = location;
+ this.errors = [];
+ this.warnings = [];
+}
+
+AppValidator.prototype.error = function (message) {
+ this.errors.push(message);
+};
+
+AppValidator.prototype.warning = function (message) {
+ this.warnings.push(message);
+};
+
+AppValidator.prototype._getPackagedManifestFile = function () {
+ let manifestFile = FileUtils.File(this.location);
+ if (!manifestFile.exists()) {
+ this.error(strings.GetStringFromName("validator.nonExistingFolder"));
+ return null;
+ }
+ if (!manifestFile.isDirectory()) {
+ this.error(strings.GetStringFromName("validator.expectProjectFolder"));
+ return null;
+ }
+
+ let appManifestFile = manifestFile.clone();
+ appManifestFile.append("manifest.webapp");
+
+ let jsonManifestFile = manifestFile.clone();
+ jsonManifestFile.append("manifest.json");
+
+ let hasAppManifest = appManifestFile.exists() && appManifestFile.isFile();
+ let hasJsonManifest = jsonManifestFile.exists() && jsonManifestFile.isFile();
+
+ if (!hasAppManifest && !hasJsonManifest) {
+ this.error(strings.GetStringFromName("validator.noManifestFile"));
+ return null;
+ }
+
+ return hasAppManifest ? appManifestFile : jsonManifestFile;
+};
+
+AppValidator.prototype._getPackagedManifestURL = function () {
+ let manifestFile = this._getPackagedManifestFile();
+ if (!manifestFile) {
+ return null;
+ }
+ return Services.io.newFileURI(manifestFile).spec;
+};
+
+AppValidator.checkManifest = function (manifestURL) {
+ let deferred = promise.defer();
+ let error;
+
+ let req = new XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+
+ try {
+ req.open("GET", manifestURL, true);
+ req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
+ } catch (e) {
+ error = strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1);
+ deferred.reject(error);
+ return deferred.promise;
+ }
+
+ req.onload = function () {
+ let manifest = null;
+ try {
+ manifest = JSON.parse(req.responseText);
+ } catch (e) {
+ error = strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2);
+ deferred.reject(error);
+ }
+
+ deferred.resolve({manifest, manifestURL});
+ };
+
+ req.onerror = function () {
+ error = strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2);
+ deferred.reject(error);
+ };
+
+ try {
+ req.send(null);
+ } catch (e) {
+ error = strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2);
+ deferred.reject(error);
+ }
+
+ return deferred.promise;
+};
+
+AppValidator.findManifestAtOrigin = function (manifestURL) {
+ let fixedManifest = Services.io.newURI(manifestURL, null, null).prePath + "/manifest.webapp";
+ return AppValidator.checkManifest(fixedManifest);
+};
+
+AppValidator.findManifestPath = function (manifestURL) {
+ let deferred = promise.defer();
+
+ if (manifestURL.endsWith("manifest.webapp")) {
+ deferred.reject();
+ } else {
+ let fixedManifest = manifestURL + "/manifest.webapp";
+ deferred.resolve(AppValidator.checkManifest(fixedManifest));
+ }
+
+ return deferred.promise;
+};
+
+AppValidator.checkAlternateManifest = function (manifestURL) {
+ return Task.spawn(function* () {
+ let result;
+ try {
+ result = yield AppValidator.findManifestPath(manifestURL);
+ } catch (e) {
+ result = yield AppValidator.findManifestAtOrigin(manifestURL);
+ }
+
+ return result;
+ });
+};
+
+AppValidator.prototype._fetchManifest = function (manifestURL) {
+ let deferred = promise.defer();
+ this.manifestURL = manifestURL;
+
+ AppValidator.checkManifest(manifestURL)
+ .then(({manifest, manifestURL}) => {
+ deferred.resolve(manifest);
+ }, error => {
+ AppValidator.checkAlternateManifest(manifestURL)
+ .then(({manifest, manifestURL}) => {
+ this.manifestURL = manifestURL;
+ deferred.resolve(manifest);
+ }, () => {
+ this.error(error);
+ deferred.resolve(null);
+ });
+ });
+
+ return deferred.promise;
+};
+
+AppValidator.prototype._getManifest = function () {
+ let manifestURL;
+ if (this.type == "packaged") {
+ manifestURL = this._getPackagedManifestURL();
+ if (!manifestURL)
+ return promise.resolve(null);
+ } else if (this.type == "hosted") {
+ manifestURL = this.location;
+ try {
+ Services.io.newURI(manifestURL, null, null);
+ } catch (e) {
+ this.error(strings.formatStringFromName("validator.invalidHostedManifestURL", [manifestURL, e.message], 2));
+ return promise.resolve(null);
+ }
+ } else {
+ this.error(strings.formatStringFromName("validator.invalidProjectType", [this.type], 1));
+ return promise.resolve(null);
+ }
+ return this._fetchManifest(manifestURL);
+};
+
+AppValidator.prototype.validateManifest = function (manifest) {
+ if (!manifest.name) {
+ this.error(strings.GetStringFromName("validator.missNameManifestProperty"));
+ }
+
+ 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.missIconMarketplace2"));
+ }
+};
+
+AppValidator.prototype._getOriginURL = function () {
+ if (this.type == "packaged") {
+ let manifestURL = Services.io.newURI(this.manifestURL, null, null);
+ return Services.io.newURI(".", null, manifestURL).spec;
+ } else if (this.type == "hosted") {
+ return Services.io.newURI(this.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.type == "packaged") {
+ path = "." + (manifest.launch_path || "/index.html");
+ } else if (this.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.accessFailedLaunchPath", [origin + path], 1));
+ deferred.resolve();
+ return deferred.promise;
+ }
+
+ let req = new XMLHttpRequest();
+ req.overrideMimeType("text/plain");
+ try {
+ req.open("HEAD", indexURL, true);
+ req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
+ } catch (e) {
+ this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
+ deferred.resolve();
+ return deferred.promise;
+ }
+ req.onload = () => {
+ if (req.status >= 400)
+ this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2));
+ deferred.resolve();
+ };
+ req.onerror = () => {
+ this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
+ deferred.resolve();
+ };
+
+ try {
+ req.send(null);
+ } catch (e) {
+ this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [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.type == "hosted" &&
+ ["certified", "privileged"].indexOf(appType) !== -1) {
+ this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1));
+ }
+
+ // certified app are not fully supported on the simulator
+ if (appType === "certified") {
+ this.warning(strings.GetStringFromName("validator.noCertifiedSupport"));
+ }
+};
+
+AppValidator.prototype.validate = function () {
+ this.errors = [];
+ this.warnings = [];
+ return this._getManifest().
+ then((manifest) => {
+ if (manifest) {
+ this.manifest = manifest;
+
+ // Skip validations for add-ons
+ if (manifest.role === "addon" || manifest.manifest_version) {
+ return promise.resolve();
+ }
+
+ this.validateManifest(manifest);
+ this.validateType(manifest);
+ return this.validateLaunchPath(manifest);
+ }
+ });
+};
+
+exports.AppValidator = AppValidator;