<!DOCTYPE HTML> <html> <head> <title>Test for XMLHttpRequest Progress Events</title> <script type="text/javascript" src="/MochiKit/packed.js"></script> <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script> <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" /> </head> <body onload="gen.next();"> <pre id=l></pre> <script type="application/javascript;version=1.7"> SimpleTest.waitForExplicitFinish(); var gen = runTests(); function log(s) { // Uncomment these to get debugging information /* document.getElementById("l").textContent += s + "\n"; dump(s + "\n"); */ } function getEvent(e) { log("got event: " + e.type + " (" + e.target.readyState + ")"); gen.send(e); } function startsWith(a, b) { return a.substr(0, b.length) === b; } function updateProgress(e, data, testName) { var test = " while running " + testName; is(e.type, "progress", "event type" + test); let response; if (data.nodata) { is(e.target.response, null, "response should be null" + test); response = null; } else if (data.text) { is(typeof e.target.response, "string", "response should be a string" + test); response = e.target.response; } else if (data.blob) { ok(e.target.response instanceof Blob, "response should be a Blob" + test); response = e.target.response; } else { ok(e.target.response instanceof ArrayBuffer, "response should be an ArrayBuffer" + test); response = bufferToString(e.target.response); } is(e.target.response, e.target.response, "reflexivity should hold" + test); if (!data.nodata && !data.encoded) { if (data.blob) { is(e.loaded, response.size, "event.loaded matches response size" + test); } else if (!data.chunked) { is(e.loaded, response.length, "event.loaded matches response size" + test); } else { is(e.loaded - data.receivedBytes, response.length, "event.loaded grew by response size" + test); } } ok(e.loaded > data.receivedBytes, "event.loaded increased" + test); ok(e.loaded - data.receivedBytes <= data.pendingBytes, "event.loaded didn't increase too much" + test); if (!data.nodata && !data.blob) { var newData; ok(startsWith(response, data.receivedResult), "response strictly grew" + test); newData = response.substr(data.receivedResult.length); if (!data.encoded) { ok(newData.length > 0, "sanity check for progress" + test); } ok(startsWith(data.pendingResult, newData), "new data matches expected" + test); } is(e.lengthComputable, "total" in data, "lengthComputable" + test); if ("total" in data) { is(e.total, data.total, "total" + test); } if (!data.nodata && !data.blob) { data.pendingResult = data.pendingResult.substr(newData.length); } data.pendingBytes -= e.loaded - data.receivedBytes; data.receivedResult = response; data.receivedBytes = e.loaded; } function sendData(s) { var xhr = new XMLHttpRequest(); xhr.open("POST", "progressserver.sjs?send"); // The Blob constructor encodes String elements as UTF-8; // for straight bytes, manually convert to ArrayBuffer first var buffer = new Uint8Array(s.length); for (var i = 0; i < s.length; ++i) { buffer[i] = s.charCodeAt(i) & 0xff; }; xhr.send(new Blob([buffer])); } function closeConn() { log("in closeConn"); var xhr = new XMLHttpRequest(); xhr.open("POST", "progressserver.sjs?close"); xhr.send(); return xhr; } var longString = "long"; while(longString.length < 65536) longString += longString; function utf8encode(s) { return unescape(encodeURIComponent(s)); } function bufferToString(buffer) { return String.fromCharCode.apply(String, new Uint8Array(buffer)); } function runTests() { var xhr = new XMLHttpRequest(); xhr.onprogress = xhr.onload = xhr.onerror = xhr.onreadystatechange = xhr.onloadend = getEvent; var responseTypes = [{ type: "text", text: true }, { type: "arraybuffer", text: false, nodata: true }, { type: "blob", text: false, nodata: true, blob: true }, { type: "moz-blob", text: false, nodata: false, blob: true }, { type: "document", text: true, nodata: true }, { type: "json", text: true, nodata: true }, { type: "", text: true }, { type: "moz-chunked-text", text: true, chunked: true }, { type: "moz-chunked-arraybuffer", text: false, chunked: true }, ]; var responseType; var fileExpectedResult = ""; for (var i = 0; i < 65536; i++) { fileExpectedResult += String.fromCharCode(i & 255); } while (responseType = responseTypes.shift()) { let tests = [{ open: "Content-Type=text/plain", name: "simple test" }, { data: "hello world" }, { data: "\u0000\u0001\u0002\u0003" }, { data: longString }, { data: "x" }, { close: true }, { open: "Content-Type=text/plain&Content-Length=20", name: "with length", total: 20 }, // 5 bytes from the "ready" in the open step { data: "abcde" }, { data: "0123456789" }, { close: true }, { open: "Content-Type=application/xml", name: "without length, as xml" }, { data: "<out>" }, { data: "text" }, { data: "</foo>invalid" }, { close: true }, { open: "Content-Type=text/plain;charset%3dutf-8", name: "utf8 data", encoded: true }, { data: utf8encode("räksmörgås"), utf16: "räksmörgås" }, { data: utf8encode("Å").substr(0,1), utf16: "" }, { data: utf8encode("Å").substr(1), utf16: "Å" }, { data: utf8encode("aöb").substr(0,2), utf16: "a" }, { data: utf8encode("aöb").substr(2), utf16: "öb" }, { data: utf8encode("a\u867Eb").substr(0,3), utf16: "a" }, { data: utf8encode("a\u867Eb").substr(3,1), utf16: "\u867E" }, { data: utf8encode("a\u867Eb").substr(4), utf16: "b" }, { close: true }, ]; if (responseType.blob) { tests.push({ file: "file_XHR_binary2.bin", name: "cacheable data", total: 65536 }, { close: true }, { file: "file_XHR_binary2.bin", name: "cached data", total: 65536 }, { close: true }); } let testState = { index: 0 }; for (let i = 0; i < tests.length; ++i) { let test = tests[i]; testState.index++; if ("open" in test || "file" in test) { log("opening " + testState.name); testState = { name: test.name + " for " + responseType.type, index: 0, pendingResult: "ready", pendingBytes: 5, receivedResult: "", receivedBytes: 0, total: test.total, encoded: test.encoded, nodata: responseType.nodata, chunked: responseType.chunked, text: responseType.text, blob: responseType.blob, file: test.file }; xhr.onreadystatechange = null; if (testState.file) xhr.open("GET", test.file); else xhr.open("POST", "progressserver.sjs?open&" + test.open); xhr.responseType = responseType.type; xhr.send("ready"); xhr.onreadystatechange = getEvent; let e = yield undefined; is(e.type, "readystatechange", "should readystate to headers-received starting " + testState.name); is(xhr.readyState, xhr.HEADERS_RECEIVED, "should be in state HEADERS_RECEIVED starting " + testState.name); e = yield undefined; is(e.type, "readystatechange", "should readystate to loading starting " + testState.name); is(xhr.readyState, xhr.LOADING, "should be in state LOADING starting " + testState.name); if (typeof testState.total == "undefined") delete testState.total; } if ("file" in test) { testState.pendingBytes = testState.total; testState.pendingResult = fileExpectedResult; } if ("close" in test) { log("closing"); let xhrClose; if (!testState.file) xhrClose = closeConn(); e = yield undefined; is(e.type, "readystatechange", "should readystate to done closing " + testState.name); is(xhr.readyState, xhr.DONE, "should be in state DONE closing " + testState.name); log("readystate to 4"); if (responseType.chunked) { xhr.responseType; is(xhr.response, null, "chunked data has null response for " + testState.name); } e = yield undefined; is(e.type, "load", "should fire load closing " + testState.name); is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during load closing " + testState.name); log("got load"); if (responseType.chunked) { is(xhr.response, null, "chunked data has null response for " + testState.name); } e = yield undefined; is(e.type, "loadend", "should fire loadend closing " + testState.name); is(e.lengthComputable, e.total != 0, "length should " + (e.total == 0 ? "not " : "") + "be computable during loadend closing " + testState.name); log("got loadend"); // if we closed the connection using an explicit request, make sure that goes through before // running the next test in order to avoid reordered requests from closing the wrong // connection. if (xhrClose && xhrClose.readyState != xhrClose.DONE) { log("wait for closeConn to finish"); xhrClose.onloadend = getEvent; yield undefined; is(xhrClose.readyState, xhrClose.DONE, "closeConn finished"); } if (responseType.chunked) { is(xhr.response, null, "chunked data has null response for " + testState.name); } if (!testState.nodata && !responseType.blob || responseType.chunked) { // This branch intentionally left blank // Under these conditions we check the response during updateProgress } else if (responseType.type === "arraybuffer") { is(bufferToString(xhr.response), testState.pendingResult, "full response for " + testState.name); } else if (responseType.blob) { let reader = new FileReader; reader.readAsBinaryString(xhr.response); reader.onloadend = getEvent; yield undefined; is(reader.result, testState.pendingResult, "full response in blob for " + testState.name); } testState.name = ""; } if ("data" in test) { log("sending"); if (responseType.text) { testState.pendingResult += "utf16" in test ? test.utf16 : test.data; } else { testState.pendingResult += test.data; } testState.pendingBytes = test.data.length; sendData(test.data); } while(testState.pendingBytes) { log("waiting for more bytes: " + testState.pendingBytes); e = yield undefined; // Readystate can fire several times between each progress event. if (e.type === "readystatechange") continue; updateProgress(e, testState, "data for " + testState.name + "[" + testState.index + "]"); if (responseType.chunked) { testState.receivedResult = ""; } } if (!testState.nodata && !testState.blob) { is(testState.pendingResult, "", "should have consumed the expected result"); } log("done with this test"); } is(testState.name, "", "forgot to close last test"); } SimpleTest.finish(); yield undefined; } </script> </body> </html>