BOOMR.plugins. AutoXHR


Instrument and measure XMLHttpRequest (AJAX) requests.

With this plugin, sites can measure the performance of XMLHttpRequests (XHRs) and other in-page interactions after the page has been loaded.

This plugin also monitors DOM manipulations following a XHR to filter out "background" XHRs.

For information on how to include this plugin, see the Building tutorial.

What is Measured

When AutoXHR is enabled, after the Page Load has occurred, this plugin will monitor several events:

  • XMLHttpRequest fetches
  • Clicks
  • window.History changes

When any of these events occur, AutoXHR will start monitoring the page for other events, DOM manipulations and other networking activity.

As long as the event isn't determined to be background activity (i.e an XHR that didn't change the DOM at all), the event will be measured until all networking activity has completed.

This means if your click generated an XHR that triggered an updated view to fetch more HTML that added images to the page, the entire event will be measured from the click to the last image completing.

Usage

To enable AutoXHR, you should set instrument_xhr to true:

BOOMR.init({
  instrument_xhr: true
});

Once enabled and initialized, the window.XMLHttpRequest object will be replaced with a "proxy" object that instruments all XHRs.

Monitoring XHRs

After AutoXHR is enabled, any XMLHttpRequest.send will be monitored:

xhr = new XMLHttpRequest();
xhr.open("GET", "/api/foo");
xhr.send(null);

If this XHR triggers DOM changes, a beacon will eventually be sent.

This beacon will have http.initiator=xhr and the beacon parameters will differ from a Page Load beacon. See BOOMR.plugins.RT and BOOMR.plugins.NavigationTiming for details.

Combining XHR Beacons

By default AutoXHR groups all XHR activity that happens in the same event together.

If you have one XHR that immediately triggers a second XHR, you will get a single XHR beacon. The u (URL) will be of the first XHR.

If you don't want this behavior, and want to measure every XHR on the page, you can enable alwaysSendXhr=true. When set, every distinct XHR will get its own XHR beacon.

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 and XMLHttpRequest.

We will not use MutationObserver in IE 11 due to several browser bugs. See BOOMR.utils.isMutationObserverSupported for details.

Excluding Certain Requests From Instrumentation

Whenever Boomerang intercepts an XMLHttpRequest, it will check if that request matches anything in the XHR exclude list. If it does, Boomerang will not instrument, time, send a beacon for that request, or include it in the BOOMR.plugins.SPA calculations.

The XHR exclude list is defined by creating an BOOMR.xhr_excludes map, and adding URL parts that you would like to exclude from instrumentation. You can put any of the following in BOOMR.xhr_excludes:

  1. A full HREF
  2. A hostname
  3. A path

Example:

BOOMR = window.BOOMR || {};

BOOMR.xhr_excludes = {
  "www.mydomain.com":  true,
  "a.anotherdomain.net": true,
  "/api/v1/foobar":  true,
  "https://mydomain.com/dashboard/": true
};

Beacon Parameters

This plugin doesn't add any specific parameters to the beacon. However, XHR beacons have different parameters in general than Page Load beacons.

  • Many of the timestamps will differ, see BOOMR.plugins.RT
  • All of the nt_* parameters are ResourceTiming, see BOOMR.plugins.NavigationTiming
  • u: the URL of the resource that was fetched
  • pgu: The URL of the page the resource was fetched on
  • http.initiator: xhr

Algorithm

