blob: 7df72b17532d6681a50e1d1f266688921c33a6d2 [file] [log] [blame]
<import src="/gen/mojo/public/sky/core.sky" as="core" />
<import src="/gen/mojo/public/sky/unicode.sky" as="unicode" />
<import src="/gen/mojo/services/network/public/interfaces/network_service.mojom.sky" as="net" />
<import src="/gen/mojo/services/network/public/interfaces/url_loader.mojom.sky" as="loader" />
<import src="shell.sky" as="shell" />
<script>
// XHR keeps itself alive.
var outstandingRequests = new Set();
const kPrivate = Symbol("XMLHttpRequestPrivate");
class Private {
constructor() {
this.networkService = shell.connectToService(
"mojo:network_service", net.NetworkService);
this.request = null;
this.loader = null;
this.headers = new Map();
this.responseArrayBuffer = null;
this.responseText = null; // Cached to avoid re-decoding each access.
}
}
// 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() {
}
onerror(error) {
}
get responseText() {
if (this.responseType !== '' && this.responseType !== 'text')
throw 'Non-text responseType ' + this.responseType;
if (this[kPrivate].responseArrayBuffer === null)
return null;
if (this[kPrivate].responseText === null) {
var intArray = new Uint8Array(this[kPrivate].responseArrayBuffer);
this[kPrivate].responseText = unicode.decodeUtf8String(intArray);
}
return this[kPrivate].responseText;
}
get response() {
if (this.responseType === 'text' || this.responseType == '')
return this.responseText;
else if (this.responseType === 'arraybuffer')
return this[kPrivate].responseArrayBuffer;
throw 'Unknown responseType ' + this.responseType;
}
open(method, url) {
var request = new loader.URLRequest();
request.url = String(new URL(url, document.URL));
request.method = method;
request.auto_follow_redirects = true;
var priv = this[kPrivate];
priv.request = request;
priv.headers.clear();
}
setRequestHeader(header, value) {
this[kPrivate].headers.set(header, value);
}
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");
var bodyAsBufferView = new Uint8Array(unicode.utf8Length(body));
unicode.encodeUtf8String(body, bodyAsBufferView);
} 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);
});
priv.request.headers = requestHeaders;
priv.networkService.createURLLoader(function(urlLoaderProxy) {
priv.loader = urlLoaderProxy;
});
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;
// 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);
});
}
}
module.exports = XMLHttpRequest;
</script>