diff --git a/.eslintrc b/.eslintrc
index 293a92806..3af0afdc2 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -84,6 +84,13 @@
"operator-linebreak": [2, "after"],
"space-in-parens": [2, "never"],
"no-debugger": "error",
+
+ //
+ // To enable soon
+ //
+
+ //"require-jsdoc": "error",
+ //"valid-jsdoc": "error",
//
// Disabled rules
diff --git a/Gruntfile.js b/Gruntfile.js
index 4f0853b88..42921b5e8 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -685,7 +685,8 @@ module.exports = function() {
destination: "build/doc",
package: "package.json",
readme: "README.md",
- configure: "jsdoc.conf.json"
+ configure: "jsdoc.conf.json",
+ template: "doc-template"
}
}
},
@@ -728,7 +729,8 @@ module.exports = function() {
files: [
"boomerang.js",
"plugins/*.js",
- "doc/**/**"
+ "doc/**/**",
+ "README.md"
],
tasks: ["clean", "jsdoc"]
}
diff --git a/README.md b/README.md
index 79d918720..87422124e 100644
--- a/README.md
+++ b/README.md
@@ -1,104 +1,199 @@
-Copyright (c) 2011, Yahoo! Inc. All rights reserved.
-Copyright (c) 2011-2012, Log-Normal Inc. All rights reserved.
-Copyright (c) 2012-2017 SOASTA, Inc. All rights reserved.
-Copyright (c) 2017, Akamai Technologies, Inc. All rights reserved.
-
-Copyrights licensed under the BSD License. See the accompanying LICENSE.txt file for terms.
-
-boomerang always comes back, except when it hits something.
-
-summary
----
-
-[![Join the chat at https://gitter.im/SOASTA/boomerang](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/SOASTA/boomerang?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
-
-boomerang is a JavaScript library that measures the page load time experienced by real users, commonly called RUM.
-
-Apart from page load time, boomerang measures a whole bunch of performance characteristics of your user's web browsing experience. All you have to do is stick it into your web pages and call the
-init() method.
-
-usage
----
-
+* _Copyright (c) 2011, Yahoo! Inc. All rights reserved._
+* _Copyright (c) 2011-2012, Log-Normal Inc. All rights reserved._
+* _Copyright (c) 2012-2017 SOASTA, Inc. All rights reserved._
+* _Copyright (c) 2017, Akamai Technologies, Inc. All rights reserved._
+* _Copyrights licensed under the BSD License. See the accompanying LICENSE.txt file for terms._
+
+**boomerang always comes back, except when it hits something.**
+
+# Summary
+
+[![Join the chat at https://gitter.im/SOASTA/boomerang](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/SOASTA/boomerang)
+
+boomerang is a JavaScript library that measures the page load time experienced by
+real users, commonly called RUM (Real User Measurement). It has the ability to
+send this data back to your server for further analysis. With boomerang, you
+find out exactly how fast your users think your site is.
+
+Apart from page load time, boomerang measures performance timings, metrics and
+characteristics of your user's web browsing experience. All you have to do is
+include it in your web pages and call the `BOOMR.init()` method. Once the
+performance data is captured, it will be beaconed to your chosen URL.
+
+boomerang is designed to be a performant and flexible library that can be adapted
+to your site's needs. It has an extensive plugin architecture, and works with
+both traditional and modern websites (including Single Page Apps).
+
+boomerang's goal is to not affect the load time of the page (avoiding the
+[Observer Effect](https://en.wikipedia.org/wiki/Observer_effect_(information_technology)).
+It can be loaded in an asynchronous way that will not delay the page load even
+if `boomerang.js` is unavailable.
+
+# Features
+
+* Supports:
+ * IE 6+, Edge, all major versions of Firefox, Chrome, Opera, and Safari
+ * Desktop and mobile devices
+* Captures (all optional):
+ * Page characteristics such as the URL and Referrer
+ * Overall page load times (via [NavigationTiming](https://www.w3.org/TR/navigation-timing/) if available)
+ * DNS, TCP, Request and Response timings (via [NavigationTiming](https://www.w3.org/TR/navigation-timing/))
+ * Browser characteristics such as screen size, orientation, memory usage, visibility state
+ * DOM characteristics such as the number of nodes, HTML length, number of images, scripts, etc
+ * [ResourceTiming](https://www.w3.org/TR/resource-timing-1/) data (to reconstruct the page's Waterfall)
+ * Bandwidth
+ * Mobile connection data
+ * DNS latency
+ * JavaScript Errors
+ * XMLHttpRequest instrumentation
+ * Third-Party analytics providers IDs
+ * Single Page App interactions
+
+# Usage
+
+boomerang can be included on your page in one of two ways: [synchronously](#synchronously) or [asynchronously](#asynchronously).
+
+The asynchronous method is recommended.
+
+
## The simple synchronous way
```html
-
-
+
+
+
```
-**Note** - you must include at least one plugin (it doesn't have to be rt) or else the beacon will never actually be called.
+**Note:** You must include at least one plugin (it doesn't have to be `RT`) or
+else the beacon will never fire.
+
+Each plugin has its own configuration as well -- these configuration options
+should be included in the `BOOMR.init()` call:
+
+```html
+BOOMR.init({
+ beacon_url: "http://yoursite.com/beacon/",
+ ResourceTiming: {
+ enabled: true,
+ clearOnBeacon: true
+ }
+});
+```
+
## The faster, more involved, asynchronous way
-This is what I like to do for sites I control.
+Loading boomerang asynchronously ensures that even if `boomerang.js` is
+unavailable (or loads slowly), your host page will not be affected.
### 1. Add a plugin to init your code
-Create a plugin (call it zzz_init.js or whatever you like) with your init code in there:
+Create a plugin (or use the sample `zzz-last-plugin.js`) with a call
+to `BOOMR.init`:
+
```javascript
BOOMR.init({
- config: parameters,
- ...
+ config: parameters,
+ ...
});
+BOOMR.t_end = new Date().getTime();
```
-You could also include any other code you need. For example, I include a timer to measure when boomerang has finished loading.
-I call my plugin `zzz_init.js` to remind me to include it last in the plugin list
+You could also include any other code you need. For example, you could include
+a timer to measure when boomerang has finished loading (as above).
### 2. Build boomerang
-The build process picks up all the plugins referenced in the `plugins.json` file. To change the plugins included in the boomerang build, change the contents of the file to your needs.
+
+The [build process](#documentation) bundles `boomerang.js` and all of the plugins
+listed in `plugins.json` (in that order).
+
+To build boomerang with all of your desired plugins, you would run:
```bash
grunt clean build
```
-This creates deployable boomerang versions in the `build` directory, e.g. `build/boomerang-.min.js`.
+This creates a deployable boomerang in the `build` directory, e.g. `build/boomerang-.min.js`.
-Install this file on your web server or origin server where your CDN can pick it up. Set a far future max-age header for it. This file will never change.
+Install this file on your web server or origin server where your CDN can pick it
+up. Set a far future
+[max-age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
+header for it. This file will never change.
### 3. Asynchronously include the script on your page
+There are two methods of asynchronously including boomerang on your page: by
+adding it to your main document, or via an IFRAME.
+
+The former method could block your `onload` event (affecting the measured
+performance of your page), so the later method is recommended.
+
+
#### 3.1. Adding it to the main document
+
Include the following code at the *top* of your HTML document:
+
```html
```
-Yes, the best practices say to include scripts at the bottom. That's different. That's for scripts that block downloading of other resources. Including a script this
-way will not block other resources, however it _will_ block onload. Including the script at the top of your page gives it a good chance of loading
-before the rest of your page does thereby reducing the probability of it blocking the `onload` event. If you don't want to block `onload` either, follow Stoyan's
-advice from the Meebo team.
+Best practices will suggest including all scripts at the bottom of your page.
+However, that only applies to scripts that block downloading of other resources.
+
+Including a script this way will not block other resources, however it _will_
+block `onload`.
+
+Including the script at the top of your page gives it a good chance of loading
+before the rest of your page does, thereby reducing the probability of it
+blocking the `onload` event.
+
+If you don't want to block `onload` either, use the following IFRAME method:
-#### 3.2. Adding it via an iframe
+
+#### 3.2. Adding it via an IFRAME
-The method described in 3.1 will still block `onload` on most browsers (Internet Explorer not included). To avoid
-blocking `onload`, we could load boomerang in an iframe. Stoyan's documented
-the technique on his blog. We've modified it to work across browsers with different configurations, documented on
-the lognormal blog.
+The method described in 3.1 will still block `onload` on most browsers.
-For boomerang, this is the code you'll include:
+To avoid blocking `onload`, we can load boomerang in an asynchronous IFRAME.
+The general process is documented on in
+[this blog post](http://www.lognormal.com/blog/2012/12/12/the-script-loader-pattern/).
+
+For boomerang, the asynchronous loader snippet you'll use is:
```html
```
-The `id` of the script node created by this code MUST be `boomr-if-as` as boomerang looks for that id to determine if it's running within an iframe or not.
-Boomerang will still export the `BOOMR` object to the parent window if running inside an iframe, so the rest of your code should remain unchanged.
+The `id` of the script node created by this code MUST be `boomr-if-as` as
+boomerang looks for that id to determine if it's running within an IFRAME or not.
+
+boomerang will still export the `BOOMR` object to the parent window if running
+inside an IFRAME, so the rest of your code should remain unchanged.
#### 3.3. Identifying when boomerang has loaded
-If you load boomerang asynchronously, there's some uncertainty in when boomerang has completed loading. To get around this, you can subscribe to the
+If you load boomerang asynchronously, there's some uncertainty in when boomerang
+has completed loading. To get around this, you can subscribe to the
`onBoomerangLoaded` Custom Event on the `document` object:
```javascript
- // Modern browsers
- if (document.addEventListener) {
- document.addEventListener("onBoomerangLoaded", function(e) {
- // e.detail.BOOMR is a reference to the BOOMR global object
- });
- }
- // IE 6, 7, 8 we use onPropertyChange and look for propertyName === "onBoomerangLoaded"
- else if (document.attachEvent) {
- document.attachEvent("onpropertychange", function(e) {
- if (!e) e=event;
- if (e.propertyName === "onBoomerangLoaded") {
- // e.detail.BOOMR is a reference to the BOOMR global object
- }
- });
- }
-
+// Modern browsers
+if (document.addEventListener) {
+ document.addEventListener("onBoomerangLoaded", function(e) {
+ // e.detail.BOOMR is a reference to the BOOMR global object
+ });
+}
+// IE 6, 7, 8 we use onPropertyChange and look for propertyName === "onBoomerangLoaded"
+else if (document.attachEvent) {
+ document.attachEvent("onpropertychange", function(e) {
+ if (!e) e=event;
+ if (e.propertyName === "onBoomerangLoaded") {
+ // e.detail.BOOMR is a reference to the BOOMR global object
+ }
+ });
+}
```
-Note that this only works on browsers that support the CustomEvent interface, which at this time is Chrome (including Android), Firefox 6+ (including Android),
-Opera (including Android, but not Opera Mini), Safari (including iOS), IE 6+ (but see the code above for the special way to listen for the event on IE6, 7 & 8).
+Note that this only works on browsers that support the CustomEvent interface,
+which is Chrome (including Android), Firefox 6+ (including Android), Opera
+(including Android, but not Opera Mini), Safari (including iOS), IE 6+
+(but see the code above for the special way to listen for the event on IE6, 7 & 8).
-Boomerang also fires the `onBeforeBoomerangBeacon` and `onBoomerangBeacon` events just before and during beaconing.
+boomerang also fires the `onBeforeBoomerangBeacon` and `onBoomerangBeacon`
+events just before and during beaconing.
-#### 3.4. Method queue pattern
+
+# Documentation
-If you want to call a public method that lives on `BOOMR`, but either don't know if Boomerang has loaded or don't want to wait, you can use the method queue pattern!
+Documentation is in the `docs/` directory. Boomerang documentation is
+written in Markdown and is built via [JSDoc](http://usejsdoc.org/).
-Instead of:
-```javascript
-BOOMR.addVar('myVarName', 'myVarValue')
-```
+You can build the current documentation by running Grunt:
-... you can write:
-```javascript
-BOOMR_mq = window.BOOMR_mq || [];
-BOOMR_mq.push(['addVar', 'myVarName', 'myVarValue']);
```
-
-Or, if you care about the return value, instead of:
-```javascript
-var hasMyVar = BOOMR.hasVar('myVarName');
-```
-... you can write:
-```javascript
-var hasMyVar;
-BOOMR_mq = window.BOOMR_mq || [];
-BOOMR_mq.push({
- arguments: ['hasVar', 'myVarName'],
- callback: function(returnValue) {
- hasMyVar = returnValue;
- }
-});
+grunt jsdoc
```
-docs
----
-Documentation is in the docs/ sub directory, and is written in HTML. Your best bet is to check it out and view it locally, though it works best through a web server (you'll need cookies).
-Thanks to github's awesome `gh-pages` feature, we're able to host the boomerang docs right here on github. Visit http://soasta.github.com/boomerang/doc/ for a browsable version where all
-the examples work.
+HTML files will be built under `build/docs`.
+
+Documentation is also currently published at [docs.soasta.com/boomerang-api/](https://docs.soasta.com/boomerang-api/).
+
+mPulse-specific Boomerang documentation is also available at [docs.soasta.com/boomerang/](https://docs.soasta.com/boomerang/).
+
+There is a lot more documentation available:
+
+- [API Documentation](https://docs.soasta.com/boomerang-api/): The `BOOMR` API
+- [Building Boomerang](https://docs.soasta.com/boomerang-api/tutorial-building.html): How to build boomerang with plugins
+- [Contributing](https://docs.soasta.com/boomerang-api/tutorial-contributing.html): Contributing to the open-source project
+- [Creating Plugins](https://docs.soasta.com/boomerang-api/tutorial-creating-plugins.html): Creating a plugin
+- [Methodology](https://docs.soasta.com/boomerang-api/tutorial-methodology.html): How boomerang works internally
+- [How-Tos](https://docs.soasta.com/boomerang-api/tutorial-howtos.html): Short recipes on how to do a bunch of things with boomerang
+
+# Source code
+
+The boomerang source code is primarily on GitHub at [github.com/SOASTA/boomerang](https://github.com/SOASTA/boomerang).
+
+Feel free to fork it and [contribute](https://docs.soasta.com/boomerang-api/tutorial-contributing.html) to it.
+
+You can also get a [check out the releases](https://github.com/SOASTA/boomerang/releases)
+or download a [tarball](https://github.com/SOASTA/boomerang/archive/master.tar.gz) or
+[zip](http://github.com/SOASTA/boomerang/archive/master.zip) of the code.
+
+# Support
+
+We use [GitHub Issues](https://github.com/SOASTA/boomerang/issues) for discussions,
+feature requests and bug reports.
+
+Get in touch at [github.com/SOASTA/boomerang/issues](https://github.com/SOASTA/boomerang/issues).
+
+boomerang is supported by the developers at [Akamai](http://akamai.com/), and the
+awesome community of open-source developers that use and hack it. That's you. Thank you!
-In case you're browsing this elsewhere, the latest development version of the code and docs are available at https://github.com/bluesmoon/boomerang/, while the latest stable version is
-at https://github.com/SOASTA/boomerang/
+# Contributions
-support
----
-We use github issues for discussions, feature requests and bug reports. Get in touch at https://github.com/SOASTA/boomerang/issues
-You'll need a github account to participate, but then you'll need one to check out the code as well :)
+Boomerang is brought to you by:
-Thanks for dropping by, and please leave us a message telling us if you use boomerang.
+* the former [Exceptional Performance](http://developer.yahoo.com/performance/) team at the company once known as
+ [Yahoo!](http://www.yahoo.com/), aided by the [Yahoo! Developer Network](http://developer.yahoo.com/),
+* the folks at [LogNormal](http://www.lognormal.com/), continued by
+* the mPulse team at [SOASTA](https://www.soasta.com/), ongoing by
+* the mPulse team at [Akamai](https://www.akamai.com/), and
+* many independent contributors whose contributions are cemented in our git history
-boomerang is supported by the devs at Akamai, and the awesome community of opensource developers that use
-and hack it. That's you. Thank you!
+To help out, please read our [contributing](https://docs.soasta.com/boomerang-api/tutorial-contributing.html) page.
diff --git a/boomerang.js b/boomerang.js
index 2eb531463..fdc376846 100644
--- a/boomerang.js
+++ b/boomerang.js
@@ -7,7 +7,7 @@
*/
/**
- * @namespace Boomerang
+ * @class BOOMR
* @desc
* boomerang measures various performance characteristics of your user's browsing
* experience and beacons it back to your server.
@@ -15,34 +15,74 @@
* To use this you'll need a web site, lots of users and the ability to do
* something with the data you collect. How you collect the data is up to
* you, but we have a few ideas.
-*/
+ *
+ * Everything in boomerang is accessed through the `BOOMR` object, which is
+ * available on `window.BOOMR`. It contains the public API, utility functions
+ * ({@link BOOMR.utils}) and all of the plugins ({@link BOOMR.plugins}).
+ *
+ * Each plugin has its own API, but is reachable through {@link BOOMR.plugins}.
+ *
+ * ## Beacon Parameters
+ *
+ * The core boomerang object will add the following parameters to the beacon.
+ *
+ * Note that each individual {@link BOOMR.plugins plugin} will add its own
+ * parameters as well.
+ *
+ * * `v`: Boomerang version
+ * * `u`: The page's URL (for most beacons), or the `XMLHttpRequest` URL
+ * * `pgu`: The page's URL (for `XMLHttpRequest` beacons)
+ * * `pid`: Page ID (8 characters)
+ * * `r`: Navigation referrer (from the cookie)
+ * * `r2`: Navigation referrer (from `document.location`, if different than `r`)
+ * * `vis.pre`: `1` if the page transitioned from prerender to visible
+ * * `xhr.pg`: The `XMLHttpRequest` page group
+ * * `errors`: Error messages of errors detected in Boomerang code, separated by a newline
+ */
/**
- * @memberof Boomerang
+ * @typedef TimeStamp
+ * @type {number}
+ *
+ * @desc
+ * A [Unix Epoch](https://en.wikipedia.org/wiki/Unix_time) timestamp (milliseconds
+ * since 1970) created by [BOOMR.now()]{@link BOOMR.now}.
+ *
+ * If `DOMHighResTimeStamp` (`performance.now()`) is supported, it is
+ * a `DOMHighResTimeStamp` (with microsecond resolution in the fractional),
+ * otherwise, it is `Date.now()`.
+ */
+
+/**
+ * @global
* @type {TimeStamp}
* @desc
- * Measure the time the script started
- * This has to be global so that we don't wait for the entire
- * BOOMR function to download and execute before measuring the
+ * Timestamp the boomerang.js script started executing.
+ *
+ * This has to be global so that we don't wait for this entire
+ * script to download and execute before measuring the
* time. We also declare it without `var` so that we can later
- * `delete` it. This is the only way that works on Internet Explorer
-*/
+ * `delete` it. This is the only way that works on Internet Explorer.
+ */
BOOMR_start = new Date().getTime();
/**
* @function
+ * @global
* @desc
- * Check the value of document.domain and fix it if incorrect.
+ * Check the value of `document.domain` and fix it if incorrect.
+ *
* This function is run at the top of boomerang, and then whenever
- * init() is called. If boomerang is running within an iframe, this
+ * {@link BOOMR.init} is called. If boomerang is running within an IFRAME, this
* function checks to see if it can access elements in the parent
- * iframe. If not, it will fudge around with document.domain until
+ * IFRAME. If not, it will fudge around with `document.domain` until
* it finds a value that works.
*
- * This allows site owners to change the value of document.domain at
+ * This allows site owners to change the value of `document.domain` at
* any point within their page's load process, and we will adapt to
* it.
- * @param {string} domain - domain name as retrieved from page url
+ *
+ * @param {string} domain Domain name as retrieved from page URL
*/
function BOOMR_check_doc_domain(domain) {
/*eslint no-unused-vars:0*/
@@ -106,11 +146,9 @@ function BOOMR_check_doc_domain(domain) {
BOOMR_check_doc_domain();
-
-// beaconing section
-// the parameter is the window
+// Construct BOOMR
+// w is window
(function(w) {
-
var impl, boomr, d, myurl, createCustomEvent, dispatchEvent, visibilityState, visibilityChange, orig_w = w;
// This is the only block where we use document without the w. qualifier
@@ -124,18 +162,66 @@ BOOMR_check_doc_domain();
d = w.document;
// Short namespace because I don't want to keep typing BOOMERANG
- if (!w.BOOMR) { w.BOOMR = {}; }
+ if (!w.BOOMR) {
+ w.BOOMR = {};
+ }
+
BOOMR = w.BOOMR;
+
// don't allow this code to be included twice
if (BOOMR.version) {
return;
}
+ /**
+ * Boomerang version, formatted as major.minor.patchlevel.
+ *
+ * This variable is replaced during build (`grunt build`).
+ *
+ * @type {string}
+ *
+ * @memberof BOOMR
+ */
BOOMR.version = "%boomerang_version%";
+
+ /**
+ * The main document window.
+ * * If Boomerang was loaded in an IFRAME, this is the parent window
+ * * If Boomerang was loaded inline, this is the current window
+ *
+ * @type {Window}
+ *
+ * @memberof BOOMR
+ */
BOOMR.window = w;
+
+ /**
+ * The Boomerang frame:
+ * * If Boomerang was loaded in an IFRAME, this is the IFRAME
+ * * If Boomerang was loaded inline, this is the current window
+ *
+ * @type {Window}
+ *
+ * @memberof BOOMR
+ */
BOOMR.boomerang_frame = orig_w;
- if (!BOOMR.plugins) { BOOMR.plugins = {}; }
+ /**
+ * @class BOOMR.plugins
+ * @desc
+ * Boomerang plugin namespace.
+ *
+ * All plugins should add their plugin object to `BOOMR.plugins`.
+ *
+ * A plugin should have, at minimum, the following exported functions:
+ * * `init(config)`
+ * * `is_complete()`
+ *
+ * See {@tutorial creating-plugins} for details.
+ */
+ if (!BOOMR.plugins) {
+ BOOMR.plugins = {};
+ }
// CustomEvent proxy for IE9 & 10 from https://developer.mozilla.org/en-US/docs/Web/API/CustomEvent
(function() {
@@ -181,12 +267,12 @@ BOOMR_check_doc_domain();
}());
/**
- dispatch a custom event to the browser
- @param e_name The custom event name that consumers can subscribe to
- @param e_data Any data passed to subscribers of the custom event via the `event.detail` property
- @param async By default, custom events are dispatched immediately.
- Set to true if the event should be dispatched once the browser has finished its current
- JavaScript execution.
+ * Dispatch a custom event to the browser
+ * @param {string} e_name The custom event name that consumers can subscribe to
+ * @param {object} e_data Any data passed to subscribers of the custom event via the `event.detail` property
+ * @param {boolean} async By default, custom events are dispatched immediately.
+ * Set to true if the event should be dispatched once the browser has finished its current
+ * JavaScript execution.
*/
dispatchEvent = function(e_name, e_data, async) {
var ev = createCustomEvent(e_name, {"detail": e_data});
@@ -223,7 +309,7 @@ BOOMR_check_doc_domain();
// https://developer.mozilla.org/en-US/docs/Web/Guide/User_experience/Using_the_Page_Visibility_API
// Set the name of the hidden property and the change event for visibility
- if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
+ if (typeof document.hidden !== "undefined") {
visibilityState = "visibilityState";
visibilityChange = "visibilitychange";
}
@@ -240,30 +326,37 @@ BOOMR_check_doc_domain();
visibilityChange = "webkitvisibilitychange";
}
- // impl is a private object not reachable from outside the BOOMR object
- // users can set properties by passing in to the init() method
+ // impl is a private object not reachable from outside the BOOMR object.
+ // Users can set properties by passing in to the init() method.
impl = {
- // properties
+ // Beacon URL
beacon_url: "",
- // beacon request method, either GET, POST or AUTO. AUTO will check the
- // request size then use GET if the request URL is less than MAX_GET_LENGTH chars
- // otherwise it will fall back to a POST request.
+
+ // Beacon request method, either GET, POST or AUTO. AUTO will check the
+ // request size then use GET if the request URL is less than MAX_GET_LENGTH
+ // chars. Otherwise, it will fall back to a POST request.
beacon_type: "AUTO",
- // beacon authorization key value. Most systems will use the 'Authentication' keyword, but some
- // some services use keys like 'X-Auth-Token' or other custom keys
+
+ // Beacon authorization key value. Most systems will use the 'Authentication'
+ // keyword, but some some services use keys like 'X-Auth-Token' or other
+ // custom keys.
beacon_auth_key: "Authorization",
- // beacon authorization token. This is only needed if your are using a POST and
- // the beacon requires an Authorization token to accept your data
+
+ // Beacon authorization token. This is only needed if your are using a POST
+ // and the beacon requires an Authorization token to accept your data.
beacon_auth_token: undefined,
- // strip out everything except last two parts of hostname.
+
+ // Strip out everything except last two parts of hostname.
// This doesn't work well for domains that end with a country tld,
// but we allow the developer to override site_domain for that.
- // You can disable all cookies by setting site_domain to a falsy value
+ // You can disable all cookies by setting site_domain to a falsy value.
site_domain: w.location.hostname.
replace(/.*?([^.]+\.[^.]+)\.?$/, "$1").
toLowerCase(),
- //! User's ip address determined on the server. Used for the BA cookie
+
+ // User's ip address determined on the server. Used for the BW cookie.
user_ip: "",
+
// Whether or not to send beacons on page load
autorun: true,
@@ -276,37 +369,326 @@ BOOMR_check_doc_domain();
// document.referrer
r2: undefined,
- //! strip_query_string: false,
+ // strip_query_string: false,
- //! onloadfired: false,
+ // onloadfired: false,
- //! handlers_attached: false,
+ // handlers_attached: false,
events: {
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when the page is usable by the user.
+ *
+ * By default this is fired when `window.onload` fires, but if you
+ * set `autorun` to false when calling {@link BOOMR.init}, then you
+ * must explicitly fire this event by calling {@link BOOMR#event:page_ready}.
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload}
+ * @event BOOMR#page_ready
+ * @property {Event} [event] Event triggering the page_ready
+ */
"page_ready": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired just before the browser unloads the page.
+ *
+ * The first event of `window.pagehide`, `window.beforeunload`,
+ * or `window.unload` will trigger this.
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/Events/pagehide}
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload}
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onunload}
+ * @event BOOMR#page_unload
+ */
"page_unload": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired before the document is about to be unloaded.
+ *
+ * `window.beforeunload` will trigger this.
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/WindowEventHandlers/onbeforeunload}
+ * @event BOOMR#before_unload
+ */
"before_unload": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired on `document.DOMContentLoaded`.
+ *
+ * The `DOMContentLoaded` event is fired when the initial HTML document
+ * has been completely loaded and parsed, without waiting for stylesheets,
+ * images, and subframes to finish loading
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/Events/DOMContentLoaded}
+ * @event BOOMR#dom_loaded
+ */
"dom_loaded": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired on `document.visibilitychange`.
+ *
+ * The `visibilitychange` event is fired when the content of a tab has
+ * become visible or has been hidden.
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/Events/visibilitychange}
+ * @event BOOMR#visibility_changed
+ */
"visibility_changed": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when the `visibilityState` of the document has changed from
+ * `prerender` to `visible`
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/Events/visibilitychange}
+ * @event BOOMR#prerender_to_visible
+ */
"prerender_to_visible": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when a beacon is about to be sent.
+ *
+ * The subscriber can still add variables to the beacon at this point,
+ * either by modifying the `vars` paramter or calling {@link BOOMR.addVar}.
+ *
+ * @event BOOMR#before_beacon
+ * @property {object} vars Beacon variables
+ */
"before_beacon": [],
- "onbeacon": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when a beacon was sent.
+ *
+ * The beacon variables cannot be modified at this point. Any calls
+ * to {@link BOOMR.addVar} or {@link BOOMR.removeVar} will apply to the
+ * next beacon.
+ *
+ * Also known as `onbeacon`.
+ *
+ * @event BOOMR#beacon
+ * @property {object} vars Beacon variables
+ */
+ "beacon": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when the page load beacon has been sent.
+ *
+ * This event should only happen once on a page. It does not apply
+ * to SPA soft navigations.
+ *
+ * @event BOOMR#page_load_beacon
+ * @property {object} vars Beacon variables
+ */
"page_load_beacon": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when an XMLHttpRequest has finished, or, if something calls
+ * {@link BOOMR.responseEnd}.
+ *
+ * @event BOOMR#xhr_load
+ * @property {object} data Event data
+ */
"xhr_load": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when the `click` event has happened on the `document`.
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onclick}
+ * @event BOOMR#click
+ */
"click": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired when any `FORM` element is submitted.
+ *
+ * @see {@link https://developer.mozilla.org/en-US/docs/Web/API/HTMLFormElement/submit}
+ * @event BOOMR#form_submit
+ */
"form_submit": [],
- "onconfig": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever new configuration data is applied via {@link BOOMR.init}.
+ *
+ * Also known as `onconfig`.
+ *
+ * @event BOOMR#config
+ * @property {object} data Configuration data
+ */
+ "config": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever `XMLHttpRequest.open` is called.
+ *
+ * This event will only happen if {@link BOOMR.plugins.AutoXHR} is enabled.
+ *
+ * @event BOOMR#xhr_init
+ * @property {string} type XHR type ("xhr")
+ */
"xhr_init": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever a SPA plugin is about to track a new navigation.
+ *
+ * @event BOOMR#spa_init
+ * @property {string} navType Navigation type (`spa` or `spa_hard`)
+ * @property {object} param SPA navigation parameters
+ */
"spa_init": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever a SPA navigation is complete.
+ *
+ * @event BOOMR#spa_navigation
+ */
"spa_navigation": [],
- "xhr_send": []
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever `XMLHttpRequest.send` is called.
+ *
+ * This event will only happen if {@link BOOMR.plugins.AutoXHR} is enabled.
+ *
+ * @event BOOMR#xhr_send
+ * @property {object} xhr `XMLHttpRequest` object
+ */
+ "xhr_send": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever and `XMLHttpRequest` has an error (if its `status` is
+ * set).
+ *
+ * This event will only happen if {@link BOOMR.plugins.AutoXHR} is enabled.
+ *
+ * Also known as `onxhrerror`.
+ *
+ * @event BOOMR#xhr_error
+ * @property {object} data XHR data
+ */
+ "xhr_error": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever a page error has happened.
+ *
+ * This event will only happen if {@link BOOMR.plugins.Errors} is enabled.
+ *
+ * Also known as `onerror`.
+ *
+ * @event BOOMR#error
+ * @property {object} err Error
+ */
+ "error": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever an `XMLHttpRequest.send()` is called
+ *
+ * This event will only happen if {@link BOOMR.plugins.AutoXHR} is enabled.
+ *
+ * @event BOOMR#xhr_send
+ * @property {object} req XMLHttpRequest
+ */
+ "xhr_send": [],
+
+ /**
+ * Boomerang event, subscribe via {@link BOOMR.subscribe}.
+ *
+ * Fired whenever connection information changes via the
+ * Network Information API.
+ *
+ * This event will only happen if {@link BOOMR.plugins.Mobile} is enabled.
+ *
+ * @event BOOMR#netinfo
+ * @property {object} connection `navigator.connection`
+ */
+ "netinfo": []
},
+ /**
+ * Public events
+ */
public_events: {
+ /**
+ * Public event (fired on `document`), and can be subscribed via
+ * `document.addEventListener("onBeforeBoomerangBeacon", ...)` or
+ * `document.attachEvent("onpropertychange", ...)`.
+ *
+ * Maps to {@link BOOMR#event:before_beacon}
+ *
+ * @event document#onBeforeBoomerangBeacon
+ * @property {object} vars Beacon variables
+ */
"before_beacon": "onBeforeBoomerangBeacon",
- "onbeacon": "onBoomerangBeacon",
+
+ /**
+ * Public event (fired on `document`), and can be subscribed via
+ * `document.addEventListener("onBoomerangBeacon", ...)` or
+ * `document.attachEvent("onpropertychange", ...)`.
+ *
+ * Maps to {@link BOOMR#event:before_beacon}
+ *
+ * @event document#onBoomerangBeacon
+ * @property {object} vars Beacon variables
+ */
+ "beacon": "onBoomerangBeacon",
+
+ /**
+ * Public event (fired on `document`), and can be subscribed via
+ * `document.addEventListener("onBoomerangLoaded", ...)` or
+ * `document.attachEvent("onpropertychange", ...)`.
+ *
+ * Fired when {@link BOOMR} has loaded and can be used.
+ *
+ * @event document#onBoomerangLoaded
+ */
"onboomerangloaded": "onBoomerangLoaded"
},
+ /**
+ * Maps old event names to their updated name
+ */
+ translate_events: {
+ "onbeacon": "beacon",
+ "onconfig": "config",
+ "onerror": "error",
+ "onxhrerror": "xhr_error"
+ },
+
listenerCallbacks: {},
vars: {},
@@ -338,7 +720,9 @@ BOOMR_check_doc_domain();
// don't capture events on flash objects
// because of context slowdowns in PepperFlash
- if (target && target.nodeName.toUpperCase() === "OBJECT" && target.type === "application/x-shockwave-flash") {
+ if (target &&
+ target.nodeName.toUpperCase() === "OBJECT" &&
+ target.type === "application/x-shockwave-flash") {
return;
}
impl.fireEvent(type, target);
@@ -379,6 +763,11 @@ BOOMR_check_doc_domain();
e_name = e_name.toLowerCase();
+ // translate old names
+ if (this.translate_events[e_name]) {
+ e_name = this.translate_events[e_name];
+ }
+
if (!this.events.hasOwnProperty(e_name)) {
return;// false;
}
@@ -391,7 +780,7 @@ BOOMR_check_doc_domain();
// Before we fire any event listeners, let's call real_sendBeacon() to flush
// any beacon that is being held by the setImmediate.
- if (e_name !== "before_beacon" && e_name !== "onbeacon") {
+ if (e_name !== "before_beacon" && e_name !== "beacon") {
BOOMR.real_sendBeacon();
}
@@ -430,26 +819,78 @@ BOOMR_check_doc_domain();
// we don't overwrite anything additional that was added to BOOMR before this
// was called... for example, a plugin.
boomr = {
- //! t_lstart: value of BOOMR_lstart set in host page
+ /**
+ * The timestamp when boomerang.js showed up on the page.
+ *
+ * This is the value of `BOOMR_start` we set earlier.
+ * @type {TimeStamp}
+ *
+ * @memberof BOOMR
+ */
t_start: BOOMR_start,
- //! t_end: value set in zzz-last-plugin.js
+ /**
+ * When the Boomerang plugins have all run.
+ *
+ * This value is generally set in zzz-last-plugin.js.
+ * @type {TimeStamp}
+ *
+ * @memberof BOOMR
+ */
+ t_end: undefined,
+
+ /**
+ * URL of boomerang.js. This is only set if using the asynchronous loader snippet.
+ *
+ * @type {string}
+ *
+ * @memberof BOOMR
+ */
url: myurl,
- // constants visible to the world
+ /**
+ * Whether or not Boomerang was loaded after the `onload` event.
+ *
+ * @type {boolean}
+ *
+ * @memberof BOOMR
+ */
+ loadedLate: false,
+
+ /**
+ * Constants visible to the world
+ * @class BOOMR.constants
+ */
constants: {
- // SPA beacon types
+ /**
+ * SPA beacon types
+ *
+ * @type {string[]}
+ *
+ * @memberof BOOMR.constants
+ */
BEACON_TYPE_SPAS: ["spa", "spa_hard"],
- // using 2000 here as a de facto maximum URL length based on:
- // http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
+
+ /**
+ * Maximum GET URL length.
+ * Using 2000 here as a de facto maximum URL length based on:
+ * http://stackoverflow.com/questions/417142/what-is-the-maximum-length-of-a-url-in-different-browsers
+ *
+ * @type {number}
+ *
+ * @memberof BOOMR.constants
+ */
MAX_GET_LENGTH: 2000
},
- // Utility functions
+ /**
+ * @class BOOMR.utils
+ */
utils: {
/**
- * Validate that the current frame has support for postMessage and can send iframe postMessages
- * @returns {boolean} - true if we have postMessage support, false if we don't
+ * Determines whether or not the browser has `postMessage` support
+ *
+ * @returns {boolean} True if supported
*/
hasPostMessageSupport: function() {
if (!w.postMessage || typeof w.postMessage !== "function" && typeof w.postMessage !== "object") {
@@ -457,6 +898,18 @@ BOOMR_check_doc_domain();
}
return true;
},
+
+ /**
+ * Converts an object to a string.
+ *
+ * @param {object} o Object
+ * @param {string} separator Member separator
+ * @param {number} nest_level Number of levels to recruse
+ *
+ * @returns {string} String representation of the object
+ *
+ * @memberof BOOMR.utils
+ */
objectToString: function(o, separator, nest_level) {
var value = [], k;
@@ -519,6 +972,15 @@ BOOMR_check_doc_domain();
return value.join(separator);
},
+ /**
+ * Gets the value of the cookie identified by `name`.
+ *
+ * @param {string} name Cookie name
+ *
+ * @returns {string|null} Cookie value, if set.
+ *
+ * @memberof BOOMR.utils
+ */
getCookie: function(name) {
if (!name) {
return null;
@@ -535,6 +997,23 @@ BOOMR_check_doc_domain();
}
},
+ /**
+ * Sets the cookie named `name` to the serialized value of `subcookies`.
+ *
+ * @param {string} name The name of the cookie
+ * @param {object} subcookies Key/value pairs to write into the cookie.
+ * These will be serialized as an & separated list of URL encoded key=value pairs.
+ * @param {number} max_age Lifetime in seconds of the cookie.
+ * Set this to 0 to create a session cookie that expires when
+ * the browser is closed. If not set, defaults to 0.
+ *
+ * @returns {boolean} True if the cookie was set successfully
+ *
+ * @example
+ * BOOMR.utils.setCookie("RT", { s: t_start, r: url });
+ *
+ * @memberof BOOMR.utils
+ */
setCookie: function(name, subcookies, max_age) {
var value, nameval, savedval, c, exp;
@@ -542,7 +1021,8 @@ BOOMR_check_doc_domain();
BOOMR.debug("No cookie name or site domain: " + name + "/" + impl.site_domain);
BOOMR.addVar("nocookie", 1);
- return null;
+
+ return false;
}
value = this.objectToString(subcookies, "&");
@@ -573,6 +1053,18 @@ BOOMR_check_doc_domain();
return false;
},
+ /**
+ * Parse a cookie string returned by {@link BOOMR.utils.getCookie} and
+ * split it into its constituent subcookies.
+ *
+ * @param {string} cookie Cookie value
+ *
+ * @returns {object} On success, an object of key/value pairs of all
+ * sub cookies. Note that some subcookies may have empty values.
+ * `null` if `cookie` was not set or did not contain valid subcookies.
+ *
+ * @memberof BOOMR.utils
+ */
getSubCookies: function(cookie) {
var cookies_a,
i, l, kv,
@@ -602,6 +1094,14 @@ BOOMR_check_doc_domain();
return gotcookies ? cookies : null;
},
+ /**
+ * Removes the cookie identified by `name` by nullifying its value,
+ * and making it a session cookie.
+ *
+ * @param {string} name Cookie name
+ *
+ * @memberof BOOMR.utils
+ */
removeCookie: function(name) {
return this.setCookie(name, {}, -86400);
},
@@ -614,6 +1114,8 @@ BOOMR_check_doc_domain();
* @param {number} urlLimit Maximum size, in characters, of the URL
*
* @returns {string} Cleaned up URL
+ *
+ * @memberof BOOMR.utils
*/
cleanupURL: function(url, urlLimit) {
if (!url || BOOMR.utils.isArray(url)) {
@@ -639,6 +1141,16 @@ BOOMR_check_doc_domain();
return url;
},
+ /**
+ * Gets the URL with the query string replaced with a MD5 hash of its contents.
+ *
+ * @param {string} url URL
+ * @param {boolean} stripHash Whether or not to strip the hash
+ *
+ * @returns {string} URL with query string hashed
+ *
+ * @memberof BOOMR.utils
+ */
hashQueryString: function(url, stripHash) {
if (!url) {
return url;
@@ -660,9 +1172,27 @@ BOOMR_check_doc_domain();
if (!BOOMR.utils.MD5) {
return url;
}
- return url.replace(/\?([^#]*)/, function(m0, m1) { return "?" + (m1.length > 10 ? BOOMR.utils.MD5(m1) : m1); });
+ return url.replace(/\?([^#]*)/, function(m0, m1) {
+ return "?" + (m1.length > 10 ? BOOMR.utils.MD5(m1) : m1);
+ });
},
+ /**
+ * Sets the object's properties if anything in config matches
+ * one of the property names.
+ *
+ * @param {object} o The plugin's `impl` object within which it stores
+ * all its configuration and private properties
+ * @param {object} config The config object passed in to the plugin's
+ * `init()` method.
+ * @param {string} plugin_name The plugin's name in the {@link BOOMR.plugins} object.
+ * @param {string[]} properties An array containing a list of all configurable
+ * properties that this plugin has.
+ *
+ * @returns {boolean} True if a property was set
+ *
+ * @memberof BOOMR.utils
+ */
pluginConfig: function(o, config, plugin_name, properties) {
var i, props = 0;
@@ -679,13 +1209,16 @@ BOOMR_check_doc_domain();
return (props > 0);
},
+
/**
* `filter` for arrays
*
- * @private
* @param {Array} array The array to iterate over.
* @param {Function} predicate The function invoked per iteration.
+ *
* @returns {Array} Returns the new filtered array.
+ *
+ * @memberof BOOMR.utils
*/
arrayFilter: function(array, predicate) {
var result = [];
@@ -712,13 +1245,30 @@ BOOMR_check_doc_domain();
}
return result;
},
+
/**
- * `find` for arrays
+ * The callback function may return a falsy value to disconnect the
+ * observer after it returns, or a truthy value to keep watching for
+ * mutations. If the return value is numeric and greater than 0, then
+ * this will be the new timeout. If it is boolean instead, then the
+ * timeout will not fire any more so the caller MUST call disconnect()
+ * at some point.
*
- * @private
- * @param {Array} array The array to iterate over.
- * @param {Function} predicate The function invoked per iteration.
- * @returns {Array} Returns the value of first element that satisfies the predicate.
+ * @callback BOOMR~addObserverCallback
+ * @param {object[]} mutations List of mutations detected by the observer or `undefined` if the observer timed out
+ * @param {object} callback_data Is the passed in `callback_data` parameter without modifications
+ */
+
+ /**
+ * `find` for Arrays
+ *
+ * @param {Array} array The array to iterate over
+ * @param {Function} predicate The function invoked per iteration
+ *
+ * @returns {Array} Returns the value of first element that satisfies
+ * the predicate
+ *
+ * @memberof BOOMR.utils
*/
arrayFind: function(array, predicate) {
if (!(BOOMR.utils.isArray(array) || (array && typeof array.length === "number")) ||
@@ -743,32 +1293,29 @@ BOOMR_check_doc_domain();
return undefined;
}
},
+
/**
- * @desc
* Add a MutationObserver for a given element and terminate after `timeout`ms.
- * @param el DOM element to watch for mutations
- * @param config MutationObserverInit object (https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationObserverInit)
- * @param timeout Number of milliseconds of no mutations after which the observer should be automatically disconnected
- * If set to a falsy value, the observer will wait indefinitely for Mutations.
- * @param callback Callback function to call either on timeout or if mutations are detected. The signature of this method is:
- * function(mutations, callback_data)
- * Where:
- * mutations is the list of mutations detected by the observer or `undefined` if the observer timed out
- * callback_data is the passed in `callback_data` parameter without modifications
- *
- * The callback function may return a falsy value to disconnect the observer after it returns, or a truthy value to
- * keep watching for mutations. If the return value is numeric and greater than 0, then this will be the new timeout
- * if it is boolean instead, then the timeout will not fire any more so the caller MUST call disconnect() at some point
- * @param callback_data Any data to be passed to the callback function as its second parameter
- * @param callback_ctx An object that represents the `this` object of the `callback` method. Leave unset the callback function is not a method of an object
- *
- * @returns {?object} - `null` if a MutationObserver could not be created OR
- * - An object containing the observer and the timer object:
- * { observer: , timer: }
- *
- * The caller can use this to disconnect the observer at any point by calling `retval.observer.disconnect()`
- * Note that the caller should first check to see if `retval.observer` is set before calling `disconnect()` as it may
- * have been cleared automatically.
+ *
+ * @param {DOMElement} el DOM element to watch for mutations
+ * @param {MutationObserverInit} config MutationObserverInit object (https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver#MutationObserverInit)
+ * @param {number} timeout Number of milliseconds of no mutations after which the observer should be automatically disconnected.
+ * If set to a falsy value, the observer will wait indefinitely for Mutations.
+ * @param {BOOMR~addObserverCallback} callback Callback function to call either on timeout or if mutations are detected.
+ * @param {object} callback_data Any data to be passed to the callback function as its second parameter.
+ * @param {object} callback_ctx An object that represents the `this` object of the `callback` method.
+ * Leave unset the callback function is not a method of an object.
+ *
+ * @returns {object|null}
+ * - `null` if a MutationObserver could not be created OR
+ * - An object containing the observer and the timer object:
+ * `{ observer: , timer: }`
+ * - The caller can use this to disconnect the observer at any point
+ * by calling `retval.observer.disconnect()`
+ * - Note that the caller should first check to see if `retval.observer`
+ * is set before calling `disconnect()` as it may have been cleared automatically.
+ *
+ * @memberof BOOMR.utils
*/
addObserver: function(el, config, timeout, callback, callback_data, callback_ctx) {
var o = {observer: null, timer: null};
@@ -814,6 +1361,15 @@ BOOMR_check_doc_domain();
return o;
},
+ /**
+ * Adds an event listener.
+ *
+ * @param {DOMElement} el DOM element
+ * @param {string} type Event name
+ * @param {function} fn Callback function
+ *
+ * @memberof BOOMR.utils
+ */
addListener: function(el, type, fn) {
if (el.addEventListener) {
el.addEventListener(type, fn, false);
@@ -829,6 +1385,15 @@ BOOMR_check_doc_domain();
impl.listenerCallbacks[type].push({ el: el, fn: fn});
},
+ /**
+ * Removes an event listener.
+ *
+ * @param {DOMElement} el DOM element
+ * @param {string} type Event name
+ * @param {function} fn Callback function
+ *
+ * @memberof BOOMR.utils
+ */
removeListener: function(el, type, fn) {
var i;
@@ -841,45 +1406,38 @@ BOOMR_check_doc_domain();
if (impl.listenerCallbacks.hasOwnProperty(type)) {
for (var i = 0; i < impl.listenerCallbacks[type].length; i++) {
- if (fn === impl.listenerCallbacks[type][i].fn &&
- el === impl.listenerCallbacks[type][i].el) {
- impl.listenerCallbacks[type].splice(i, 1);
- return;
- }
- }
- }
- },
-
- pushVars: function(form, vars, prefix) {
- var k, i, l = 0, input;
-
- for (k in vars) {
- if (vars.hasOwnProperty(k)) {
- if (BOOMR.utils.isArray(vars[k])) {
- for (i = 0; i < vars[k].length; ++i) {
- l += BOOMR.utils.pushVars(form, vars[k][i], k + "[" + i + "]");
- }
- }
- else {
- input = document.createElement("input");
- input.type = "hidden"; // we need `hidden` to preserve newlines. see commit message for more details
- input.name = (prefix ? (prefix + "[" + k + "]") : k);
- input.value = (vars[k] === undefined || vars[k] === null ? "" : vars[k]);
-
- form.appendChild(input);
-
- l += encodeURIComponent(input.name).length + encodeURIComponent(input.value).length + 2;
+ if (fn === impl.listenerCallbacks[type][i].fn &&
+ el === impl.listenerCallbacks[type][i].el) {
+ impl.listenerCallbacks[type].splice(i, 1);
+ return;
}
}
}
-
- return l;
},
+ /**
+ * Determines if the specified object is an `Array` or not
+ *
+ * @param {object} ary Object in question
+ *
+ * @returns {boolean} True if the object is an `Array`
+ *
+ * @memberof BOOMR.utils
+ */
isArray: function(ary) {
return Object.prototype.toString.call(ary) === "[object Array]";
},
+ /**
+ * Determines if the specified value is in the array
+ *
+ * @param {object} val Value to check
+ * @param {object} ary Object in question
+ *
+ * @returns {boolean} True if the value is in the Array
+ *
+ * @memberof BOOMR.utils
+ */
inArray: function(val, ary) {
var i;
@@ -900,9 +1458,12 @@ BOOMR_check_doc_domain();
* Get a query parameter value from a URL's query string
*
* @param {string} param Query parameter name
- * @param {string|Object} [url] URL containing the query string, or a link object. Defaults to BOOMR.window.location
+ * @param {string|Object} [url] URL containing the query string, or a link object.
+ * Defaults to `BOOMR.window.location`
*
* @returns {string|null} URI decoded value or null if param isn't a query parameter
+ *
+ * @memberof BOOMR.utils
*/
getQueryParamValue: function(param, url) {
var l, params, i, kv;
@@ -940,6 +1501,8 @@ BOOMR_check_doc_domain();
* https://en.wikipedia.org/wiki/Universally_unique_identifier
*
* @returns {string} UUID
+ *
+ * @memberof BOOMR.utils
*/
generateUUID: function() {
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function(c) {
@@ -954,7 +1517,10 @@ BOOMR_check_doc_domain();
* characters a-z0-9.
*
* @param {number} chars Number of characters (max 40)
+ *
* @returns {string} Random ID
+ *
+ * @memberof BOOMR.utils
*/
generateId: function(chars) {
return "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx".substr(0, chars || 40).replace(/x/g, function(c) {
@@ -1004,19 +1570,54 @@ BOOMR_check_doc_domain();
}, // closes `utils`
+ /**
+ * Initializes Boomerang by applying the specified configuration.
+ *
+ * All plugins' `init()` functions will be called with the same config as well.
+ *
+ * @param {object} config Configuration object
+ * @param {boolean} [config.autorun] By default, boomerang runs automatically
+ * and attaches its `page_ready` handler to the `window.onload` event.
+ * If you set `autorun` to `false`, this will not happen and you will
+ * need to call {@link BOOMR.page_ready} yourself.
+ * @param {string} config.beacon_auth_key Beacon authorization key value
+ * @param {string} config.beacon_auth_token Beacon authorization token.
+ * @param {string} config.beacon_url The URL to beacon results back to.
+ * If not set, no beacon will be sent.
+ * @param {string} config.beacon_type `GET`, `POST` or `AUTO`
+ * @param {string[]} [config.secondary_beacons] Additional beacon URLs to send data to
+ * @param {string} [config.site_domain] The domain that all cookies should be set on
+ * Boomerang will try to auto-detect this, but unless your site is of the
+ * `foo.com` format, it will probably get it wrong. It's a good idea
+ * to set this to whatever part of your domain you'd like to share
+ * bandwidth and performance measurements across.
+ * Set this to a falsy value to disable all cookies.
+ * @param {boolean} [config.strip_query_string] Whether or not to strip query strings from all URLs (e.g. `u`, `pgu`, etc.)
+ * @param {string} [config.user_ip] Despite its name, this is really a free-form
+ * string used to uniquely identify the user's current internet
+ * connection. It's used primarily by the bandwidth test to determine
+ * whether it should re-measure the user's bandwidth or just use the
+ * value stored in the cookie. You may use IPv4, IPv6 or anything else
+ * that you think can be used to identify the user's network connection.
+ * @param {function} [config.log] Logger to use. Set to `null` to disable logging.
+ * @param {function} [] Each plugin has its own section
+ *
+ * @returns {BOOMR} Boomerang object
+ *
+ * @memberof BOOMR
+ */
init: function(config) {
var i, k,
properties = [
- "beacon_url",
- "beacon_type",
+ "autorun",
"beacon_auth_key",
"beacon_auth_token",
+ "beacon_url",
+ "beacon_type",
+ "secondary_beacons",
"site_domain",
- "user_ip",
"strip_query_string",
- "secondary_beacons",
- "autorun",
- "site_domain"
+ "user_ip"
];
BOOMR_check_doc_domain();
@@ -1112,8 +1713,8 @@ BOOMR_check_doc_domain();
}
BOOMR.utils.addListener(w, "DOMContentLoaded", function() { impl.fireEvent("dom_loaded"); });
- BOOMR.fireEvent("onconfig", config);
- BOOMR.subscribe("onconfig", function(beaconConfig) {
+ BOOMR.fireEvent("config", config);
+ BOOMR.subscribe("config", function(beaconConfig) {
if (beaconConfig.beacon_url) {
impl.beacon_url = beaconConfig.beacon_url;
}
@@ -1174,7 +1775,9 @@ BOOMR_check_doc_domain();
* Attach a callback to the `pageshow` or `onload` event if `onload` has not
* been fired otherwise queue it to run immediately
*
- * @param {function} cb - Callback to run when `onload` fires or page is visible (`pageshow`)
+ * @param {function} cb Callback to run when `onload` fires or page is visible (`pageshow`)
+ *
+ * @memberof BOOMR
*/
attach_page_ready: function(cb) {
if (BOOMR.hasBrowserOnloadFired()) {
@@ -1194,8 +1797,12 @@ BOOMR_check_doc_domain();
},
/**
- * Sends the page_ready beacon only if 'autorun' is still true after init
- * is called.
+ * Sends the `page_ready` event only if `autorun` is still true after
+ * {@link BOOMR.init} is called.
+ *
+ * @param {Event} ev Event
+ *
+ * @memberof BOOMR
*/
page_ready_autorun: function(ev) {
if (impl.autorun) {
@@ -1203,8 +1810,24 @@ BOOMR_check_doc_domain();
}
},
- // The page dev calls this method when they determine the page is usable.
- // Only call this if autorun is explicitly set to false
+ /**
+ * Method that fires the {@link BOOMR#event:page_ready} event. Call this
+ * only if you've set `autorun` to `false` when calling the {@link BOOMR.init}
+ * method. You should call this method when you determine that your page
+ * is ready to be used by your user. This will be the end-time used in
+ * the page load time measurement.
+ *
+ * @param {Event} ev Ready event
+ *
+ * @returns {BOOMR} Boomerang object
+ *
+ * @example
+ * BOOMR.init({ autorun: false, ... });
+ * // wait until the page is ready, i.e. your view has loaded
+ * BOOMR.page_ready();
+ *
+ * @memberof BOOMR
+ */
page_ready: function(ev, auto) {
if (!ev) {
ev = w.event;
@@ -1254,24 +1877,44 @@ BOOMR_check_doc_domain();
/**
* Determines whether or not the page's `onload` event has fired, or
- * if `autorun` is false, whether `BOOMR.page_ready()` was called.
+ * if `autorun` is false, whether {@link BOOMR.page_ready} was called.
*
- * @returns {boolean} True if onload or page_ready() were called
+ * @returns {boolean} True if `onload` or {@link BOOMR.page_ready} were called
+ *
+ * @memberof BOOMR
*/
onloadFired: function() {
return impl.onloadfired;
},
/**
- * Defer the function `fn` until the next instant the browser is free from user tasks
- * @param [Function] fn The callback function. This function accepts the following arguments:
- * - data: The passed in data object
- * - cb_data: The passed in cb_data object
- * - call stack: An Error object that holds the callstack for when setImmediate was called, used to determine what called the callback
- * @param [object] data Any data to pass to the callback function
- * @param [object] cb_data Any passthrough data for the callback function. This differs from `data` when setImmediate is called via an event handler and `data` is the Event object
- * @param [object] cb_scope The scope of the callback function if it is a method of an object
+ * The callback function may return a falsy value to disconnect the observer
+ * after it returns, or a truthy value to keep watching for mutations. If
+ * the return value is numeric and greater than 0, then this will be the new timeout.
+ * If it is boolean instead, then the timeout will not fire any more so
+ * the caller MUST call disconnect() at some point
+ *
+ * @callback BOOMR~setImmediateCallback
+ * @param {object} data The passed in `data` object
+ * @param {object} cb_data The passed in `cb_data` object
+ * @param {Error} callstack An Error object that holds the callstack for
+ * when `setImmediate` was called, used to determine what called the callback
+ */
+
+ /**
+ * Defer the function `fn` until the next instant the browser is free from
+ * user tasks.
+ *
+ * @param {BOOMR~setImmediateCallback} fn The callback function.
+ * @param {object} [data] Any data to pass to the callback function
+ * @param {object} [cb_data] Any passthrough data for the callback function.
+ * This differs from `data` when `setImmediate` is called via an event
+ * handler and `data` is the Event object
+ * @param {object} [cb_scope] The scope of the callback function if it is a method of an object
+ *
* @returns nothing
+ *
+ * @memberof BOOMR
*/
setImmediate: function(fn, data, cb_data, cb_scope) {
var cb, cstack;
@@ -1302,16 +1945,29 @@ BOOMR_check_doc_domain();
/**
* Gets the current time in milliseconds since the Unix Epoch (Jan 1 1970).
*
- * In browsers that support DOMHighResTimeStamp, this will be replaced
- * by a function that adds BOOMR.now() to navigationStart
+ * In browsers that support `DOMHighResTimeStamp`, this will be replaced
+ * by a function that adds `performance.now()` to `navigationStart`
* (with milliseconds.microseconds resolution).
*
- * @returns {Number} Milliseconds since Unix Epoch
+ * @function
+ *
+ * @returns {TimeStamp} Milliseconds since Unix Epoch
+ *
+ * @memberof BOOMR
*/
now: (function() {
return Date.now || function() { return new Date().getTime(); };
}()),
+ /**
+ * Gets the `window.performance` object of the root window.
+ *
+ * Checks vendor prefixes for older browsers (e.g. IE9).
+ *
+ * @returns {Performance|undefined} `window.performance` if it exists
+ *
+ * @memberof BOOMR
+ */
getPerformance: function() {
try {
if (BOOMR.window) {
@@ -1320,7 +1976,9 @@ BOOMR_check_doc_domain();
}
// vendor-prefixed fallbacks
- return BOOMR.window.msPerformance || BOOMR.window.webkitPerformance || BOOMR.window.mozPerformance;
+ return BOOMR.window.msPerformance ||
+ BOOMR.window.webkitPerformance ||
+ BOOMR.window.mozPerformance;
}
}
catch (ignore) {
@@ -1328,16 +1986,39 @@ BOOMR_check_doc_domain();
}
},
- visibilityState: (visibilityState === undefined ? function() { return "visible"; } : function() { return d[visibilityState]; }),
+ /**
+ * Gets the `document.visibilityState`, or `visible` if Page Visibility
+ * is not supported.
+ *
+ * @function
+ *
+ * @returns {string} Visibility state
+ *
+ * @memberof BOOMR
+ */
+ visibilityState: (visibilityState === undefined ? function() {
+ return "visible";
+ } : function() {
+ return d[visibilityState];
+ }),
+ /**
+ * An mapping of visibliity event states to the latest time they happened
+ *
+ * @type {object}
+ *
+ * @memberof BOOMR
+ */
lastVisibilityEvent: {},
/**
- * Registers an event
+ * Registers a Boomerang event.
*
* @param {string} e_name Event name
*
* @returns {BOOMR} Boomerang object
+ *
+ * @memberof BOOMR
*/
registerEvent: function(e_name) {
if (impl.events.hasOwnProperty(e_name)) {
@@ -1355,6 +2036,8 @@ BOOMR_check_doc_domain();
* Disables boomerang from doing anything further:
* 1. Clears event handlers (such as onload)
* 2. Clears all event listeners
+ *
+ * @memberof BOOMR
*/
disable: function() {
impl.clearEvents();
@@ -1362,33 +2045,50 @@ BOOMR_check_doc_domain();
},
/**
- * Fires an event
+ * Fires a Boomerang event
*
* @param {string} e_name Event name
* @param {object} data Event payload
*
* @returns {BOOMR} Boomerang object
+ *
+ * @memberof BOOMR
*/
fireEvent: function(e_name, data) {
return impl.fireEvent(e_name, data);
},
/**
- * Subscribe to an event
+ * @callback BOOMR~subscribeCallback
+ * @param {object} eventData Event data
+ * @param {object} cb_data Callback data
+ */
+
+ /**
+ * Subscribes to a Boomerang event
*
- * @param {string} e_name Event name
- * @param {function} fn callback function
- * @param {object} cb_data Any passthrough data for the callback function
- * @param {object} cb_scope The scope of the callback function if it is a method of an object
- * @param {Boolean} once If true subscribe to only one event call
+ * @param {string} e_name Event name, i.e. {@link BOOMR#event:page_ready}.
+ * @param {BOOMR~subscribeCallback} fn Callback function
+ * @param {object} cb_data Callback data, passed as the second parameter to the callback function
+ * @param {object} cb_scope Callback scope. If set to an object, then the
+ * callback function is called as a method of this object, and all
+ * references to `this` within the callback function will refer to `cb_scope`.
+ * @param {boolean} once Whether or not this callback should only be run once
*
* @returns {BOOMR} Boomerang object
+ *
+ * @memberof BOOMR
*/
subscribe: function(e_name, fn, cb_data, cb_scope, once) {
var i, handler, ev;
e_name = e_name.toLowerCase();
+ // translate old names
+ if (impl.translate_events[e_name]) {
+ e_name = impl.translate_events[e_name];
+ }
+
if (!impl.events.hasOwnProperty(e_name)) {
// allow subscriptions before they're registered
impl.events[e_name] = [];
@@ -1455,6 +2155,19 @@ BOOMR_check_doc_domain();
return this;
},
+ /**
+ * Logs an internal Boomerang error.
+ *
+ * If the {@link BOOMR.plugins.Errors} plugin is enabled, this data will
+ * be compressed on the `err` beacon parameter. If not, it will be included
+ * in uncompressed form on the `errors` parameter.
+ *
+ * @param {string|object} err Error
+ * @param {string} [src] Source
+ * @param {object} [extra] Extra data
+ *
+ * @memberof BOOMR
+ */
addError: function BOOMR_addError(err, src, extra) {
var str, E = BOOMR.plugins.Errors;
@@ -1507,6 +2220,15 @@ BOOMR_check_doc_domain();
}
},
+ /**
+ * Determines if the specified Error is a Cross-Origin error.
+ *
+ * @param {string|object} err Error
+ *
+ * @returns {boolean} True if the Error is a Cross-Origin error.
+ *
+ * @memberof BOOMR
+ */
isCrossOriginError: function(err) {
// These are expected for cross-origin iframe access, although the Internet Explorer check will only
// work for browsers using English.
@@ -1515,7 +2237,36 @@ BOOMR_check_doc_domain();
(err.name === "Error" && err.message && err.message.match(/^(Permission|Access is) denied/));
},
- addVar: function(name, value, singleBeacon) {
+ /**
+ * Add one or more parameters to the beacon.
+ *
+ * This method may either be called with a single object containing
+ * key/value pairs, or with two parameters, the first is the variable
+ * name and the second is its value.
+ *
+ * All names should be strings usable in a URL's query string.
+ *
+ * We recommend only using alphanumeric characters and underscores, but you
+ * can use anything you like.
+ *
+ * Values should be strings (or numbers), and have the same restrictions
+ * as names.
+ *
+ * Parameters will be on all subsequent beacons unless `singleBeacon` is
+ * set.
+ *
+ * @param {string} name Variable name
+ * @param {string|object} val Value
+ *
+ * @returns {BOOMR} Boomerang object
+ *
+ * @example
+ * BOOMR.addVar("page_id", 123);
+ * BOOMR.addVar({"page_id": 123, "user_id": "Person1"});
+ *
+ * @memberof BOOMR
+ */
+ addVar: function(name, value, singleBeacon) {
if (typeof name === "string") {
impl.vars[name] = value;
}
@@ -1535,6 +2286,22 @@ BOOMR_check_doc_domain();
return this;
},
+ /**
+ * Removes one or more variables from the beacon URL. This is useful within
+ * a plugin to reset the values of parameters that it is about to set.
+ *
+ * Plugins can also use this in the {@link BOOMR#event:beacon} event to clear
+ * any variables that should only live on a single beacon.
+ *
+ * This method accepts either a list of variable names, or a single
+ * array containing a list of variable names.
+ *
+ * @param {string[]|string} name Variable name or list
+ *
+ * @returns {BOOMR} Boomerang object
+ *
+ * @memberof BOOMR
+ */
removeVar: function(arg0) {
var i, params;
if (!arguments.length) {
@@ -1557,6 +2324,15 @@ BOOMR_check_doc_domain();
return this;
},
+ /**
+ * Determines whether or not the beacon has the specified variable.
+ *
+ * @param {string} name Variable name
+ *
+ * @returns {boolean} True if the variable is set.
+ *
+ * @memberof BOOMR
+ */
hasVar: function(name) {
return impl.vars.hasOwnProperty(name);
},
@@ -1586,6 +2362,10 @@ BOOMR_check_doc_domain();
*
* @param {string} name Variable name
* @param {number} pri Priority (-1 or 1)
+ *
+ * @returns {BOOMR} Boomerang object
+ *
+ * @memberof BOOMR
*/
setVarPriority: function(name, pri) {
if (typeof pri !== "number" || Math.abs(pri) !== 1) {
@@ -1598,9 +2378,12 @@ BOOMR_check_doc_domain();
},
/**
- * Sets the Referrers
+ * Sets the Referrers variables.
+ *
* @param {string} r Referrer from the cookie
* @param {string} [r2] Referrer from document.referrer, if different
+ *
+ * @memberof BOOMR
*/
setReferrer: function(r, r2) {
// cookie referrer
@@ -1615,6 +2398,24 @@ BOOMR_check_doc_domain();
}
},
+ /**
+ * Starts a timer for a dynamic request.
+ *
+ * Once the named request has completed, call `loaded()` to send a beacon
+ * with the duration.
+ *
+ * @example
+ * var timer = BOOMR.requestStart("my-timer");
+ * // do stuff
+ * timer.loaded();
+ *
+ * @param {string} name Timer name
+ *
+ * @returns {object} An object with a `.loaded()` function that you can call
+ * when the dynamic timer is complete.
+ *
+ * @memberof BOOMR
+ */
requestStart: function(name) {
var t_start = BOOMR.now();
BOOMR.plugins.RT.startTimer("xhr_" + name, t_start);
@@ -1627,14 +2428,16 @@ BOOMR_check_doc_domain();
},
/**
- * Determines is Boomerang can send a beacon.
+ * Determines if Boomerang can send a beacon.
*
- * Queryies all plugins to see if they implement readyToSend(),
- * and if so, that they return true;
+ * Queryies all plugins to see if they implement `readyToSend()`,
+ * and if so, that they return `true`.
*
* If not, the beacon cannot be sent.
*
* @returns {boolean} True if Boomerang can send a beacon
+ *
+ * @memberof BOOMR
*/
readyToSend: function() {
var plugin;
@@ -1656,6 +2459,18 @@ BOOMR_check_doc_domain();
return true;
},
+ /**
+ * Sends a beacon for a dynamic request.
+ *
+ * @param {string|object} name Timer name or timer object data.
+ * @param {string} [name.initiator] Initiator, such as `xhr` or `spa`
+ * @param {string} [name.url] URL of the request
+ * @param {TimeStamp} t_start Start time
+ * @param {object} data Request data
+ * @param {TimeStamp} t_end End time
+ *
+ * @memberof BOOMR
+ */
responseEnd: function(name, t_start, data, t_end) {
// take the now timestamp for start and end, if unspecified, in case we delay this beacon
t_start = typeof t_start === "number" ? t_start : BOOMR.now();
@@ -1714,16 +2529,59 @@ BOOMR_check_doc_domain();
// by auto-xhr.js if active.
//
/**
- * Undo XMLHttpRequest instrumentation and reset the original
+ * Undo XMLHttpRequest instrumentation and reset the original `XMLHttpRequest`
+ * object
+ *
+ * This is implemented in `plugins/auto-xhr.js` {@link BOOMR.plugins.AutoXHR}.
+ *
+ * @memberof BOOMR
*/
uninstrumentXHR: function() {
},
+
/**
- * Instrument all requests made via XMLHttpRequest to send beacons
- * This is implemented in plugins/auto-xhr.js
+ * Instrument all requests made via XMLHttpRequest to send beacons.
+ *
+ * This is implemented in `plugins/auto-xhr.js` {@link BOOMR.plugins.AutoXHR}.
+ *
+ * @memberof BOOMR
*/
instrumentXHR: function() { },
+ /**
+ * Request boomerang to send its beacon with all queued beacon data
+ * (via {@link BOOMR.addVar}).
+ *
+ * Boomerang may ignore this request.
+ *
+ * When this method is called, boomerang checks all plugins. If any
+ * plugin has not completed its checks (ie, the plugin's `is_complete()`
+ * method returns `false`, then this method does nothing.
+ *
+ * If all plugins have completed, then this method fires the
+ * {@link BOOMR#event:before_beacon} event with all variables that will be
+ * sent on the beacon.
+ *
+ * After all {@link BOOMR#event:before_beacon} handlers return, this method
+ * checks if a `beacon_url` has been configured and if there are any
+ * beacon parameters to be sent. If both are true, it fires the beacon.
+ *
+ * The {@link BOOMR#event:beacon} event is then fired.
+ *
+ * `sendBeacon()` should be called any time a plugin goes from
+ * `is_complete() = false` to `is_complete = true` so the beacon is
+ * sent.
+ *
+ * The actual beaconing is handled in {@link BOOMR.real_sendBeacon} after
+ * a short delay (via {@link BOOMR.setImmediate}). If other calls to
+ * `sendBeacon` happen before {@link BOOMR.real_sendBeacon} is called,
+ * those calls will be discarded (so it's OK to call this in quick
+ * succession).
+ *
+ * @param {string} [beacon_url_override] Beacon URL override
+ *
+ * @memberof BOOMR
+ */
sendBeacon: function(beacon_url_override) {
// This plugin wants the beacon to go somewhere else,
// so update the location
@@ -1739,6 +2597,16 @@ BOOMR_check_doc_domain();
return true;
},
+ /**
+ * Sends all beacon data.
+ *
+ * This function should be called directly any time a "new" beacon is about
+ * to be constructed. For example, if you're creating a new XHR or other
+ * custom beacon, you should ensure the existing beacon data is flushed
+ * by calling `BOOMR.real_sendBeacon();` first.
+ *
+ * @memberof BOOMR
+ */
real_sendBeacon: function() {
var k, form, url, errors = [], params = [], paramsJoined, varsSent = {};
@@ -1846,7 +2714,7 @@ BOOMR_check_doc_domain();
impl.fireEvent("before_beacon", impl.vars);
// clone the vars object for two reasons: first, so all listeners of
- // onbeacon get an exact clone (in case listeners are doing
+ // 'beacon' get an exact clone (in case listeners are doing
// BOOMR.removeVar), and second, to help build our priority list of vars.
for (k in impl.vars) {
if (impl.vars.hasOwnProperty(k)) {
@@ -1917,7 +2785,7 @@ BOOMR_check_doc_domain();
}
// If we reach here, we've figured out all of the beacon data we'll send.
- impl.fireEvent("onbeacon", data);
+ impl.fireEvent("beacon", data);
// get high- and low-priority variables first, which remove any of
// those vars from data
@@ -2006,13 +2874,28 @@ BOOMR_check_doc_domain();
this.sendXhrPostBeacon(xhr, paramsJoined);
}
}
+
+ return true;
+ },
+
+ /**
+ * Determines whether or not a Page Load beacon has been sent.
+ *
+ * @returns {boolean} True if a Page Load beacon has been sent.
+ *
+ * @memberof BOOMR
+ */
+ hasSentPageLoadBeacon: function() {
+ return impl.hasSentPageLoadBeacon;
},
/**
- * Sends an XHR beacon
+ * Sends a beacon via XMLHttpRequest
*
* @param {object} xhr XMLHttpRequest object
* @param {object} [paramsJoined] XMLHttpRequest.send() argument
+ *
+ * @memberof BOOMR
*/
sendXhrPostBeacon: function(xhr, paramsJoined) {
xhr.open("POST", impl.beacon_url);
@@ -2037,6 +2920,8 @@ BOOMR_check_doc_domain();
* @param {number} pri Priority (-1, 0, or 1)
*
* @return {string[]} Array of URI-encoded vars
+ *
+ * @memberof BOOMR
*/
getVarsOfPriority: function(vars, pri) {
var name, url = [];
@@ -2076,6 +2961,8 @@ BOOMR_check_doc_domain();
* @param {string} value Value
*
* @returns {string} URI-encoded string
+ *
+ * @memberof BOOMR
*/
getUriEncodedVar: function(name, value) {
if (value === undefined || value === null) {
@@ -2093,12 +2980,17 @@ BOOMR_check_doc_domain();
},
/**
- * Gets the latest ResourceTiming entry for the specified URL
- * Default sort order is chronological startTime
+ * Gets the latest ResourceTiming entry for the specified URL.
+ *
+ * Default sort order is chronological startTime.
+ *
* @param {string} url Resource URL
* @param {function} [sort] Sort the entries before returning the last one
+ *
* @returns {PerformanceEntry|undefined} Entry, or undefined if ResourceTiming is not
- * supported or if the entry doesn't exist
+ * supported or if the entry doesn't exist
+ *
+ * @memberof BOOMR
*/
getResourceTiming: function(url, sort) {
var entries, p = BOOMR.getPerformance();
@@ -2123,7 +3015,22 @@ BOOMR_check_doc_domain();
delete BOOMR_start;
+ /**
+ * @global
+ * @type {TimeStamp}
+ * @name BOOMR_lstart
+ * @desc
+ * Time the loader script started fetching boomerang.js (if the asynchronous
+ * loader snippet is used).
+ */
if (typeof BOOMR_lstart === "number") {
+ /**
+ * Time the loader script started fetching boomerang.js (if using the
+ * asynchronous loader snippet) (`BOOMR_lstart`)
+ * @type {TimeStamp}
+ *
+ * @memberof BOOMR
+ */
boomr.t_lstart = BOOMR_lstart;
delete BOOMR_lstart;
}
@@ -2131,7 +3038,24 @@ BOOMR_check_doc_domain();
boomr.t_lstart = BOOMR.window.BOOMR_lstart;
}
+ /**
+ * Time the `window.onload` event fired (if using the asynchronous loader snippet).
+ *
+ * This timestamp is logged in the case boomerang.js loads after the onload event
+ * for browsers that don't support NavigationTiming.
+ *
+ * @global
+ * @name BOOMR_onload
+ * @type {TimeStamp}
+ */
if (typeof BOOMR.window.BOOMR_onload === "number") {
+ /**
+ * Time the loader script started (if using the asynchronous loader snippet)
+ * (`BOOMR_onload`)
+ *
+ * @type {TimeStamp}
+ * @memberof BOOMR
+ */
boomr.t_onload = BOOMR.window.BOOMR_onload;
}
@@ -2139,7 +3063,24 @@ BOOMR_check_doc_domain();
var make_logger;
if (typeof console === "object" && console.log !== undefined) {
- boomr.log = function(m, l, s) { console.log("(" + BOOMR.now() + ") " + "{" + BOOMR.pageId + "}" + ": " + s + ": [" + l + "] " + m); };
+ /**
+ * Logs the message to the console
+ *
+ * @param {string} m Message
+ * @param {string} l Log level
+ * @param {string} [s] Source
+ *
+ * @function log
+ *
+ * @memberof BOOMR
+ */
+ boomr.log = function(m, l, s) {
+ console.log("(" + BOOMR.now() + ") " +
+ "{" + BOOMR.pageId + "}" +
+ ": " + s +
+ ": [" + l + "] " +
+ m);
+ };
}
make_logger = function(l) {
@@ -2149,9 +3090,54 @@ BOOMR_check_doc_domain();
};
};
+ /**
+ * Logs debug messages to the console
+ *
+ * Debug messages are stripped out of production builds.
+ *
+ * @param {string} m Message
+ * @param {string} [s] Source
+ *
+ * @function debug
+ *
+ * @memberof BOOMR
+ */
boomr.debug = make_logger("debug");
+
+ /**
+ * Logs info messages to the console
+ *
+ * @param {string} m Message
+ * @param {string} [s] Source
+ *
+ * @function info
+ *
+ * @memberof BOOMR
+ */
boomr.info = make_logger("info");
+
+ /**
+ * Logs warning messages to the console
+ *
+ * @param {string} m Message
+ * @param {string} [s] Source
+ *
+ * @function warn
+ *
+ * @memberof BOOMR
+ */
boomr.warn = make_logger("warn");
+
+ /**
+ * Logs error messages to the console
+ *
+ * @param {string} m Message
+ * @param {string} [s] Source
+ *
+ * @function error
+ *
+ * @memberof BOOMR
+ */
boomr.error = make_logger("error");
}());
@@ -2160,7 +3146,8 @@ BOOMR_check_doc_domain();
var p = boomr.getPerformance();
if (p &&
typeof p.now === "function" &&
- /\[native code\]/.test(String(p.now)) && // #545 handle bogus performance.now from broken shims
+ // #545 handle bogus performance.now from broken shims
+ /\[native code\]/.test(String(p.now)) &&
p.timing &&
p.timing.navigationStart) {
boomr.now = function() {
@@ -2179,8 +3166,26 @@ BOOMR_check_doc_domain();
BOOMR[ident] = boomr[ident];
}
}
+
if (!BOOMR.xhr_excludes) {
- //! URLs to exclude from automatic XHR instrumentation
+ /**
+ * URLs to exclude from automatic `XMLHttpRequest` instrumentation.
+ *
+ * You can put any of the following in it:
+ * * A full URL
+ * * A hostname
+ * * A path
+ *
+ * @example
+ * BOOMR = window.BOOMR || {};
+ * BOOMR.xhr_excludes = {
+ * "mysite.com": true,
+ * "/dashboard/": true,
+ * "https://mysite.com/dashboard/": true
+ * };
+ *
+ * @memberof BOOMR
+ */
BOOMR.xhr_excludes = {};
}
}());
diff --git a/doc-template/README.txt b/doc-template/README.txt
new file mode 100644
index 000000000..990e632c6
--- /dev/null
+++ b/doc-template/README.txt
@@ -0,0 +1,24 @@
+Modified from docstrap: https://github.com/docstrap/docstrap
+
+Copyright (c) 2012-15 Terry Weiss & Contributors. All rights reserved.
+
+Permission is hereby granted, free of charge, to any person
+obtaining a copy of this software and associated documentation
+files (the "Software"), to deal in the Software without
+restriction, including without limitation the rights to use,
+copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the
+Software is furnished to do so, subject to the following
+conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
+OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
+OTHER DEALINGS IN THE SOFTWARE.
diff --git a/doc-template/publish.js b/doc-template/publish.js
new file mode 100644
index 000000000..99718ae54
--- /dev/null
+++ b/doc-template/publish.js
@@ -0,0 +1,881 @@
+"use strict";
+
+/**
+ * @module template/publish
+ * @type {*}
+ */
+/*global env: true */
+
+var template = require('jsdoc/template'),
+ doop = require('jsdoc/util/doop'),
+ fs = require('jsdoc/fs'),
+ _ = require('underscore'),
+ path = require('jsdoc/path'),
+
+ taffy = require('taffydb').taffy,
+ handle = require('jsdoc/util/error').handle,
+ helper = require('jsdoc/util/templateHelper'),
+ moment = require("moment"),
+ htmlsafe = helper.htmlsafe,
+ sanitizeHtml = require('sanitize-html'),
+ linkto = helper.linkto,
+ resolveAuthorLinks = helper.resolveAuthorLinks,
+ scopeToPunc = helper.scopeToPunc,
+ hasOwnProp = Object.prototype.hasOwnProperty,
+ conf = env.conf.templates || {},
+ data,
+ view,
+ outdir = env.opts.destination,
+ searchEnabled = conf.search !== false;
+
+var globalUrl = helper.getUniqueFilename('global');
+var indexUrl = helper.getUniqueFilename('index');
+
+var navOptions = {
+ includeDate: conf.includeDate !== false,
+ logoFile: conf.logoFile,
+ systemName: conf.systemName || "Documentation",
+ navType: conf.navType || "vertical",
+ footer: conf.footer || "",
+ copyright: conf.copyright || "",
+ theme: conf.theme || "simplex",
+ syntaxTheme: conf.syntaxTheme || "default",
+ linenums: conf.linenums,
+ collapseSymbols: conf.collapseSymbols || false,
+ inverseNav: conf.inverseNav,
+ outputSourceFiles: conf.outputSourceFiles === true,
+ sourceRootPath: conf.sourceRootPath,
+ disablePackagePath: conf.disablePackagePath,
+ outputSourcePath: conf.outputSourcePath,
+ dateFormat: conf.dateFormat,
+ analytics: conf.analytics || null,
+ methodHeadingReturns: conf.methodHeadingReturns === true,
+ sort: conf.sort,
+ search: searchEnabled
+};
+var searchableDocuments = {};
+
+var navigationMaster = {
+ index: {
+ title: navOptions.systemName,
+ link: indexUrl,
+ members: []
+ },
+ namespace: {
+ title: "Namespaces",
+ link: helper.getUniqueFilename("namespaces.list"),
+ members: []
+ },
+ module: {
+ title: "Modules",
+ link: helper.getUniqueFilename("modules.list"),
+ members: []
+ },
+ class: {
+ title: "Classes",
+ link: helper.getUniqueFilename('classes.list'),
+ members: []
+ },
+
+ mixin: {
+ title: "Mixins",
+ link: helper.getUniqueFilename("mixins.list"),
+ members: []
+ },
+ event: {
+ title: "Events",
+ link: helper.getUniqueFilename("events.list"),
+ members: []
+ },
+ interface: {
+ title: "Interfaces",
+ link: helper.getUniqueFilename("interfaces.list"),
+ members: []
+ },
+ tutorial: {
+ title: "Tutorials",
+ link: helper.getUniqueFilename("tutorials.list"),
+ members: []
+ },
+ global: {
+ title: "Global",
+ link: globalUrl,
+ members: []
+
+ },
+ external: {
+ title: "Externals",
+ link: helper.getUniqueFilename("externals.list"),
+ members: []
+ }
+};
+
+function find(spec) {
+ return helper.find(data, spec);
+}
+
+function tutoriallink(tutorial) {
+ return helper.toTutorial(tutorial, null, {
+ tag: 'em',
+ classname: 'disabled',
+ prefix: 'Tutorial: '
+ });
+}
+
+function getAncestorLinks(doclet) {
+ return helper.getAncestorLinks(data, doclet);
+}
+
+function hashToLink(doclet, hash) {
+ if (!/^(#.+)/.test(hash)) {
+ return hash;
+ }
+
+ var url = helper.createLink(doclet);
+
+ url = url.replace(/(#.+|$)/, hash);
+ return '' + hash + '';
+}
+
+function needsSignature(doclet) {
+ var needsSig = false;
+
+ // function and class definitions always get a signature
+ if (doclet.kind === 'function' || doclet.kind === 'class') {
+ needsSig = true;
+ }
+ // typedefs that contain functions get a signature, too
+ else if (doclet.kind === 'typedef' && doclet.type && doclet.type.names &&
+ doclet.type.names.length) {
+ for (var i = 0, l = doclet.type.names.length; i < l; i++) {
+ if (doclet.type.names[i].toLowerCase() === 'function') {
+ needsSig = true;
+ break;
+ }
+ }
+ }
+
+ return needsSig;
+}
+
+function addSignatureParams(f) {
+ var optionalClass = 'optional';
+ var params = helper.getSignatureParams(f, optionalClass);
+
+ f.signature = (f.signature || '') + '(';
+
+ for (var i = 0, l = params.length; i < l; i++) {
+ var element = params[i];
+ var seperator = (i > 0) ? ', ' : '';
+
+ if (!new RegExp("class=[\"|']"+optionalClass+"[\"|']").test(element)) {
+ f.signature += seperator + element;
+ } else {
+ var regExp = new RegExp("(.*?)<\\/span>", "i");
+ f.signature += element.replace(regExp, " $`["+seperator+"$1$']");
+ }
+
+ }
+
+ f.signature += ')';
+}
+
+function addSignatureReturns(f) {
+ if (navOptions.methodHeadingReturns) {
+ var returnTypes = helper.getSignatureReturns(f);
+
+ f.signature = '' + (f.signature || '') + '' + '' + (returnTypes.length ? ' → {' + returnTypes.join('|') + '}' : '') + '';
+ }
+ else {
+ f.signature = f.signature || '';
+ }
+}
+
+function addSignatureTypes(f) {
+ var types = helper.getSignatureTypes(f);
+
+ f.signature = (f.signature || '') + '' + (types.length ? ' :' + types.join('|') : '') + '';
+}
+
+function addAttribs(f) {
+ var attribs = helper.getAttribs(f);
+
+ f.attribs = '' + htmlsafe(attribs.length ? '<' + attribs.join(', ') + '> ' : '') + '';
+}
+
+function shortenPaths(files, commonPrefix) {
+ Object.keys(files).forEach(function(file) {
+ files[file].shortened = files[file].resolved.replace(commonPrefix, '')
+ // always use forward slashes
+ .replace(/\\/g, '/');
+ });
+
+
+ return files;
+}
+
+function getPathFromDoclet(doclet) {
+ if (!doclet.meta) {
+ return;
+ }
+
+ return path.normalize(doclet.meta.path && doclet.meta.path !== 'null' ?
+ doclet.meta.path + '/' + doclet.meta.filename :
+ doclet.meta.filename);
+}
+
+function searchData(html) {
+ var startOfContent = html.indexOf("
");
+ if (startOfContent > 0) {
+ var startOfSecondContent = html.indexOf("
", startOfContent + 2);
+ if (startOfSecondContent > 0) {
+ startOfContent = startOfSecondContent;
+ }
+ html = html.slice(startOfContent);
+ }
+ var endOfContent = html.indexOf("");
+ if (endOfContent > 0) {
+ html = html.substring(0, endOfContent);
+ }
+ var stripped = sanitizeHtml(html, {allowedTags: [], allowedAttributes: []});
+ stripped = stripped.replace(/\s+/g, ' ');
+ return stripped;
+}
+
+function generate(docType, title, docs, filename, resolveLinks) {
+ resolveLinks = resolveLinks === false ? false : true;
+
+ var docData = {
+ title: title,
+ docs: docs,
+ docType: docType
+ };
+
+ var outpath = path.join(outdir, filename),
+ html = view.render('container.tmpl', docData);
+
+ if (resolveLinks) {
+ html = helper.resolveLinks(html); // turn {@link foo} into foo
+ }
+
+ if (searchEnabled) {
+ searchableDocuments[filename] = {
+ "id": filename,
+ "title": title,
+ "body": searchData(html)
+ };
+ }
+
+ fs.writeFileSync(outpath, html, 'utf8');
+}
+
+function generateSourceFiles(sourceFiles) {
+ Object.keys(sourceFiles).forEach(function(file) {
+ var source;
+ // links are keyed to the shortened path in each doclet's `meta.shortpath` property
+ var sourceOutfile = helper.getUniqueFilename(sourceFiles[file].shortened);
+ helper.registerLink(sourceFiles[file].shortened, sourceOutfile);
+
+ try {
+ source = {
+ kind: 'source',
+ code: helper.htmlsafe(fs.readFileSync(sourceFiles[file].resolved, 'utf8'))
+ };
+ } catch (e) {
+ handle(e);
+ }
+
+ generate('source', 'Source: ' + sourceFiles[file].shortened, [source], sourceOutfile,
+ false);
+ });
+}
+
+/**
+ * Look for classes or functions with the same name as modules (which indicates that the module
+ * exports only that class or function), then attach the classes or functions to the `module`
+ * property of the appropriate module doclets. The name of each class or function is also updated
+ * for display purposes. This function mutates the original arrays.
+ *
+ * @private
+ * @param {Array.} doclets - The array of classes and functions to
+ * check.
+ * @param {Array.} modules - The array of module doclets to search.
+ */
+function attachModuleSymbols(doclets, modules) {
+ var symbols = {};
+
+ // build a lookup table
+ doclets.forEach(function(symbol) {
+ symbols[symbol.longname] = symbols[symbol.longname] || [];
+ symbols[symbol.longname].push(symbol);
+ });
+
+ return modules.map(function(module) {
+ if (symbols[module.longname]) {
+ module.modules = symbols[module.longname]
+ // Only show symbols that have a description. Make an exception for classes, because
+ // we want to show the constructor-signature heading no matter what.
+ .filter(function(symbol) {
+ return symbol.description || symbol.kind === 'class';
+ })
+ .map(function(symbol) {
+ symbol = doop(symbol);
+
+ if (symbol.kind === 'class' || symbol.kind === 'function') {
+ symbol.name = symbol.name.replace('module:', '(require("') + '"))';
+ }
+
+ return symbol;
+ });
+ }
+ });
+}
+
+/**
+ * Create the navigation sidebar.
+ * @param {object} members The members that will be used to create the sidebar.
+ * @param {array
',trigger:"hover focus",title:"",delay:0,html:!1,container:!1,viewport:{selector:"body",padding:0}},c.prototype.init=function(b,c,d){if(this.enabled=!0,this.type=b,this.$element=a(c),this.options=this.getOptions(d),this.$viewport=this.options.viewport&&a(a.isFunction(this.options.viewport)?this.options.viewport.call(this,this.$element):this.options.viewport.selector||this.options.viewport),this.inState={click:!1,hover:!1,focus:!1},this.$element[0]instanceof document.constructor&&!this.options.selector)throw new Error("`selector` option must be specified when initializing "+this.type+" on the window.document object!");for(var e=this.options.trigger.split(" "),f=e.length;f--;){var g=e[f];if("click"==g)this.$element.on("click."+this.type,this.options.selector,a.proxy(this.toggle,this));else if("manual"!=g){var h="hover"==g?"mouseenter":"focusin",i="hover"==g?"mouseleave":"focusout";this.$element.on(h+"."+this.type,this.options.selector,a.proxy(this.enter,this)),this.$element.on(i+"."+this.type,this.options.selector,a.proxy(this.leave,this))}}this.options.selector?this._options=a.extend({},this.options,{trigger:"manual",selector:""}):this.fixTitle()},c.prototype.getDefaults=function(){return c.DEFAULTS},c.prototype.getOptions=function(b){return b=a.extend({},this.getDefaults(),this.$element.data(),b),b.delay&&"number"==typeof b.delay&&(b.delay={show:b.delay,hide:b.delay}),b},c.prototype.getDelegateOptions=function(){var b={},c=this.getDefaults();return this._options&&a.each(this._options,function(a,d){c[a]!=d&&(b[a]=d)}),b},c.prototype.enter=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusin"==b.type?"focus":"hover"]=!0),c.tip().hasClass("in")||"in"==c.hoverState?void(c.hoverState="in"):(clearTimeout(c.timeout),c.hoverState="in",c.options.delay&&c.options.delay.show?void(c.timeout=setTimeout(function(){"in"==c.hoverState&&c.show()},c.options.delay.show)):c.show())},c.prototype.isInStateTrue=function(){for(var a in this.inState)if(this.inState[a])return!0;return!1},c.prototype.leave=function(b){var c=b instanceof this.constructor?b:a(b.currentTarget).data("bs."+this.type);return c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c)),b instanceof a.Event&&(c.inState["focusout"==b.type?"focus":"hover"]=!1),c.isInStateTrue()?void 0:(clearTimeout(c.timeout),c.hoverState="out",c.options.delay&&c.options.delay.hide?void(c.timeout=setTimeout(function(){"out"==c.hoverState&&c.hide()},c.options.delay.hide)):c.hide())},c.prototype.show=function(){var b=a.Event("show.bs."+this.type);if(this.hasContent()&&this.enabled){this.$element.trigger(b);var d=a.contains(this.$element[0].ownerDocument.documentElement,this.$element[0]);if(b.isDefaultPrevented()||!d)return;var e=this,f=this.tip(),g=this.getUID(this.type);this.setContent(),f.attr("id",g),this.$element.attr("aria-describedby",g),this.options.animation&&f.addClass("fade");var h="function"==typeof this.options.placement?this.options.placement.call(this,f[0],this.$element[0]):this.options.placement,i=/\s?auto?\s?/i,j=i.test(h);j&&(h=h.replace(i,"")||"top"),f.detach().css({top:0,left:0,display:"block"}).addClass(h).data("bs."+this.type,this),this.options.container?f.appendTo(this.options.container):f.insertAfter(this.$element),this.$element.trigger("inserted.bs."+this.type);var k=this.getPosition(),l=f[0].offsetWidth,m=f[0].offsetHeight;if(j){var n=h,o=this.getPosition(this.$viewport);h="bottom"==h&&k.bottom+m>o.bottom?"top":"top"==h&&k.top-mo.width?"left":"left"==h&&k.left-lg.top+g.height&&(e.top=g.top+g.height-i)}else{var j=b.left-f,k=b.left+f+c;jg.right&&(e.left=g.left+g.width-k)}return e},c.prototype.getTitle=function(){var a,b=this.$element,c=this.options;return a=b.attr("data-original-title")||("function"==typeof c.title?c.title.call(b[0]):c.title)},c.prototype.getUID=function(a){do a+=~~(1e6*Math.random());while(document.getElementById(a));return a},c.prototype.tip=function(){if(!this.$tip&&(this.$tip=a(this.options.template),1!=this.$tip.length))throw new Error(this.type+" `template` option must consist of exactly 1 top-level element!");return this.$tip},c.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".tooltip-arrow")},c.prototype.enable=function(){this.enabled=!0},c.prototype.disable=function(){this.enabled=!1},c.prototype.toggleEnabled=function(){this.enabled=!this.enabled},c.prototype.toggle=function(b){var c=this;b&&(c=a(b.currentTarget).data("bs."+this.type),c||(c=new this.constructor(b.currentTarget,this.getDelegateOptions()),a(b.currentTarget).data("bs."+this.type,c))),b?(c.inState.click=!c.inState.click,c.isInStateTrue()?c.enter(c):c.leave(c)):c.tip().hasClass("in")?c.leave(c):c.enter(c)},c.prototype.destroy=function(){var a=this;clearTimeout(this.timeout),this.hide(function(){a.$element.off("."+a.type).removeData("bs."+a.type),a.$tip&&a.$tip.detach(),a.$tip=null,a.$arrow=null,a.$viewport=null})};var d=a.fn.tooltip;a.fn.tooltip=b,a.fn.tooltip.Constructor=c,a.fn.tooltip.noConflict=function(){return a.fn.tooltip=d,this}}(jQuery),+function(a){"use strict";function b(b){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof b&&b;(e||!/destroy|hide/.test(b))&&(e||d.data("bs.popover",e=new c(this,f)),"string"==typeof b&&e[b]())})}var c=function(a,b){this.init("popover",a,b)};if(!a.fn.tooltip)throw new Error("Popover requires tooltip.js");c.VERSION="3.3.6",c.DEFAULTS=a.extend({},a.fn.tooltip.Constructor.DEFAULTS,{placement:"right",trigger:"click",content:"",template:'
+
+
+
diff --git a/doc-template/tmpl/type.tmpl b/doc-template/tmpl/type.tmpl
new file mode 100644
index 000000000..a35c77581
--- /dev/null
+++ b/doc-template/tmpl/type.tmpl
@@ -0,0 +1,7 @@
+
+
+|
+
diff --git a/doc/TODO.md b/doc/TODO.md
deleted file mode 100644
index e2bbb6cbf..000000000
--- a/doc/TODO.md
+++ /dev/null
@@ -1,8 +0,0 @@
-1. Add random sampling
- This needs to be a little intelligent because plugins may have different
- criteria for sampling. For example, the RT plugin requires two pages --
- one for tstart and one for tend. Since tstart is a relatively inexpensive
- operation, it makes sense for us to set tstart on all pages, but only set
- tend based on the random sample.
-
-2. Rewrite bandwidth testing code to be pretty and clean
diff --git a/doc/api/AutoXHR.md b/doc/api/AutoXHR.md
deleted file mode 100644
index 350ea164b..000000000
--- a/doc/api/AutoXHR.md
+++ /dev/null
@@ -1,92 +0,0 @@
-AutoXHR - Instrument and measure AJAX requests and DOM manipulations triggering network requests
-
-With this plugin sites can not only monitor their page load but also the XMLHttpRequests after the page has been loaded
-as well as the DOM manipulations that trigger an asset to be fetched.
-
-### Configuring Boomerang with AutoXHR included
-
-You can enable the AutoXHR plugin with:
-
-```js
-BOOMR.init({
- instrument_xhr: true
-})
-```
-
-Once this has been done and the site has been loaded and instrumented with this plugin, we have replaced the `XMLHttpRequest` object
-on the site with our very own!
-
-### Monitoring XHRs
-
-If you were to send an `XMLHttpRequest` on your page now you can see not only a page load beacon but also an XHR beacon:
-
-```js
-xhr = new XMLHttpRequest();
-xhr.open("GET", "support/script200.js"); //200, async
-xhr.send(null);
-```
-
-If you have boomerang with the auto_xhr.js plugin included in your page and someone visited the page, we are able
-to measure the time this asset takes to come down from the server. Measured performance data will include:
-
- - `u`: the URL of the image that has been fetched
- - `http.initiator`: The type of Beacon or Request that has been triggered in this case `xhr`
-
-### Using AutoXHR to monitor DOM events
-
-Say for example that you have button on the page that will insert a new picture, we can monitor this asset coming down and
-send timing information for that page:
-
-```html
-
-
-
Click Me!
-
-```
-
-### Before Page Load XHR Beacons
-
-By default AutoXHR will wait until the pages page load beacon has been sent to enable itself to start sending beacons that will correspond with the pages
-XHRs. If you wish to enable AutoXHR Beacons sending before the site itself has loaded, you can set a config flag for the AutoXHR plugin to enable it to
-send beacons as soon as it has instrumented the page.
-
-```
-BOOMR.init({
- instrument_xhr: true,
- AutoXHR: {
- alwaysSendXhr: true
- }
-})
-```
-
-### Compatibility and Browser Support
-
-Currently supported Browsers and platforms that AutoXHR will work on:
-
- - IE 9+ (not in quirks mode)
- - Chrome 38+
- - Firefox 25+
-
-In general we support all browsers that support `MutationObserver`, `XMLHttpRequest` with `addEventListener` on the XMLHttpRequest instance
-
-
diff --git a/doc/api/BOOMR.html b/doc/api/BOOMR.html
deleted file mode 100644
index d3c6fcb21..000000000
--- a/doc/api/BOOMR.html
+++ /dev/null
@@ -1,434 +0,0 @@
-
-
-
-The BOOMR API
-
-
-
-
-All Docs | Index
-
The BOOMR object
-
-Everything in boomerang is accessed through the BOOMR object. Each plugin has its
-own API, but is reachable through BOOMR.plugins. This document describes the main
-BOOMR object.
-
-
-
-To access any of the following, dereference the BOOMR object. eg: use BOOMR.version
-to get the version string.
-
-
-
Properties
-
-
-
-
version
-
-
-The version number of the boomerang library. This is a string, formatted as major.minor.patchlevel.
-Standard version numbering rules apply
-
-
-
-
t_start
-
-
-The timestamp when the boomerang code showed up on the page.
-
-
-
-
t_end
-
-
-The timestamp when the boomerang code finished loading. Note, this will only be set if you used make to make a combined version of boomerang.
-
-
-
-
plugins
-
-
-An object containing all plugins that have been added to boomerang. If you build your own plugin,
-it should be added to this object:
-
-[highly recommended]
-The URL to beacon results back to. All parameters will be added to this URL's
-query string. This URL should not already have a query string component.
-There is no default value for this parameter. If not set, no beacon will be sent.
-
-
-
beacon_type
-
-[optional]
-Specify the HTTP method for beacon requests, may be 'GET', 'POST' or 'AUTO' (the default).
-The AUTO setting will make a GET request unless the combined beacon URL plus query string
-exceeds 2000 characters, in which case it will issue a POST. The data will be
-application/x-www-form-urlencoded.
-
-
-
beacon_auth_key
-
-[optional]
-Specify the HTTP authentication key for beacon requests. This key will default to 'Authorization', but may be explicitly set using this
-setting. Some services use specialized authentication keys such as 'X-API-KEY'.
-
-
-
beacon_auth_token
-
-[optional]
-Specify the HTTP authentication token for beacon requests. This token is generally supplied by the service accepting the beacon request and will take
-a form similar to'SPLUNK 4F00X7AF-B3D3-4E07-8C6C-12345678901'. The value supplied here will be contatenated with the beacon_auth_key to
-form a complete HTTP request header field such as 'Authorization':'SPLUNK 4F00X7AF-B3D3-4E07-8C6C-12345678901'.
-
-
-
site_domain
-
-[recommended]
-The domain that all cookies should be set on. Boomerang will try to auto-detect this,
-but unless your site is of the foo.com format, it will probably get it
-wrong. It's a good idea to set this to whatever part of your domain you'd like to
-share bandwidth and performance measurements across.
-If you have multiple domains, then you're out of luck. You'll just have to get
-separate measurements across them.
-Set this to a falsy value to disable all cookies.
-
-
-
user_ip
-
-[recommended]
-Despite its name, this is really a free-form string used to uniquely identify the user's
-current internet connection. It's used primarily by the bandwidth test to determine
-whether it should re-measure the user's bandwidth or just use the value stored in the
-cookie. You may use IPv4, IPv6 or anything else that you think can be used to identify
-the user's network connection.
-
-
-
log
-
-[optional]
-By default, boomerang will attempt to use the logger component from YUI if it finds it
-or firebug if it finds that instead. If it finds neither, it will default to not
-logging anything. You can define your own logger by setting the log
-parameter to a function that logs messages.
-The signature of this function is:
-
-function log(oMessage, sLevel, sSource);
-
-Where:
-
oMessage
is the object/message to be logged. It is up to you to decide how to log objects.
-
sLevel
is the log level, with values of "error", "warn", "info" and "debug"
-
sSource
is the source of the log message. This will typically be the string "boomerang" followed by the name of a plugin
-
-Note that you can completely disable logging by setting log to null.
-
-
-
autorun
-
-[optional]
-By default, boomerang runs automatically and attaches its page_ready handler to
-the window.onload event. If you set autorun to false,
-this will not happen and you will need to call BOOMR.page_ready() yourself.
-
-
-
<plugin_name>
-
-Each plugin is configured through a sub-object of the config object. The key is the name
-of the plugin. Each plugin's documentation will have details on its configuration object.
-
-
-
-
-
Methods
-
-
-
-
init(oConfig)
-
-
-The init method that you to call to initialise boomerang. Call this method once after you've
-loaded the boomerang javascript. It accepts a single configuration object as a parameter. See
-the Configuration section for details.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
page_ready()
-
-
-Method that fires the page_ready event. Call this only if you've set autorun to
-false when calling the init() method. You should call this method when you determine that
-your page is ready to be used by your user. This will be the end-time used in the page load time measurement.
-
-
Example:
-
-See Howto #1b for an example of how to use this method.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-The subscribe method is used to subscribe an event handler to one of boomerang's events.
-It accepts four parameters:
-
-
Parameters:
-
-
sEvent
-
The event name. This may be one of page_ready, page_unload, before_beacon
-
-
fCallbackFn
-
A reference to the callback function that will be called when this event fires. The function's signature should be:
-
function(oEventData, oCallbackData);
-
-
-
oCallbackData
-
[optional] object passed as the second parameter to the callback function
-
-
oCallbackScope
-
[optional] If set to an object, then the callback function is called as a method of this object, and all references to this
- within the callback function will refer to oCallbackScope
-
-
-The page_ready and page_unload events are most useful to plugins while the
-before_beacon event is useful to code that wants to do something with the beacon parameters
-before the beacon is fired. See the events section for more details.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
addVar(sName, sValue) OR addVar(oVars)
-
-
-Add one or more parameters to the beacon. This method is used by plugins to add parameters to the beacon,
-but may also be used by the page developer to tag the current request.
-
-This method may either be called with a single object containing key/value pairs, or with two parameters,
-the first is the variable name and the second is its value. All names should be strings usable in a URL's
-query string. We recommend only using alphanumeric characters and underscores, but you can use anything you
-like. Values should be strings (or numbers), and have the same restrictions as names.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
removeVar(sName, ...)
-
-
-Removes one or more variables from the beacon URL. This is useful within a plugin to reset the values of
-parameters that it is about to set. It can also be used in a before_beacon handler to stop
-the beacon from being sent. See Howto #5 for how to do this.
-
-
-This method accepts either a list of variable names, or a single array containing a list of variable names.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
sendBeacon()
-
-
-Request boomerang to send its beacon. Boomerang may ignore this request. When this method is called,
-boomerang checks all plugins. If any plugin has not completed its checks (ie, the plugin's is_complete()
-method returns false, then this method does nothing. If all plugins have completed, then this method fires the
-before_beacon event with all variables that will be sent on the beacon.
-
-
-After all before_beacon handlers return, this method checks if a beacon_url has been
-configured and if there are any beacon parameters to be sent. If both are true, it fires the beacon.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
log(sMessage, sLevel, sSource)
-
-
-Log a sMessage to the configured logger with a level of sLevel. This method
-simply passes all logging information on to the configured logger. See
-Howto #6 for details on how to configure this.
-
-
-You probably want to use one of the convenience methods below instead that set the log level correctly.
-
-
Returns
-
-nothing
-
-
-
-
debug(sMessage, sSource)
-
-
-Log sMessage to the configured logger with a level of debug. If sSource is set,
-it is appended to the string "boomerang." and set as the source of the log message. Use this
-parameter to mention a plugin name and/or a line number/function name.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
info(sMessage, sSource)
-
-
-Log sMessage to the configured logger with a level of info. If sSource is set,
-it is appended to the string "boomerang." and set as the source of the log message. Use this
-parameter to mention a plugin name and/or a line number/function name.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
warn(sMessage, sSource)
-
-
-Log sMessage to the configured logger with a level of warn. If sSource is set,
-it is appended to the string "boomerang." and set as the source of the log message. Use this
-parameter to mention a plugin name and/or a line number/function name.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
error(sMessage, sSource)
-
-
-Log sMessage to the configured logger with a level of error. If sSource is set,
-it is appended to the string "boomerang." and set as the source of the log message. Use this
-parameter to mention a plugin name and/or a line number/function name.
-
-
Returns
-
-a reference to the BOOMR object, so you can chain methods.
-
-
-
-
-
-
Events
-
-
-
page_ready
-
-
-Fired when the page is usable by the user. By default this is fired when window.onload fires,
-but if you set autorun to false when calling BOOMR.init(), then
-you must explicitly fire this event by calling BOOMR.page_ready().
-
-
Callback
-
-No additional event data is passed to the callback function. Any callback data is passed as specified in
-the subscribe() method.
-
-
-
-
page_unload
-
-
-Fired just before the browser unloads the page. This is fired when window.onbeforeunload fires
-(onunload on Opera).
-
-
Callback
-
-No additional event data is passed to the callback function. Any callback data is passed as specified in
-the subscribe() method.
-
-
-
-
visibility_changed
-
-
-Fired if the page's visibility state changes. Currently only supported on IE10 and Chrome.
-
-
Callback
-
-No additional event data is passed to the callback function. Any callback data is passed as specified in
-the subscribe() method.
-
-
-
-
before_beacon
-
-
-Fired just before the beacon is sent to the server. You can stop the beacon from firing by calling
-BOOMR.removeVar() for all beacon parameters.
-
-
Callback
-
-The callback function is called with two parameters. The first parameter is an object containing all
-parameters that will be added to the beacon. The second parameter is the callback data object that
-was passed in to the subscribe() method. If the callback function removes all parameters
-from boomerang, the beacon will not fire.
-
-
-
-
-
-
Beacon Parameters
-
-On its own, with no plugins set up, boomerang will send the following parameters across through the beacon:
-
-
-
v
-
The version number of the boomerang library in use.
-
u
-
The URL of the page that sends the beacon.
-
-
-
-Each plugin may add its own parameters, and these are specified in each plugin's API docs.
-
-All boomerang utility functions are under the BOOMR.utils namespace.
-To access any of the following, dereference the BOOMR.utils object. eg: use BOOMR.utils.getCookie()
-to call the getCookie() method.
-
-
-
Methods
-
-
-
-
getCookie(sName)
-
-
-Gets the value of the cookie identified by sName.
-
-
Returns
-
-
A string containing the cookie identified by sName. This may be the empty string.
-
null if the cookie wasn't found or if sName was empty.
-Sets the cookie named sName to the serialized value of oSubCookies.
-
-
Parameters:
-
-
-
sName
-
The name of the cookie
-
-
oSubCookies
-
key/value pairs to write into the cookie. These will be serialized as an & separated
-list of URL encoded key=value pairs.
-
-
nMaxAge
-
Lifetime in seconds of the cookie. Set this to 0 to create a session cookie that expires when the browser
-is closed. If not set, defaults to 0.
-
-
sPath
-
The HTTP path that the cookie should be valid for. The cookie will be sent to all URLs on this domain that
-fall below sPath. If not set, defaults to the path of the current document. Unless you're on a server where
-multiple users share the same domain, you probably want to set this to /
-
-
sDomain
-
The HTTP domain that the cookie should be vali for. The cookie will be sent to all URLs that are subdomains
-of this domain. If not set, defaults to the current document's domain. If set to null, it will
-use the value of site_domain that was configured during the call to BOOMR.init().
-You probably want to set this to null.
-
-
-
bSecure
-
If set to true, then this cookie is only sent to HTTPS URLs. If set to false (or not set), this cookie is sent to all
-URLs that match the above rules. Unless your site is completely SSL based, you can leave this unset.
-
-
-
-Note that the entire cookie name and value needs to be less than 4000 characters.
-
-
-
Example:
-
-The BOOMR.plugins.RT plugin uses this function like this:
-
-The bandwidth plugin measures the bandwidth and latency of the user's network connection to your
-server. The bandwidth API is encapsulated within the BOOMR.plugins.BW namespace.
-
-
-
Configuration
-
-All bandwidth plugin configuration items are under the BW namespace.
-The full configuration object is described in Howto #6 — Configuring boomerang.
-
-
-
base_url
-
-[required]
-By default, this is set to the empty string, which has the effect of disabling the bandwidth plugin. Set the base_url
-parameter to the HTTP path of the directory that contains the bandwidth images to enable this test. This can be an absolute or
-a relative URL. If it's relative, remember that it's relative to the page that boomerang is included in and not to the javascript
-file. The trailing / is required.
-
-
-
cookie
-
-[optional]
-The name of the cookie in which to store the measured bandwidth and latency of the user's network connection.
-The default name is BA. See Howto #3 for more details on the bandwidth cookie.
-
-
-
cookie_exp
-
-[optional]
-The lifetime in seconds of the bandwidth cookie. The default is set to 7 days. This specifies how long it will be before
-we run the bandwidth test again for a user, assuming their IP address doesn't change within this time. You probably do
-not need to change this setting at all since the bandwidth of a given network connection typically does not change by an
-order of magnitude on a regular basis.
-Note that if you're doing some kind of real-time streaming, then chances are that this bandwidth test isn't right for you,
-so setting this cookie to a shorter value isn't the right solution.
-
-
-
timeout
-
-[optional]
-The timeout in seconds for the entire bandwidth test. The default is set to 15 seconds. The bandwidth test can run for
-a long time, and sometimes, due to network errors, it might never complete. The timeout forces the test to complete at
-that time. This is a hard limit. If the timeout fires, we stop further iterations of the test and attempt to calculate
-bandwidth with the data that we've collected at that point. Increasing the timeout can get you more data and increase
-the accuracy of the test, but at the same time increases the risk of the test not completing before the user leaves the
-page.
-
-
-
nruns
-
-[optional]
-The number of times the bandwidth test should run. The default is set to 5. The first test is always a pilot to figure
-out the best way to proceed with the remaining tests. Increasing this number will increase the tests accuracy, but at
-the same time increases the risk that the test will timeout. It should take about 2-4 seconds per run, so consider this
-value along with the timeout value above.
-
-
-
test_https
-
-[optional]
-By default, boomerang will skip the bandwidth test over an HTTPS connection. Establishing
-an SSL connection takes time, which could skew the bandwidth results. If all your traffic
-is sent over SSL, then running the test over SSL probably gets you what you want. If you
-set test_https to true, boomerang will run the test instead of skipping.
-
-
-
block_beacon
-
-[optional]
-By default, the bandwidth plugin will not block boomerang from sending a beacon, so the results
-will not be included in the broadcast with default settings. If you set block_beacon to
-true, boomerang will wait for the results of the test before sending the beacon.
-
-
-
-
-
-
Methods
-
-
-
-
init(oConfig)
-
-
-Called by the BOOMR.init() method to configure the bandwidth
-plugin.
-
-
Parameters
-
-
oConfig
-
The configuration object passed in via BOOMR.init(). See the Configuration section for details.
-
-
Returns
-
-a reference to the BOOMR.plugins.BW object, so you can chain methods.
-
-
-
-
run()
-
-
-Starts the bandwidth test. This method is called automatically when boomerang's
-page_ready event fires, so you won't need to call it
-yourself.
-
-
Returns
-
-a reference to the BOOMR.plugins.BW object, so you can chain methods.
-
-
-
-
abort()
-
-
-Stops the bandwidth test immediately and attempts to calculate bandwidth and latency
-from values that it has already gathered. This method is called automatically if the
-bandwidth test times out. It is better to set the timeout value appropriately
-when calling the BOOMR.init() method.
-
-
Returns
-
-a reference to the BOOMR.plugins.BW object, so you can chain methods.
-
-
-
-
is_complete()
-
-
-Called by BOOMR.sendBeacon() to determine if the bandwidth plugin has
-finished what it's doing or not.
-
-
Returns
-
-
true if the plugin has completed.
-
false if the plugin has not completed.
-
-
-
-
-
-
Beacon Parameters
-
-This plugin adds the following parameters to the beacon:
-
-
-
bw
User's measured bandwidth in bytes per second
-
bw_err
95% confidence interval margin of error in measuring user's bandwidth
-
lat
User's measured HTTP latency in milliseconds
-
lat_err
95% confidence interval margin of error in measuring user's latency
-
bw_time
Timestamp (seconds since the epoch) on the user's browser when the bandwidth and latency was measured
-Note: The DNS plugin hasn't been tested. Your help in testing it is appreciated.
-
-
-The DNS plugin measures the latency of DNS lookups from the user's browser to your server.
-The DNS API is encapsulated within the BOOMR.plugins.DNS namespace.
-
-
-
-Note that the DNS plugin requires some amount of server-side set up. See
-Howto #8 for details on how to set this up.
-
-
-
Methods
-
-
-
-
init(oConfig)
-
-
-Called by the BOOMR.init() method to configure the DNS
-plugin. There is only one configurable option:
-
-
-
base_url
-
-[required]
-The base_url parameter tells the DNS plugin where it can find its DNS testing
-images. This URL must contain a wildcard character which will be replaced with a random
-string. The images will be appended to this string without any other modification. If you
-have any pages served over HTTPS, then this URL should be configured to work over HTTPS as
-well as HTTP. The protocol part of the URL will be automatically changed to fit the current
-document.
-
-The roundtrip plugin measures page load time, or other timers associated with the page. Its API
-is encapsulated within the BOOMR.plugins.RT namespace.
-
-
-
Configuration
-
-
-All roundtrip plugin configuration items are under the RT namespace.
-The full configuration object is described in Howto #6 — Configuring boomerang.
-
-
-
-
cookie
-
-[optional]
-The name of the cookie in which to store the start time for measuring page load time. The default name is RT.
-Set this to a falsy value like null or the empty string to ignore cookies and depend completely on the
-WebTiming API for the start time.
-
-
-
cookie_exp
-
-[optional]
-The lifetime in seconds of the roundtrip cookie. This only needs to live for as long as it takes for a single page to load.
-Something like 10 seconds or so should be good for most cases, but to be safe, and to cover people with really slow
-connections, or users that are geographically far away from you, keep it to a few minutes. The default is set to 10 minutes.
-
-
-
strict_referrer
-
-[optional]
-By default, boomerang will not measure a page's roundtrip time if the URL in the RT cookie doesn't match
-the current page's document.referrer. This is so because it generally means that the user visited a
-third page while their RT cookie was still valid, and this could render the page load time invalid.
-There may be cases, though, when this is a valid flow — for example, you have an SSL page in between and the
-referrer isn't passed through. In this case, you'll want to set strict_referrer to false
-
-
-
-
-
-
Methods
-
-
-
-
init(oConfig)
-
-
-Called by the BOOMR.init() method to configure the roundtrip
-plugin.
-
-
Parameters
-
-
oConfig
-
The configuration object passed in via BOOMR.init(). See the Configuration section for details.
-
-
Returns
-
-a reference to the BOOMR.plugins.RT object, so you can chain methods.
-
-
-
-
startTimer(sName, [nValue])
-
-
-Starts the timer named sName. Timers count in milliseconds. You must call
-endTimer() when this timer has complete for the measurement to be recorded
-in boomerang's beacon.
-
-
-If passed in, the optional second parameter nValue is the timestamp in milliseconds
-to set the timer's start time to. This is useful if you need to record a timer that started
-before boomerang was loaded up.
-
-
Parameters:
-
-
sName
-
The name of the timer to start
-
-
nValue
-
[optional]
-A javascript timestamp value (milliseconds since the epoch). If set, the timer's start time will be set
-explicitly to this value. If not set, the current timestamp is used. You'd use this parameter if you
-measured a timestamp before boomerang was loaded and now need to pass that value to the
-roundtrip plugin.
-
-
-
Example:
-
-See Howto #4 for an example that uses startTimer and endTimer.
-
-
Returns
-
-a reference to the BOOMR.plugins.RT object, so you can chain methods.
-
-
Note
-
-Calling startTimer("t_page") has the side-effect of calling endTimer("t_resp").
-These timers are generally used to measure the time from (as close to) the first byte loaded to onload
-(t_page) and from onbeforeunload to the first byte time (t_resp).
-You do not need to explicitly call startTimer("t_resp").
-
-
-
-
endTimer(sName, [nValue])
-
-
-Stops the timer named sName. It is not necessary for the timer to have been started
-before you call endTimer(). If a timer with this name was not started, then the unload
-time of the previous page is used instead. This allows you to measure the time across pages.
-
-
Parameters:
-
-
sName
-
The name of the timer to stop
-
-
nValue
-
[optional]
-A javascript timestamp value (milliseconds since the epoch). If set, the timer's stop time will be set
-explicitly to this value. If not set, the current timestamp is used. You'd use this parameter if you
-measured a timestamp before boomerang was loaded and now need to pass that value to the
-roundtrip plugin.
-
-
-
Example:
-
-See Howto #4 for an example that uses startTimer and endTimer.
-
-
Returns
-
-a reference to the BOOMR.plugins.RT object, so you can chain methods.
-
-
-
-
setTimer(sName, nValue)
-
-
-Sets the timer named sName to an explicit time measurement. You'd use this method if you
-measured time values within your page before boomerang was loaded and now need to pass
-those values to the roundtrip plugin for inclusion in the beacon. It is not necessary to call
-startTimer() or endTimer() before calling setTimer(). If you
-do, the old values will be ignored and the value passed in to this function will be used.
-
-
Parameters:
-
-
sName
-
The name of the timer to set
-
-
nValue
-
-The value in milliseconds to set this timer to.
-
-
-
-
Returns
-
-a reference to the BOOMR.plugins.RT object, so you can chain methods.
-
-
-
-
done()
-
-
-Typically called automatically when boomerang's page_ready
-event fires, but it may also be called explicitly to measure the load time of transitions that
-do not involve a onload event.
-
-
-This method calculates page load time, and determines whether the values we have are good enough
-to be beaconed. It then signals the BOOMR object via its sendBeacon
-method.
-
-
-
Example:
-
-See Howto #2 for an example of explicitly calling the done() method.
-
-
-
Returns
-
-a reference to the BOOMR.plugins.RT object, so you can chain methods.
-
-Called by BOOMR.sendBeacon() to determine if the roundtrip plugin has
-finished what it's doing or not.
-
-
Returns
-
-
true if the plugin has completed.
-
false if the plugin has not completed.
-
-
-
-
-
-
Beacon Parameters
-
-This plugin adds the following parameters to the beacon:
-
-
-
t_done
[optional] Perceived load time of the page.
-
t_page
[optional] Time taken from the head of the page to page_ready.
-
t_resp
[optional] Time taken from the user initiating the request to the first byte of the response.
-
t_other
[optional] Comma separated list of additional timers set by page developer. Each timer is of the format name|value
-
t_load
[optional] If the page were prerendered, this is the time to fetch and prerender the page.
-
t_prerender
[optional] If the page were prerendered, this is the time from start of prefetch to the actual page display. It may only be useful for debugging.
-
t_postrender
[optional] If the page were prerendered, this is the time from prerender finish to actual page display. It may only be useful for debugging.
-
r
URL of page that set the start time of the beacon.
-
r2
[optional] URL of referrer of current page. Only set if different from r and strict_referrer has been explicitly turned off.
-
nu
[optional] URL of next page if the user clicked a link or submitted a form
-
rt.start
Specifies where the start time came from. May be one of cookie for the start cookie, navigation for the W3C navigation timing API, csi for older versions of Chrome or gtb for the Google Toolbar.
-
rt.tstart
The start time timestamp.
-
rt.cstart
[optional] The start time stored in the cookie if different from rt.tstart
-
rt.bstart
The timestamp when boomerang started executing
-
rt.end
The timestamp when the t_done timer ended (rt.end - rt.tstart === t_done)
-
rt.bmr.{...}
[optional] Several parameters that include resource timing information for boomerang itself, ie, how long did boomerang take to load
-
rt.subres
[optional] Set to 1 if this beacon is for a sub-resource of a primary page beacon. This is typically set by XHR beacons, and you will need to use a separate identifier to tie the primary beacon and the subresource beacon together on the server-side.
-
rt.quit
[optional] This parameter will exist (but have no value) if the beacon was fired as part of the onbeforeunload event. This is typically used to find out how much time the user spent on the page before leaving, and is not guaranteed to fire.
-
rt.abld
[optional] This parameter will exist (but have no value) if the onbeforeunload event fires before the onload event fires. This can happen, for example, if the user left the page before it completed loading.
-
rt.ntvu
[optional] This parameter will exist (but have no value) if the onbeforeunload event fires before the page ever became visible. This can happen if the user opened the page in a background tab, and closed it without viewing it, and also if the page was pre-rendered, but never made visible. Use this to check your pre-render success ratio.
- Note: These plugins require the AutoXHR and SPA
- Plugins to be included in your build of Boomerang.
-
-
-
- The Single Page Application (SPA) Plugins are designed to support Boomerang
- in cases where SPAs are used on the page and therefore the page's
- onload event doesn't matter. In those cases Boomerang will
- send Beacons when a route change event appears.
-
-
- Between the start and end of the route changes Boomerang will monitor the
- DOM for MutationObserver events and XHR
- requests. If those happen we will automatically assume that they are relevant
- to the route change event.
-
-
-
- Note: It is not advisable to provide support for multiple SPA
- Plugins in the same boomerang build.
-
-
-
- Note: If you are unsure if your version of a specific SPA is supported by the plugins presented here please refer to the compatibillity list below.
-
-
-
The code and examples described in this document apply for the following
- frameworks:
- Each plugin requires to be either enabled or disabled in boomerang's init()
- function of BoomerangJS. As mentioned above you should only compile one SPA plugin plus its depenedncies
- into your Boomerang release. This avoids breaking Boomerang by running multiple SPA-plugins at the same time.
-
-
-
-
init(oConfig)
-
-
- Called by the BOOMR.init() method to configure the
- SPA plugin. There is only one configurable option:
-
-
-
enabled
-
- [required]
- Set this to true only for the SPA you wish to work with in your project or application.
-
NOTE:
- It is advisable to tell Boomerang not to start automatically by setting the
- autorun flag to false.
-
-
NOTE:
- If you are not using a SPA-Framework but rely mostly on XMLHttpRequests to build your site, you can leave out the SPA plugin configuration and just enable instrument_xhr to measure your site.
-
-
-
-
-
is_complete()
-
-
This should always return true as EventListeners
- for MutationObserver and route change will trigger
- independent to the onLoad event.
-
-
-
-
-
hook()
-
-
Hook specific to the SPA. Please refer to the SPA-Framework specific documentation
- below on which parameters to fill this with
-
-
-
-
AngularJS Configuration
-
-
- For AngularJS it is necessary to hook Boomerang into the application
- from the run Callback.
-
-
-
In your AngularJS Application or module add this to the run() callback:
-
-
// If boomerang is loaded to late to watch the first route change happen
-// toggle hadRouteChange to true using the routeChangeStart callback.
-// This will tell the plugin to fire a beacon immediately as it gets
-// initialized and not wait for a routeChange event.
-var hadRouteChange = false;
-$rootScope.$on("$routeChangeStart", function() {
- hadRouteChange = true;
-});
-function hookAngularBoomerang() {
- if (window.BOOMR && BOOMR.version) {
- if (BOOMR.plugins && BOOMR.plugins.Angular) {
-// pass your $rootScope object and the aforementioned hadRoueChange variable to
-// the hook to both make sure we are listening for route changes and check whether
-// or not we we're on time
- BOOMR.plugins.Angular.hook($rootScope, hadRouteChange);
- }
- return true;
- }
-}
-// If we hooked in correctly we would return true if not we wait for the onBoomerangLoaded event
-// to fire and try again as we can be sure then that Boomerang has arrived in the page
-if (!hookAngularBoomerang()) {
- if (document.addEventListener) {
- document.addEventListener("onBoomerangLoaded", hookAngularBoomerang);
- } else if (document.attachEvent) {
- document.attachEvent("onpropertychange", function(e) {
- e = e || window.event;
- if (e && e.propertyName === "onBoomerangLoaded") {
- hookAngularBoomerang();
- }
- });
- }
-}
-
-
-
- Internally the AngularJS plugin will hook into the $routeChangeStart
- event and set timers for when the request for the route change started. After
- that — once the routeChange has happend — the
- MutationObserver that has been set up will realize that
- everything has been loaded, give itself another second to make sure it hasn't
- missed any final changes and then send a beacon out.
-
-
-
Backbone.js Configuration
-
-
- For Backbone.js we need to be hooked into the application prior to the first
- Backbone.history.start() call in the application. Failing that
- (eg. Boomerang was too late to initialize) a variable called
- hadRouteChange passed to the Backbone.js hook should be set to true.
-
-
-
Given these rules you can add the following to your Router definition (Backbone.Router.extend({}))
- in the init() function.
-
var hadRouteChange = false;
-app.Router.on("route", function() {
- hadRouteChange = true;
-});
-function hookBackboneBoomerang() {
- if (window.BOOMR && BOOMR.version) {
- if (BOOMR.plugins && BOOMR.plugins.Backbone) {
- BOOMR.plugins.Backbone.hook(app.Router, hadRouteChange);
- }
- return true;
- }
-}
-
-if (!hookBackboneBoomerang()) {
- if (document.addEventListener) {
- document.addEventListener("onBoomerangLoaded", hookBackboneBoomerang);
- } else if (document.attachEvent) {
- document.attachEvent("onpropertychange", function(e) {
- e = e || window.event;
- if (e && e.propertyName === "onBoomerangLoaded") {
- hookBackboneBoomerang();
- }
- });
- }
-}
-
-
-
Ember.js Configuration
-
-
- To make Ember.js work with Boomerang it is necessary to pass the Application
- itself to the Boomerang plugin.
-
-
-
In your Ember.js Application add this to the route() callback:
-
-
function hookEmberBoomerang() {
- if (window.BOOMR && BOOMR.version) {
- if (BOOMR.plugins && BOOMR.plugins.Ember) {
- BOOMR.plugins.Ember.hook(App);
- }
- return true;
- }
-}
-
-if (!hookEmberBoomerang()) {
- if (document.addEventListener) {
- document.addEventListener("onBoomerangLoaded", hookEmberBoomerang);
- }
- else if (document.attachEvent) {
- document.attachEvent("onpropertychange", function(e) {
- e = e || window.event;
- if (e && e.propertyName === "onBoomerangLoaded") {
- hookEmberBoomerang();
- }
- });
- }
-}
-
-
Just like the AngularJS/Backbone.js SPA plugin we intend to hook into the application
- as early as possible. However the difference here lies in the point we
- hook into. The Ember.js plugins hook() function will either
- reopen the already set ApplicationRoute or set
- the Ember.js applications ApplicationRoute by
- extend()ing the Ember.Route Class.
- We will hook into the applications activate function and
- willTransition event.
-
-
-
Custom SPA Support
-
-
If you use a SPA framework that isn't one of the above or want to write your own
- integration into your setup, we allow you to do so by using the underlying code used by
- the pre-built plugins to hook in.
-
-
BOOMR.plugins.SPA API
-
-
is_complete()
-
- Since the SPA plugin is just another BOOMR plugin we have to implement is_complete. It shoudl always return
- true as it isn't bound to the normal boomerang lifecycle and does not have to complete for the classic onload beacon to fire.
-
-
-
init(config)
-
- As is the case with is_complete() we have to implement init() to configure the SPA plugin. Here we
- configure the SPA plugins ability to enable or disable AutoXHR on demand after the first route change has happend.
-
-
-
register
-
-
- Since there can be multiple SPA frameworks compiled into your version of boomerang
- each SPA plugin should register itself by calling register with the name of the plugin as a string argument.
- This will make sure, that when you call BOOMR.init() with the option to enable the SPA plugin of
- your choice AutoXHR will acknowledge that and disable itself until re-enabled by the SPA plugin after the first
- route change has finished.
-
-
- This means your SPA plugin should register itself early on in the evaluation of your plugins code. Even before defining
- the plugin itself. Here is an example:
-
-
(function(){
- // If our plugin is already defined just skip re-evaluating.
- // same goes for if BOOMR.plugins.SPA is not defined
- if (BOOMR.plugins.MySPAPlugin || typeof BOOMR.plugins.SPA === "undefined") {
- return;
- }
-
- // register as a SPA plugin
- BOOMR.plugins.SPA.register("MySPAPlugin");
-
- BOOMR.plugins.MySPAPlugin = {
- /* Your SPA Code */
- };
-}());
-
-
-
supported_frameworks
-
- Used internally in the AutoXHR plugin to check if one of the registered SPA plugins has been enabled.
-
-
-
hook(hadRouteChange)
-
-
Called by a SPA plugin once it has hooked into the framework of choice. The boolean argument hadRouteChange
- is used to flag if Boomerang has loaded late or not. This will decide if AutoXHR will be enabled right away or only when the
- first route_change call has happend. hook will set the flag http.initiator to
- spa to flag that the next beacon comes from a SPA event.
-
-
-
-
route_change
-
-
- As soon as the first route_change event, triggered by the SPA, will be initiated this function should be called
- enablinge listening for any DOM changes (using MutationObserver) and XHR calls required to finish the route change.
- This will make sure that all required assets for the route_change have been fetched. Since this is a SPA event
- AutoXHR will wait an entire second for everything to settle.
-
-
-
-
last_location(url)
-
-
- To make sure that the sent beacon at the end of the route_change event will also contain the refferal URL
- we came from, last_location documents this URL to put into the beacon.
-
-
-
-
-
SPA-Plugin compatibillity:
-
-
The SPA plugins were tested and used in production environments with the following versions of each SPA-Framework:
Compatible versions of each framework should work as intended.
-
-
Browser compatibility considerations
-
-
As these plugins require AutoXHR and the availabilltiy of MutationObserver it is advisable to
- have a polyfill for browsers where the framework may be supported but MutationObserver isn't. See
- the MutationObserversupported browser list.
- If you are required to support browsers that don't support MutationObserver, consider adding a polyfill.
-
-The cache reload plugin forces the browser to update its cached copy of boomerang. Details are on the lognormal
-blog here
-and here.
-
-
-
Configuration
-
-The Cache Reload plugin's configuration is under the CACHE_RELOAD namespace.
-The full configuration object is described in Howto #6 — Configuring boomerang.
-
-
-
url
-
-[required]
-By default, this is set to the empty string, which has the effect of disabling the Cache Reload plugin. Set the url
-parameter to the url that will do handle forcing the reload. See the example below for what this url's output should look like.
-
-
-
-
-
-
Methods
-
-
-
-
init(oConfig)
-
-
-Called by the BOOMR.init() method to configure the cache reload plugin.
-
-
Parameters
-
-
oConfig
-
The configuration object passed in via BOOMR.init(). See the Configuration section for details.
-
-
Returns
-
-a reference to the BOOMR.plugins.CACHE_RELOAD object, so you can chain methods.
-
-
-
-
is_complete()
-
-
-Called by BOOMR.sendBeacon() to determine if the bandwidth plugin has
-finished what it's doing or not. This method always returns true.
-
-
Returns
-
-
true.
-
-
-
-
-
-
Beacon Parameters
-
-This plugin does not add any parameters to the beacon.
-
-
-
Example HTML document
-
-The cache reloading HTML document should look something like this:
-
-
-<!doctype html>
-<html>
-<head>
-<script src="boomerang.js"></script>
-</head>
-<body>
-<script>
-// required version needs to be passed in as a query string parameter
-// like v=0.9.123456789
-
-var boom_ver = BOOMR.version.split('.'),
- reqd_ver = location.search.replace(/.*v=([0-9\.]+).*/, '$1').split('.');
-if ( (boom_ver[0] < reqd_ver[0]) // javascript will do type coercion
- || (boom_ver[0] == reqd_ver[0] && boom_ver[1] < reqd_ver[1])
- || (boom_ver[0] == reqd_ver[0] && boom_ver[1] == reqd_ver[1] && boom_ver[2] < reqd_ver[2])
-)
-{
- location.reload(true);
-}
-</script>
-</body>
-</html>
-
- [required]
- The Url the click events are supposed to be sent to. You need to specify this.
-
-
onbeforeunload
-
- [optional]
- A boolean value for when to send click events. If this is true clicks will be sent when the site is being closed
- so as to not interfere with the normal control flow and network events sent during the lifecycle of the page.
-
- Otherwise click events are sent immediately as they occur
-
-
-
-
Methods
-
-
-
-
init(oConfig)
-
-
- Called by the BOOMR.init() method to configure the clicks plugin.
-
-Note: The IPv6 plugin hasn't been tested. Your help in testing it is appreciated.
-
-
-The IPv6 plugin measures various IPv6 related metrics. It is encapsulated within
-the BOOMR.plugins.IPv6 namespace. This plugin tries to do a few things:
-
-
-
Check if the client can connect to an ipv6 address
-
Check if the client can resolve DNS that points to an ipv6 address
-
Check latency of connecting to an ipv6 address
-
Check avg latency of doing dns lookup to an ipv6 address (not worstcase)
-
-
-This plugin needs a server that has an IPv6 address, and a DNS name to point to it.
-Additionally, the server needs to be configured to serve content requested
-from the IPv6 address and should not require a virtual host name. This means
-that you probably cannot use shared hosting that puts multiple hosts on the
-same IP address.
-
-
-
Configuration
-
-All configuration parameters are within the IPv6 namespace.
-
-
-
ipv6_url
-
An image URL referenced by its IPv6 address, eg, http://fe80::1/image-i.png.
-If not specified, the test will abort.
-
host_url
-
[recommended]
-An image URL on an IPv6 only host referenced by its DNS hostname. The hostname should not
-resolve to an IPv4 address. If not specified, the host test will be skipped.
-
-
timeout
-
[optional]
-The time, in milliseconds, that boomerang should wait for a network response before giving up
-and assuming that the request failed. The default is 1200ms.
-
-Note: The Navigation Timing plugin hasn't been tested. Your help in testing it is appreciated.
-
-
-The Navigation Timing plugin collects metrics collected by modern user agents that support the
-W3C Navigation Timing specification. The Navigation
-Timing API is encapsulated within the BOOMR.plugins.NavigationTiming namespace.
-
-
-
-Note that the Navigation Timing plugin isn't included by default in boomerang.js. See
-Howto #9 for details on how to include the plugin in your boomerang
-deployment.
-
-
-
Methods
-
-
-
-
init()
-
-
-Called by the BOOMR.init() method to configure the Navigation Timing
-plugin. The Navigation Timing plugin doesn't require any configuration parameters, since it simply
-reads values out of the browser's window.performance object (if available) and adds them to the
-beacon query string.
-
-
-
Returns
-
-a reference to the BOOMR.plugins.NavigationTiming object, so you can chain methods.
-
-
Note
-
-If the user agent being examined doesn't implement the Navigation Timing spec, the plugin won't
-add any parameters to the beacon.
-
-
-
-
is_complete()
-
-
-Called by BOOMR.sendBeacon() to determine
-if the Navigation Timing plugin has finished what it's doing or not.
-
-
Returns
-
-
true if the plugin has completed.
-
false if the plugin has not completed.
-
-
-
-
-
-
Beacon Parameters
-
-The NavigationTiming plugin adds the following parameters to the beacon. Each maps onto a attribute from the
-browser's NavigationTiming API.
-
[optional] 1 if page was loaded over SPDY, 0 otherwise
-
nt_first_paint
[optional] The time when the first paint happened. On Internet Explorer, this is milliseconds since the epoch, while on Chrome this is seconds.microseconds since the epoch. If you detect a decimal point in this number, multiply it by 1000 to compare it to the other timers.
-The Resource Timing plugin collects metrics
-from modern user agents that support the W3C
-Resource Timing specification.
-The Resource Timing API is encapsulated
-within the BOOMR.plugins.ResourceTiming namespace.
-
-
-
-Note that the Resource Timing plugin isn't included by default in boomerang.js.
-See Howto #9
-for details on how to include the plugin in your boomerang deployment.
-
-
-
Methods
-
-
-
-
init()
-
-
-Called by BOOMR.init()
-to configure the Resource Timing plugin.
-The Resource Timing plugin doesn't require any configuration parameters,
-since it simply reads values from
-the browser's window.performance object (if available)
-and adds them to the beacon request data.
-
-
-
Returns
-
-a reference to the BOOMR.plugins.ResourceTiming object.
-
-
Note
-
-If the executing user agent
-doesn't implement the Resource Timing specification,
-the plugin won't add any data to the beacon.
-
-
-
-
is_complete()
-
-
-Called by BOOMR.sendBeacon()
-to determine if the Resource Timing plugin has finished.
-
-
Returns
-
-
true if the plugin has completed.
-
false if the plugin has not completed.
-
-
-
-
-
-
Beacon Parameters
-
-The ResourceTiming plugin adds an object named restiming to the beacon data.
-
-
-restiming is an optimized Trie structure, where the
-keys are the ResourceTiming URLs, and the values correspond to those URLs'
-PerformanceResourceTiming timestamps:
-
-
-
-{ "[url]": "[data]"}
-
-
-
-The Trie structure is used to minimize the data transmitted from the ResourceTimings.
-
-
-Keys in the Trie are the ResourceTiming URLs. For example, with a root page and three resources:
-
-If a resource's URL is a prefix of another resource, then it terminates with a pipe symbol (|).
-In Example 1, http://abc.com (the root page) is a prefix of http://abc.com/js/foo.js,
-so it is listed as http://abc.com| in the Trie.
-
-
-If there is more than one ResourceTiming entry for a URL, each entry is separated by a pipe symbol (|)
-in the data. In Example 1 above, foo.png has been downloaded twice, so it is listed with two separate page loads,
-1c,3 and 1d,a.
-
-
-The value of each key is a string, which contains the following components:
-
-
-If the resulting timestamp is 0, it is replaced with an empty string ("").
-
-
-All trailing commas are removed from the final string. This compresses the timing string from timestamps
-that are often 0. For example, here is what a fully-redirected resource might look like:
-
-
-
-
diff --git a/doc/api/usertiming.md b/doc/api/usertiming.md
deleted file mode 100644
index d82426564..000000000
--- a/doc/api/usertiming.md
+++ /dev/null
@@ -1,55 +0,0 @@
-# UserTiming Plugin
-
-## Collect W3C UserTiming API performance marks and measures
-
-
-This plugin collects all W3C UserTiming API performance marks and measures that were added since navigation start or since the last beacon fired for the current navigation. The data is added to the beacon as the `usertiming` parameter. The value is a compressed string using Nic Jansma's [usertiming-compression.js](https://github.com/nicjansma/usertiming-compression.js) library. A decompression function is also available in the library.
-
-Timing data is rounded to the nearest millisecond.
-
-Please see the [W3C UserTiming API Reference](https://www.w3.org/TR/user-timing/) for details on how to use the UserTiming API.
-
-### Configuring Boomerang
-
-You can enable the `UserTiming` plugin with:
-```js
-BOOMR.init({
- UserTiming: {
- 'enabled': true
- }
-})
-```
-
-### Example
-
-```js
-performance.mark('mark1'); //mark current timestamp as mark1
-performance.mark('mark2');
-performance.measure('measure1', 'mark1', 'mark2'); //measure1 will be the delta between mark1 and mark2 timestamps
-performance.measure('measure2', 'mark2'); //measure2 will be the delta between the mark2 timestamp and the current time
-```
-
-The compressed data added to the beacon will look similar to the following:
-
-`usertiming=~(m~(ark~(1~'2s~2~'5k)~easure~(1~'2s_2s~2~'5k_5k)))`
-
-
-Decompressing the above value will give us the original data for the marks and measures collected:
-```json
-[{"name":"mark1","startTime":100,"duration":0,"entryType":"mark"},
-{"name":"measure1","startTime":100,"duration":100,"entryType":"measure"},
-{"name":"mark2","startTime":200,"duration":0,"entryType":"mark"},
-{"name":"measure2","startTime":200,"duration":200,"entryType":"measure"}]
-```
-
-### Compatibility and Browser Support
-
-
-Many browsers [support](http://caniuse.com/#feat=user-timing) the UserTiming API, e.g.:
-* Chrome 25+
-* Edge
-* Firefox 38+
-* IE 10+
-* Opera 15+
-
-See Nic Jansma's [usertiming.js](https://github.com/nicjansma/usertiming.js) polyfill library to add UserTiming API support for browsers that don't implement it natively.
diff --git a/doc/boomerang-docs.css b/doc/boomerang-docs.css
deleted file mode 100644
index 63741c87e..000000000
--- a/doc/boomerang-docs.css
+++ /dev/null
@@ -1,35 +0,0 @@
-/*
- * Copyright (c) 2011, Yahoo! Inc. All rights reserved.
- * Copyrights licensed under the BSD License. See the accompanying LICENSE.txt file for terms.
- */
-
-body { font-size: 0.9em; background-color: #eeeee4; padding: 5% 10% 15% 10%; margin: 0; font-family: "Trebuchet MS",Trebuchet,Tahoma,Verdana,Sans-Serif; }
-a { color: #571; }
-a:hover, a:active { color: #350; }
-h1 { padding-left: .8in; text-indent: -.8in; }
-h2, h2 a, h3, h3 a, h4, h4 a { color:#793; }
-
-p#results { background-color: #ffa; border: solid 2px #d82; padding: 1em; position: absolute; width: 25%; overflow: hidden; white-space: pre;}
-p#results:hover { width: auto; }
-div#dynamic-content { font-size: 0.5em; border: solid 3px #9a5; padding: 1em; margin: 1em 10% 1em 40%; text-align: justify; color: #9a8; }
-div#dynamic-content > p:first-child { font-size: 2em; }
-
-dd strong { color: #a43; background-color: #ddc; }
-dd h3 { color: black; margin-left: -0.8em; }
-
-dl.api {border-bottom: solid 1px #aaa; }
-dl.api > dt { font-weight: bolder; border-top: solid 1px #aaa; padding: 3px; }
-dl.api > dd { font-size: 0.9em; color: #222; }
-dl.api > dd > ul { padding-bottom: 0.5em; }
-
-p.perma-link { margin-top: 4em; font-size: 0.9em; }
-
-pre { line-height:1.4em; font-size: 1.1em; margin-right: 4%; padding: 1em 1em 1em 1.5em; background-color: #bba; border-left: solid 5px #9a5; }
-pre em { background-color: #ccb; color: #a32; font-style: normal; padding: 2px;}
-pre span.comment { background-color: #ccb; color: #56a; padding: 2px;}
-
-pre span.ni { color: #886; }
-
-a.fn { position: relative; top: -0.4em; font-size: 0.7em; text-decoration: none; }
-#footnotes { font-size: 0.8em; width: 50%; text-align: justify; border-top: solid 1px #888; padding-right: 0.5em; margin: 3em 0 0 0;}
-#footnotes li { margin-bottom: 0.8em; }
diff --git a/doc/building.md b/doc/building.md
new file mode 100644
index 000000000..94d8f6353
--- /dev/null
+++ b/doc/building.md
@@ -0,0 +1,176 @@
+Boomerang is split into the main framework (`boomerang.js`) and plugins (`plugins/*.js`).
+
+`boomerang.js` on its own will not do anything interesting. To enable performance
+measurements of your site, you will want to include several plugins.
+
+## Choosing Plugins
+
+Each plugin lives on its own in the `plugins/` directory. Plugins are split into
+core measurement components, though some depend on each other.
+
+The default set of plugins for a "full" build of Boomerang can be seen in `plugins.json`
+in the root directory. You can modify this file to choose which plugins you want for
+measurement.
+
+You can read about each plugin in its documentation. Here is a basic description
+of each plugin:
+
+* {@link BOOMR.plugins.Angular} enables support for measuring AngularJS websites
+* {@link BOOMR.plugins.AutoXHR} tracks `XMLHttpRequest`s and other in-page interactions
+* {@link BOOMR.plugins.Backbone} enables support for measuring Backbone.js websites
+* {@link BOOMR.plugins.BW} measures HTTP bandwidth
+* {@link BOOMR.plugins.CACHE_RELOAD} forces the browser to update its cached copy of boomerang
+* {@link BOOMR.plugins.Clicks} tracks in-page clicks
+* {@link BOOMR.plugins.CrossDomain} allows cross-domain session tracking
+* {@link BOOMR.plugins.CT} tests whether a script was cached
+* {@link BOOMR.plugins.DNS} measures DNS latency
+* {@link BOOMR.plugins.Ember} enables support for measuring Ember.js websites
+* {@link BOOMR.plugins.Errors} adds JavaScript error tracking
+* {@link BOOMR.plugins.GUID} adds a unique ID for each session
+* {@link BOOMR.plugins.IPv6} measures various IPv6 metrics
+* {@link BOOMR.plugins.History} enables support for measuring React and other `window.history` websites
+* {@link BOOMR.plugins.Memory} captures browser memory metrics
+* {@link BOOMR.plugins.Mobile} captures mobile connection type
+* {@link BOOMR.plugins.NavigationTiming} captures NavigationTiming data
+* {@link BOOMR.plugins.ResourceTiming} captures ResoureTiming (waterfall) data
+* {@link BOOMR.plugins.RT} captures round-trip (load) performance
+* {@link BOOMR.plugins.SPA} is required by any of the SPA plugins
+* {@link BOOMR.plugins.TPAnalytics} adds third-party analytics IDs to the beacon
+
+There are also a few utility plugins:
+
+* `plugins/compression.js` adds {@link BOOMR.utils.Compression} and is used by some plugins for compressing their data
+* `plugins/md5.js` adds {@link BOOMR.utils.MD5} support
+
+To monitor basic page load performance for a traditional website, we would recommend:
+* {@link BOOMR.plugins.RT}
+* {@link BOOMR.plugins.NavigationTiming} captures NavigationTiming data
+
+To monitor a Single Page App website, we would additionally recommend:
+* {@link BOOMR.plugins.SPA}
+* {@link BOOMR.plugins.Angular}, {@link BOOMR.plugins.Ember}, {@link BOOMR.plugins.Backbone} or
+ {@link BOOMR.plugins.History}
+
+## Including Boomerang on your site.
+
+boomerang can be included on your page in one of two ways: [synchronously](#synchronously) or [asynchronously](#asynchronously).
+
+The asynchronous method is recommended.
+
+After the core JavaScript files are loaded, you will need to call {@link BOOMR.init}
+to initialize Boomerang and all of its plugins. See each plugin's documentation
+for the available configuration options.
+
+
+## The simple synchronous way
+
+Simply include `boomerang.js` and any desired plugins as a `
+
+
+
+```
+
+Each plugin has its own configuration as well -- these configuration options
+should be included in the `BOOMR.init()` call:
+
+```html
+BOOMR.init({
+ beacon_url: "http://yoursite.com/beacon/",
+ ResourceTiming: {
+ enabled: true,
+ clearOnBeacon: true
+ }
+});
+```
+
+
+## The faster, more involved, asynchronous way
+
+Loading boomerang asynchronously ensures that even if `boomerang.js` is
+unavailable (or loads slowly), your host page will not be affected.
+
+### 1. Add a plugin to init your code
+
+Create a plugin (or use the sample `zzz-last-plugin.js`) with a call to `BOOMR.init`:
+
+```javascript
+BOOMR.init({
+ config: parameters,
+ ...
+});
+BOOMR.t_end = new Date().getTime();
+```
+
+You could also include any other code you need. For example, you could include
+a timer to measure when boomerang has finished loading (as above).
+
+### 2. Build boomerang
+
+The build process bundles `boomerang.js` and all of the plugins listed in
+`plugins.json` (in that order).
+
+To build boomerang with all of your desired plugins, you would run:
+
+```bash
+grunt clean build
+```
+
+This creates a deployable boomerang in the `build` directory, e.g.
+`build/boomerang-.min.js`.
+
+Install this file on your web server or origin server where your CDN can pick it
+up. Set a far future [max-age](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Cache-Control)
+header for it. This file will never change.
+
+## The Build Process
+
+Build requires [NodeJS](https://nodejs.org/) to execute [Grunt.js](https://gruntjs.com/)
+to build Boomerang.
+
+To install Grunt globally:
+
+```bash
+npm install -g grunt-cli
+```
+
+You can get a full build of boomerang by running the following:
+
+```bash
+grunt clean build
+```
+
+The main build targets are:
+
+* `clean` cleans the `build/` directory
+* `build` builds a new version of Boomerang from scratch
+* `lint` runs lint on the project
+* `test` runs tests
+
+A full list of build targets are avaialble in `Gruntfile.js`.
+
+Grunt build options:
+
+* `--build-number` Specifies the minor build number
+* `--build-revision` Specifies the revision build number
+
+## Build Numbers
+
+Boomerang follows [SemVer](http://semver.org/):
+
+ major.minor.revision
+
+For each build of Boomerang, the major build version is specified in `package.json` as
+`releaseVersion`.
+
+The minor version defaults to 0. Each build can then specify its `--build-number` to
+change the minor version.
+
+The revision defaults to 0. Each build can then specify its `--build-revision`
+to change the revision.
diff --git a/doc/community.md b/doc/community.md
deleted file mode 100644
index 76d21d572..000000000
--- a/doc/community.md
+++ /dev/null
@@ -1,20 +0,0 @@
-boomerang is an opensource javascript library released under the BSD license.
-It depends on its community to keep it alive.
-
-## Source code
-
-The source code is on github at [github.com/SOASTA/boomerang](https://github.com/SOASTA/boomerang). Feel
-free to fork it and contribute to it. You can also get a [tarball](https://github.com/SOASTA/boomerang/archive/master.tar.gz)
-or [zip](http://github.com/SOASTA/boomerang/archive/master.zip) of the code.
-
-## Bugs & Discussions
-
-Use github's [issue tracking system](https://github.com/SOASTA/boomerang/issues) to report or follow bugs or to
-discuss features and get support.
-
-## Other projects
-
-Other opensource devs have incorporated boomerang into their own projects. The following is a partial list:
-
- - [Drupal module for boomerang](http://drupal.org/project/boomerang)
-
diff --git a/doc/contributing.md b/doc/contributing.md
new file mode 100644
index 000000000..2452f4978
--- /dev/null
+++ b/doc/contributing.md
@@ -0,0 +1,29 @@
+We welcome all Pull Requests, though we ask that you follow our guidelines for
+new code:
+
+## Contribution Check-List
+
+1. Ensure your code passes the lint checks: `grunt lint`
+2. Ensure your code passes all existing tests: `grunt test`
+3. New code must be accompanied by new Unit or End-to-End tests
+4. [Open a Pull Request](github.com/SOASTA/boomerang/pull) with a thorough
+ description of your change
+
+## Browser Compatibility
+
+Boomerang is compatible with all browsers from Internet Explorer 5.5 through all
+modern browsers. While not all plugins work in older browsers, Boomerang should
+still be able to execute in older browsers and not cause any script errors.
+
+To ensure Boomerang works in as many browsers as possible, please pay attention
+to the following guidelines:
+
+1. Boomerang code should not directly rely on any EcmaScript 5 features.
+2. Polyfills for utility functions (such as `Array.filter`) should **not** be added
+ directly to the page. Instead, add a {@link BOOMR.utils} utility
+ function such as {@link BOOMR.utils.arrayFilter} that uses native interfaces when
+ available, and when not, executes polyfill-like code.
+3. For plugins that depend entirely on newer browser features (such as ResourceTiming
+ for the {@link BOOMR.plugins.ResourceTiming ResourceTiming plugin}), the plugin
+ should attempt to do feature-detection and disable itself if browser support
+ does not exist.
diff --git a/doc/creating-plugins.md b/doc/creating-plugins.md
new file mode 100644
index 000000000..ac45eae7a
--- /dev/null
+++ b/doc/creating-plugins.md
@@ -0,0 +1,54 @@
+Boomerang plugins can be used for adding core functionality (such
+as ResourceTiming support) as well as for site-specific custom needs.
+
+Use the below example for creating a new Boomerang plugin.
+
+Once created, you can add the new plugin to your Boomerang build by adding
+it to `plugins.json`. See {@tutorial building} for more details.
+
+```
+/**
+ * Skeleton template for all boomerang plugins.
+ *
+ * Use this code as a starting point for your own plugins.
+ */
+(function() {
+ // First, make sure BOOMR is actually defined. It's possible that your plugin
+ // is loaded before boomerang, in which case you'll need this.
+ BOOMR = window.BOOMR || {};
+ BOOMR.plugins = BOOMR.plugins || {};
+
+ // A private object to encapsulate all your implementation details
+ // This is optional, but the way we recommend you do it.
+ var impl = {
+ };
+
+ //
+ // Public exports
+ //
+ BOOMR.plugins.MyPlugin = {
+ init: function(config) {
+ // list of user configurable properties
+ var properties = ["prop1", "prop2"];
+
+ // This block is only needed if you actually have user configurable properties
+ BOOMR.utils.pluginConfig(impl, config, "MyPlugin", properties);
+
+ // Other initialization code here
+
+ // Subscribe to any BOOMR events here.
+ // Unless your code will explicitly be called by the developer
+ // or by another plugin, you must to do this.
+
+ return this;
+ },
+
+ // Any other public methods would be defined here
+
+ is_complete: function() {
+ // This method should determine if the plugin has completed doing what it
+ // needs to do and return true if so or false otherwise
+ }
+ };
+}());
+```
diff --git a/doc/ja/ja_howtos.md b/doc/howtos.md
similarity index 100%
rename from doc/ja/ja_howtos.md
rename to doc/howtos.md
diff --git a/doc/howtos/dynamic-content.txt b/doc/howtos/dynamic-content.txt
deleted file mode 100644
index 66dc8ee7e..000000000
--- a/doc/howtos/dynamic-content.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-This content is loaded via XHR and put into a div.
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur nec tortor eros, et iaculis erat. Nullam at justo eu leo tincidunt pulvinar at eu nisi. Ut eu lacus lacus, a auctor est. Praesent eu justo arcu, nec lobortis erat. Sed convallis sapien dictum nisl molestie sit amet dapibus velit vestibulum. Ut sed massa orci, eu congue felis. Mauris pharetra dignissim mollis. Sed vestibulum lacus et orci fermentum bibendum. Aliquam condimentum, mauris eu placerat commodo, massa diam bibendum lacus, vel fringilla nulla turpis id mi. In egestas consequat cursus. Phasellus non purus vitae lectus blandit dignissim ut eget lectus. Phasellus ligula risus, sodales in lobortis in, interdum vitae nunc. Duis at diam tortor, non ultrices ipsum. Aenean iaculis dolor ut tortor tempor eget mattis nibh lobortis.
-
-Morbi eu risus non tellus condimentum hendrerit. Suspendisse nec nulla leo. Praesent non leo in libero aliquet aliquam eu nec nulla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec diam odio, faucibus id viverra sed, egestas eu quam. Maecenas lorem nisi, pretium sit amet sollicitudin in, imperdiet id elit. Aenean convallis, odio consectetur pulvinar vehicula, ipsum nunc dapibus sapien, non congue nisl elit eu lacus. Phasellus ut elit nisl, eu auctor libero. Suspendisse potenti. Maecenas nunc quam, commodo nec vehicula in, elementum volutpat tellus. In hac habitasse platea dictumst. Vivamus porttitor turpis mauris, semper sagittis elit. Mauris vitae vehicula lacus. Aliquam sed velit at sem placerat scelerisque.
-
-Pellentesque eget ante eu libero dignissim commodo ornare porttitor mi. Curabitur a nibh ut sapien eleifend consectetur sit amet quis mi. Sed lacinia neque elit. Quisque sit amet sem magna, sagittis adipiscing arcu. Etiam auctor cursus lectus quis tincidunt. Aliquam scelerisque, est porta varius fermentum, nisi mauris tempor eros, tincidunt dapibus purus odio at velit. Duis eu metus nibh, sit amet faucibus ipsum. Vestibulum nunc dolor, consequat vitae lobortis ac, pulvinar quis metus. In sollicitudin luctus vehicula. Quisque velit lacus, scelerisque nec lobortis vel, varius sed leo. Proin a felis eget felis tempus vestibulum. Nullam eu augue non velit placerat venenatis. Phasellus at mauris quis massa varius sodales. Vestibulum id arcu tortor. Ut egestas ipsum nec dolor scelerisque ullamcorper. Morbi hendrerit ligula orci, id faucibus dui. Phasellus et libero a turpis tincidunt molestie. Mauris sed erat eu nisl dignissim consectetur id sed erat. In lacus tellus, viverra ut tincidunt at, pretium id eros. Curabitur tortor urna, consectetur eu accumsan at, cursus eu libero.
-
-Praesent eu neque tortor, quis dapibus diam. Curabitur a mauris in mi dapibus suscipit. Maecenas porta interdum viverra. Integer accumsan fringilla laoreet. Integer hendrerit auctor eros, laoreet aliquet magna malesuada sit amet. Mauris tincidunt mattis varius. Suspendisse sit amet sapien eu nibh feugiat volutpat. Mauris consequat, metus sed pulvinar iaculis, massa lectus faucibus elit, vel viverra metus orci a elit. Sed vitae velit non est luctus molestie. Curabitur sollicitudin, dui ut fermentum scelerisque, magna sapien vulputate odio, eu blandit diam turpis et diam. Sed viverra neque vitae justo gravida non posuere felis adipiscing. Duis metus eros, pulvinar et adipiscing ut, egestas ac orci. Donec porttitor, ligula vitae porttitor luctus, augue tortor tincidunt arcu, consequat adipiscing ligula lectus sed risus. Suspendisse potenti. Fusce vitae consequat lacus. Quisque id facilisis sem. Aenean dignissim, turpis ut adipiscing hendrerit, nisi lorem sagittis arcu, egestas tempor ante tellus suscipit dui. Etiam mattis mauris in ipsum faucibus convallis. Aenean ut turpis nisl. In elit risus, pretium vitae hendrerit non, porta eget quam.
-
-Duis nec metus in metus facilisis dictum eget sed elit. Ut sem est, consequat a molestie ut, lobortis vel tortor. Fusce a erat neque, nec molestie lacus. Donec luctus mi nec ante iaculis vulputate. Quisque pellentesque commodo lectus, convallis aliquet nisi dignissim nec. Cras in est vitae arcu mollis egestas vitae id purus. Cras non purus quis orci consequat sodales ut nec justo. Donec suscipit convallis velit vel scelerisque. Fusce vitae neque nulla. Proin scelerisque ullamcorper libero, eget ultricies neque lacinia vel. Donec mattis condimentum vulputate. Vestibulum vel nisi est, in egestas libero. Suspendisse condimentum purus ac diam tempus malesuada. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
-
-Aliquam erat volutpat. Sed rhoncus elit id diam sagittis ut viverra leo blandit. Nulla hendrerit purus a orci dapibus pretium pulvinar justo ornare. Vivamus nulla elit, tincidunt non bibendum sed, semper ut ipsum. Nulla ut lectus ut nibh elementum bibendum quis pellentesque odio. Sed quis ante urna. Nunc ut tincidunt nulla. Etiam semper scelerisque aliquam. Nulla pharetra odio sit amet turpis blandit blandit. Suspendisse sagittis aliquet fermentum. Integer nibh felis, dictum eget mollis a, sagittis tempus quam. Quisque luctus euismod nunc. Sed quis velit felis, vitae sodales lorem. Vestibulum mauris odio, accumsan sit amet pellentesque eget, molestie eget magna.
-
-Phasellus eu neque eu lectus vestibulum porta. Donec tortor purus, faucibus et elementum vel, faucibus quis mauris. Donec eu ligula mi, vel fringilla est. Pellentesque consectetur ullamcorper accumsan. Nulla eget nibh leo, nec congue nunc. Fusce varius sodales hendrerit. Nulla ultricies dapibus luctus. Cras in libero nisi. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum tristique diam id nunc luctus interdum. Integer commodo libero sit amet turpis varius faucibus lobortis lectus aliquet. Proin consequat velit vel lacus posuere sit amet sollicitudin justo molestie. Praesent risus nisi, molestie eget pharetra consectetur, congue id libero. In ipsum velit, fermentum ac placerat dignissim, auctor vel leo. Donec a pellentesque risus.
-
-Quisque dolor dui, tempus eu euismod at, condimentum eget felis. Pellentesque dolor magna, ultricies in vestibulum at, elementum quis nunc. Vestibulum sagittis vehicula purus cursus vehicula. Cras nibh erat, sollicitudin in lacinia in, aliquam non libero. Integer at erat tellus. Praesent pharetra, sem commodo ornare interdum, leo magna elementum lectus, at feugiat mauris lacus nec turpis. Phasellus posuere convallis rutrum. Duis vel orci sed urna pharetra dapibus. Duis hendrerit arcu non enim accumsan nec molestie turpis tincidunt. Donec ac ipsum magna. Aliquam congue ante congue magna ultricies a molestie orci dignissim. Etiam lacus tortor, malesuada non mattis sit amet, molestie sit amet augue.
-
-Quisque volutpat scelerisque ipsum, ac vulputate lorem dapibus quis. Donec et diam nulla. Nunc pellentesque viverra egestas. Etiam eget neque magna, a venenatis ante. Nulla facilisi. Ut quis nisl vel felis vestibulum consectetur sed id dolor. Sed ac metus eu turpis elementum porttitor. Proin euismod accumsan dui, at aliquet dui aliquam vitae. Proin vitae euismod erat. Etiam tincidunt est at libero mollis egestas. Nunc vitae lectus quis velit ullamcorper gravida vel in purus. Fusce feugiat odio vel lorem viverra lacinia. Curabitur adipiscing condimentum arcu sit amet tempor. Proin eget leo arcu, ac dictum justo. Curabitur viverra ultrices lorem, nec aliquet odio dictum vitae. Nam at volutpat velit. Sed sed rutrum magna. Cras ut felis a nunc euismod laoreet.
-
-Cras euismod rhoncus erat, et placerat sem pulvinar at. Sed tempus libero nec arcu luctus hendrerit. In tincidunt vehicula pretium. Mauris nec orci a magna aliquet faucibus sed quis ligula. Nulla id lacus id eros iaculis mollis vel non risus. Nam eget libero quam. Nullam sed magna purus, eu pretium sapien. Curabitur ullamcorper, tellus vitae sodales accumsan, arcu erat egestas augue, et adipiscing turpis diam ullamcorper nunc. Suspendisse dignissim scelerisque porttitor. Integer quis congue mauris. Mauris dignissim viverra tincidunt. Duis viverra malesuada nisl. Maecenas libero orci, porta eget mattis sit amet, mollis ut sapien. Aenean vulputate felis vitae sem aliquam vulputate eu sed est. Nunc sed enim nisi, ac cursus massa. Praesent quam ligula, tristique ut tincidunt dapibus, facilisis pulvinar turpis. Mauris ut eros tortor, at semper nulla. Integer facilisis scelerisque mauris, eu congue purus placerat in. Aenean augue augue, lacinia vitae dapibus non, posuere vitae neque.
diff --git a/doc/howtos/howto-0.html b/doc/howtos/howto-0.html
deleted file mode 100644
index 908d97ce2..000000000
--- a/doc/howtos/howto-0.html
+++ /dev/null
@@ -1,222 +0,0 @@
-
-
-
-Boomerang Howto #0: How to read data out of a beacon or before_beacon event handler
-
-
-
-
-All Docs | Index
-
Boomerang Howto #0: How to read data out of a beacon or before_beacon event handler
-
-For all subsequent examples, you'll need to pull performance data out of the beacon or out of a
-before_beacon event handler. This howto explains how to do that.
-
-
-
Beacon results back to your server
-
-For most cases you'd want to send performance data back to your server so you can analyse it later
-and take action against it. The first thing you need to do is set up a url that the javascript
-will use as a beacon. We'll look at the back end details a little later. You tell boomerang
-about your beacon URL by passing the beacon_url parameter to the BOOMR.init()
-method:
-
-I've used beacon.gif as an example, but it could really be any thing. You could write a script
-in PHP or C# or JSP to handle the beacons as well. I just use a URL that does nothing on the
-back end, and then later look at my apache web logs to get the beaconed parameters in a batch.
-
-
-
Beacon parameters
-
-The beacon that hits your server will have several parameters. Each plugin also adds its own
-parameters, so if you have custom plugins set up, you'll get parameters from them as well.
-This is what you get from the default install:
-
-
-
boomerang parameters
-
-
v
Version number of the boomerang library in use.
-
u
URL of page that sends the beacon.
-
pid
Page ID - unique 8-character ID for each browser page load.
-
-
-
roundtrip plugin parameters
-
-
t_done
[optional] Perceived load time of the page.
-
t_page
[optional] Time taken from the head of the page to page_ready.
-
t_resp
[optional] Time taken from the user initiating the request to the first byte of the response.
-
t_other
[optional] Comma separated list of additional timers set by page developer. Each timer is of the format name|value. The following timers may be included:
-
-
t_load
[optional] If the page were prerendered, this is the time to fetch and prerender the page.
-
t_prerender
[optional] If the page were prerendered, this is the time from start of prefetch to the actual page display. It may only be useful for debugging.
-
t_postrender
[optional] If the page were prerendered, this is the time from prerender finish to actual page display. It may only be useful for debugging.
-
boomerang
The time it took boomerang to load up from first byte to last byte
-
boomr_fb
[optional The time it took from the start of page load to the first byte of boomerang. Only included if we know when page load started.
-
-
r
URL of page that set the start time of the beacon.
-
r2
[optional] URL of referrer of current page. Only set if different from r and strict_referrer has been explicitly turned off.
-
rt.start
Specifies where the start time came from. May be one of cookie for the start cookie, navigation for the W3C navigation timing API, csi for older versions of Chrome or gtb for the Google Toolbar.
-
rt.bstart
The timestamp when boomerang showed up on the page
-
rt.end
The timestamp when the done() method was called
-
-
-
bandwidth & latency plugin
-
-
bw
User's measured bandwidth in bytes per second
-
bw_err
95% confidence interval margin of error in measuring user's bandwidth
-
lat
User's measured HTTP latency in milliseconds
-
lat_err
95% confidence interval margin of error in measuring user's latency
-
bw_time
Timestamp (seconds since the epoch) on the user's browser when the bandwidth and latency was measured
-
-
-
Read results from javascript
-
-There may be cases where rather than beacon results back to your server (or alongside beaconing), you may want
-to inspect performance numbers in javascript itself and perhaps make some decisions based on this data.
-You can get at this data before the beacon fires by subscribing to the before_beacon event.
-
-
-
-BOOMR.subscribe('before_beacon', function(o) {
- // Do something with o
-});
-
-
-
-Your event handler is called with a single object parameter. This object contains all of the beacon parameters
-described above except for the v (version) parameter. To get boomerang's version number, use
-BOOMR.version.
-
-
-
-In all these howto documents, we use the following code in the before_beacon handler:
-
-
-BOOMR.subscribe('before_beacon', function(o) {
- var html = "";
- if(o.t_done) { html += "This page took " + o.t_done + "ms to load<br>"; }
- if(o.bw) { html += "Your bandwidth to this server is " + parseInt(o.bw/1024) + "kbps (±" + parseInt(o.bw_err*100/o.bw) + "%)<br>"; }
- if(o.lat) { html += "Your latency to this server is " + parseInt(o.lat) + "±" + o.lat_err + "ms<br>"; }
-
- document.getElementById('results').innerHTML = html;
-});
-
-
-
Back end script
-
-A simple back end script would look something like this. Note, I won't include the code that gets the
-URL parameters out of your environment. I assume you know how to do that. The following code assumes
-these parameters are in a variable named params. The code is in Javascript, but you can write
-it in any language that you like.
-
-
-
-function extract_boomerang_data(params)
-{
- var bw_buckets = [64, 256, 1024, 8192, 30720],
- bw_bucket = bw_buckets.length,
- i, url, page_id, ip, ua, woeid;
-
-
- // First validate your beacon, make sure all datatypes
- // are correct and values within reasonable range
- // We'll also want to detect fake beacons, but that's more complex
- if(! validate_beacon(params)) {
- return false;
- }
-
- // You may also want to do some kind of random sampling at this point
-
- // Figure out a bandwidth bucket.
- // we could get more complex and consider bw_err as well,
- // but for this example I'll ignore it
- for(i=0; i<bw_buckets.length; i++) {
- if(params.bw <= bw_buckets[i]) {
- bw_bucket = i;
- break;
- }
- }
-
- // Now figure out a page id from the u parameter
- // Since we might have a very large number of URLs that all
- // map onto a very small number (possibly 1) of distinct page types
- // It's good to create page groups to simplify performance analysis.
-
- url = canonicalize_url(params.u); // get a canonical form for the URL
- page_id = get_page_id(url); // get a page id. (many->1 map?)
-
-
- // At this point we can extract other information from the request
- // eg, the user's IP address (good for geo location) and user agent
- ip = get_user_ip(); // get user's IP from request
- woeid = ip_to_woeid(ip); // convert IP to a Where on earth ID
- ua = get_normalized_uastring(); // get a normalized useragent string
-
- // Now insert the data into our database
- insert_data(page_id, params.t_done, params.bw, params.bw_err, bw_bucket, params.lat, params.lat_err, ip, woeid, ua);
-
- return true;
-}
-
-
-
Scaling up
-
-The above code works well when you have a few thousand requests in a day. If that number starts growing to the
-hundreds of thousands or millions, then your beacon handler quickly becomes a bottleneck. It can then make
-sense to simply batch process the beacons. Let the requests come in to your apache (or other webserver) logs,
-and then periodically (say once an hour), process those logs as a batch and do a single batch insert into your
-database.
-
-
-My talk from IPC Berlin 2010 on scaling MySQL writes
-goes into how we handled a large number of beacon results with a single mysql instance. It may help you or you
-may come up with a better solution.
-
-
-
Statistical analysis of the data
-
-Once you've got your data, it's useful to do a bunch of statistical analysis on it. We'll cover this in a
-future howto, but for now, have a look at my ConFoo 2010 talk on
-the statistics of web performance.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-10-page#1.html b/doc/howtos/howto-10-page#1.html
deleted file mode 100644
index 1929fc92f..000000000
--- a/doc/howtos/howto-10-page#1.html
+++ /dev/null
@@ -1,72 +0,0 @@
-
-
-
-Boomerang Howto #10: Load time of a page prerendered by Google Chrome
-
-
-
-
-
-all docs | index
-
boomerang howto #10: load time of a page prerendered by Google Chrome
-
-This use case is based on Google Chrome's prerender
-capabilities introduced with Chrome 13. The code to include on a page is the same regardless
-of whether you use prerender or not, so this howto will not cover that. However, to enable
-prerendering of a particular page, you include that page's URL as a link element in the current
-document. For example, we include this code in the HEAD of the current page:
-
-This tells Chrome to prefetch howto-10-page#2.html and all its assets, and to
-start rendering it in the background, invisible to the user. When the user eventually clicks
-on a link to that document, it should show up immediately.
-
-
-As performance concious engineers, however, we'd like to know how long it all took. In
-particular, the numbers we care about are:
-
-
-
Time from click to display
-
Time from fetchStart/navigationStart to prerender finish
-
Time from fetchStart/navigationStart to display
-
Time from prerender finish to display
-
-
-Let's hope you've spent enough time reading this page to allow page#2's rendering to complete.
-
-
-
-Go to Page #2 now to see the results of the page load test.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-10-page#2.html b/doc/howtos/howto-10-page#2.html
deleted file mode 100644
index 0ff93006b..000000000
--- a/doc/howtos/howto-10-page#2.html
+++ /dev/null
@@ -1,65 +0,0 @@
-
-
-
-Boomerang Howto #10: Load time of a page prerendered by Google Chrome
-
-
-
-
-All Docs | Index
-
boomerang howto #10: load time of a page prerendered by Google Chrome
-
-This use case is based on Google Chrome's prerender
-capabilities introduced with Chrome 13. We use two pages for this use case. This is page #2 of the
-example. See Page #1 for the full explanation.
-
-
-
-If you clicked the link to this page from Page #1, you should see
-page performance results show up below. It may take a while if this is the first time you're doing
-the test since testing your bandwidth takes about 6 seconds. You can also click the link to Page #1
-to see the same output on Page #1.
-
-
-
-In the box below, if rt.start is set to cookie, you should see the following numbers:
-
-
-
t_done (load time): Time from click to display (perceived load time)
-
t_load: Time from fetchStart/navigationStart to prerender finish
-
t_prerender: Time from fetchStart/navigationStart to display
-
t_postrender: Time from prerender finish to display
-
-
-If rt.start is set to navigation (or something else), then t_done is the same as t_prerender. If t_prerender is not set, then this
-page wasn't prerendered, and t_done is the actual perceived load time.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-1a-page#1.html b/doc/howtos/howto-1a-page#1.html
deleted file mode 100644
index 1c90a12b0..000000000
--- a/doc/howtos/howto-1a-page#1.html
+++ /dev/null
@@ -1,101 +0,0 @@
-
-
-
-Boomerang Howto #1a: User clicks a link on a page we control and page is usable when onload fires
-
-
-
-
-All Docs | Index
-
Boomerang Howto #1a: User clicks a link on a page we control and page is usable when onload fires
-
-See use case #1 for a description of this requirement.
-
-
-We use two pages for this use case. They may be any two pages on your site, and the code you put into them
-is identical, so you could just put it on all pages on your site. Assuming you're starting out with nothing,
-this is what you do:
-
-
-
Copy boomerang.js and the images/ directory into your document root
-This should be sufficient to measure page load time on all but the very first page that a user visits on
-your site. You'll need to get the user's IP address using some server-side programming
-language like PHP, Python or C#. This is necessary in order to save bandwidth calculations
-across requests, and makes it a little easier on your users.
-
-
-
-Go to Page #2 now to see the results of the page load test.
-
-
-
More complex sites
-
-If you've been doing this website thing for a while, chances are that you use a CDN
-to host your javascript, and have several subdirectories with pages. If you do that,
-then change the link to boomerang.js above to point to the absolute location
-of that file. You will also need to tell boomerang where to find its bandwidth testing
-images. Your init() call will then change to this:
-
-Note, that you point to the image directory. It is recommended that you put these
-images on a server that you want to measure the user's bandwidth and latency to.
-In most cases this will be your own server, however, there may be cases where you'd
-want to put them on a CDN and measure bandwidth and latency to those servers instead.
-This decision is left up to you. We recommend putting them on your own server.
-
-
-
-Go to Page #2 now to see the results of the page load test.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-1a-page#2.html b/doc/howtos/howto-1a-page#2.html
deleted file mode 100644
index 7f26b1009..000000000
--- a/doc/howtos/howto-1a-page#2.html
+++ /dev/null
@@ -1,55 +0,0 @@
-
-
-
-Boomerang Howto #1a: User clicks a link on a page we control and page is usable when onload fires
-
-
-
-
-All Docs | Index
-
Boomerang Howto #1a: User clicks a link on a page we control and page is usable when onload fires
-
-See use case #1 for a description of this requirement.
-
-
-We use two pages for this use case. This is page #2 of the example. See Page #1
-for full explanation.
-
-
-
-If you clicked the link to this page from Page #1, you should see
-page performance results show up below. It may take a while if this is the first time you're doing
-the test since testing your bandwidth takes about 6 seconds. You can also click the link to Page #1
-to see the same output on Page #1.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-1b-page#1.html b/doc/howtos/howto-1b-page#1.html
deleted file mode 100644
index 755901fc6..000000000
--- a/doc/howtos/howto-1b-page#1.html
+++ /dev/null
@@ -1,85 +0,0 @@
-
-
-
-Boomerang Howto #1b: User clicks a link on a page we control and page is usable at some developer determined point
-
-
-
-
-All Docs | Index
-
Boomerang Howto #1b: User clicks a link on a page we control and page is usable at some developer determined point
-
-See use case #1 for a description of this requirement.
-
-
-We use two pages for this use case. They may be any two pages on your site, and the code you put into them
-is identical, so you could just put it on all pages on your site. Unlike case 1a, in this case, we do not
-allow the beacon to fire when the onload event fires. Instead, we fire the page_ready event
-when we determine that the page is ready. We also set the autorun parameter to false to stop
-boomerang from running automatically.
-
-
-
Copy boomerang.js and the images/ directory into your document root
-
Add the code below to all your pages. You may add it at any point before your page is considered complete.
-The rest of your page will load normally. When you determine (through javascript, perhaps) that your
-page is usable by a user browsing your website, you need to fire the page_ready event like this:
-
-
-
-BOOMR.page_ready(); // Tell boomerang that the page is now usable
-
-
-
-As in howto-1a, you need to populate the user_ip field using a back end programming language.
-
-
-
-Go to Page #2 now to see the results of the page load test.
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-1b-page#2.html b/doc/howtos/howto-1b-page#2.html
deleted file mode 100644
index 719407b57..000000000
--- a/doc/howtos/howto-1b-page#2.html
+++ /dev/null
@@ -1,60 +0,0 @@
-
-
-
-Boomerang Howto #1b: User clicks a link on a page we control and page is usable at some developer determined point
-
-
-
-
-All Docs | Index
-
Boomerang Howto #1b: User clicks a link on a page we control and page is usable at some developer determined point
-
-See use case #1 for a description of this requirement.
-
-
-We use two pages for this use case. This is page #2 of the example. See Page #1
-for full explanation.
-
-
-
-If you clicked the link to this page from Page #1, you should see
-page performance results show up below. We introduce an artificial delay of 750ms to show how you can
-fire the page_ready event after the page has loaded. It may take a while if this is the
-first time you're doing the test since testing your bandwidth takes about 6 seconds. You can also click
-the link to Page #1 to see the same output on Page #1.
-
-Next fetch your content. Right before the call to fetch content, call var timer = BOOMR.requestStart("page-name").
-In the callback function where the content has been fetched, call the timer.loaded()
-method. This measures and beacons back the response time. I use YUI 3 in the code below, but you could use
-anything you like.
-
-
-
-YUI().use("io-base", function(Y) {
- var uri = "dynamic-content.txt";
- var timer;
-
- function complete(id, o) {
- var html = o.responseText;
- document.getElementById("dynamic-content").innerHTML = html;
- if(timer) timer.loaded(); // Tell boomerang to measure time and fire a beacon
- };
-
- Y.on('io:complete', complete);
-
- timer = BOOMR.requestStart("my-timer"); // Start measuring download time
- var request = Y.io(uri);
-});
-
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-3.html b/doc/howtos/howto-3.html
deleted file mode 100644
index 9b30a6746..000000000
--- a/doc/howtos/howto-3.html
+++ /dev/null
@@ -1,235 +0,0 @@
-
-
-
-Boomerang Howto #3: Measure a user's bandwidth/latency along with page load time
-
-
-
-
-All Docs | Index
-
Boomerang Howto #3: Measure a user's bandwidth/latency along with page load time
-
-See use cases #3 & #5 for a description of this requirement.
-
-
-
-Boomerang's bandwidth plugin (BOOMR.plugins.BW) measures the user's network throughput and network latency
-and adds this to the beacon. You will either need to build boomerang with this plugin or include it as part of your HTML
-page. Both methods will be described below.
-
-
-
-Also note that, bandwidth detection through javascsript is not accurate. If the user's network is lossy or is shared with
-other users, or network traffic is bursty, real bandwidth can vary over time. The measurement we take is based
-over a short period of time, and this may not be representative of the best or worst cases. We try to cover for
-that by measuring not just the bandwidth, but also the error value in that measurement.
-
-
-
Option 1: Including boomerang and the bandwidth plugin
-
-
-To include boomerang and the bandwidth plugin on your page, use the following code in your HTML (assuming the bw.js
-file is in a directory called plugins/ under the current directory):
-
Option 2: Building boomerang to include the bandwidth plugin
-
-
-To build a version of boomerang with the bandwidth plugin included, run the following command on your command line inside the boomerang directory:
-
-
-
-make PLUGINS=plugins/bw.js
-
-
-
-This will create a file called boomerang-0.9.XXXXXXXX.js (where XXXXXXXX is a numeric timestamp) in the current directory and you
-will include that file in your HTML:
-
-Note that the default for the make command is to build boomerang with the rt.js and bw.js plugins,
-so simply running make is sufficient, although that will get you the extra rt.js plugin that you may not want.
-
-
-
Copy the bandwidth images to your server
-
-
-The bandwidth images are located in the images/ folder of your local boomerang
-git repository. You need to copy all of these images to a location on your HTTP server. Some people choose to put these images on their CDN, but
-be aware that this could result in increased CDN charges. You will need to configure your CDN to ignore the query string when caching these images.
-
-
-
Using the bandwidth plugin
-
-
-Add boomerang to your page using one of the two methods outlined above, and then call the init() method. This will start the bandwidth
-test once the page loads. If you want the beacon results to include the results of the bandwidth test, setting block_beacon to true
-will force boomerang to wait for the test to complete before sending the beacon. If you do not turn on the block_beacon feature, you will
-only receive bandwidth results if they were cached in a cookie by a previous test run.
-
-The default value of the BW.base_url parameter is images/, so if your bandwidth detection
-images are placed in a subdirectory of the current directory called images, then you do not need
-to set the BW.base_url parameter. It is a good practice though, as you might have pages in multiple
-directories.
-
-
-
-Now while this is the minimum code required to measure bandwidth and latency and have it beaconed back, it isn't
-the best option for your user. The test will run every time the user visits a page on your site even though
-their bandwidth probably hasn't changed (apart from regular fluctuations). It's far better to store the bandwidth
-in a cookie for a fixed period of time, and read it out of the cookie if it exists. Now it is possible that the
-user moves between several networks, eg: a laptop used at home, at work and at a coffee shop. The bandwidth and
-latency at these locations may be different, and it's necessary to measure them separately. We detect a change
-in network through the user's IP address, so in order to store the user's bandwidth in a cookie, you will need
-to tell boomerang what the user's IP address is. You do this through the user_ip parameter.
-
-As far as I know, there's no way in javascript to figure out the user's IP address. You'll have to do this server
-side and write the value into your code.
-
-
-
IPv4 optimisations
-
-If your user has an IPv4 address, then we also strip out the last part of the IP and use that rather than the entire
-IP address. This helps if users use DHCP on the same ISP where their IP address changes frequently, but they stay
-within the same subnet. If the user has an IPv6 address, we use the entire address.
-
-
-
The Cookie
-
-You may want to customise the name of the cookie where the bandwidth will be stored. By default this is
-set to BA, but you can change it using the BW.cookie parameter.
-
-This cookie is set to expire in 7 days. You can change its lifetime using the BW.cookie_exp parameter.
-The value is in seconds. During that time, you can also read the value of the cookie on the server side. Its
-format is as follows:
-
[integer] [bytes/s] The user's bandwidth to your server
-
be
-
[float] [bytes/s] The 95% confidence interval margin of error in measuring the user's bandwidth
-
l
-
[float] [ms] The HTTP latency between the user's computer and your server
-
le
-
[float] [ms] The 95% confidence interval margin of error in measuring the user's latency
-
ip
-
[ip address] The user's IPv4 or IPv6 address that was passed as the user_ip parameter to the init() method
-
t
-
[timestamp] The browser time (in seconds since the epoch) when the cookie was set
-
-
-
-These parameters are also sent in the beacon (See HOWTO #0), but having them in the
-cookie means that you can customise your users experience based on the bandwidth before you serve a request.
-
-
-
Disabling the bandwidth check
-
-Finally, there may be cases when you want to completely disable the bandwidth test. Perhaps you know that
-your user is on a slow network, or pays by the byte (the bandwidth test uses a lot of bandwidth), or is on
-a mobile device that cannot handle the load. In such cases you have two options.
-
-
-
Delete the bandwdith plugin from your copy of boomerang.js
-
Set the BW.enabled parameter to false:
-
-
-<!-- Include boomerang as outlined above -->
-<script type="text/javascript">
-BOOMR.init({
- BW: { enabled: false }
-});
-</script>
-
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-4.html b/doc/howtos/howto-4.html
deleted file mode 100644
index 3483566c3..000000000
--- a/doc/howtos/howto-4.html
+++ /dev/null
@@ -1,157 +0,0 @@
-
-
-
-
-
-
-
-
-
-Boomerang Howto #4: Measure more than just page load time
-
-
-
-
-
-All Docs | Index
-
Boomerang Howto #4: Measure more than just page load time
-
-See use case #4 for a description of this requirement.
-
-
-Up until now we've measured the time it takes for a page to become usable by the user. It may also be
-useful to measure the time it took for various components within the page to load and match that up with
-the full page load time. boomerang provides additional timers that you can configure to measure separate
-parts of your page. You use the startTimer() and endTimer() methods of the
-roundtrip (BOOMR.plugin.RT) plugin to do this.
-
-
-
-In the following examples, you'll need to make sure that boomerang.js is loaded and the
-init() method called before you call any of these methods, so perhaps putting it at the
-top of your page is a good idea. We'll see later how to work around this.
-
-
-
-First, identify sections of your page that you want to measure. Call startTimer() before
-and endTimer() after. Each timer has its own name. The names are free-form strings,
-but stay simple to be efficient. Stick to alphanumeric characters and underscores, and limit names
-to around 5 characters, eg: t_ads, t_head, t_js. The following
-names are reserved: t_done, t_page, t_resp.
-
-
-
-Make sure you've included boomerang.js before starting the timers.
-
-Your timers will now be included in the beacon along with t_done.
-
-
-
-Notice in the second invocation how we chain the calls to endTimer and startTimer.
-This is possible for most methods that you call since they return a reference to the object. Note
-that the timer methods are run on the BOOMR.plugins.RT object, so they return a reference
-to that object and not to the BOOMR object.
-
-
-
Measuring time for content loaded before boomerang
-
-Now we've said for years that putting javascript at the bottom of your document is good for
-performance, so asking you to load boomerang at the top may not be the best advice. You can
-still measure in-page times though, and then report them to boomerang once it has loaded.
-You do this using the BOOMR.plugins.RT.setTimer() method. This method takes
-two parameters — the timer name and its value in milliseconds. The code above will
-change to this:
-
-See use case #5 for a description of this requirement.
-
-
-There will be several occassions when you need to add more information to the beacon that's sent
-back to the server. For example, you may want to tag the beacon with a page_id or
-you may want to do A/B testing and tag the beacon with a parameter specifying which bucket this
-beacon is for. You can achieve all this using the BOOMR.addVar() method.
-
-
-
-Before you use this method, remember that each plugin adds its own parameters and you shouldn't
-overwrite these with your own values. See Howto #0 for a list of
-parameters set by boomerang's built-in plugins. Other plugins may add their own parameters,
-consult the documentation of the plugin to find out what these are.
-
-
-
-BOOMR.addVar("page_id", 123);
-
-
-
-The parameter name must be a string. We recommend only using alphanumeric characters and underscores,
-but you can really use anything you like. Parameter values may only be numbers or strings, ie,
-something that you can put into a URL.
-
-
-
-If you need to set multiple parameters, you can pass in an object instead:
-
-Make sure you've included boomerang.js before calling BOOMR.addVar().
-
-
-
The beacon
-
-The beacon will include all variables that you add in the URL. Both keys and values will be
-URI encoded. Your back end application will need to understand the passed in parameters.
-
-You can also remove a parameter that you've added (or that a plugin has added) from the beacon.
-To do this, call the BOOMR.removeVar() method. This method takes in a list of
-name, and removes all of them from the parameter list. Any name that isn't in the parameter
-list is ignored.
-
-
-
-// don't send the stooges to the server
-BOOMR.removeVar("larry", "moe", "curly");
-
-
-
Stopping the beacon
-
-You can also this as a crude way to prevent the beacon from firing. Inside your before_beacon
-event handler, simply remove all parameters.
-
-
-
-BOOMR.subscribe('before_beacon', function(o) {
- var p_names = [], k;
-
- if( "t_done" in o ) {
- return;
- }
-
- // t_done is not set, so don't beacon
- for(k in o) {
- if(o.hasOwnProperty(k)) {
- p_names.push(k);
- }
- }
-
- // removeVar accepts either a list or an array
- BOOMR.removeVar(p_names);
-});
-
-To use boomerang, you first include boomerang.js in your html file and
-then call the BOOMR.init() method. This should be sufficient to measure
-page performance, but may not be useful enough to you as a site owner. You still won't
-get data back to your server, and probably won't be able to measure the user's bandwidth
-either.
-
-
-In this document we'll look at all the different parameters you can use to configure
-boomerang and its built in plugins. If you have additional plugins, consult their
-documentation for details on how to configure them.
-
-
-
Configuring boomerang
-
-To configure boomerang and its plugins, you pass in a configuration object to the init()
-method:
-
-
-BOOMR.init({
- key: value,
- ...
- });
-
-
-boomerang has the following configurable parameters:
-
-
-
beacon_url
-
-[highly recommended]
-The URL to beacon results back to. All parameters will be added to this URL's
-query string. This URL should not already have a query string component.
-There is no default value for this parameter. If not set, no beacon will be sent.
-
-
-
site_domain
-
-[recommended]
-The domain that all cookies should be set on. Boomerang will try to auto-detect this,
-but unless your site is of the foo.com format, it will probably get it
-wrong. It's a good idea to set this to whatever part of your domain you'd like to
-share bandwidth and performance measurements across.
-If you have multiple domains, then you're out of luck. You'll just have to get
-separate measurements across them.
-
-
-
user_ip
-
-[recommended]
-Despite its name, this is really a free-form string used to uniquely identify the user's
-current internet connection. It's used primarily by the bandwidth test to determine
-whether it should re-measure the user's bandwidth or just use the value stored in the
-cookie. You may use IPv4, IPv6 or anything else that you think can be used to identify
-the user's network connection.
-
-
-
log
-
-[optional]
-By default, boomerang will attempt to use the logger component from YUI if it finds it
-or firebug if it finds that instead. If it finds neither, it will default to not
-logging anything. You can define your own logger by setting the log
-parameter to a function that logs messages.
-The signature of this function is:
-
-function log(oMessage, sLevel, sSource);
-
-Where:
-
oMessage
is the object/message to be logged. It is up to you to decide how to log objects.
-
sLevel
is the log level, with values of "error", "warn", "info" and "debug"
-
sSource
is the source of the log message. This will typically be the string "boomerang" followed by the name of a plugin
-
-Note that you can completely disable logging by setting log to null.
-
-
-
autorun
-
-[optional]
-By default, boomerang runs automatically and attaches its page_ready handler to
-the window.onload event. If you set autorun to false,
-this will not happen and you will need to call BOOMR.page_ready() yourself.
-
-
-
<plugin_name>
-
-Each plugin is configured through a sub-object of the config object. The key is the name
-of the plugin. In the following sections we'll see how to configure our built-in plugins.
-
-All roundtrip plugin configuration items are under the RT namespace.
-
-
-
-
cookie
-
-[optional]
-The name of the cookie in which to store the start time for measuring page load time. The default name is RT.
-Set this to a falsy value like null or the empty string to ignore cookies and depend completely on the
-WebTiming API for the start time.
-
-
-
cookie_exp
-
-[optional]
-The lifetime in seconds of the roundtrip cookie. This only needs to live for as long as it takes for a single page to load.
-Something like 10 seconds or so should be good for most cases, but to be safe, and to cover people with really slow
-connections, or users that are geographically far away from you, keep it to a few minutes. The default is set to 10 minutes.
-
-
-
strict_referrer
-
-[optional]
-By default, boomerang will not measure a page's roundtrip time if the URL in the RT cookie doesn't match
-the current page's document.referrer. This is so because it generally means that the user visited a
-third page while their RT cookie was still valid, and this could render the page load time invalid.
-There may be cases, though, when this is a valid flow — for example, you have an SSL page in between and the
-referrer isn't passed through. In this case, you'll want to set strict_referrer to false
-
-All bandwidth plugin configuration items are under the BW namespace.
-
-
-
-
base_url
-
-[required]
-By default, this is set to the empty string, which has the effect of disabling the bandwidth plugin. Set the base_url
-parameter to the HTTP path of the directory that contains the bandwidth images to enable this test. This can be an absolute or
-a relative URL. If it's relative, remember that it's relative to the page that boomerang is included in and not to the javascript
-file. The trailing / is required.
-
-
-
cookie
-
-[optional]
-The name of the cookie in which to store the measured bandwidth and latency of the user's network connection.
-The default name is BA. See Howto #3 for more details on the bandwidth cookie.
-
-
-
cookie_exp
-
-[optional]
-The lifetime in seconds of the bandwidth cookie. The default is set to 7 days. This specifies how long it will be before
-we run the bandwidth test again for a user, assuming their IP address doesn't change within this time. You probably do
-not need to change this setting at all since the bandwidth of a given network connection typically does not change by an
-order of magnitude on a regular basis.
-Note that if you're doing some kind of real-time streaming, then chances are that this bandwidth test isn't right for you,
-so setting this cookie to a shorter value isn't the right solution.
-
-
-
timeout
-
-[optional]
-The timeout in milliseconds for the entire bandwidth test. The default is set to 15 seconds. The bandwidth test can run for
-a long time, and sometimes, due to network errors, it might never complete. The timeout forces the test to complete at
-that time. This is a hard limit. If the timeout fires, we stop further iterations of the test and attempt to calculate
-bandwidth with the data that we've collected at that point. Increasing the timeout can get you more data and increase
-the accuracy of the test, but at the same time increases the risk of the test not completing before the user leaves the
-page.
-
-
-
nruns
-
-[optional]
-The number of times the bandwidth test should run. The default is set to 5. The first test is always a pilot to figure
-out the best way to proceed with the remaining tests. Increasing this number will increase the tests accuracy, but at
-the same time increases the risk that the test will timeout. It should take about 2-4 seconds per run, so consider this
-value along with the timeout value above.
-
-
-
test_https
-
-[optional]
-By default, boomerang will skip the bandwidth test over an HTTPS connection. Establishing
-an SSL connection takes time, which could skew the bandwidth results. If all your traffic
-is sent over SSL, then running the test over SSL probably gets you what you want. If you
-set test_https to true, boomerang will run the test instead of skipping.
-
-
-
block_beacon
-
-[optional]
-By default, the bandwidth plugin will not block boomerang from sending a beacon, so the results
-will not be included in the broadcast with default settings. If you set block_beacon to
-true, boomerang will wait for the results of the test before sending the beacon.
-
-
-
-
-
All optional
-
-All configuration parameters are optional, but not setting some of them can lead to unexpected or incomplete results,
-so they should be set. It is, however, possible to get up and running by doing nothing more than putting your bandwidth
-images in the right directory, including the code and calling init().
-
Boomerang Howto #7: Protecting the beacon from abuse
-
-See use case #9 for a description of this requirement.
-
-
-
-There are two types of beacon abuse you may need to protect against:
-
-
-
Denial of service attacks
-
Fake beacons that do not originate from a page you own
-
-
-
Denial of service
-
-There's nothing you can do in javascript to prevent denial of service attacks, but you can configure
-your server to rate limit beacons originating from a single IP. You typically shouldn't be receiving
-beacons faster than a user can open websites. You'll also want to do some operating system/web server
-level configurations to detect abusive access patterns. The majority of these are beyond the scope of
-this document, but we have a few tips on building your beaconing back end to protect you from an attack.
-
-
-
-Most developers will create a back end script that they use as the beacon_url parameter
-of boomerang. The problem here is that since these requests cannot be cached, this script will run
-on every beacon request, and will consume web server resources, and probably database resources on
-every request. During a denial of service attack, this can bring your servers down.
-
-
-
-A better way to handle it is to have a lightweight web server running on your beaconing server, and
-have this server configured to only respond to requests for the beacon URL. It should send back a
-HTTP 204 response to all beacon requests. This is easy enough to configure within the
-server and means that it doesn't have to do a disk lookup for any request. The server will still
-write the request to its access logs, and this should include all query string parameters and cookies.
-
-
-
-Periodically - say once an hour or once a day (depending on the volume of beacons), you can batch process
-your logs and figure out your actual beacon parameters from there, discarding any obviously fake or
-abusive beacons. The actual code to extract data from a beacon is the same regardless of whether you
-do it on every request or as a batch. See Howto #0 for more information
-on extracting data from a beacon.
-
-
-
-There's much
-more
-information online about DoS attacks
-and how to protect yourself from them.
-
-
-
Fake beacons that do not originate from a page you own
-
-The most common reason for this kind of abuse is that someone really liked your page design and copied
-it to their own server, including the boomerang javascript, only they didn't update the beacon_url,
-so it still beacons to your server. You probably don't want this.
-
-
-
-The easiest way to fix this is to just check the referrer of all requests and block any that don't
-come from your own domain. This works for the clueless abuser case, but not for the intentional
-abuser.
-
-
-
-The intentional abuser is someone who will try to exploit all URLs to your site just to see if they
-can get something out of it. What they try isn't really important. There's only one legitimate way
-of using your beacon, and you should block all other uses. The best way to do this is through a
-nonce or a
-crumb.
-This is a string that is valid for one use only. It probably includes the current timestamp and a
-validity period as part of its hash. You generate it on every page requests and add it to boomerang
-using the BOOMR.addVar() method. On your beacon server, you then validate the nonce
-before accepting the beacon. If you're batch processing, you'd use the timestamp of the request and
-not the time that you're running the batch job in order to validate the nonce.
-
-
-
-BOOMR.addVar("nonce", "125a7b79de989876cce970f0768a07"); // your nonce will be different
-
-
-
-While the nonce can protect you from someone hitting your beacon URL directly, it does mean that your
-main page cannot be cached, since the nonce will change on every request.
-
-
-
-The nonce also doesn't protect you from someone pulling the beacon out of your page — with a valid
-nonce, then modifying the beacon parameters and sending it off to your server. Protecting against this
-requires you to sign the parameters in the beacon, but this isn't something that you can do in javascript.
-I do not know of a general purpose solution to this problem.
-
-See use case #7 for a description of this requirement.
-
-
-Note: The DNS plugin hasn't been tested. Your help in testing it is appreciated.
-
-
-Measuring DNS requires some server-side set up. The entire set up was
-documented in detail
-by Yahoo! engineer Carlos Bueno, so go read his post for everything you'll need to set
-this up. In brief, the points he covers are:
-
-
-
Set up a wildcard hostname, perferably one that does not share cookies with your main site.
-Give it a low TTL, say, 60 seconds, so you don't pollute downstream caches.
-
Set up a webserver for the wildcard hostname that serves the images named A.gif
-and B.gif (from the images/ subdirectory) as fast as possible.
-Make sure that KeepAlive, Nagle, and any caching headers are turned off.
-
Include dns.js along with boomerang.js (you can just concatenate the two
-files)
-
Tell the DNS plugin where to get its images from
-
-
-
-Steps 1 and 2 are complicated, and if you don't have full control over your DNS server (eg: you use
-Dreamhost), then it may be impossible for you to do this. If you can go forward, read on.
-
-
-
-To configure the plugin, you only need to tell it where to get its images from. Unlike the bandwidth
-plugin though, this URL needs a wildcard:
-
-If you've set things up correctly, this should measure your DNS latency within a margin of error.
-We could run the test multiple times to find out what this error is, but for now we'll just do
-it once.
-
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-9.html b/doc/howtos/howto-9.html
deleted file mode 100644
index 7413f7d0f..000000000
--- a/doc/howtos/howto-9.html
+++ /dev/null
@@ -1,100 +0,0 @@
-
-
-
-Boomerang Howto #9: Collect performance data from the W3C Navigation Timing API
-
-
-
-
-All Docs | Index
-
Boomerang Howto #9: Collect performance data from the Navigation Timing API
-
-
The W3C Navigation Timing API is an interface implemented by modern browsers that
-provides broad and deep data related to the performance of page loads. At the time
-of this writing, it is supported by the following browsers:
-
-
-
Chrome 6+
-
Internet Explorer 9+
-
Firefox 7+ (note that a bug in Firefox 7 and 8 reports the incorrect time for navigationStart. Use unloadEventStart or fetchStart for a close proximation.)
-
-
-
The navtiming.js plugin doesn't require any configuration options as it simply
-reads data out of the browser (if available) and adds it to the beacon query
-string.
-
-
You will have to build your own version of boomerang.js since it
-isn't one of the default plugins. To do this, run make in the
-boomerang directory with the following option:
-
-
-make PLUGINS=plugins/navtiming.js
-
-OR with the rt & bw plugins:
-
-make PLUGINS="plugins/rt.js plugins/bw.js plugins/navtiming.js"
-
-
-
-
Then you can include the new boomerang file (don't forget to run it through
-your favorite Javascript minifier first) as you normally would.
-
-
The new query parameters and the browser attributes they map
-to are shown below. More information about the definition of each attribute can be found
-in the W3C Navigation Timing specification.
-
-
-
-
-
-
-
-
-
diff --git a/doc/howtos/howto-add-additional-timers-to-the-beacon.md b/doc/howtos/howto-add-additional-timers-to-the-beacon.md
new file mode 100644
index 000000000..c715632b3
--- /dev/null
+++ b/doc/howtos/howto-add-additional-timers-to-the-beacon.md
@@ -0,0 +1,47 @@
+By default, Boomerang includes several timers on each beacon, such as:
+
+* Total Page Load time ({@link BOOMR.plugins.RT t_done})
+* Back-End time ({@link BOOMR.plugins.RT t_resp})
+* Front-End time ({@link BOOMR.plugins.RT t_page})
+
+You can also add additional timers to a beacon via
+{@link BOOMR.plugins.RT.startTimer} and {@link BOOMR.plugins.RT.setTimer}.
+
+These timers can be used to track sub-components on the page or other arbitrary
+events.
+
+The timer name given to {@link BOOMR.plugins.RT.startTimer} or
+{@link BOOMR.plugins.RT.setTimer} and elapsed time of the timer is added to
+the {@link BOOMR.plugins.RT t_other} beacon parameter.
+
+If there are multiple additional timers, they are appended to each other, separated
+by commas.
+
+Example beacon data:
+
+```
+t_other=header|1234,ads|500
+```
+
+Example usage:
+
+```html
+
+
+
+ Page Title
+
+
+
+
+
+```
diff --git a/doc/howtos/howto-add-arbitrary-data-to-the-beacon.md b/doc/howtos/howto-add-arbitrary-data-to-the-beacon.md
new file mode 100644
index 000000000..a62b0894b
--- /dev/null
+++ b/doc/howtos/howto-add-arbitrary-data-to-the-beacon.md
@@ -0,0 +1,49 @@
+You may want to add additional parameters to each beacon. For example, you may
+want to tag the beacon with a Page Group or you may want to do A/B testing and
+tag the beacon specifying which bucket this beacon is for. You can achieve all
+of this using {@link BOOMR.addVar BOOMR.addVar()}.
+
+Before you use this method, remember that each plugin adds its own parameters
+and you should avoid overwriting these with your own values. See
+{@link BOOMR Boomerang} and each {@link BOOMR.plugins plugin} for documentation
+on what data it adds to the beacon.
+
+## Adding Data to the Beacon
+
+{@link BOOMR.addVar} can be used to add any arbitrary data you want to the beacon.
+
+Example usage:
+
+```javascript
+BOOMR.addVar("ab_test", "a");
+```
+
+If you need to set multiple parameters, you can supply an object instead:
+
+```javascript
+BOOMR.addVar({
+ "ab_test": "a"
+ "customer_id": 123
+});
+```
+
+The beacon will include all variables that you add in the URL. Both keys and
+values will be URI encoded. Your beacon endpoint will need to understand
+the data.
+
+```
+http://yoursite.com/beacon/?t_done=500&ab_test=1&customer_id=123&...
+```
+
+## Removing Data from the Beacon
+
+You can also remove a parameter that you've added (or that a plugin has added)
+from the beacon. To do this, call {@link BOOMR.removeVar}:
+
+```javascript
+// remove a single parameter
+BOOMR.removeVar("ab_test");
+
+// remove multiple parameters
+BOOMR.removeVar("ab_test", "customer_id", "something_else");
+```
diff --git a/doc/howtos/howto-delay-page-load-time.md b/doc/howtos/howto-delay-page-load-time.md
new file mode 100644
index 000000000..cffb308f5
--- /dev/null
+++ b/doc/howtos/howto-delay-page-load-time.md
@@ -0,0 +1,30 @@
+By default, Boomerang will wait for the
+[window `load` event](https://developer.mozilla.org/en-US/docs/Web/Events/load)
+before it sends a beacon, and the Page Load timestamp ({@link BOOMR.plugins.RT t_done})
+will be measured until the end of that `load` event.
+
+For some cases, you may want to have the Page Load time measure to a timestamp
+other than the window `load` event. For example, your application may load
+additional libraries or images at `load`, and you want the Page Load time to
+reflect that.
+
+To have Boomerang ignore the window `load` event, you must do two things:
+
+1. Set `autorun` to `false` in {@link BOOMR.init}
+2. When you want to mark the Page Load time done, you need to call
+ {@link BOOMR.page_ready}
+
+Example code:
+
+```javascript
+BOOMR.init({
+ beacon_url: "http://yoursite.com/beacon/",
+ autorun: false
+});
+
+// ...
+// at some later point, when the page is loaded:
+BOOMR.page_ready();
+```
+
+Boomerang will send the Page Load beacon when {@link BOOMR.page_ready} is called.
diff --git a/doc/howtos/howto-measure-arbitrary-events.md b/doc/howtos/howto-measure-arbitrary-events.md
new file mode 100644
index 000000000..88f76e0e1
--- /dev/null
+++ b/doc/howtos/howto-measure-arbitrary-events.md
@@ -0,0 +1,53 @@
+Boomerang has utility methods to assist in measuring the elapsed time of
+any arbitrary event you want to measure.
+
+Note: If you want to measure `XMLHttpRequests`, you should utilize the
+{@link BOOMR.plugins.AutoXHR AutoXHR} plugin.
+
+There are two ways of measuring events:
+
+1. Call {@link BOOMR.requestStart BOOMR.requestStart()} to mark the beginning of the event, then
+ call `.loaded()` to mark when it is complete
+2. Call {@link BOOMR.responseEnd BOOMR.responseEnd()} with your own timestamps or elapsed time
+
+Both of these methods will trigger a beacon with the Page Group
+({@link BOOMR.plugins.RT h.pg}) set to the input name.
+
+## Using `BOOMR.requestStart`
+
+{@link BOOMR.requestStart} can be used to have Boomerang track a complete event.
+When {@link BOOMR.requestStart} is called, Boomerang will mark the start time.
+
+Once the event is complete, you can call `.loaded()` on the returned object to
+mark the end timestamp.
+
+Example:
+
+```javascript
+var timer = BOOMR.requestStart("my-timer");
+setTimeout(function() {
+ // will send a beacon with the page group of "my-timer"
+ // and an elapsed time of approximately 1 second
+ timer.loaded();
+}, 1000);
+```
+
+## Using `BOOMR.responseEnd`
+
+{@link BOOMR.responseEnd} can be used to immediately send a beacon based on the
+given start time (and optional end time).
+
+Example:
+
+```javascript
+var startTime = BOOMR.now();
+
+setTimeout(function() {
+ // immediately sends a beacon with the page group of "my-timer" and
+ // measured from the startTime to now.
+ BOOMR.responseEnd("my-timer", startTime);
+
+ // you can also specify the end time, i.e. 500ms ago
+ BOOMR.responseEnd("my-other-timer", startTime, {}, BOOMR.now() - 500);
+}, 1000);
+```
diff --git a/doc/howtos/howto-read-data-from-a-beacon.md b/doc/howtos/howto-read-data-from-a-beacon.md
new file mode 100644
index 000000000..fd2ad70c4
--- /dev/null
+++ b/doc/howtos/howto-read-data-from-a-beacon.md
@@ -0,0 +1,139 @@
+While Boomerang excels at collecting Real User Monitoring performance metrics,
+you'll probably want to do something with the data that it collects.
+
+What can you do with the data?
+
+## Beacon results back to your server
+
+For most cases you'll want to send the collected performance data back to your
+server or to the cloud so you can analyze it in aggregate.
+
+The first thing you need to do is configure a url that beacon data will be sent to.
+
+The beacon URL can be configured via the {@link BOOMR.init beacon_url} parameter
+of {@link BOOMR.init}:
+
+```html
+
+
+```
+
+`beacon_url` should be a HTTP(s) endpoint that accepts beacon data encoded in
+the query string (via `` `GET` requests) and `application/x-www-form-urlencoded`
+form data (via `POST` requests from `XMLHttpRequest` or `navigator.sendBeacon()`).
+
+### Beacon Parameters
+
+{@link BOOMR Boomerang} and each {@link BOOMR.plugins plugin} adds its own parameters to
+the beacon. Please refer to those documentation pages for details.
+
+### Backend Servers
+
+There are several open-source projects that can receive and analyze Boomerang data:
+
+* [boomcatch](http://cruft.io/posts/introducing-boomcatch/)
+* [boomerang-express](https://github.com/andreas-marschke/boomerang-express)
+* [PIWIK](https://piwik.org/)
+
+### Protecting the Beacon from Abuse
+
+There are two types of beacon abuse you may need to protect against:
+
+* Denial of Service (DoS) attacks
+* Fake beacons that do not originate from a page you own
+
+#### Denial of Service (DoS) attacks
+
+There's nothing you can do in JavaScript to prevent Denial of Service (DoS) attacks,
+but you can configure your server to rate limit beacons originating from a single
+IP. You typically shouldn't be receiving beacons faster than a user can navigate
+through your website. However, you may need to allow for multiple users originating
+from the same proxy IP address.
+
+You'll also want to do some operating system/web server level configuration to
+detect abusive access patterns. The majority of these are beyond the scope of
+this document, but we have a few tips on building your back-end to protect you
+from an attack.
+
+One recommendation is to have a lightweight web server running as your beacon
+endpoint, and have this server quickly log the request and immediately respond
+without processing the data. You can send a HTTP `204 No Content` response and
+close the connection so there is minimal overhead.
+
+Once the data is logged, you can periodically batch process and analyze the
+incoming data.
+
+More references for preventing DoS attacks:
+
+* [learn-netowrking.com](http://learn-networking.com/network-security/how-to-prevent-denial-of-service-attacks)
+* [wikipedia.org](http://en.wikipedia.org/wiki/Denial-of-service_attack)
+* [cert.org](http://www.cert.org/tech_tips/denial_of_service.html)
+
+#### Fake beacons that do not originate from a page you own
+
+The most common reason for this kind of abuse is that someone liked your page
+design and copied it to their own server, including the boomerang JavaScript --
+only they didn't update the `beacon_url`, so it still beacons to your server. You
+probably don't want these beacons.
+
+The easiest way to fix this is to just check the HTTP referrer of all requests and
+block any that don't come from your own domain. This works for the clueless
+abuser case, but not for the intentional abuser.
+
+The intentional abuser is someone who will try to exploit URLs on your site
+to see if they can get something out of it. What they try isn't really important --
+there's only one legitimate way of using your beacon, and you should block all
+other uses. The best way to do this is through a
+[nonce](http://en.wikipedia.org/wiki/Cryptographic_nonce) or a
+[crumb](http://abhinavsingh.com/blog/2009/10/web-security-using-crumbs-to-protect-your-php-api-ajax-call-from-cross-site-request-forgery-csrfxsrf-and-other-vulnerabilities/).
+
+This is a string that is valid for one use only. It probably includes the
+current timestamp and a validity period as part of its hash. You generate it
+on every page request and add it to Boomerang using {@link BOOMR.addVar}.
+
+On your beacon endpoint, you then validate the nonce before accepting the beacon.
+
+You can either include this nonce in your (non-cached) HTML page, or, fetch it
+from your server via a `XMLHttpRequest`.
+
+The nonce also doesn't protect you from someone pulling the beacon out of your
+page (with a valid nonce), then modifying the beacon parameters and sending
+it off to your server. Protecting against this requires you to sign the
+parameters in the beacon, but this isn't something that you can do in JavaScript
+in a way that an attacker couldn't replicate.
+
+## Read beacon data from JavaScript
+
+You can get a notification of each beacon before it is sent by subscribing to
+the {@link BOOMR#event:before_beacon before_beacon} event:
+
+```javascript
+BOOMR.subscribe('before_beacon', function(beaconData) {
+ if (beaconData.u.indexOf("/some/page")) {
+ // take some action based on the page
+ }
+
+ // you can still add data to the beacon at this point
+ beaconData.someData = 1;
+});
+```
+
+You can also get a notification of each beacon after it was sent by subscribing to
+the {@link BOOMR#event:beacon beacon} event:
+
+```javascript
+BOOMR.subscribe('beacon', function(beaconData) {
+ // remove beacon data that was only relevant for a single beacon
+ BOOMR.removeVar('someData');
+});
+```
+
+## References
+
+* [IPC Berlin 2010 talk on MySQL scaling via Philip Tellis](http://www.slideshare.net/bluesmoon/scaling-mysql-writes-through-partitioning-ipc-spring-edition)
+
+* [ConFoo 2010 talk on the statistics of web performance via Philip Tellis](http://www.slideshare.net/bluesmoon/index-3441823)
diff --git a/doc/howtos/howto-resourcetiming-buffer.md b/doc/howtos/howto-resourcetiming-buffer.md
new file mode 100644
index 000000000..83fde89be
--- /dev/null
+++ b/doc/howtos/howto-resourcetiming-buffer.md
@@ -0,0 +1,54 @@
+[ResourceTiming](http://www.w3.org/TR/resource-timing/) is a browser performance
+API that gathers accurate performance metrics about all of the resources
+fetched during the page load, such as images, CSS and JavaScript. Boomerang can
+capture this data automatically.
+
+By default, the Resource Timing API only tracks the first 150 resources (per IFRAME).
+While this limit can be manipulated by the developer in order to track more resources
+via [`window.performance.setResourceTimingBufferSize()`](http://www.w3.org/TR/resource-timing/),
+there are performance trade-offs (additional memory consumption) when doing this,
+so Boomerang doesn't make these changes automatically.
+
+If you are using one of the Boomerang SPA plugins, the browser might hit the
+150 limit quickly, as the browser will not clear the resources for SPA
+navigtations. Therefore, you may want to increase the buffer size or clear the
+resources every time a beacon is sent.
+
+The following code examples show how you can increase the limit, or clear the
+resources after each Boomerang beacon.
+
+### Set the Resource Timings Buffer
+
+To increase the ResourceTiming buffer size above the default of 150, you can use
+[`window.performance.setResourceTimingBufferSize(n)`](http://www.w3.org/TR/resource-timing/):
+
+```javascript
+(function(w){
+ if (!w ||
+ !("performance" in w) ||
+ !w.performance ||
+ !w.performance.setResourceTimingBufferSize) {
+ return;
+ }
+
+ w.performance.setResourceTimingBufferSize();
+})(window);
+```
+
+### Clear the Resource Timings Buffer
+
+To clear the ResourceTimings buffer on each beacon, you can use
+[`window.performance.clearResourceTimings()`](http://www.w3.org/TR/resource-timing/):
+
+```javascript
+(function(w){
+ if (!w ||
+ !("performance" in w) ||
+ !w.performance ||
+ !w.performance.clearResourceTimings) {
+ return;
+ }
+
+ document.addEventListener("onBoomerangBeacon", w.performance.clearResourceTimings.bind(w.performance));
+})(window);
+```
diff --git a/doc/howtos/howtos.js b/doc/howtos/howtos.js
deleted file mode 100644
index c6144cc81..000000000
--- a/doc/howtos/howtos.js
+++ /dev/null
@@ -1,55 +0,0 @@
-// Since we don't set a beacon_url, we'll just subscribe to the before_beacon function
-// and print the results into the browser itself.
-BOOMR.subscribe("before_beacon", function(o) {
- var html = "", t_other, others = [];
-
- if (!o.t_other) {
- o.t_other = "";
- }
-
- for (var k in o) {
- if (!k.match(/^(t_done|t_other|bw|lat|bw_err|lat_err|u|r2?)$/)) {
- if (k.match(/^t_/)) {
- o.t_other += "," + k + "|" + o[k];
- }
- else {
- others.push(k + " = " + o[k]);
- }
- }
- }
-
- if (o.t_done) {
- html += "This page took " + o.t_done + " ms to load ";
- }
-
- if (o.t_other) {
- t_other = o.t_other.replace(/^,/, "").replace(/\|/g, " = ").split(",");
- html += "Other timers measured: ";
- for (var i = 0; i < t_other.length; i++) {
- html += " " + t_other[i] + " ms ";
- }
- }
- if (o.bw) {
- html += "Your bandwidth to this server is " + parseInt(o.bw * 8 / 1024) + "kbps (±" + parseInt(o.bw_err * 100 / o.bw) + "%) ";
- }
-
- if (o.lat) {
- html += "Your latency to this server is " + parseInt(o.lat) + "±" + o.lat_err + "ms ";
- }
-
- var r = document.getElementById("results");
- r.innerHTML = html;
-
- if (others.length) {
- r.innerHTML += "Other parameters: ";
-
- for (i = 0; i < others.length; i++) {
- var t = document.createTextNode(others[i]);
- r.innerHTML += " ";
- r.appendChild(t);
- r.innerHTML += " ";
-
- }
- }
-
-});
diff --git a/doc/ja/index.html b/doc/ja/index.html
deleted file mode 100644
index 9b74e370d..000000000
--- a/doc/ja/index.html
+++ /dev/null
@@ -1,74 +0,0 @@
-
-
-
-
-this, is boomerang
-
-
-
-
-
-
-
-English
-
-
-
-
-
-
diff --git a/doc/ja/ja_about/ja_TODO.txt b/doc/ja/ja_about/ja_TODO.txt
deleted file mode 100644
index a4aa2a456..000000000
--- a/doc/ja/ja_about/ja_TODO.txt
+++ /dev/null
@@ -1,20 +0,0 @@
-1. Add random sampling
- This needs to be a little intelligent because plugins may have different
- criteria for sampling. For example, the RT plugin requires two pages --
- one for tstart and one for tend. Since tstart is a relatively inexpensive
- operation, it makes sense for us to set tstart on all pages, but only set
- tend based on the random sample.
-
-2. Measure time from page start to page_load
- Since we may not always have control over the exact moment the user
- initiated a request for our page, the next best thing would be to measure
- the time from the first byte to reach the user to the time the page loaded.
-
- Note that comparing with server time is not a good idea since the user's
- system clock may not actually be correct.
-
- See use-case #1c & #1d.
-
-3. Rewrite bandwidth testing code to be pretty and clean
-
-4. Create a yui-gallery module
diff --git a/doc/ja/ja_about/ja_community.html b/doc/ja/ja_about/ja_community.html
deleted file mode 100644
index 9e29d02b1..000000000
--- a/doc/ja/ja_about/ja_community.html
+++ /dev/null
@@ -1,30 +0,0 @@
-
-
-
-
-boomerang コミュニティ
-
-
-
-ドキュメント一覧
-
-私たちはユーザーがリソースにリクエストを始めてから、そのリソースがユーザーにとって完全に有効になるまでの時間を往復(round trip)時間として定義します。私たちが測定できるものは HTML ページのリソースでなおかつ、私たちがコントロールできるページ上からのクリックまたは画面遷移によってリクエストされたリソースに限ります。
-
-DNS の遅延は DNS のリクエストを行うためにどれくらい時間がかかるかを教えてくれます。これは Web サーバーや DNS の設定、ユーザーの ISP の DNS サーバーがどのような構成になっているか、など様々な要因によって影響を受ける可能性があります。この遅延の測定で私たちは一つのページ上で安全にルックアップした多くのドメインの目安を得られます。これらは私たちがルックアップできる DNS の数と複数のドメインが私たちにもたらす並列性を交換することになります。HTTP の遅延と DNS の遅延は一緒にユーザーのためにこの点がどこにあるかを私たちに教えてくれます。
-
-IPv6 プラグインは IPv6 に関する様々なメトリクスを測定します。IPv6 API は BOOMR.plugins.IPv6 に内包されます。このプラグインはいくつかの事を試そうとします:
-
-
-
クライアントが IPv6 アドレスに接続できるかどうかチェック
-
クライアントが IPv6 アドレスを指した DNS を解決できるかどうかチェック
-
IPv6 アドレスに接続するレイテンシーをチェック
-
IPv6 アドレスに DNS をルックアップした時のレイテンシーの平均値をチェック(最低値ではなく)
-
-
-このプラグインは IPv6 アドレスを持ったサーバーと DNS がそのサーバーを指している必要があります。さらに、サーバーは IPv6 アドレスからのリクエストを処理できるように設定されている必要があり、バーチャルホスト名は必要ありません。これはおそらく、同じ IP アドレス上に複数のホストを共有してホスティングできないということになります。
-
-
-
-
-
diff --git a/doc/ja/ja_howtos/dynamic-content.txt b/doc/ja/ja_howtos/dynamic-content.txt
deleted file mode 100644
index 66dc8ee7e..000000000
--- a/doc/ja/ja_howtos/dynamic-content.txt
+++ /dev/null
@@ -1,21 +0,0 @@
-This content is loaded via XHR and put into a div.
-
-Lorem ipsum dolor sit amet, consectetur adipiscing elit. Curabitur nec tortor eros, et iaculis erat. Nullam at justo eu leo tincidunt pulvinar at eu nisi. Ut eu lacus lacus, a auctor est. Praesent eu justo arcu, nec lobortis erat. Sed convallis sapien dictum nisl molestie sit amet dapibus velit vestibulum. Ut sed massa orci, eu congue felis. Mauris pharetra dignissim mollis. Sed vestibulum lacus et orci fermentum bibendum. Aliquam condimentum, mauris eu placerat commodo, massa diam bibendum lacus, vel fringilla nulla turpis id mi. In egestas consequat cursus. Phasellus non purus vitae lectus blandit dignissim ut eget lectus. Phasellus ligula risus, sodales in lobortis in, interdum vitae nunc. Duis at diam tortor, non ultrices ipsum. Aenean iaculis dolor ut tortor tempor eget mattis nibh lobortis.
-
-Morbi eu risus non tellus condimentum hendrerit. Suspendisse nec nulla leo. Praesent non leo in libero aliquet aliquam eu nec nulla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Donec diam odio, faucibus id viverra sed, egestas eu quam. Maecenas lorem nisi, pretium sit amet sollicitudin in, imperdiet id elit. Aenean convallis, odio consectetur pulvinar vehicula, ipsum nunc dapibus sapien, non congue nisl elit eu lacus. Phasellus ut elit nisl, eu auctor libero. Suspendisse potenti. Maecenas nunc quam, commodo nec vehicula in, elementum volutpat tellus. In hac habitasse platea dictumst. Vivamus porttitor turpis mauris, semper sagittis elit. Mauris vitae vehicula lacus. Aliquam sed velit at sem placerat scelerisque.
-
-Pellentesque eget ante eu libero dignissim commodo ornare porttitor mi. Curabitur a nibh ut sapien eleifend consectetur sit amet quis mi. Sed lacinia neque elit. Quisque sit amet sem magna, sagittis adipiscing arcu. Etiam auctor cursus lectus quis tincidunt. Aliquam scelerisque, est porta varius fermentum, nisi mauris tempor eros, tincidunt dapibus purus odio at velit. Duis eu metus nibh, sit amet faucibus ipsum. Vestibulum nunc dolor, consequat vitae lobortis ac, pulvinar quis metus. In sollicitudin luctus vehicula. Quisque velit lacus, scelerisque nec lobortis vel, varius sed leo. Proin a felis eget felis tempus vestibulum. Nullam eu augue non velit placerat venenatis. Phasellus at mauris quis massa varius sodales. Vestibulum id arcu tortor. Ut egestas ipsum nec dolor scelerisque ullamcorper. Morbi hendrerit ligula orci, id faucibus dui. Phasellus et libero a turpis tincidunt molestie. Mauris sed erat eu nisl dignissim consectetur id sed erat. In lacus tellus, viverra ut tincidunt at, pretium id eros. Curabitur tortor urna, consectetur eu accumsan at, cursus eu libero.
-
-Praesent eu neque tortor, quis dapibus diam. Curabitur a mauris in mi dapibus suscipit. Maecenas porta interdum viverra. Integer accumsan fringilla laoreet. Integer hendrerit auctor eros, laoreet aliquet magna malesuada sit amet. Mauris tincidunt mattis varius. Suspendisse sit amet sapien eu nibh feugiat volutpat. Mauris consequat, metus sed pulvinar iaculis, massa lectus faucibus elit, vel viverra metus orci a elit. Sed vitae velit non est luctus molestie. Curabitur sollicitudin, dui ut fermentum scelerisque, magna sapien vulputate odio, eu blandit diam turpis et diam. Sed viverra neque vitae justo gravida non posuere felis adipiscing. Duis metus eros, pulvinar et adipiscing ut, egestas ac orci. Donec porttitor, ligula vitae porttitor luctus, augue tortor tincidunt arcu, consequat adipiscing ligula lectus sed risus. Suspendisse potenti. Fusce vitae consequat lacus. Quisque id facilisis sem. Aenean dignissim, turpis ut adipiscing hendrerit, nisi lorem sagittis arcu, egestas tempor ante tellus suscipit dui. Etiam mattis mauris in ipsum faucibus convallis. Aenean ut turpis nisl. In elit risus, pretium vitae hendrerit non, porta eget quam.
-
-Duis nec metus in metus facilisis dictum eget sed elit. Ut sem est, consequat a molestie ut, lobortis vel tortor. Fusce a erat neque, nec molestie lacus. Donec luctus mi nec ante iaculis vulputate. Quisque pellentesque commodo lectus, convallis aliquet nisi dignissim nec. Cras in est vitae arcu mollis egestas vitae id purus. Cras non purus quis orci consequat sodales ut nec justo. Donec suscipit convallis velit vel scelerisque. Fusce vitae neque nulla. Proin scelerisque ullamcorper libero, eget ultricies neque lacinia vel. Donec mattis condimentum vulputate. Vestibulum vel nisi est, in egestas libero. Suspendisse condimentum purus ac diam tempus malesuada. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae;
-
-Aliquam erat volutpat. Sed rhoncus elit id diam sagittis ut viverra leo blandit. Nulla hendrerit purus a orci dapibus pretium pulvinar justo ornare. Vivamus nulla elit, tincidunt non bibendum sed, semper ut ipsum. Nulla ut lectus ut nibh elementum bibendum quis pellentesque odio. Sed quis ante urna. Nunc ut tincidunt nulla. Etiam semper scelerisque aliquam. Nulla pharetra odio sit amet turpis blandit blandit. Suspendisse sagittis aliquet fermentum. Integer nibh felis, dictum eget mollis a, sagittis tempus quam. Quisque luctus euismod nunc. Sed quis velit felis, vitae sodales lorem. Vestibulum mauris odio, accumsan sit amet pellentesque eget, molestie eget magna.
-
-Phasellus eu neque eu lectus vestibulum porta. Donec tortor purus, faucibus et elementum vel, faucibus quis mauris. Donec eu ligula mi, vel fringilla est. Pellentesque consectetur ullamcorper accumsan. Nulla eget nibh leo, nec congue nunc. Fusce varius sodales hendrerit. Nulla ultricies dapibus luctus. Cras in libero nisi. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Vestibulum tristique diam id nunc luctus interdum. Integer commodo libero sit amet turpis varius faucibus lobortis lectus aliquet. Proin consequat velit vel lacus posuere sit amet sollicitudin justo molestie. Praesent risus nisi, molestie eget pharetra consectetur, congue id libero. In ipsum velit, fermentum ac placerat dignissim, auctor vel leo. Donec a pellentesque risus.
-
-Quisque dolor dui, tempus eu euismod at, condimentum eget felis. Pellentesque dolor magna, ultricies in vestibulum at, elementum quis nunc. Vestibulum sagittis vehicula purus cursus vehicula. Cras nibh erat, sollicitudin in lacinia in, aliquam non libero. Integer at erat tellus. Praesent pharetra, sem commodo ornare interdum, leo magna elementum lectus, at feugiat mauris lacus nec turpis. Phasellus posuere convallis rutrum. Duis vel orci sed urna pharetra dapibus. Duis hendrerit arcu non enim accumsan nec molestie turpis tincidunt. Donec ac ipsum magna. Aliquam congue ante congue magna ultricies a molestie orci dignissim. Etiam lacus tortor, malesuada non mattis sit amet, molestie sit amet augue.
-
-Quisque volutpat scelerisque ipsum, ac vulputate lorem dapibus quis. Donec et diam nulla. Nunc pellentesque viverra egestas. Etiam eget neque magna, a venenatis ante. Nulla facilisi. Ut quis nisl vel felis vestibulum consectetur sed id dolor. Sed ac metus eu turpis elementum porttitor. Proin euismod accumsan dui, at aliquet dui aliquam vitae. Proin vitae euismod erat. Etiam tincidunt est at libero mollis egestas. Nunc vitae lectus quis velit ullamcorper gravida vel in purus. Fusce feugiat odio vel lorem viverra lacinia. Curabitur adipiscing condimentum arcu sit amet tempor. Proin eget leo arcu, ac dictum justo. Curabitur viverra ultrices lorem, nec aliquet odio dictum vitae. Nam at volutpat velit. Sed sed rutrum magna. Cras ut felis a nunc euismod laoreet.
-
-Cras euismod rhoncus erat, et placerat sem pulvinar at. Sed tempus libero nec arcu luctus hendrerit. In tincidunt vehicula pretium. Mauris nec orci a magna aliquet faucibus sed quis ligula. Nulla id lacus id eros iaculis mollis vel non risus. Nam eget libero quam. Nullam sed magna purus, eu pretium sapien. Curabitur ullamcorper, tellus vitae sodales accumsan, arcu erat egestas augue, et adipiscing turpis diam ullamcorper nunc. Suspendisse dignissim scelerisque porttitor. Integer quis congue mauris. Mauris dignissim viverra tincidunt. Duis viverra malesuada nisl. Maecenas libero orci, porta eget mattis sit amet, mollis ut sapien. Aenean vulputate felis vitae sem aliquam vulputate eu sed est. Nunc sed enim nisi, ac cursus massa. Praesent quam ligula, tristique ut tincidunt dapibus, facilisis pulvinar turpis. Mauris ut eros tortor, at semper nulla. Integer facilisis scelerisque mauris, eu congue purus placerat in. Aenean augue augue, lacinia vitae dapibus non, posuere vitae neque.
diff --git a/doc/ja/ja_howtos/ja_howto-0.html b/doc/ja/ja_howtos/ja_howto-0.html
deleted file mode 100644
index e0b6975f1..000000000
--- a/doc/ja/ja_howtos/ja_howto-0.html
+++ /dev/null
@@ -1,191 +0,0 @@
-
-
-
-Boomerang 使用方法 #0: ビーコンや before_beacon イベントハンドラーからデータを取得する
-
-
-
-
-ドキュメント一覧 | 使用方法一覧
-
-BOOMR.subscribe('before_beacon', function(o) {
- var html = "";
- if(o.t_done) { html += "This page took " + o.t_done + "ms to load<br>"; }
- if(o.bw) { html += "Your bandwidth to this server is " + parseInt(o.bw/1024) + "kbps (±" + parseInt(o.bw_err*100/o.bw) + "%)<br>"; }
- if(o.lat) { html += "Your latency to this server is " + parseInt(o.lat) + "±" + o.lat_err + "ms<br>"; }
-
- document.getElementById('results').innerHTML = html;
-});
-
-function extract_boomerang_data(params)
-{
- var bw_buckets = [64, 256, 1024, 8192, 30720],
- bw_bucket = bw_buckets.length,
- i, url, page_id, ip, ua, woeid;
-
-
- // First validate your beacon, make sure all datatypes
- // are correct and values within reasonable range
- // We'll also want to detect fake beacons, but that's more complex
- if(! validate_beacon(params)) {
- return false;
- }
-
- // You may also want to do some kind of random sampling at this point
-
- // Figure out a bandwidth bucket.
- // we could get more complex and consider bw_err as well,
- // but for this example I'll ignore it
- for(i=0; i<bw_buckets.length; i++) {
- if(params.bw <= bw_buckets[i]) {
- bw_bucket = i;
- break;
- }
- }
-
- // Now figure out a page id from the u parameter
- // Since we might have a very large number of URLs that all
- // map onto a very small number (possibly 1) of distinct page types
- // It's good to create page groups to simplify performance analysis.
-
- url = canonicalize_url(params.u); // get a canonical form for the URL
- page_id = get_page_id(url); // get a page id. (many->1 map?)
-
-
- // At this point we can extract other information from the request
- // eg, the user's IP address (good for geo location) and user agent
- ip = get_user_ip(); // get user's IP from request
- woeid = ip_to_woeid(ip); // convert IP to a Where on earth ID
- ua = get_normalized_uastring(); // get a normalized useragent string
-
- // Now insert the data into our database
- insert_data(page_id, params.t_done, params.bw, params.bw_err, bw_bucket, params.lat, params.lat_err, ip, woeid, ua);
-
- return true;
-}
-
-YUI().use("io-base", function(Y) {
- var uri = "dynamic-content.txt";
-
- function complete(id, o) {
- var html = o.responseText;
- document.getElementById("dynamic-content").innerHTML = html;
- BOOMR.plugins.RT.done(); // Tell boomerang to measure time and fire a beacon
- };
-
- Y.on('io:complete', complete);
-
- BOOMR.plugins.RT.startTimer("t_done"); // Start measuring download time
- var request = Y.io(uri);
-});
-
-BOOMR.subscribe('before_beacon', function(o) {
- var p_names = [], k;
-
- if( "t_done" in o ) {
- return;
- }
-
- // t_done is not set, so don't beacon
- for(k in o) {
- if(o.hasOwnProperty(k)) {
- p_names.push(k);
- }
- }
-
- // removeVar accepts either a list or an array
- BOOMR.removeVar(p_names);
-});
-
-
-
-
-
-
-
-
-
diff --git a/doc/methodology.md b/doc/methodology.md
index 67373ac1a..b79182224 100644
--- a/doc/methodology.md
+++ b/doc/methodology.md
@@ -1,88 +1,62 @@
-## So how does this thing work?
-
-### 1. Roundtrip measurements
-
-We define round trip time as the time taken from the user initiating a resource request
-to when that resource is completely available for the user to interact with. We limit
-our measurements only to HTML page type resources.
-
-The round trip time is therefore the time from the user clicking on a link to the page
-referenced by that link becoming usable by the user. For most cases, this is as good
-as measuring the time from the previous page's onbeforeunload event firing to the current
-page's onload event firing. In some cases this may be different, but we let the
-developer determine those events.
-
-This is how we measure:
-
- - attach a function to the `window.onbeforeunload` event.
- - Inside this function, we take a time reading (in milliseconds) and store it into a
- session cookie along with the URL of the current page.
- - attach a function to the `window.onload` event.
- - Inside this function, we take a time reading (in milliseconds). If the browser has implemented the
- [WebTiming](http://dev.w3.org/2006/webapi/WebTiming/) API, we
- pull out `navigationStart` (or `fetchStart` if `navigationStart`
- is unset). To get around a bug in Firefox 7 and 8, we use `unloadEventStart` instead.
- - If the WebTiming API is not supported, we look for the cookie where we set the start time, and if found,
- use that. If we find neither, we abort [1].
- - If we find a cookie, we check the URL stored in the cookie with the `document.referrer`
- of the current document. If these two differ, it means that the user possibly
- visited a third party page in between the two pages from our site and the measurement
- is invalid, so we abort [2].
- - If we're still going, we pull the time out of the cookie and remove the cookie. We
- measure the difference in the two times and this is the round trip time for the page.
-
-### 2. Bandwidth & Latency measurements
-
-Bandwidth and latency are measured by downloading fixed size images from a server
-and measuring the time it took to download them. We run it in the following order:
-
- - First download a 32 byte gif 10 times serially. This is used to measure latency
-
- - We discard the first measurement because that pays the price for the TCP handshake
-(3 packets) and TCP slow-start (4 more packets). All other image requests take
-two TCP packets (one for the request and one for the response). This gives us a
-good idea of how much time it takes to make an HTTP request from the browser to
-our server.
-
- - Once done, we calculate the arithmetic mean, standard deviation and standard error
-at 95% confidence for the 9 download times that we have. This is the latency number
-that we beacon back to our server.
-
- - Next download images of increasing size until one of the times out
-
-We choose image sizes so that we can narrow down on a bandwidth range as soon as
-possible. See the code comments in boomerang.js for
-full details.
-
-- Image timeouts are set at between 1.2 and 1.5 seconds. If an image times out, we
-stop downloading larger images, and retry the largest image 4 more times[3].
-We then calculate the bandwidth for the largest 3 images that we downloaded. This
-should result in 7 readings unless the test timed out before that [4].
-We calculate the median, standard deviation and standard error from these values
-and this is the bandwidth that we beacon back to our server.
-
-
-#### Footnotes:
-
-
-We don't actually abort at this point, but give the developer the ability to
-salvage the moment by setting his/her own start time. This is most useful when the
-developer isn't measuring full page load time, but possibly the load time of some
-dynamic content loaded via JavaScript.
-
-
-We offer the developer the ability to not abort at this point, but instead
-pass all URLs to the back end and let the server decide whether to discard the
-beacon or not. This is useful for sites that have a login page behind SSL and
-possibly redirect to the login page if the user clicks on certain links. In this
-case there might either be no referrer, or the referrer may not match.
-
-
-This value is configured using the BW.nruns parameter. See
-Howto #6 for details on configuring boomerang.
-
-
-The bandwidth test times out after 15 seconds. At that point it will try to
-determine the bandwidth based on data that it has already collected.
-