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 | |
35 // https://xhr.spec.whatwg.org | 24 // https://xhr.spec.whatwg.org |
36 class XMLHttpRequest { | 25 class XMLHttpRequest { |
37 constructor() { | 26 constructor() { |
38 this[kPrivate] = new Private; | 27 this[kPrivate] = new Private; |
39 this.responseType = ''; // Only text and arraybuffer support for now. | 28 this.responseType = ''; // Only text and arraybuffer support for now. |
40 this.status = null; | |
41 this.statusText = null; | |
42 } | 29 } |
43 | 30 |
44 onload() { | 31 onload() { |
45 } | 32 } |
46 | 33 |
47 onerror(error) { | 34 onerror(error) { |
48 } | 35 } |
49 | 36 |
50 get responseText() { | 37 get responseText() { |
51 if (this.responseType !== '' && this.responseType !== 'text') | 38 if (this.responseType !== '' && this.responseType !== 'text') |
(...skipping 23 matching lines...) Expand all Loading... |
75 | 62 |
76 var priv = this[kPrivate]; | 63 var priv = this[kPrivate]; |
77 priv.request = request; | 64 priv.request = request; |
78 priv.headers.clear(); | 65 priv.headers.clear(); |
79 } | 66 } |
80 | 67 |
81 setRequestHeader(header, value) { | 68 setRequestHeader(header, value) { |
82 this[kPrivate].headers.set(header, value); | 69 this[kPrivate].headers.set(header, value); |
83 } | 70 } |
84 | 71 |
85 send(body) { | 72 send() { |
86 var priv = this[kPrivate]; | 73 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 | |
108 var requestHeaders = []; | 74 var requestHeaders = []; |
109 priv.headers.forEach(function(value, key) { | 75 priv.headers.forEach(function(value, key) { |
110 requestHeaders.push(key + ': ' + value); | 76 requestHeaders.push(key + ': ' + value); |
111 }); | 77 }); |
112 priv.request.headers = requestHeaders; | 78 priv.request.headers = requestHeaders; |
113 | 79 |
114 // FIXME: Factor this into the JS bindings. | 80 // FIXME: Factor this into the JS bindings. |
115 var pipe = new core.createMessagePipe(); | 81 var pipe = new core.createMessagePipe(); |
116 priv.networkService.createURLLoader(pipe.handle1); | 82 priv.networkService.createURLLoader(pipe.handle1); |
117 priv.loader = shell.wrapHandle(pipe.handle0, loader.URLLoader); | 83 priv.loader = shell.wrapHandle(pipe.handle0, loader.URLLoader); |
118 | 84 |
119 var self = this; | 85 var self = this; |
120 outstandingRequests.add(this); | 86 outstandingRequests.add(this); |
121 priv.loader.start(priv.request).then(function(result) { | 87 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); | |
126 return core.drainData(result.response.body).then(function(result) { | 88 return core.drainData(result.response.body).then(function(result) { |
127 outstandingRequests.delete(self); | 89 outstandingRequests.delete(self); |
128 priv.responseArrayBuffer = result.buffer; | 90 priv.responseArrayBuffer = result.buffer; |
129 // Use a setTimeout to avoid exceptions in onload tripping onerror. | 91 // FIXME: Catch exceptions during onload so they don't trip onerror. |
130 window.setTimeout(function() { | 92 self.onload(); |
131 self.onload(); | |
132 }); | |
133 }); | 93 }); |
134 }).catch(function(error) { | 94 }).catch(function(error) { |
135 outstandingRequests.delete(self); | 95 outstandingRequests.delete(self); |
136 // Technically this should throw a ProgressEvent. | |
137 self.onerror(error); | 96 self.onerror(error); |
138 }); | 97 }); |
139 } | 98 } |
140 } | 99 } |
141 | 100 |
142 module.exports = XMLHttpRequest; | 101 module.exports = XMLHttpRequest; |
143 </script> | 102 </script> |
OLD | NEW |