Header Snippets are small pieces of JavaScript code that can be injected into a Page's HTML so that even before Boomerang loads, components of the page load experience (such as JavaScript errors) are still fully measured.
These Header Snippets should be added as inline <script>
tags within the <head>
of a document, ideally right before the Non-Blocking Loader Snippet is included.
JavaScript Errors (for BOOMR.plugins.Errors
)
Boomerang listens for JavaScript errors via the BOOMR.plugins.Errors
plugin by monitoring the onerror
global event handler. However, JavaScript errors that occur prior to Boomerang being loaded will be missed.
This Errors Snippet monitors for JavaScript Errors via the onerror
event and will "hand off" the errors to Boomerang once it has loaded.
Source:
<script>
(function(w){
w.BOOMR = w.BOOMR || {};
w.BOOMR.globalOnErrorOrig = w.BOOMR.globalOnError = w.onerror;
w.BOOMR.globalErrors = [];
// 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 global onerror to listen for errors, but forward all messages to the original one if it exists
w.onerror = function BOOMR_plugins_errors_onerror(message, fileName, lineNumber, columnNumber, error) {
if (w.BOOMR.version) {
// If Boomerang has already loaded, the only reason this function would still be alive would be if
// we're in the chain from another handler that overwrote window.onerror. In that case, we should
// run globalOnErrorOrig which presumably hasn't been overwritten by Boomerang.
if (typeof w.BOOMR.globalOnErrorOrig === "function") {
w.BOOMR.globalOnErrorOrig.apply(w, arguments);
}
return;
}
// Save this error for when Boomerang loads
if (typeof error !== "undefined" && error !== null) {
error.timestamp = now();
w.BOOMR.globalErrors.push(error);
}
else {
w.BOOMR.globalErrors.push({
message: message,
fileName: fileName,
lineNumber: lineNumber,
columnNumber: columnNumber,
noStack: true,
timestamp: now()
});
}
// Call the original window.onerror
if (typeof w.BOOMR.globalOnError === "function") {
w.BOOMR.globalOnError.apply(w, arguments);
}
};
// make it easier to detect this is our wrapped handler
w.onerror._bmr = true;
})(window);
</script>
Minified:
<script>(function(i){i.BOOMR=i.BOOMR||{};i.BOOMR.globalOnErrorOrig=i.BOOMR.globalOnError=i.onerror;i.BOOMR.globalErrors=[];var a=function(){try{if("performance"in i&&i.performance.timing){return function(){return Math.round(i.performance.now()+performance.timing.navigationStart)}}}catch(r){}return Date.now||function(){return(new Date).getTime()}}();i.onerror=function r(n,o,e,t,O){if(i.BOOMR.version){if(typeof i.BOOMR.globalOnErrorOrig==="function"){i.BOOMR.globalOnErrorOrig.apply(i,arguments)}return}if(typeof O!=="undefined"&&O!==null){O.timestamp=a();i.BOOMR.globalErrors.push(O)}else{i.BOOMR.globalErrors.push({message:n,fileName:o,lineNumber:e,columnNumber:t,noStack:true,timestamp:a()})}if(typeof i.BOOMR.globalOnError==="function"){i.BOOMR.globalOnError.apply(i,arguments)}};i.onerror._bmr=true})(window);</script>
Frame Rate (for BOOMR.plugins.Continuity
)
The BOOMR.plugins.Continuity
plugin measures performance and user experience metrics beyond just the
traditional Page Load timings.
One of the metrics that the BOOMR.plugins.Continuity
plugin measures is Frame Rate (FSP) data via
requestAnimationFrame
. However,
FPS data is "real-time" and is not available for scripts like Boomerang that may load later in the page load process.
This Header Snippet will start monitoring
requestAnimationFrame
and hand the
FPS data off to Boomerang once it has loaded.
Source:
<script>
(function() {
if (window && window.requestAnimationFrame) {
window.BOOMR = window.BOOMR || {};
window.BOOMR.fpsLog = [];
function frame(now) {
// window.BOOMR.fpsLog will get deleted once Boomerang has loaded
if (window.BOOMR.fpsLog) {
window.BOOMR.fpsLog.push(Math.round(now));
// if we've added more than 30 seconds of data, stop
if (window.BOOMR.fpsLog.length > 30 * 60) {
return;
}
window.requestAnimationFrame(frame);
}
}
window.requestAnimationFrame(frame);
}
})();
</script>
Minified:
<script>(function(){if(window&&window.requestAnimationFrame){window.BOOMR=window.BOOMR||{};window.BOOMR.fpsLog=[];function i(n){if(window.BOOMR.fpsLog){window.BOOMR.fpsLog.push(Math.round(n));if(window.BOOMR.fpsLog.length>30*60){return}window.requestAnimationFrame(i)}}window.requestAnimationFrame(i)}})();</script>
Instrumenting XMLHttpRequests (for BOOMR.plugins.AutoXHR
)
The BOOMR.plugins.AutoXHR
plugin monitors XMLHttpRequests
on the page.
The performance data of XMLHttpRequests
that start before Boomerang is loaded may not be monitored.
This Header Snippet will start monitoring XMLHttpRequests
and hand the performance data off to Boomerang once it
has loaded.
Source:
<script>
(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);
</script>
Minified:
<script>(function(f){if(!f.XMLHttpRequest||!(new f.XMLHttpRequest).addEventListener){return}var c=document.createElement("A"),t=f.XMLHttpRequest,p=[],d,m=["uninitialized","open","responseStart","domInteractive","responseEnd"];f.BOOMR=f.BOOMR||{};BOOMR.xhr={stop:function(e){d=e;f.XMLHttpRequest=t;delete BOOMR.xhr;setTimeout(function(){p=[]},10);return p}};var g=function(){try{if("performance"in f&&f.performance.timing){return function(){return Math.round(f.performance.now()+performance.timing.navigationStart)}}}catch(e){}return Date.now||function(){return(new Date).getTime()}}();f.XMLHttpRequest=function(){var s=new t,u=s.open;s.open=function(e,t,n){c.href=t;var r={timing:{},url:c.href,method:e};function i(){if(!r.timing.loadEventEnd){r.timing.loadEventEnd=g();if("performance"in f&&f.performance&&typeof f.performance.getEntriesByName==="function"){var e=f.performance.getEntriesByName(r.url);var t=e&&e.length&&e[e.length-1];if(t){var n=f.performance.timing.navigationStart;if(t.responseEnd!==0){r.timing.responseEnd=Math.round(n+t.responseEnd)}if(t.responseStart!==0){r.timing.responseStart=Math.round(n+t.responseStart)}if(t.startTime!==0){r.timing.requestStart=Math.round(n+t.startTime)}}}if(d){d(r)}else{p.push(r)}}}function a(e,t){s.addEventListener(e,function(){if(e==="readystatechange"){r.timing[m[s.readyState]]=g();if(s.readyState===4){i()}}else{r.status=t===undefined?s.status:t;i()}},false)}if(n===true){a("readystatechange")}else{r.synchronous=true}a("load");a("timeout",-1001);a("error",-998);a("abort",-999);try{u.apply(s,arguments);var o=s.send;s.send=function(){r.timing.requestStart=g();o.apply(s,arguments)}}catch(e){r.status=-997;i()}};return s}})(window);</script>