Here's how the general AutoXHR algorithm works:

  • 0.0 History changed

    • Pass new URL and timestamp of change on to most recent event (which might not have happened yet)
  • 0.1 History changes as a result of a pushState or replaceState

    • In this case we get the new URL when the developer calls pushState or replaceState
    • we do not know if they plan to make an XHR call or use a dynamic script node, or do nothing interesting (eg: just make a div visible/invisible)
    • we also do not know if they will do this before or after they've called pushState/replaceState
    • so our best bet is to check if either an XHR event or an interesting Mutation event happened in the last 50ms, and if not, then hold on to this state for 50ms to see if an interesting event will happen.
  • 0.2 History changes as a result of the user hitting Back/Forward and we get a window.popstate event

    • In this case we get the new URL from location.href when our event listener runs
    • we do not know if this event change will result in some interesting network activity or not
    • we do not know if the developer's event listener has already run before ours or if it will run in the future or even if they do have an event listener
    • so our best bet is the same as 0.1 above
  • 1 Click initiated

    • User clicks on something
    • We create a resource with the start time and no URL
    • We turn on DOM observer, and wait up to 50 milliseconds for something
      • If nothing happens after the timeout, we stop watching and clear the resource without firing the event
      • If a history event happened recently/will happen shortly, use the URL as the resource.url
      • Else if something uninteresting happens, we set the timeout for 1 second if it wasn't already started
        • We don't want to continuously extend the timeout with each uninteresting event
      • Else if an interesting node is added, we add load and error listeners and turn off the timeout but keep watching
        • If we do not have a resource.url, and if this is a script, then we use the script's URL
        • Once all listeners have fired, we stop watching, fire the event and clear the resource
  • 2 XHR initiated

    • XHR request is sent
    • We create a resource with the start time and the request URL
    • If a history event happened recently/will happen shortly, use the URL as the resource.url
    • We watch for all changes in state (for async requests) and for load (for all requests)
    • On load, we turn on DOM observer, and wait up to 50 milliseconds for something
      • If something uninteresting happens, we set the timeout for 1 second if it wasn't already started
        • We don't want to continuously extend the timeout with each uninteresting event
      • Else if an interesting node is added, we add load and error listeners and turn off the timeout
        • Once all listeners have fired, we stop watching, fire the event and clear the resource
      • If nothing happens after the timeout, we stop watching fire the event and clear the resource

What about overlap?

  • 3.1 XHR initiated while click watcher is on

    • If first click watcher has not detected anything interesting or does not have a URL, abort it
    • If the click watcher has detected something interesting and has a URL, then
      • Proceed with 2 above.
      • concurrently, click stops watching for new resources
        • once all resources click is waiting for have completed, fire the event and clear click resource
  • 3.2 click initiated while XHR watcher is on

    • Ignore click
  • 3.3 click initiated while click watcher is on

    • If first click watcher has not detected anything interesting or does not have a URL, abort it
    • Else proceed with parallel resource steps from 3.1 above
  • 3.4 XHR initiated while XHR watcher is on

    • Allow anything interesting detected by first XHR watcher to complete and fire event
    • Start watching for second XHR and proceed with 2 above.

Methods


addExcludeFilter(cb, ctx [, name])

Add a filter function to the list of functions to run to validate if an XHR should be instrumented.

Parameters:

Name Type Argument Description
cb BOOMR.plugins.AutoXHR.htmlElementCallback

Callback to run to validate filtering of an XHR Request

ctx Object

Context to run {@param cb} in

name string <optional>

Optional name for the filter, called out when running exclude filters for debugging purposes

Example

BOOMR.plugins.AutoXHR.addExcludeFilter(function(anchor) {
  var m = anchor.href.match(/some-page\.html/g);

  // If matching flag to not instrument
  if (m && m.length > 0) {
    return true;
  }
  return false;
}, null, "exampleFilter");

enableAutoXhr()

Enables AutoXHR if not already enabled.


getMutationHandler()

Gets the MutationHandler

Returns:

Type: MutationHandler

Handler


getPathName(anchor)

Tries to resolve href links from relative URLs.

This implementation takes into account a bug in the way IE handles relative paths on anchors and resolves this by assigning a.href to itself which triggers the URL resolution in IE and will fix missing leading slashes if necessary.

Parameters:

Name Type Description
anchor string

The anchor object to resolve

Returns:

Type: string

The unrelativized URL href


init(config)

Initializes the plugin.

Parameters:

Name Type Description
config object

Configuration

Properties
Name Type Argument Description
instrument_xhr boolean <optional>

Whether or not to instrument XHR

AutoXHR.spaBackEndResources Array.<string> <optional>

Default resources to count as Back-End during a SPA nav

AutoXHR.alwaysSendXhr boolean <optional>

Whether or not to send XHR beacons for every XHR.

Returns:

BOOMR.plugins.AutoXHR The AutoXHR plugin for chaining


is_complete()

This plugin is always complete (ready to send a beacon)

Returns:

Type: boolean

true


shouldExcludeXhr(anchor)

Based on the contents of BOOMR.xhr_excludes check if the URL that we instrumented as XHR request matches any of the URLs we are supposed to not send a beacon about.

Parameters:

Name Type Description
anchor HTMLAnchorElement

HTML anchor element with URL of the element checked against BOOMR.xhr_excludes

Returns:

Type: boolean

true if intended to be excluded, false if it is not in the list of excludables

Type Definitions


htmlElementCallback(elem)

A callback with a HTML element.

Parameters:

Name Type Description
elem HTMLAnchorElement

HTML a element