|
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"; |
|
5 |
|
6 let {Ci,Cu,CC} = require("chrome"); |
|
7 const promise = require("devtools/toolkit/deprecated-sync-thenables"); |
|
8 |
|
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"); |
|
13 |
|
14 function AppValidator(project) { |
|
15 this.project = project; |
|
16 this.errors = []; |
|
17 this.warnings = []; |
|
18 } |
|
19 |
|
20 AppValidator.prototype.error = function (message) { |
|
21 this.errors.push(message); |
|
22 }; |
|
23 |
|
24 AppValidator.prototype.warning = function (message) { |
|
25 this.warnings.push(message); |
|
26 }; |
|
27 |
|
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 }; |
|
45 |
|
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 }; |
|
53 |
|
54 AppValidator.prototype._fetchManifest = function (manifestURL) { |
|
55 let deferred = promise.defer(); |
|
56 this.manifestURL = manifestURL; |
|
57 |
|
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); |
|
80 |
|
81 try { |
|
82 req.send(null); |
|
83 } catch(e) { |
|
84 this.error(strings.formatStringFromName("validator.noAccessManifestURL", [e, manifestURL], 2)); |
|
85 deferred.resolve(); |
|
86 } |
|
87 |
|
88 return deferred.promise; |
|
89 }; |
|
90 |
|
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 }; |
|
111 |
|
112 AppValidator.prototype.validateManifest = function (manifest) { |
|
113 if (!manifest.name) { |
|
114 this.error(strings.GetStringFromName("validator.missNameManifestProperty")); |
|
115 } |
|
116 |
|
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 }; |
|
123 |
|
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 }; |
|
132 |
|
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 } |
|
156 |
|
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 }; |
|
175 |
|
176 try { |
|
177 req.send(null); |
|
178 } catch(e) { |
|
179 this.error(strings.formatStringFromName("validator.accessFailedLaunchPath", [indexURL], 1)); |
|
180 deferred.resolve(); |
|
181 } |
|
182 |
|
183 return deferred.promise; |
|
184 }; |
|
185 |
|
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 } |
|
194 |
|
195 // certified app are not fully supported on the simulator |
|
196 if (appType === "certified") { |
|
197 this.warning(strings.GetStringFromName("validator.noCertifiedSupport")); |
|
198 } |
|
199 }; |
|
200 |
|
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 }; |
|
214 |
|
215 exports.AppValidator = AppValidator; |