|
1 var gBasePath = "tests/dom/apps/tests/"; |
|
2 var gAppTemplatePath = "tests/dom/apps/tests/file_app.template.html"; |
|
3 var gAppcacheTemplatePath = "tests/dom/apps/tests/file_cached_app.template.appcache"; |
|
4 var gDefaultIcon = "default_icon"; |
|
5 |
|
6 function makeResource(templatePath, version, apptype) { |
|
7 let icon = getState('icon') || gDefaultIcon; |
|
8 var res = readTemplate(templatePath).replace(/VERSIONTOKEN/g, version) |
|
9 .replace(/APPTYPETOKEN/g, apptype) |
|
10 .replace(/ICONTOKEN/g, icon); |
|
11 |
|
12 // Hack - This is necessary to make the tests pass, but hbambas says it |
|
13 // shouldn't be necessary. Comment it out and watch the tests fail. |
|
14 if (templatePath == gAppTemplatePath && apptype == 'cached') { |
|
15 res = res.replace('<html>', '<html manifest="file_app.sjs?apptype=cached&getappcache=true">'); |
|
16 } |
|
17 |
|
18 return res; |
|
19 } |
|
20 |
|
21 function handleRequest(request, response) { |
|
22 var query = getQuery(request); |
|
23 |
|
24 // If this is a version update, update state and return. |
|
25 if ("setVersion" in query) { |
|
26 setState('version', query.setVersion); |
|
27 response.setHeader("Content-Type", "text/html", false); |
|
28 response.setHeader("Access-Control-Allow-Origin", "*", false); |
|
29 response.write('OK'); |
|
30 return; |
|
31 } |
|
32 |
|
33 if ("setIcon" in query) { |
|
34 let icon = query.setIcon; |
|
35 if (icon === 'DEFAULT') { |
|
36 icon = null; |
|
37 } |
|
38 |
|
39 setState('icon', icon); |
|
40 |
|
41 response.setHeader("Content-Type", "text/html", false); |
|
42 response.setHeader("Access-Control-Allow-Origin", "*", false); |
|
43 response.write('OK'); |
|
44 return; |
|
45 } |
|
46 |
|
47 // Get the app type. |
|
48 var apptype = query.apptype; |
|
49 if (apptype != 'hosted' && apptype != 'cached') |
|
50 throw "Invalid app type: " + apptype; |
|
51 |
|
52 // Get the version from server state and handle the etag. |
|
53 var version = Number(getState('version')); |
|
54 var etag = getEtag(request, version); |
|
55 dump("Server Etag: " + etag + "\n"); |
|
56 |
|
57 if (etagMatches(request, etag)) { |
|
58 dump("Etags Match. Sending 304\n"); |
|
59 response.setStatusLine(request.httpVersion, "304", "Not Modified"); |
|
60 return; |
|
61 } |
|
62 |
|
63 response.setHeader("Etag", etag, false); |
|
64 if (request.hasHeader("If-None-Match")) |
|
65 dump("Client Etag: " + request.getHeader("If-None-Match") + "\n"); |
|
66 else |
|
67 dump("No Client Etag\n"); |
|
68 |
|
69 // Check if we're generating a webapp manifest. |
|
70 if ('getmanifest' in query) { |
|
71 var template = gBasePath + 'file_' + apptype + '_app.template.webapp'; |
|
72 response.setHeader("Content-Type", "application/x-web-app-manifest+json", false); |
|
73 response.write(makeResource(template, version, apptype)); |
|
74 return; |
|
75 } |
|
76 |
|
77 // If apptype==cached, we might be generating the appcache manifest. |
|
78 // |
|
79 // NB: Among other reasons, we use the same sjs file here so that the version |
|
80 // state is shared. |
|
81 if (apptype == 'cached' && 'getappcache' in query) { |
|
82 response.setHeader("Content-Type", "text/cache-manifest", false); |
|
83 response.write(makeResource(gAppcacheTemplatePath, version, apptype)); |
|
84 return; |
|
85 } |
|
86 |
|
87 // Generate the app. |
|
88 response.setHeader("Content-Type", "text/html", false); |
|
89 response.write(makeResource(gAppTemplatePath, version, apptype)); |
|
90 } |
|
91 |
|
92 function getEtag(request, version) { |
|
93 return request.queryString.replace(/&/g, '-').replace(/=/g, '-') + '-' + version; |
|
94 } |
|
95 |
|
96 function etagMatches(request, etag) { |
|
97 return request.hasHeader("If-None-Match") && request.getHeader("If-None-Match") == etag; |
|
98 } |
|
99 |
|
100 function getQuery(request) { |
|
101 var query = {}; |
|
102 request.queryString.split('&').forEach(function (val) { |
|
103 var [name, value] = val.split('='); |
|
104 query[name] = unescape(value); |
|
105 }); |
|
106 return query; |
|
107 } |
|
108 |
|
109 // Copy-pasted incantations. There ought to be a better way to synchronously read |
|
110 // a file into a string, but I guess we're trying to discourage that. |
|
111 function readTemplate(path) { |
|
112 var file = Components.classes["@mozilla.org/file/directory_service;1"]. |
|
113 getService(Components.interfaces.nsIProperties). |
|
114 get("CurWorkD", Components.interfaces.nsILocalFile); |
|
115 var fis = Components.classes['@mozilla.org/network/file-input-stream;1']. |
|
116 createInstance(Components.interfaces.nsIFileInputStream); |
|
117 var cis = Components.classes["@mozilla.org/intl/converter-input-stream;1"]. |
|
118 createInstance(Components.interfaces.nsIConverterInputStream); |
|
119 var split = path.split("/"); |
|
120 for(var i = 0; i < split.length; ++i) { |
|
121 file.append(split[i]); |
|
122 } |
|
123 fis.init(file, -1, -1, false); |
|
124 cis.init(fis, "UTF-8", 0, 0); |
|
125 |
|
126 var data = ""; |
|
127 let (str = {}) { |
|
128 let read = 0; |
|
129 do { |
|
130 read = cis.readString(0xffffffff, str); // read as much as we can and put it in str.value |
|
131 data += str.value; |
|
132 } while (read != 0); |
|
133 } |
|
134 cis.close(); |
|
135 return data; |
|
136 } |