Add POST support to XHR as well as .status and statusText support Also fixed all the XHR tests to actually run and work I learned from elliot that the it function has to take a "done" argument to be treated asynchronously. My utf8 conversion is a hack, but we deleted window.TextEncoder (it was a module) when we forked. We could resurrect TextEncoder (and probably should) but I've left that for a separate change. I also augmented sky_server to have a special /echo_post handler so we can test POST requests. R=esprehn@chromium.org BUG= Committed: https://chromium.googlesource.com/external/mojo/+/5ef81d249b841f44c9593ffb0158d64c7e0febfc Review URL: https://codereview.chromium.org/810523002
diff --git a/sky/framework/xmlhttprequest.sky b/sky/framework/xmlhttprequest.sky index 897ad97..eb8aa9e 100644 --- a/sky/framework/xmlhttprequest.sky +++ b/sky/framework/xmlhttprequest.sky
@@ -21,11 +21,24 @@ } } +// Somewhat hacky, but works. +function stringToUTF8Buffer(string) { + var string = unescape(encodeURIComponent(string)); + var charList = string.split(''); + var uintArray = []; + for (var i = 0; i < charList.length; i++) { + uintArray.push(charList[i].charCodeAt(0)); + } + return new Uint8Array(uintArray); +} + // https://xhr.spec.whatwg.org class XMLHttpRequest { constructor() { this[kPrivate] = new Private; this.responseType = ''; // Only text and arraybuffer support for now. + this.status = null; + this.statusText = null; } onload() { @@ -56,7 +69,7 @@ open(method, url) { var request = new loader.URLRequest(); - request.url = new URL(url, document.URL); + request.url = String(new URL(url, document.URL)); request.method = method; request.auto_follow_redirects = true; @@ -69,8 +82,29 @@ this[kPrivate].headers.set(header, value); } - send() { + send(body) { var priv = this[kPrivate]; + // Handle the body before the headers as it can affect Content-Type. + if (body) { + var bodyAsBufferView = null; + if (typeof(body) === "string") { + this.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); + bodyAsBufferView = stringToUTF8Buffer(body); + } else { + bodyAsBufferView = new Uint8Array(body); + } + var dataPipe = new core.createDataPipe(); + // FIXME: body is currently assumed to be an ArrayBuffer. + var writeResult = core.writeData(dataPipe.producerHandle, + bodyAsBufferView, core.WRITE_DATA_FLAG_ALL_OR_NONE); + core.close(dataPipe.producerHandle); + // FIXME: Much better error handling needed. + console.assert(writeResult.result === core.RESULT_OK); + console.assert(writeResult.numBytes === body.length); + // 'body' is actually an array of body segments. + priv.request.body = [dataPipe.consumerHandle]; + } + var requestHeaders = []; priv.headers.forEach(function(value, key) { requestHeaders.push(key + ': ' + value); @@ -85,14 +119,21 @@ var self = this; outstandingRequests.add(this); priv.loader.start(priv.request).then(function(result) { + self.status = result.response.status_code; + self.statusText = result.response.status_line; + if (result.response.error) + throw new Error(result.response.error.description); return core.drainData(result.response.body).then(function(result) { outstandingRequests.delete(self); priv.responseArrayBuffer = result.buffer; - // FIXME: Catch exceptions during onload so they don't trip onerror. - self.onload(); + // Use a setTimeout to avoid exceptions in onload tripping onerror. + window.setTimeout(function() { + self.onload(); + }); }); }).catch(function(error) { outstandingRequests.delete(self); + // Technically this should throw a ProgressEvent. self.onerror(error); }); }
diff --git a/sky/tests/framework/xmlhttprequest/empty-responseType.sky b/sky/tests/framework/xmlhttprequest/empty-responseType.sky index 19a132b..f97e254 100644 --- a/sky/tests/framework/xmlhttprequest/empty-responseType.sky +++ b/sky/tests/framework/xmlhttprequest/empty-responseType.sky
@@ -4,9 +4,7 @@ <import src="/sky/framework/xmlhttprequest.sky" as="XMLHttpRequest" /> <script> describe("xmlhttprequest.responseType", function() { - this.enableTimeouts(false); - - it("should default to text when empty", function() { + it("should default to text when empty", function(done) { var xhr = new XMLHttpRequest(); assert.equal(xhr.responseType, ""); xhr.responseType = 'foo';
diff --git a/sky/tests/framework/xmlhttprequest/responseType.sky b/sky/tests/framework/xmlhttprequest/responseType.sky index 5153093..f220009 100644 --- a/sky/tests/framework/xmlhttprequest/responseType.sky +++ b/sky/tests/framework/xmlhttprequest/responseType.sky
@@ -4,12 +4,11 @@ <import src="/sky/framework/xmlhttprequest.sky" as="XMLHttpRequest" /> <script> describe("xmlhttprequest.responseType", function() { - this.enableTimeouts(false); - - it("should support arraybuffer", function() { + it("should support arraybuffer", function(done) { var xhr = new XMLHttpRequest(); + xhr.responseType = 'arraybuffer'; xhr.onload = function() { - assert.typeOf(this.response, "arraybuffer", "Response is an arraybuffer\n"); + assert.instanceOf(xhr.response, ArrayBuffer, "Response is an ArrayBuffer\n"); done(); }; xhr.open("GET", "resources/pass.txt");
diff --git a/sky/tests/framework/xmlhttprequest/unicode-post-expected.txt b/sky/tests/framework/xmlhttprequest/unicode-post-expected.txt new file mode 100644 index 0000000..64c865d --- /dev/null +++ b/sky/tests/framework/xmlhttprequest/unicode-post-expected.txt
@@ -0,0 +1,5 @@ +Running 1 tests +ok 1 XMLHttpRequest should be able to post non-ascii +1 tests +1 pass +0 fail
diff --git a/sky/tests/framework/xmlhttprequest/unicode-post.sky b/sky/tests/framework/xmlhttprequest/unicode-post.sky new file mode 100644 index 0000000..e785741 --- /dev/null +++ b/sky/tests/framework/xmlhttprequest/unicode-post.sky
@@ -0,0 +1,22 @@ +<html> +<import src="/sky/tests/resources/chai.sky" /> +<import src="/sky/tests/resources/mocha.sky" /> +<import src="/sky/framework/xmlhttprequest.sky" as="XMLHttpRequest" /> +<script> +describe('XMLHttpRequest', function() { + it('should be able to post non-ascii', function(done) { + // example utf8, #114, "I can eat glass" in arabic. + // http://www.columbia.edu/~kermit/utf8.html + var utf8_text = "أنا قادر على أكل الزجاج و هذا لا يؤلمني."; + + var xhr = new XMLHttpRequest(); + xhr.onload = function() { + assert.equal(this.responseText, utf8_text); + done(); + }; + xhr.open("GET", "/echo_post"); + xhr.send(utf8_text); + }); +}); +</script> +</html>
diff --git a/sky/tests/framework/xmlhttprequest/xhr-does-not-exist-expected.txt b/sky/tests/framework/xmlhttprequest/xhr-does-not-exist-expected.txt new file mode 100644 index 0000000..fd4506d --- /dev/null +++ b/sky/tests/framework/xmlhttprequest/xhr-does-not-exist-expected.txt
@@ -0,0 +1,5 @@ +Running 1 tests +ok 1 xmlhttprequest should call onerror when endpoint does not exist +1 tests +1 pass +0 fail
diff --git a/sky/tests/framework/xmlhttprequest/xhr-does-not-exist.sky b/sky/tests/framework/xmlhttprequest/xhr-does-not-exist.sky new file mode 100644 index 0000000..e88c4c9 --- /dev/null +++ b/sky/tests/framework/xmlhttprequest/xhr-does-not-exist.sky
@@ -0,0 +1,26 @@ +<sky> +<import src="/sky/tests/resources/chai.sky" /> +<import src="/sky/tests/resources/mocha.sky" /> +<import src="/sky/framework/xmlhttprequest.sky" as="XMLHttpRequest" /> +<script> +describe("xmlhttprequest", function() { + it("should call onerror when endpoint does not exist", function(done) { + var xhr = new XMLHttpRequest(); + xhr.open("GET", "does_not_exist.html"); + xhr.onerror = function() { + assert.fail("onload", "onerror", "onerror should not be called."); + done(); + } + xhr.onload = function() { + // Missing files are application-level errors, not network errors + // so onload fires, not onerror. + assert.equal(xhr.status, 404); + assert.equal(xhr.statusText, "HTTP/1.1 404 Not Found", + "status text should also be 404"); + done(); + } + xhr.send(); + }); +}); +</script> +</sky>
diff --git a/sky/tests/framework/xmlhttprequest/xhr-relative.sky b/sky/tests/framework/xmlhttprequest/xhr-relative.sky index 19a9034..632810f 100644 --- a/sky/tests/framework/xmlhttprequest/xhr-relative.sky +++ b/sky/tests/framework/xmlhttprequest/xhr-relative.sky
@@ -4,8 +4,6 @@ <import src="/sky/framework/xmlhttprequest.sky" as="XMLHttpRequest" /> <script> describe('XMLHttpRequest', function() { - this.enableTimeouts(false); - it('should be able to fetch relative urls', function(done) { var xhr = new XMLHttpRequest();
diff --git a/sky/tests/framework/xmlhttprequest/xhr.sky b/sky/tests/framework/xmlhttprequest/xhr.sky index c19f36d..d730963 100644 --- a/sky/tests/framework/xmlhttprequest/xhr.sky +++ b/sky/tests/framework/xmlhttprequest/xhr.sky
@@ -4,8 +4,6 @@ <import src="/sky/framework/xmlhttprequest.sky" as="XMLHttpRequest" /> <script> describe('XMLHttpRequest', function() { - this.enableTimeouts(false); - it('should be able to fetch text files', function(done) { var xhr = new XMLHttpRequest();
diff --git a/sky/tools/skygo/sky_server.go b/sky/tools/skygo/sky_server.go index c69fdd5..e851487 100644 --- a/sky/tools/skygo/sky_server.go +++ b/sky/tools/skygo/sky_server.go
@@ -6,6 +6,7 @@ import ( "flag" + "io/ioutil" "net/http" "path" "strings" @@ -38,6 +39,11 @@ genRoot := path.Join(root, "out", *configuration, "gen") http.Handle("/", skyHandler(root)) + http.HandleFunc("/echo_post", func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + body, _ := ioutil.ReadAll(r.Body) + w.Write(body) + }) http.Handle("/mojo/public/", http.StripPrefix("/mojo/public/", skyHandler(path.Join(genRoot, "mojo", "public")))) http.Handle("/mojo/services/", http.StripPrefix("/mojo/services/", skyHandler(path.Join(genRoot, "mojo", "services")))) http.Handle("/sky/services/", http.StripPrefix("/sky/services/", skyHandler(path.Join(genRoot, "sky", "services"))))
diff --git a/sky/tools/skygo/sky_server.sha1 b/sky/tools/skygo/sky_server.sha1 index 38a08c7..8b7306e 100644 --- a/sky/tools/skygo/sky_server.sha1 +++ b/sky/tools/skygo/sky_server.sha1
@@ -1 +1 @@ -7b6d0d60949ad71a35a88430b2eb7d70fe7334b6 \ No newline at end of file +f6b808791e8ab0290cb18bc8b444159074c395ae \ No newline at end of file