|
1 const {Ci, Cc, Cu, Cr} = require("chrome"); |
|
2 Cu.import("resource://gre/modules/osfile.jsm"); |
|
3 const {Services} = Cu.import("resource://gre/modules/Services.jsm"); |
|
4 const {FileUtils} = Cu.import("resource://gre/modules/FileUtils.jsm"); |
|
5 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {}); |
|
6 const {Promise: promise} = Cu.import("resource://gre/modules/Promise.jsm", {}); |
|
7 |
|
8 // XXX: bug 912476 make this module a real protocol.js front |
|
9 // by converting webapps actor to protocol.js |
|
10 |
|
11 const PR_USEC_PER_MSEC = 1000; |
|
12 const PR_RDWR = 0x04; |
|
13 const PR_CREATE_FILE = 0x08; |
|
14 const PR_TRUNCATE = 0x20; |
|
15 |
|
16 const CHUNK_SIZE = 10000; |
|
17 |
|
18 const appTargets = new Map(); |
|
19 |
|
20 function addDirToZip(writer, dir, basePath) { |
|
21 let files = dir.directoryEntries; |
|
22 |
|
23 while (files.hasMoreElements()) { |
|
24 let file = files.getNext().QueryInterface(Ci.nsIFile); |
|
25 |
|
26 if (file.isHidden() || |
|
27 file.isSpecial() || |
|
28 file.equals(writer.file)) |
|
29 { |
|
30 continue; |
|
31 } |
|
32 |
|
33 if (file.isDirectory()) { |
|
34 writer.addEntryDirectory(basePath + file.leafName + "/", |
|
35 file.lastModifiedTime * PR_USEC_PER_MSEC, |
|
36 true); |
|
37 addDirToZip(writer, file, basePath + file.leafName + "/"); |
|
38 } else { |
|
39 writer.addEntryFile(basePath + file.leafName, |
|
40 Ci.nsIZipWriter.COMPRESSION_DEFAULT, |
|
41 file, |
|
42 true); |
|
43 } |
|
44 } |
|
45 } |
|
46 |
|
47 /** |
|
48 * Convert an XPConnect result code to its name and message. |
|
49 * We have to extract them from an exception per bug 637307 comment 5. |
|
50 */ |
|
51 function getResultTest(code) { |
|
52 let regexp = |
|
53 /^\[Exception... "(.*)" nsresult: "0x[0-9a-fA-F]* \((.*)\)" location: ".*" data: .*\]$/; |
|
54 let ex = Cc["@mozilla.org/js/xpc/Exception;1"]. |
|
55 createInstance(Ci.nsIXPCException); |
|
56 ex.initialize(null, code, null, null, null, null); |
|
57 let [, message, name] = regexp.exec(ex.toString()); |
|
58 return { name: name, message: message }; |
|
59 } |
|
60 |
|
61 function zipDirectory(zipFile, dirToArchive) { |
|
62 let deferred = promise.defer(); |
|
63 let writer = Cc["@mozilla.org/zipwriter;1"].createInstance(Ci.nsIZipWriter); |
|
64 writer.open(zipFile, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE); |
|
65 |
|
66 this.addDirToZip(writer, dirToArchive, ""); |
|
67 |
|
68 writer.processQueue({ |
|
69 onStartRequest: function onStartRequest(request, context) {}, |
|
70 onStopRequest: (request, context, status) => { |
|
71 if (status == Cr.NS_OK) { |
|
72 writer.close(); |
|
73 deferred.resolve(zipFile); |
|
74 } |
|
75 else { |
|
76 let { name, message } = getResultText(status); |
|
77 deferred.reject(name + ": " + message); |
|
78 } |
|
79 } |
|
80 }, null); |
|
81 |
|
82 return deferred.promise; |
|
83 } |
|
84 |
|
85 function uploadPackage(client, webappsActor, packageFile) { |
|
86 let deferred = promise.defer(); |
|
87 |
|
88 let request = { |
|
89 to: webappsActor, |
|
90 type: "uploadPackage" |
|
91 }; |
|
92 client.request(request, (res) => { |
|
93 openFile(res.actor); |
|
94 }); |
|
95 |
|
96 function openFile(actor) { |
|
97 OS.File.open(packageFile.path) |
|
98 .then(function (file) { |
|
99 uploadChunk(actor, file); |
|
100 }); |
|
101 } |
|
102 function uploadChunk(actor, file) { |
|
103 file.read(CHUNK_SIZE) |
|
104 .then(function (bytes) { |
|
105 // To work around the fact that JSON.stringify translates the typed |
|
106 // array to object, we are encoding the typed array here into a string |
|
107 let chunk = String.fromCharCode.apply(null, bytes); |
|
108 |
|
109 let request = { |
|
110 to: actor, |
|
111 type: "chunk", |
|
112 chunk: chunk |
|
113 }; |
|
114 client.request(request, (res) => { |
|
115 if (bytes.length == CHUNK_SIZE) { |
|
116 uploadChunk(actor, file); |
|
117 } else { |
|
118 file.close().then(function () { |
|
119 endsUpload(actor); |
|
120 }); |
|
121 } |
|
122 }); |
|
123 }); |
|
124 } |
|
125 function endsUpload(actor) { |
|
126 let request = { |
|
127 to: actor, |
|
128 type: "done" |
|
129 }; |
|
130 client.request(request, (res) => { |
|
131 deferred.resolve(actor); |
|
132 }); |
|
133 } |
|
134 return deferred.promise; |
|
135 } |
|
136 |
|
137 function removeServerTemporaryFile(client, fileActor) { |
|
138 let request = { |
|
139 to: fileActor, |
|
140 type: "remove" |
|
141 }; |
|
142 client.request(request); |
|
143 } |
|
144 |
|
145 function installPackaged(client, webappsActor, packagePath, appId) { |
|
146 let deferred = promise.defer(); |
|
147 let file = FileUtils.File(packagePath); |
|
148 let packagePromise; |
|
149 if (file.isDirectory()) { |
|
150 let tmpZipFile = FileUtils.getDir("TmpD", [], true); |
|
151 tmpZipFile.append("application.zip"); |
|
152 tmpZipFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, parseInt("666", 8)); |
|
153 packagePromise = zipDirectory(tmpZipFile, file) |
|
154 } else { |
|
155 packagePromise = promise.resolve(file); |
|
156 } |
|
157 packagePromise.then((zipFile) => { |
|
158 uploadPackage(client, webappsActor, zipFile) |
|
159 .then((fileActor) => { |
|
160 let request = { |
|
161 to: webappsActor, |
|
162 type: "install", |
|
163 appId: appId, |
|
164 upload: fileActor |
|
165 }; |
|
166 client.request(request, (res) => { |
|
167 // If the install method immediatly fails, |
|
168 // reject immediatly the installPackaged promise. |
|
169 // Otherwise, wait for webappsEvent for completion |
|
170 if (res.error) { |
|
171 deferred.reject(res); |
|
172 } |
|
173 if ("error" in res) |
|
174 deferred.reject({error: res.error, message: res.message}); |
|
175 else |
|
176 deferred.resolve({appId: res.appId}); |
|
177 }); |
|
178 // Ensure deleting the temporary package file, but only if that a temporary |
|
179 // package created when we pass a directory as `packagePath` |
|
180 if (zipFile != file) |
|
181 zipFile.remove(false); |
|
182 // In case of success or error, ensure deleting the temporary package file |
|
183 // also created on the device, but only once install request is done |
|
184 deferred.promise.then( |
|
185 () => removeServerTemporaryFile(client, fileActor), |
|
186 () => removeServerTemporaryFile(client, fileActor)); |
|
187 }); |
|
188 }); |
|
189 return deferred.promise; |
|
190 } |
|
191 exports.installPackaged = installPackaged; |
|
192 |
|
193 function installHosted(client, webappsActor, appId, metadata, manifest) { |
|
194 let deferred = promise.defer(); |
|
195 let request = { |
|
196 to: webappsActor, |
|
197 type: "install", |
|
198 appId: appId, |
|
199 metadata: metadata, |
|
200 manifest: manifest |
|
201 }; |
|
202 client.request(request, (res) => { |
|
203 if (res.error) { |
|
204 deferred.reject(res); |
|
205 } |
|
206 if ("error" in res) |
|
207 deferred.reject({error: res.error, message: res.message}); |
|
208 else |
|
209 deferred.resolve({appId: res.appId}); |
|
210 }); |
|
211 return deferred.promise; |
|
212 } |
|
213 exports.installHosted = installHosted; |
|
214 |
|
215 function getTargetForApp(client, webappsActor, manifestURL) { |
|
216 // Ensure always returning the exact same JS object for a target |
|
217 // of the same app in order to show only one toolbox per app and |
|
218 // avoid re-creating lot of objects twice. |
|
219 let existingTarget = appTargets.get(manifestURL); |
|
220 if (existingTarget) |
|
221 return promise.resolve(existingTarget); |
|
222 |
|
223 let deferred = promise.defer(); |
|
224 let request = { |
|
225 to: webappsActor, |
|
226 type: "getAppActor", |
|
227 manifestURL: manifestURL, |
|
228 } |
|
229 client.request(request, (res) => { |
|
230 if (res.error) { |
|
231 deferred.reject(res.error); |
|
232 } else { |
|
233 let options = { |
|
234 form: res.actor, |
|
235 client: client, |
|
236 chrome: false |
|
237 }; |
|
238 |
|
239 devtools.TargetFactory.forRemoteTab(options).then((target) => { |
|
240 target.isApp = true; |
|
241 appTargets.set(manifestURL, target); |
|
242 target.on("close", () => { |
|
243 appTargets.delete(manifestURL); |
|
244 }); |
|
245 deferred.resolve(target) |
|
246 }, (error) => { |
|
247 deferred.reject(error); |
|
248 }); |
|
249 } |
|
250 }); |
|
251 return deferred.promise; |
|
252 } |
|
253 exports.getTargetForApp = getTargetForApp; |
|
254 |
|
255 function reloadApp(client, webappsActor, manifestURL) { |
|
256 let deferred = promise.defer(); |
|
257 getTargetForApp(client, |
|
258 webappsActor, |
|
259 manifestURL). |
|
260 then((target) => { |
|
261 // Request the ContentActor to reload the app |
|
262 let request = { |
|
263 to: target.form.actor, |
|
264 type: "reload", |
|
265 manifestURL: manifestURL |
|
266 }; |
|
267 client.request(request, (res) => { |
|
268 deferred.resolve(); |
|
269 }); |
|
270 }, () => { |
|
271 deferred.reject("Not running"); |
|
272 }); |
|
273 return deferred.promise; |
|
274 } |
|
275 exports.reloadApp = reloadApp; |
|
276 |
|
277 function launchApp(client, webappsActor, manifestURL) { |
|
278 let deferred = promise.defer(); |
|
279 let request = { |
|
280 to: webappsActor, |
|
281 type: "launch", |
|
282 manifestURL: manifestURL |
|
283 }; |
|
284 client.request(request, (res) => { |
|
285 if (res.error) { |
|
286 deferred.reject(res.error); |
|
287 } else { |
|
288 deferred.resolve(res); |
|
289 } |
|
290 }); |
|
291 return deferred.promise; |
|
292 } |
|
293 exports.launchApp = launchApp; |
|
294 |
|
295 function closeApp(client, webappsActor, manifestURL) { |
|
296 let deferred = promise.defer(); |
|
297 let request = { |
|
298 to: webappsActor, |
|
299 type: "close", |
|
300 manifestURL: manifestURL |
|
301 }; |
|
302 client.request(request, (res) => { |
|
303 if (res.error) { |
|
304 deferred.reject(res.error); |
|
305 } else { |
|
306 deferred.resolve(res); |
|
307 } |
|
308 }); |
|
309 return deferred.promise; |
|
310 } |
|
311 exports.closeApp = closeApp; |