diff --git a/boomerang.js b/boomerang.js index a4cb5b17c..7f0afdcec 100644 --- a/boomerang.js +++ b/boomerang.js @@ -113,6 +113,10 @@ BOOMR.window = w; impl = { // properties 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 2000 chars + // otherwise it will fall back to a POST request. + beacon_type: "AUTO", // 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. @@ -358,12 +362,69 @@ boomr = { } else { el.detachEvent('on' + type, fn); } + }, + + pushVars: function (arr, vars, prefix) { + var k, i, n=0; + + for(k in vars) { + if(vars.hasOwnProperty(k)) { + if(Object.prototype.toString.call(vars[k]) === "[object Array]") { + for(i = 0; i < vars[k].length; ++i) { + n += BOOMR.utils.pushVars(arr, vars[k][i], k + "[" + i + "]"); + } + } else { + ++n; + arr.push( + encodeURIComponent(prefix ? (prefix + "[" + k + "]") : k) + + "=" + + (vars[k]===undefined || vars[k]===null ? '' : encodeURIComponent(vars[k])) + ); + } + } + } + + return n; + }, + + postData: function (urlenc) { + var iframe = document.createElement("iframe"), + form = document.createElement("form"), + input = document.createElement("input"); + + iframe.name = "boomerang_post"; + iframe.style.display = form.style.display = "none"; + + form.method = "POST"; + form.action = impl.beacon_url; + form.target = iframe.name; + + input.name = "data"; + + if (window.JSON) { + form.enctype = "text/plain"; + input.value = JSON.stringify(impl.vars); + } else { + form.enctype = "application/x-www-form-urlencoded"; + input.value = urlenc; + } + + document.body.appendChild(iframe); + form.appendChild(input); + document.body.appendChild(form); + + BOOMR.utils.addListener(iframe, "load", function() { + document.body.removeChild(form); + document.body.removeChild(iframe); + }); + + form.submit(); } }, init: function(config) { var i, k, - properties = ["beacon_url", "site_domain", "user_ip", "strip_query_string"]; + properties = ["beacon_url", "beacon_type", "site_domain", "user_ip", "strip_query_string"]; BOOMR_check_doc_domain(); @@ -605,7 +666,7 @@ boomr = { }, sendBeacon: function() { - var k, url, img, nparams=0; + var k, data, url, img, nparams; BOOMR.debug("Checking if we can send beacon"); @@ -642,33 +703,32 @@ boomr = { return this; } - // if there are already url parameters in the beacon url, - // change the first parameter prefix for the boomerang url parameters to & + data = []; + nparams = BOOMR.utils.pushVars(data, impl.vars); - url = []; - - for(k in impl.vars) { - if(impl.vars.hasOwnProperty(k)) { - nparams++; - url.push(encodeURIComponent(k) - + "=" - + ( - impl.vars[k]===undefined || impl.vars[k]===null - ? '' - : encodeURIComponent(impl.vars[k]) - ) - ); - } + if(!nparams) { + // do not make the request if there is no data + return this; } - url = impl.beacon_url + ((impl.beacon_url.indexOf('?') > -1)?'&':'?') + url.join('&'); + data = data.join('&'); - BOOMR.debug("Sending url: " + url.replace(/&/g, "\n\t")); + if(impl.beacon_type === 'POST') { + BOOMR.utils.postData(data); + } else { + // if there are already url parameters in the beacon url, + // change the first parameter prefix for the boomerang url parameters to & + url = impl.beacon_url + ((impl.beacon_url.indexOf('?') > -1)?'&':'?') + data; - // only send beacon if we actually have something to beacon back - if(nparams) { - img = new Image(); - img.src=url; + // 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 + if(url.length > 2000 && impl.beacon_type === "AUTO") { + BOOMR.utils.postData(data); + } else { + BOOMR.debug("Sending url: " + url.replace(/&/g, "\n\t")); + img = new Image(); + img.src=url; + } } return this; diff --git a/doc/api/BOOMR.html b/doc/api/BOOMR.html index f35b16fc3..9f2634a68 100644 --- a/doc/api/BOOMR.html +++ b/doc/api/BOOMR.html @@ -73,6 +73,20 @@

Configuration

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. In modern browsers, POST requests +will encode the data as JSON, with a Content-Type of text/plain. +In browsers where JSON.stringify is not available, the data will be +application/x-www-form-urlencoded. To ensure reliable POST behaviour in old browsers, a +hidden form submission +is used, meaning that the request body will look like data={"foo":"bar","baz":"qux"} +or data=foo%3Dbar%26baz%3Dqux, depending on the content type. +
+
site_domain
[recommended] diff --git a/doc/api/index.html b/doc/api/index.html index bce84f6b7..5011b8ac5 100644 --- a/doc/api/index.html +++ b/doc/api/index.html @@ -27,6 +27,9 @@

plugins

  • BOOMR.plugins.NavigationTiming — A plugin that collects performance data from user agents that implement the W3C Navigation Timing specification.
  • +
  • BOOMR.plugins.ResourceTiming — A plugin that collects performance data from user +agents that implement the W3C Resource Timing specification.
  • +
  • BOOMR.plugins.CACHE_RELOAD — The Cache Reload plugin that forces a browser to update its cached version of boomerang.
  • diff --git a/doc/api/restiming.html b/doc/api/restiming.html new file mode 100644 index 000000000..68589538a --- /dev/null +++ b/doc/api/restiming.html @@ -0,0 +1,102 @@ + + + +Resource Timing plugin API + + + + +All Docs | Index +

    Resource Timing plugin API

    +

    +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 array named restiming to the beacon data, +items in the array representing resources on the page in the order that they were loaded. +Each item contains properties that correspond to attributes from the +PerformanceResourceTiming interface. +Note that some of the metrics are restricted and will not be provided cross-origin +unless the Timing-Allow-Origin header permits. +

    + + + + + + + + + + + + + + + + + + + +
    restiming array item propertyResource Timing attribute
    rt_namename
    rt_ststartTime
    rt_durduration
    rt_red_stredirectStart
    rt_red_endredirectEnd
    rt_fet_stfetchStart
    rt_dns_stdomainLookupStart
    rt_dns_enddomainLookupEnd
    rt_con_stconnectStart
    rt_con_endconnectEnd
    rt_scon_stsecureConnectionStart
    rt_req_strequestStart
    rt_res_stresponseStart
    rt_res_endresponseEnd
    + + + + + diff --git a/plugins/restiming.js b/plugins/restiming.js new file mode 100644 index 000000000..135c59702 --- /dev/null +++ b/plugins/restiming.js @@ -0,0 +1,75 @@ +/** +\file restiming.js +Plugin to collect metrics from the W3C Resource Timing API. +For more information about Resource Timing, +see: http://www.w3.org/TR/resource-timing/ +*/ + +(function() { + +BOOMR = BOOMR || {}; +BOOMR.plugins = BOOMR.plugins || {}; + +var restricted = { + redirectStart: "rt_red_st", + redirectEnd: "rt_red_end", + domainLookupStart: "rt_dns_st", + domainLookupEnd: "rt_dns_end", + connectStart: "rt_con_st", + connectEnd: "rt_con_end", + secureConnectionStart: "rt_scon_st", + requestStart: "rt_req_st", + responseStart: "rt_res_st" +}, + +impl = { + complete: false, + done: function() { + var p = BOOMR.window.performance, r, data, i, k; + if(impl.complete) { + return; + } + BOOMR.removeVar("restiming"); + if(p && typeof p.getEntriesByType === "function") { + r = p.getEntriesByType("resource"); + if(r) { + BOOMR.info("Client supports Resource Timing API", "restiming"); + data = { + restiming: new Array(r.length) + }; + for(i = 0; i < r.length; ++i) { + data.restiming[i] = { + rt_name: r[i].name, + // reinstate this if entryType is ever something other than "resource" + //rt_type: r[i].entryType, + rt_st: r[i].startTime, + rt_dur: r[i].duration, + rt_fet_st: r[i].fetchStart, + rt_res_end: r[i].responseEnd + }; + for(k in restricted) { + if(restricted.hasOwnProperty(k) && r[i][k] > 0) { + data.restiming[i][restricted[k]] = r[i][k]; + } + } + } + BOOMR.addVar(data); + } + } + this.complete = true; + BOOMR.sendBeacon(); + } +}; + +BOOMR.plugins.ResourceTiming = { + init: function() { + BOOMR.subscribe("page_ready", impl.done, null, impl); + return this; + }, + is_complete: function() { + return impl.complete; + } +}; + +}()); +