OLD | NEW |
1 <import src="/mojo/public/sky/core.sky" as="core" /> | 1 <import src="/mojo/public/sky/core.sky" as="core" /> |
2 <import src="/mojo/public/sky/unicode.sky" as="unicode" /> | 2 <import src="/mojo/public/sky/unicode.sky" as="unicode" /> |
3 <import src="/mojo/services/network/public/interfaces/network_service.mojom.sky"
as="net" /> | 3 <import src="/mojo/services/network/public/interfaces/network_service.mojom.sky"
as="net" /> |
4 <import src="/mojo/services/network/public/interfaces/url_loader.mojom.sky" as="
loader" /> | 4 <import src="/mojo/services/network/public/interfaces/url_loader.mojom.sky" as="
loader" /> |
5 <import src="/sky/framework/shell.sky" as="shell" /> | 5 <import src="/sky/framework/shell.sky" as="shell" /> |
6 <script> | 6 <script> |
7 // XHR keeps itself alive. | 7 // XHR keeps itself alive. |
8 var outstandingRequests = new Set(); | 8 var outstandingRequests = new Set(); |
9 | 9 |
10 const kPrivate = Symbol("XMLHttpRequestPrivate"); | 10 const kPrivate = Symbol("XMLHttpRequestPrivate"); |
11 | 11 |
12 class Private { | 12 class Private { |
13 constructor() { | 13 constructor() { |
14 this.networkService = shell.connectToService( | 14 this.networkService = shell.connectToService( |
15 "mojo:network_service", net.NetworkService); | 15 "mojo:network_service", net.NetworkService); |
16 this.request = null; | 16 this.request = null; |
17 this.loader = null; | 17 this.loader = null; |
18 this.headers = new Map(); | 18 this.headers = new Map(); |
19 this.responseArrayBuffer = null; | 19 this.responseArrayBuffer = null; |
20 this.responseText = null; // Cached to avoid re-decoding each access. | 20 this.responseText = null; // Cached to avoid re-decoding each access. |
21 } | 21 } |
22 } | 22 } |
23 | 23 |
| 24 // Somewhat hacky, but works. |
| 25 function stringToUTF8Buffer(string) { |
| 26 var string = unescape(encodeURIComponent(string)); |
| 27 var charList = string.split(''); |
| 28 var uintArray = []; |
| 29 for (var i = 0; i < charList.length; i++) { |
| 30 uintArray.push(charList[i].charCodeAt(0)); |
| 31 } |
| 32 return new Uint8Array(uintArray); |
| 33 } |
| 34 |
24 // https://xhr.spec.whatwg.org | 35 // https://xhr.spec.whatwg.org |
25 class XMLHttpRequest { | 36 class XMLHttpRequest { |
26 constructor() { | 37 constructor() { |
27 this[kPrivate] = new Private; | 38 this[kPrivate] = new Private; |
28 this.responseType = ''; // Only text and arraybuffer support for now. | 39 this.responseType = ''; // Only text and arraybuffer support for now. |
| 40 this.status = null; |
| 41 this.statusText = null; |
29 } | 42 } |
30 | 43 |
31 onload() { | 44 onload() { |
32 } | 45 } |
33 | 46 |
34 onerror(error) { | 47 onerror(error) { |
35 } | 48 } |
36 | 49 |
37 get responseText() { | 50 get responseText() { |
38 if (this.responseType !== '' && this.responseType !== 'text') | 51 if (this.responseType !== '' && this.responseType !== 'text') |
(...skipping 23 matching lines...) Expand all Loading... |
62 | 75 |
63 var priv = this[kPrivate]; | 76 var priv = this[kPrivate]; |
64 priv.request = request; | 77 priv.request = request; |
65 priv.headers.clear(); | 78 priv.headers.clear(); |
66 } | 79 } |
67 | 80 |
68 setRequestHeader(header, value) { | 81 setRequestHeader(header, value) { |
69 this[kPrivate].headers.set(header, value); | 82 this[kPrivate].headers.set(header, value); |
70 } | 83 } |
71 | 84 |
72 send() { | 85 send(body) { |
73 var priv = this[kPrivate]; | 86 var priv = this[kPrivate]; |
| 87 // Handle the body before the headers as it can affect Content-Type. |
| 88 if (body) { |
| 89 var bodyAsBufferView = null; |
| 90 if (typeof(body) === "string") { |
| 91 this.setRequestHeader("Content-Type", "text/plain;charset=UTF-8"); |
| 92 bodyAsBufferView = stringToUTF8Buffer(body); |
| 93 } else { |
| 94 bodyAsBufferView = new Uint8Array(body); |
| 95 } |
| 96 var dataPipe = new core.createDataPipe(); |
| 97 // FIXME: body is currently assumed to be an ArrayBuffer. |
| 98 var writeResult = core.writeData(dataPipe.producerHandle, |
| 99 bodyAsBufferView, core.WRITE_DATA_FLAG_ALL_OR_NONE); |
| 100 core.close(dataPipe.producerHandle); |
| 101 // FIXME: Much better error handling needed. |
| 102 console.assert(writeResult.result === core.RESULT_OK); |
| 103 console.assert(writeResult.numBytes === body.length); |
| 104 // 'body' is actually an array of body segments. |
| 105 priv.request.body = [dataPipe.consumerHandle]; |
| 106 } |
| 107 |
74 var requestHeaders = []; | 108 var requestHeaders = []; |
75 priv.headers.forEach(function(value, key) { | 109 priv.headers.forEach(function(value, key) { |
76 requestHeaders.push(key + ': ' + value); | 110 requestHeaders.push(key + ': ' + value); |
77 }); | 111 }); |
78 priv.request.headers = requestHeaders; | 112 priv.request.headers = requestHeaders; |
79 | 113 |
80 // FIXME: Factor this into the JS bindings. | 114 // FIXME: Factor this into the JS bindings. |
81 var pipe = new core.createMessagePipe(); | 115 var pipe = new core.createMessagePipe(); |
82 priv.networkService.createURLLoader(pipe.handle1); | 116 priv.networkService.createURLLoader(pipe.handle1); |
83 priv.loader = shell.wrapHandle(pipe.handle0, loader.URLLoader); | 117 priv.loader = shell.wrapHandle(pipe.handle0, loader.URLLoader); |
84 | 118 |
85 var self = this; | 119 var self = this; |
86 outstandingRequests.add(this); | 120 outstandingRequests.add(this); |
87 priv.loader.start(priv.request).then(function(result) { | 121 priv.loader.start(priv.request).then(function(result) { |
| 122 self.status = result.response.status_code; |
| 123 self.statusText = result.response.status_line; |
| 124 if (result.response.error) |
| 125 throw new Error(result.response.error.description); |
88 return core.drainData(result.response.body).then(function(result) { | 126 return core.drainData(result.response.body).then(function(result) { |
89 outstandingRequests.delete(self); | 127 outstandingRequests.delete(self); |
90 priv.responseArrayBuffer = result.buffer; | 128 priv.responseArrayBuffer = result.buffer; |
91 // FIXME: Catch exceptions during onload so they don't trip onerror. | 129 // Use a setTimeout to avoid exceptions in onload tripping onerror. |
92 self.onload(); | 130 window.setTimeout(function() { |
| 131 self.onload(); |
| 132 }); |
93 }); | 133 }); |
94 }).catch(function(error) { | 134 }).catch(function(error) { |
95 outstandingRequests.delete(self); | 135 outstandingRequests.delete(self); |
| 136 // Technically this should throw a ProgressEvent. |
96 self.onerror(error); | 137 self.onerror(error); |
97 }); | 138 }); |
98 } | 139 } |
99 } | 140 } |
100 | 141 |
101 module.exports = XMLHttpRequest; | 142 module.exports = XMLHttpRequest; |
102 </script> | 143 </script> |
OLD | NEW |