boomerangjs
Version:
boomerang always comes back, except when it hits something
161 lines (130 loc) • 4.09 kB
JavaScript
(function(w){
if (!w.XMLHttpRequest || !(new w.XMLHttpRequest()).addEventListener) {
return;
}
var a = document.createElement("A"),
xhrNative = w.XMLHttpRequest,
resources = [],
sendResource,
readyStateMap = ["uninitialized", "open", "responseStart", "domInteractive", "responseEnd"];
w.BOOMR = w.BOOMR || {};
// xhr object is what the AutoXHR plugin will interact with once it's loaded
BOOMR.xhr = {
/**
* Stops XHR collection and forwards any additional reporting to Boomerang
*
* @param {function} sendResourceCallback Callback for any ongoing monitoring
*
* @returns {object} Array of XHRs
*/
stop: function(sendResourceCallback) {
sendResource = sendResourceCallback;
// swap back in the native XHR function
w.XMLHttpRequest = xhrNative;
delete BOOMR.xhr;
// clear our queue after a moment
setTimeout(function(){
resources = [];
}, 10);
return resources;
}
};
// Gathers a high-resolution timestamp (when available), or falls back to Date.getTime()
var now = (function() {
try {
if ("performance" in w && w.performance.timing) {
return function() {
return Math.round(w.performance.now() + performance.timing.navigationStart);
};
}
}
catch (ignore) {
// NOP
}
return Date.now || function() {
return new Date().getTime();
};
})();
// Overwrite the native XHR with our monitored object
w.XMLHttpRequest = function() {
var xhr = new xhrNative(),
open = xhr.open;
xhr.open = function(method, url, async) {
// Normalize the URL
a.href = url;
var resource = {
timing: {},
url: a.href,
method: method
};
// Callback when the XHR is finished
function loadFinished() {
if (!resource.timing.loadEventEnd) {
resource.timing.loadEventEnd = now();
if ("performance" in w && w.performance && typeof w.performance.getEntriesByName === "function") {
var entries = w.performance.getEntriesByName(resource.url);
var entry = entries && entries.length && entries[entries.length - 1];
if (entry) {
var navSt = w.performance.timing.navigationStart;
if (entry.responseEnd !== 0) {
resource.timing.responseEnd = Math.round(navSt + entry.responseEnd);
}
if (entry.responseStart !== 0) {
resource.timing.responseStart = Math.round(navSt + entry.responseStart);
}
if (entry.startTime !== 0) {
resource.timing.requestStart = Math.round(navSt + entry.startTime);
}
}
}
if (sendResource) {
sendResource(resource);
}
else {
resources.push(resource);
}
}
}
function addListener(ename, stat) {
xhr.addEventListener(
ename,
function() {
if (ename === "readystatechange") {
resource.timing[readyStateMap[xhr.readyState]] = now();
if (xhr.readyState === 4) {
loadFinished();
}
}
else {
resource.status = (stat === undefined ? xhr.status : stat);
loadFinished();
}
},
false);
}
if (async === true) {
addListener("readystatechange");
}
else {
resource.synchronous = true;
}
addListener("load");
addListener("timeout", -1001);
addListener("error", -998);
addListener("abort", -999);
try {
open.apply(xhr, arguments);
var send = xhr.send;
xhr.send = function() {
resource.timing.requestStart = now();
send.apply(xhr, arguments);
};
}
catch (e) {
resource.status = -997;
loadFinished();
}
};
return xhr;
};
})(window);