|
1 <!DOCTYPE HTML> |
|
2 <html> |
|
3 <head> |
|
4 <title>Test for XMLHttpRequest Progress Events</title> |
|
5 <script type="text/javascript" src="/MochiKit/packed.js"></script> |
|
6 <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> |
|
7 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> |
|
8 </head> |
|
9 <body onload="gen.next();"> |
|
10 <pre id=l></pre> |
|
11 <script type="application/javascript;version=1.7"> |
|
12 SimpleTest.waitForExplicitFinish(); |
|
13 |
|
14 var gen = runTests(); |
|
15 |
|
16 function log(s) { |
|
17 // Uncomment these to get debugging information |
|
18 /* |
|
19 document.getElementById("l").textContent += s + "\n"; |
|
20 dump(s + "\n"); |
|
21 */ |
|
22 } |
|
23 |
|
24 function getEvent(e) { |
|
25 log("got event: " + e.type + " (" + e.target.readyState + ")"); |
|
26 gen.send(e); |
|
27 } |
|
28 |
|
29 function startsWith(a, b) { |
|
30 return a.substr(0, b.length) === b; |
|
31 } |
|
32 |
|
33 function updateProgress(e, data, testName) { |
|
34 var test = " while running " + testName; |
|
35 is(e.type, "progress", "event type" + test); |
|
36 |
|
37 let response; |
|
38 if (data.nodata) { |
|
39 is(e.target.response, null, "response should be null" + test); |
|
40 response = null; |
|
41 } |
|
42 else if (data.text) { |
|
43 is(typeof e.target.response, "string", "response should be a string" + test); |
|
44 response = e.target.response; |
|
45 } |
|
46 else if (data.blob) { |
|
47 ok(e.target.response instanceof Blob, "response should be a Blob" + test); |
|
48 response = e.target.response; |
|
49 } |
|
50 else { |
|
51 ok(e.target.response instanceof ArrayBuffer, "response should be an ArrayBuffer" + test); |
|
52 response = bufferToString(e.target.response); |
|
53 } |
|
54 is(e.target.response, e.target.response, "reflexivity should hold" + test); |
|
55 |
|
56 if (!data.nodata && !data.encoded) { |
|
57 if (data.blob) { |
|
58 is(e.loaded, response.size, "event.loaded matches response size" + test); |
|
59 } |
|
60 else if (!data.chunked) { |
|
61 is(e.loaded, response.length, "event.loaded matches response size" + test); |
|
62 } |
|
63 else { |
|
64 is(e.loaded - data.receivedBytes, response.length, |
|
65 "event.loaded grew by response size" + test); |
|
66 } |
|
67 } |
|
68 ok(e.loaded > data.receivedBytes, "event.loaded increased" + test); |
|
69 ok(e.loaded - data.receivedBytes <= data.pendingBytes, |
|
70 "event.loaded didn't increase too much" + test); |
|
71 |
|
72 if (!data.nodata && !data.blob) { |
|
73 var newData; |
|
74 ok(startsWith(response, data.receivedResult), |
|
75 "response strictly grew" + test); |
|
76 newData = response.substr(data.receivedResult.length); |
|
77 |
|
78 if (!data.encoded) { |
|
79 ok(newData.length > 0, "sanity check for progress" + test); |
|
80 } |
|
81 ok(startsWith(data.pendingResult, newData), "new data matches expected" + test); |
|
82 } |
|
83 |
|
84 is(e.lengthComputable, "total" in data, "lengthComputable" + test); |
|
85 if ("total" in data) { |
|
86 is(e.total, data.total, "total" + test); |
|
87 } |
|
88 |
|
89 if (!data.nodata && !data.blob) { |
|
90 data.pendingResult = data.pendingResult.substr(newData.length); |
|
91 } |
|
92 data.pendingBytes -= e.loaded - data.receivedBytes; |
|
93 data.receivedResult = response; |
|
94 data.receivedBytes = e.loaded; |
|
95 } |
|
96 |
|
97 function sendData(s) { |
|
98 var xhr = new XMLHttpRequest(); |
|
99 xhr.open("POST", "progressserver.sjs?send"); |
|
100 xhr.sendAsBinary(s); |
|
101 } |
|
102 |
|
103 function closeConn() { |
|
104 log("in closeConn"); |
|
105 var xhr = new XMLHttpRequest(); |
|
106 xhr.open("POST", "progressserver.sjs?close"); |
|
107 xhr.send(); |
|
108 return xhr; |
|
109 } |
|
110 |
|
111 var longString = "long"; |
|
112 while(longString.length < 65536) |
|
113 longString += longString; |
|
114 |
|
115 function utf8encode(s) { |
|
116 return unescape(encodeURIComponent(s)); |
|
117 } |
|
118 |
|
119 function bufferToString(buffer) { |
|
120 return String.fromCharCode.apply(String, new Uint8Array(buffer)); |
|
121 } |
|
122 |
|
123 function runTests() { |
|
124 var xhr = new XMLHttpRequest(); |
|
125 xhr.onprogress = xhr.onload = xhr.onerror = xhr.onreadystatechange = xhr.onloadend = getEvent; |
|
126 |
|
127 var responseTypes = [{ type: "text", text: true }, |
|
128 { type: "arraybuffer", text: false, nodata: true }, |
|
129 { type: "blob", text: false, nodata: true, blob: true }, |
|
130 { type: "moz-blob", text: false, nodata: false, blob: true }, |
|
131 { type: "document", text: true, nodata: true }, |
|
132 { type: "json", text: true, nodata: true }, |
|
133 { type: "", text: true }, |
|
134 { type: "moz-chunked-text", text: true, chunked: true }, |
|
135 { type: "moz-chunked-arraybuffer", text: false, chunked: true }, |
|
136 ]; |
|
137 var responseType; |
|
138 var fileExpectedResult = ""; |
|
139 for (var i = 0; i < 65536; i++) { |
|
140 fileExpectedResult += String.fromCharCode(i & 255); |
|
141 } |
|
142 while (responseType = responseTypes.shift()) { |
|
143 let tests = [{ open: "Content-Type=text/plain", name: "simple test" }, |
|
144 { data: "hello world" }, |
|
145 { data: "\u0000\u0001\u0002\u0003" }, |
|
146 { data: longString }, |
|
147 { data: "x" }, |
|
148 { close: true }, |
|
149 { open: "Content-Type=text/plain&Content-Length=20", name: "with length", total: 20 }, |
|
150 // 5 bytes from the "ready" in the open step |
|
151 { data: "abcde" }, |
|
152 { data: "0123456789" }, |
|
153 { close: true }, |
|
154 { open: "Content-Type=application/xml", name: "without length, as xml" }, |
|
155 { data: "<out>" }, |
|
156 { data: "text" }, |
|
157 { data: "</foo>invalid" }, |
|
158 { close: true }, |
|
159 { open: "Content-Type=text/plain;charset%3dutf-8", name: "utf8 data", encoded: true }, |
|
160 { data: utf8encode("räksmörgås"), utf16: "räksmörgås" }, |
|
161 { data: utf8encode("Å").substr(0,1), utf16: "" }, |
|
162 { data: utf8encode("Å").substr(1), utf16: "Å" }, |
|
163 { data: utf8encode("aöb").substr(0,2), utf16: "a" }, |
|
164 { data: utf8encode("aöb").substr(2), utf16: "öb" }, |
|
165 { data: utf8encode("a\u867Eb").substr(0,3), utf16: "a" }, |
|
166 { data: utf8encode("a\u867Eb").substr(3,1), utf16: "\u867E" }, |
|
167 { data: utf8encode("a\u867Eb").substr(4), utf16: "b" }, |
|
168 { close: true }, |
|
169 ]; |
|
170 if (responseType.blob) { |
|
171 tests.push({ file: "file_XHR_binary2.bin", name: "cacheable data", total: 65536 }, |
|
172 { close: true }, |
|
173 { file: "file_XHR_binary2.bin", name: "cached data", total: 65536 }, |
|
174 { close: true }); |
|
175 } |
|
176 let testState = { index: 0 }; |
|
177 |
|
178 for (let i = 0; i < tests.length; ++i) { |
|
179 let test = tests[i]; |
|
180 testState.index++; |
|
181 if ("open" in test || "file" in test) { |
|
182 log("opening " + testState.name); |
|
183 testState = { name: test.name + " for " + responseType.type, |
|
184 index: 0, |
|
185 pendingResult: "ready", |
|
186 pendingBytes: 5, |
|
187 receivedResult: "", |
|
188 receivedBytes: 0, |
|
189 total: test.total, |
|
190 encoded: test.encoded, |
|
191 nodata: responseType.nodata, |
|
192 chunked: responseType.chunked, |
|
193 text: responseType.text, |
|
194 blob: responseType.blob, |
|
195 file: test.file }; |
|
196 |
|
197 xhr.onreadystatechange = null; |
|
198 if (testState.file) |
|
199 xhr.open("GET", test.file); |
|
200 else |
|
201 xhr.open("POST", "progressserver.sjs?open&" + test.open); |
|
202 xhr.responseType = responseType.type; |
|
203 xhr.send("ready"); |
|
204 xhr.onreadystatechange = getEvent; |
|
205 |
|
206 let e = yield undefined; |
|
207 is(e.type, "readystatechange", "should readystate to headers-received starting " + testState.name); |
|
208 is(xhr.readyState, xhr.HEADERS_RECEIVED, "should be in state HEADERS_RECEIVED starting " + testState.name); |
|
209 |
|
210 e = yield undefined; |
|
211 is(e.type, "readystatechange", "should readystate to loading starting " + testState.name); |
|
212 is(xhr.readyState, xhr.LOADING, "should be in state LOADING starting " + testState.name); |
|
213 if (typeof testState.total == "undefined") |
|
214 delete testState.total; |
|
215 } |
|
216 if ("file" in test) { |
|
217 testState.pendingBytes = testState.total; |
|
218 testState.pendingResult = fileExpectedResult; |
|
219 } |
|
220 if ("close" in test) { |
|
221 log("closing"); |
|
222 let xhrClose; |
|
223 if (!testState.file) |
|
224 xhrClose = closeConn(); |
|
225 |
|
226 e = yield undefined; |
|
227 is(e.type, "readystatechange", "should readystate to done closing " + testState.name); |
|
228 is(xhr.readyState, xhr.DONE, "should be in state DONE closing " + testState.name); |
|
229 log("readystate to 4"); |
|
230 |
|
231 if (responseType.chunked) { |
|
232 xhr.responseType; |
|
233 is(xhr.response, null, "chunked data has null response for " + testState.name); |
|
234 } |
|
235 |
|
236 e = yield undefined; |
|
237 is(e.type, "load", "should fire load closing " + testState.name); |
|
238 is(e.lengthComputable, true, "length should be computable during load closing " + testState.name); |
|
239 log("got load"); |
|
240 |
|
241 if (responseType.chunked) { |
|
242 is(xhr.response, null, "chunked data has null response for " + testState.name); |
|
243 } |
|
244 |
|
245 e = yield undefined; |
|
246 is(e.type, "loadend", "should fire loadend closing " + testState.name); |
|
247 is(e.lengthComputable, true, "length should be computable during loadend closing " + testState.name); |
|
248 log("got loadend"); |
|
249 |
|
250 // if we closed the connection using an explicit request, make sure that goes through before |
|
251 // running the next test in order to avoid reordered requests from closing the wrong |
|
252 // connection. |
|
253 if (xhrClose && xhrClose.readyState != xhrClose.DONE) { |
|
254 log("wait for closeConn to finish"); |
|
255 xhrClose.onloadend = getEvent; |
|
256 yield undefined; |
|
257 is(xhrClose.readyState, xhrClose.DONE, "closeConn finished"); |
|
258 } |
|
259 |
|
260 if (responseType.chunked) { |
|
261 is(xhr.response, null, "chunked data has null response for " + testState.name); |
|
262 } |
|
263 |
|
264 if (!testState.nodata && !responseType.blob || responseType.chunked) { |
|
265 // This branch intentionally left blank |
|
266 // Under these conditions we check the response during updateProgress |
|
267 } |
|
268 else if (responseType.type === "arraybuffer") { |
|
269 is(bufferToString(xhr.response), testState.pendingResult, |
|
270 "full response for " + testState.name); |
|
271 } |
|
272 else if (responseType.blob) { |
|
273 let reader = new FileReader; |
|
274 reader.readAsBinaryString(xhr.response); |
|
275 reader.onloadend = getEvent; |
|
276 yield undefined; |
|
277 |
|
278 is(reader.result, testState.pendingResult, |
|
279 "full response in blob for " + testState.name); |
|
280 } |
|
281 |
|
282 testState.name = ""; |
|
283 } |
|
284 if ("data" in test) { |
|
285 log("sending"); |
|
286 if (responseType.text) { |
|
287 testState.pendingResult += "utf16" in test ? test.utf16 : test.data; |
|
288 } |
|
289 else { |
|
290 testState.pendingResult += test.data; |
|
291 } |
|
292 testState.pendingBytes = test.data.length; |
|
293 sendData(test.data); |
|
294 } |
|
295 |
|
296 while(testState.pendingBytes) { |
|
297 log("waiting for more bytes: " + testState.pendingBytes); |
|
298 e = yield undefined; |
|
299 // Readystate can fire several times between each progress event. |
|
300 if (e.type === "readystatechange") |
|
301 continue; |
|
302 |
|
303 updateProgress(e, testState, "data for " + testState.name + "[" + testState.index + "]"); |
|
304 if (responseType.chunked) { |
|
305 testState.receivedResult = ""; |
|
306 } |
|
307 } |
|
308 |
|
309 if (!testState.nodata && !testState.blob) { |
|
310 is(testState.pendingResult, "", |
|
311 "should have consumed the expected result"); |
|
312 } |
|
313 |
|
314 log("done with this test"); |
|
315 } |
|
316 |
|
317 is(testState.name, "", "forgot to close last test"); |
|
318 } |
|
319 |
|
320 SimpleTest.finish(); |
|
321 yield undefined; |
|
322 } |
|
323 |
|
324 </script> |
|
325 |
|
326 </body> |
|
327 </html> |