@tdb/web
Version:
Common condiguration for serving a web-site and testing web-based UI components.
270 lines (246 loc) • 8.35 kB
Plain Text
// 1. TextDecoder
// 2. TextDecoder + stream option (Firefox 38 + - how to detect ?)
// 3. Promise.prototype.finally -
// 4. fetch +
// 5. Response.prototype.body +
// 6. ReadableStream.prototype.getReader (no Firefox support yet)
// 7. AbortController - (Firefox 57)
var s = "";
if (window.Uint8Array != null && window.TextDecoder != null) {
var b = function (byte) {
var x = new Uint8Array(1);
x[0] = byte;
return x;
};
var textDecoder = new TextDecoder();
var chunk = textDecoder.decode(b(208), {stream: true}) + textDecoder.decode(b(176), {stream: true});
s = chunk;
};
console.log('TextDecoder', typeof window.TextDecoder);
console.log('TextDecoder+stream', chunk === 'а');
console.log('Promise#finally', window.Promise != null ? typeof window.Promise.prototype.finally : typeof undefined);
console.log('fetch', typeof window.fetch);
console.log('Response#body', window.Response != null && 'body' in window.Response.prototype);
console.log('ReadableStream#getReader', window.ReadableStream != null ? typeof window.ReadableStream.prototype.getReader : typeof undefined);
console.log('AbortController', typeof window.AbortController);
// an example from https://fetch.spec.whatwg.org/
var url = 'https://matrixcalc.org/jstest/tests.php?events';
function consume(reader) {
var responseText = '';
var pump = function () {
return reader.read().then(function (result) {
if (result.done) {
console.log('responseText', responseText);
return;
}
responseText += String.fromCharCode.apply(undefined, result.value);
return pump();
});
};
return pump();
}
if (window.fetch != null && window.TextDecoder != null) {
fetch(url).then(function (response) {
var body = response.body;
console.log('response.body', typeof body);
if (body != null) {
console.log('response.body.getReader', typeof body.getReader);
if (typeof body.getReader == 'function') {
consume(body.getReader());
}
}
});
}
setTimeout(function () {
console.log('done');
}, 1000);
// Firefox < 40 (no "stream" option), IE, Edge
function TextDecoderPolyfill() {
}
//TODO: streaming
TextDecoderPolyfill.prototype.decode = function (octets) {
var string = "";
var i = 0;
while (i < octets.length) {
var octet = octets[i];
var bytesNeeded = 0;
var codePoint = 0;
if (octet <= 0x7F) {
bytesNeeded = 0;
codePoint = octet & 0xFF;
} else if (octet <= 0xDF) {
bytesNeeded = 1;
codePoint = octet & 0x1F;
} else if (octet <= 0xEF) {
bytesNeeded = 2;
codePoint = octet & 0x0F;
} else if (octet <= 0xF4) {
bytesNeeded = 3;
codePoint = octet & 0x07;
}
if (octets.length - i - bytesNeeded > 0) {
var k = 0;
while (k < bytesNeeded) {
octet = octets[i + k + 1];
codePoint = (codePoint << 6) | (octet & 0x3F);
k += 1;
}
} else {
codePoint = 0xFFFD;
bytesNeeded = octets.length - i;
}
string += String.fromCodePoint(codePoint);
i += bytesNeeded + 1;
}
return string
};
if (Promise.prototype.finally == undefined) {
Promise.prototype.finally = function (callback) {
return this.then(function (result) {
return Promise.resolve(callback()).then(function () {
return result;
});
}, function (error) {
return Promise.resolve(callback()).then(function () {
throw error;
});
});
};
}
function FetchTransport() {
//this.controller = undefined;
this.reader = undefined;
this.lastRequestId = 1;
}
FetchTransport.prototype.open = function (onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) {
// cache: "no-store"
// https://bugs.chromium.org/p/chromium/issues/detail?id=453190
this.cancel();
var textDecoder = new TextDecoder();
var that = this;
var lastRequestId = this.lastRequestId;
this.controller = new AbortController();
fetch(url, {
headers: headers,
credentials: withCredentials ? "include" : "same-origin",
signal: this.controller.signal
}).then(function (response) {
if (lastRequestId === that.lastRequestId) {
that.reader = response.body.getReader();
onStartCallback(response.status, response.statusText, response.headers.get("Content-Type"));
return new Promise(function (resolve, reject) {
var readNextChunk = function () {
if (that.reader != undefined) {
that.reader.read().then(function (result) {
if (result.done) {
//Note: bytes in textDecoder are ignored
resolve(undefined);
} else {
var chunk = textDecoder.decode(result.value, {stream: true});
//var chunk = String.fromCharCode.apply(undefined, result.value);
onProgressCallback(chunk);
readNextChunk();
}
})["catch"](reject);
} else {
resolve(undefined);
}
};
readNextChunk();
});
}
return undefined;
})["finally"](function () {
onFinishCallback();
});
};
FetchTransport.prototype.cancel = function () {
this.lastRequestId += 1;
if (this.reader != undefined) {
this.reader.cancel();
this.reader = undefined;
}
//if (this.controller != undefined) {
// this.controller.abort();
//}
};
function XHRTransport(xhr) {
this.xhr = xhr;
}
XHRTransport.prototype.open = function (onStartCallback, onProgressCallback, onFinishCallback, url, withCredentials, headers) {
var xhr = this.xhr;
xhr.open("GET", url);
var offset = 0;
xhr.onprogress = function () {
var responseText = xhr.responseText;
var chunk = responseText.slice(offset);
offset += chunk.length;
onProgressCallback(chunk);
};
xhr.onreadystatechange = function () {
if (xhr.readyState === 2) {
var status = xhr.status;
var statusText = xhr.statusText;
var contentType = xhr.getResponseHeader("Content-Type");
onStartCallback(status, statusText, contentType);
} else if (xhr.readyState === 4) {
onFinishCallback();
}
};
xhr.withCredentials = withCredentials;
xhr.responseType = "text";
for (var name in headers) {
if (Object.prototype.hasOwnProperty.call(headers, name)) {
xhr.setRequestHeader(name, headers[name]);
}
}
xhr.send();
};
XHRTransport.prototype.cancel = function () {
var xhr = this.xhr;
xhr.abort();
};
function EventTarget() {
this._listeners = new Map();
}
function throwError(e) {
setTimeout(function () {
throw e;
}, 0);
}
EventTarget.prototype.dispatchEvent = function (event) {
event.target = this;
var typeListeners = this._listeners.get(event.type);
if (typeListeners != undefined) {
typeListeners.forEach(function (listener) {
try {
if (typeof listener.handleEvent === "function") {
listener.handleEvent(event);
} else {
listener.call(this, event);
}
} catch (e) {
throwError(e);
}
}, this);
}
};
EventTarget.prototype.addEventListener = function (type, listener) {
var listeners = this._listeners;
var typeListeners = listeners.get(type);
if (typeListeners == undefined) {
typeListeners = new Set();
listeners.set(type, typeListeners);
}
typeListeners.add(listener);
};
EventTarget.prototype.removeEventListener = function (type, listener) {
var listeners = this._listeners;
var typeListeners = listeners.get(type);
if (typeListeners != undefined) {
typeListeners["delete"](listener);
if (typeListeners.size === 0) {
listeners["delete"](type);
}
}
};