diff --git a/plugins/navtiming.js b/plugins/navtiming.js index 1436fe843..bc15c7e10 100644 --- a/plugins/navtiming.js +++ b/plugins/navtiming.js @@ -18,6 +18,29 @@ see: http://www.w3.org/TR/navigation-timing/ return; } + /** + * Calculates a NavigationTiming timestamp for the beacon, in milliseconds + * since the Unix Epoch. + * + * The offset should be 0 if using a timestamp from performance.timing (which + * are already in milliseconds since Unix Epoch), or the value of navigationStart + * if using getEntriesByType("navigation") (which are DOMHighResTimestamps). + * + * The number is stripped of any decimals. + * + * @param {number} offset navigationStart offset (0 if using NavTiming1) + * @param {number} val DOMHighResTimestamp + * + * @returns {number} Timestamp for beacon + */ + function calcNavTimingTimestamp(offset, val) { + if (typeof val !== "number") { + return undefined; + } + + return Math.floor((offset || 0) + val); + } + // A private object to encapsulate all your implementation details var impl = { complete: false, @@ -118,7 +141,7 @@ see: http://www.w3.org/TR/navigation-timing/ }, done: function() { - var w = BOOMR.window, p, pn, pt, data, cinf; + var w = BOOMR.window, p, pn, chromeTimes, pt, data, cinf, offset = 0; if (this.complete) { return this; @@ -146,13 +169,47 @@ see: http://www.w3.org/TR/navigation-timing/ } p = BOOMR.getPerformance(); + + // This is Chrome only, so will not conflict with nt_first_paint below + if (w.chrome && w.chrome.loadTimes) { + chromeTimes = w.chrome.loadTimes(); + if (chromeTimes) { + cinf = chromeTimes.connectionInfo; + + data = { + nt_spdy: (chromeTimes.wasFetchedViaSpdy ? 1 : 0), + nt_cinf: cinf + }; + + // Chrome firstPaintTime is in seconds.microseconds, so + // we need to multiply it by 1000 to be consistent with + // msFirstPaint and other NavigationTiming timestamps that + // are in milliseconds.microseconds. + if (typeof chromeTimes.firstPaintTime === "number" && chromeTimes.firstPaintTime !== 0) { + data.nt_first_paint = Math.round(chromeTimes.firstPaintTime * 1000); + } + + BOOMR.addVar(data); + + try { + impl.addedVars.push.apply(impl.addedVars, Object.keys(data)); + } + catch (ignore) { + // NOP + } + } + } + if (p) { if (typeof p.getEntriesByType === "function") { pt = p.getEntriesByType("navigation"); if (pt && pt.length) { - BOOMR.info("This user agent supports NavigationTiming.", "nt"); + BOOMR.info("This user agent supports NavigationTiming2", "nt"); pt = pt[0]; + + // ensure DOMHighResTimestamps are added to navigationStart + offset = p.timing ? p.timing.navigationStart : 0; } else { pt = undefined; diff --git a/tests/page-templates/19-navtiming/00-onload.js b/tests/page-templates/19-navtiming/00-onload.js index 872ef6038..af9191746 100644 --- a/tests/page-templates/19-navtiming/00-onload.js +++ b/tests/page-templates/19-navtiming/00-onload.js @@ -3,6 +3,30 @@ describe("e2e/19-navtiming/00-onload", function() { var tf = BOOMR.plugins.TestFramework; + var t = BOOMR_test; + + var NT_PROPERTIES = [ + "nt_nav_st", + "nt_red_st", + "nt_red_end", + "nt_fet_st", + "nt_dns_st", + "nt_dns_end", + "nt_con_st", + "nt_con_end", + "nt_req_st", + "nt_res_st", + "nt_res_end", + "nt_domloading", + "nt_domint", + "nt_domcontloaded_st", + "nt_domcontloaded_end", + "nt_domcomp", + "nt_load_st", + "nt_load_end", + "nt_unload_st", + "nt_unload_end" + ]; it("Should have sent a beacon", function() { // ensure we fired a beacon ('onbeacon') @@ -18,74 +42,87 @@ describe("e2e/19-navtiming/00-onload", function() { assert.isString(tf.lastBeacon().v); }); - it("Should have set nt_* properties", function() { - var p = BOOMR.getPerformance(); - if (!p || !p.timing) { - // NT not supported - return; - } - - assert.isNumber(tf.lastBeacon().nt_nav_st, "nt_nav_st"); - assert.isNumber(tf.lastBeacon().nt_red_st, "nt_red_st"); - assert.isNumber(tf.lastBeacon().nt_red_end, "nt_red_end"); - assert.isNumber(tf.lastBeacon().nt_fet_st, "nt_fet_st"); - assert.isNumber(tf.lastBeacon().nt_dns_st, "nt_dns_st"); - assert.isNumber(tf.lastBeacon().nt_dns_end, "nt_dns_end"); - assert.isNumber(tf.lastBeacon().nt_con_st, "nt_con_st"); - assert.isNumber(tf.lastBeacon().nt_con_end, "nt_con_end"); - assert.isNumber(tf.lastBeacon().nt_req_st, "nt_req_st"); - assert.isNumber(tf.lastBeacon().nt_res_st, "nt_res_st"); - assert.isNumber(tf.lastBeacon().nt_res_end, "nt_res_end"); - assert.isNumber(tf.lastBeacon().nt_domloading, "nt_domloading"); - assert.isNumber(tf.lastBeacon().nt_domint, "nt_domint"); - assert.isNumber(tf.lastBeacon().nt_domcontloaded_st, "nt_domcontloaded_st"); - assert.isNumber(tf.lastBeacon().nt_domcontloaded_end, "nt_domcontloaded_end"); - assert.isNumber(tf.lastBeacon().nt_domcomp, "nt_domcomp"); - assert.isNumber(tf.lastBeacon().nt_load_st, "nt_load_st"); - assert.isNumber(tf.lastBeacon().nt_load_end, "nt_load_end"); - assert.isNumber(tf.lastBeacon().nt_unload_st, "nt_unload_st"); - assert.isNumber(tf.lastBeacon().nt_unload_end, "nt_unload_end"); + it("Should have set nt_* properties (if NavigationTiming is supported)", function() { + if (!t.isNavigationTimingSupported()) { + return this.skip(); + } + + for (var i = 0; i < NT_PROPERTIES.length; i++) { + assert.isNumber(tf.lastBeacon()[NT_PROPERTIES[i]], NT_PROPERTIES[i]); + } if (location.protocol === "https:") { assert.isNumber(tf.lastBeacon().nt_ssl_st, "nt_ssl_st"); } }); - it("Should have set Chrome nt_* properties", function() { + it("Should have set set nt_* properties as Unix-epoch timestamps, not DOMHighResTimestamps (if NavigationTiming is supported)", function() { + if (!t.isNavigationTimingSupported()) { + return this.skip(); + } + + for (var i = 0; i < NT_PROPERTIES.length; i++) { + // Jan 1 2000 + assert.operator(parseInt(tf.lastBeacon()[NT_PROPERTIES[i]], 10), ">=", 946684800, NT_PROPERTIES[i]); + + // Jan 1 2050 + assert.operator(parseInt(tf.lastBeacon()[NT_PROPERTIES[i]], 10), "<=", 2524658358000, NT_PROPERTIES[i]); + + // make sure it's not decimal + assert.notInclude(tf.lastBeacon()[NT_PROPERTIES[i]], "."); + } + }); + + it("Should have set set nt_* properties as full numbers, not decimals (if NavigationTiming is supported)", function() { + if (!t.isNavigationTimingSupported()) { + return this.skip(); + } + + for (var i = 0; i < NT_PROPERTIES.length; i++) { + assert.notInclude(tf.lastBeacon()[NT_PROPERTIES[i]], ".", NT_PROPERTIES[i]); + } + }); + + it("Should have set Chrome nt_* properties (if Chrome)", function() { var pt; if (window.chrome && window.chrome.loadTimes) { pt = window.chrome.loadTimes(); } if (!pt) { // Not supported - return; + return this.skip(); } assert.isNumber(tf.lastBeacon().nt_spdy, "nt_spdy"); - assert.isNotEmpty(tf.lastBeacon().nt_cinf, "nt_cinf"); + assert.isDefined(tf.lastBeacon().nt_cinf, "nt_cinf"); + + // validation of firstPaint assert.isNumber(tf.lastBeacon().nt_first_paint, "nt_first_paint"); + assert.operator(parseInt(tf.lastBeacon().nt_first_paint, 10), ">=", parseInt(tf.lastBeacon().nt_nav_st, 10)); }); - it("Should have set IE's nt_first_paint property", function() { + it("Should have set IE's nt_first_paint property (if IE)", function() { var p = BOOMR.getPerformance(); if (!p || !p.timing || !p.timing.msFirstPaint) { - // NT first paint supported - return; + // NT first paint not supported + return this.skip(); } assert.isNumber(tf.lastBeacon().nt_first_paint, "nt_first_paint"); + assert.operator(parseInt(tf.lastBeacon().nt_first_paint, 10), ">=", parseInt(tf.lastBeacon().nt_nav_st, 10)); }); - it("Should have set NT2 properties", function() { + it("Should have set NT2 properties (if NavigationTiming2 is supported)", function() { var pt, p = BOOMR.getPerformance(); if (!p || typeof p.getEntriesByType !== "function") { // NT2 not supported - return; + return this.skip(); } + pt = p.getEntriesByType("navigation"); if (!pt || !pt.length) { // NT2 not supported - return; + return this.skip(); } pt = pt[0]; @@ -100,20 +137,17 @@ describe("e2e/19-navtiming/00-onload", function() { } if (pt.nextHopProtocol) { - assert.isNotEmpty(tf.lastBeacon().nt_cinf, "nt_cinf"); + assert.isDefined(tf.lastBeacon().nt_cinf, "nt_cinf"); assert.equal(tf.lastBeacon().nt_cinf, pt.nextHopProtocol, "nt_cinf"); } }); - it("Should have set nt_* navigation properties", function() { - var p = BOOMR.getPerformance(); - if (!p || !p.navigation) { - // NT not supported - return; + it("Should have set nt_* navigation properties (if NavigationTiming is supported)", function() { + if (!t.isNavigationTimingSupported()) { + return this.skip(); } assert.isNumber(tf.lastBeacon().nt_red_cnt, "nt_red_cnt"); assert.isNumber(tf.lastBeacon().nt_nav_type, "nt_nav_type"); }); - });