Thu, 22 Jan 2015 13:21:57 +0100
Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6
1 /* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4 "use strict";
6 let {Ci,Cu,CC} = require("chrome");
7 const promise = require("devtools/toolkit/deprecated-sync-thenables");
9 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm");
10 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
11 let XMLHttpRequest = CC("@mozilla.org/xmlextras/xmlhttprequest;1");
12 let strings = Services.strings.createBundle("chrome://browser/locale/devtools/app-manager.properties");
14 function AppValidator(project) {
15 this.project = project;
16 this.errors = [];
17 this.warnings = [];
18 }
20 AppValidator.prototype.error = function (message) {
21 this.errors.push(message);
22 };
24 AppValidator.prototype.warning = function (message) {
25 this.warnings.push(message);
26 };
28 AppValidator.prototype._getPackagedManifestFile = function () {
29 let manifestFile = FileUtils.File(this.project.location);
30 if (!manifestFile.exists()) {
31 this.error(strings.GetStringFromName("validator.nonExistingFolder"));
32 return null;
33 }
34 if (!manifestFile.isDirectory()) {
35 this.error(strings.GetStringFromName("validator.expectProjectFolder"));
36 return null;
37 }
38 manifestFile.append("manifest.webapp");
39 if (!manifestFile.exists() || !manifestFile.isFile()) {
40 this.error(strings.GetStringFromName("validator.wrongManifestFileName"));
41 return null;
42 }
43 return manifestFile;
44 };
46 AppValidator.prototype._getPackagedManifestURL = function () {
47 let manifestFile = this._getPackagedManifestFile();
48 if (!manifestFile) {
49 return null;
50 }
51 return Services.io.newFileURI(manifestFile).spec;
52 };
54 AppValidator.prototype._fetchManifest = function (manifestURL) {
55 let deferred = promise.defer();
56 this.manifestURL = manifestURL;
58 let req = new XMLHttpRequest();
59 try {
60 req.open("GET", manifestURL, true);
61 } catch(e) {
62 this.error(strings.formatStringFromName("validator.invalidManifestURL", [manifestURL], 1));
63 deferred.resolve(null);
64 return deferred.promise;
65 }
66 req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
67 req.onload = (function () {
68 let manifest = null;
69 try {
70 manifest = JSON.parse(req.responseText);
71 } catch(e) {
72 this.error(strings.formatStringFromName("validator.invalidManifestJSON", [e, manifestURL], 2));
73 }
74 deferred.resolve(manifest);
75 }).bind(this);
76 req.onerror = (function () {
77 this.error(strings.formatStringFromName("validator.noAccessManifestURL", [req.statusText, manifestURL], 2));
78 deferred.resolve(null);
79 }).bind(this);
81 try {
82 req.send(null);
83 } catch(e) {
84 this.error(strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2));
85 deferred.resolve();
86 }
88 return deferred.promise;
89 };
91 AppValidator.prototype._getManifest = function () {
92 let manifestURL;
93 if (this.project.type == "packaged") {
94 manifestURL = this._getPackagedManifestURL();
95 if (!manifestURL)
96 return promise.resolve(null);
97 } else if (this.project.type == "hosted") {
98 manifestURL = this.project.location;
99 try {
100 Services.io.newURI(manifestURL, null, null);
101 } catch(e) {
102 this.error(strings.formatStringFromName("validator.invalidHostedManifestURL", [manifestURL, e.message]));
103 return promise.resolve(null);
104 }
105 } else {
106 this.error(strings.formatStringFromName("validator.invalidProjectType", [this.project.type], 1));
107 return promise.resolve(null);
108 }
109 return this._fetchManifest(manifestURL);
110 };
112 AppValidator.prototype.validateManifest = function (manifest) {
113 if (!manifest.name) {
114 this.error(strings.GetStringFromName("validator.missNameManifestProperty"));
115 }
117 if (!manifest.icons || Object.keys(manifest.icons).length === 0) {
118 this.warning(strings.GetStringFromName("validator.missIconsManifestProperty"));
119 } else if (!manifest.icons["128"]) {
120 this.warning(strings.GetStringFromName("validator.missIconMarketplace"));
121 }
122 };
124 AppValidator.prototype._getOriginURL = function () {
125 if (this.project.type == "packaged") {
126 let manifestURL = Services.io.newURI(this.manifestURL, null, null);
127 return Services.io.newURI(".", null, manifestURL).spec;
128 } else if (this.project.type == "hosted") {
129 return Services.io.newURI(this.project.location, null, null).prePath;
130 }
131 };
133 AppValidator.prototype.validateLaunchPath = function (manifest) {
134 let deferred = promise.defer();
135 // The launch_path field has to start with a `/`
136 if (manifest.launch_path && manifest.launch_path[0] !== "/") {
137 this.error(strings.formatStringFromName("validator.nonAbsoluteLaunchPath", [manifest.launch_path], 1));
138 deferred.resolve();
139 return deferred.promise;
140 }
141 let origin = this._getOriginURL();
142 let path;
143 if (this.project.type == "packaged") {
144 path = "." + ( manifest.launch_path || "/index.html" );
145 } else if (this.project.type == "hosted") {
146 path = manifest.launch_path || "/";
147 }
148 let indexURL;
149 try {
150 indexURL = Services.io.newURI(path, null, Services.io.newURI(origin, null, null)).spec;
151 } catch(e) {
152 this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [origin + path], 1));
153 deferred.resolve();
154 return deferred.promise;
155 }
157 let req = new XMLHttpRequest();
158 try {
159 req.open("HEAD", indexURL, true);
160 } catch(e) {
161 this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
162 deferred.resolve();
163 return deferred.promise;
164 }
165 req.channel.loadFlags |= Ci.nsIRequest.LOAD_BYPASS_CACHE | Ci.nsIRequest.INHIBIT_CACHING;
166 req.onload = () => {
167 if (req.status >= 400)
168 this.error(strings.formatStringFromName("validator.accessFailedLaunchPathBadHttpCode", [indexURL, req.status], 2));
169 deferred.resolve();
170 };
171 req.onerror = () => {
172 this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
173 deferred.resolve();
174 };
176 try {
177 req.send(null);
178 } catch(e) {
179 this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1));
180 deferred.resolve();
181 }
183 return deferred.promise;
184 };
186 AppValidator.prototype.validateType = function (manifest) {
187 let appType = manifest.type || "web";
188 if (["web", "privileged", "certified"].indexOf(appType) === -1) {
189 this.error(strings.formatStringFromName("validator.invalidAppType", [appType], 1));
190 } else if (this.project.type == "hosted" &&
191 ["certified", "privileged"].indexOf(appType) !== -1) {
192 this.error(strings.formatStringFromName("validator.invalidHostedPriviledges", [appType], 1));
193 }
195 // certified app are not fully supported on the simulator
196 if (appType === "certified") {
197 this.warning(strings.GetStringFromName("validator.noCertifiedSupport"));
198 }
199 };
201 AppValidator.prototype.validate = function () {
202 this.errors = [];
203 this.warnings = [];
204 return this._getManifest().
205 then((function (manifest) {
206 if (manifest) {
207 this.manifest = manifest;
208 this.validateManifest(manifest);
209 this.validateType(manifest);
210 return this.validateLaunchPath(manifest);
211 }
212 }).bind(this));
213 };
215 exports.AppValidator = AppValidator;