Skip to content

Commit

Permalink
Correctly detect our url on IE using non-standard loader
Browse files Browse the repository at this point in the history
In all browsers except IE, document.currentScript will get us the current script node
In IE, if it uses our standard loader, one of the document.getElementById calls will get us the current script node
In IE 6-10, if not using the standard loader, we iterate through all script nodes in reverse, and stop at the first
whose readyState is "interactive", indicating that it is currently executing.
In IE 11, we throw an Error object, and pull the filename out of the error's stack property. This property is only
populated when an error is thrown.

Also add unit tests and page tests that test if BOOMR.url exists when using different kinds of loaders

Finally, updated comments that are no longer correct, and fix one instance where we used `document` rather
than `d` to check whether `visibilityState` was supported.
  • Loading branch information
bluesmoon authored and nicjansma committed Apr 4, 2018
1 parent a99f09d commit bda14f5
Show file tree
Hide file tree
Showing 5 changed files with 93 additions and 11 deletions.
85 changes: 74 additions & 11 deletions boomerang.js
Original file line number Diff line number Diff line change
Expand Up @@ -149,17 +149,24 @@ BOOMR_check_doc_domain();
// 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
var impl, boomr, d, createCustomEvent, dispatchEvent, visibilityState, visibilityChange, orig_w = w;

// If the window that boomerang is running in is not top level (ie, we're running in an iframe)
// and if this iframe contains a script node with an id of "boomr-if-as",
// Then that indicates that we are using the iframe loader, so the page we're trying to measure
// is w.parent
//
// Note that we use `document` rather than `w.document` because we're specifically interested in
// the document of the currently executing context rather than a passed in proxy.
//
// The only other place we do this is in `BOOMR.utils.getMyURL` below, for the same reason, we
// need the full URL of the currently executing (boomerang) script.
if (w.parent !== w &&
document.getElementById("boomr-if-as") &&
document.getElementById("boomr-if-as").nodeName.toLowerCase() === "script") {
w = w.parent;
}

myurl = (document.currentScript || document.getElementById("boomr-if-as") || document.getElementById("boomr-scr-as")).src;

d = w.document;

// Short namespace because I don't want to keep typing BOOMERANG
Expand Down Expand Up @@ -310,19 +317,19 @@ 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") {
if (typeof d.hidden !== "undefined") {
visibilityState = "visibilityState";
visibilityChange = "visibilitychange";
}
else if (typeof document.mozHidden !== "undefined") {
else if (typeof d.mozHidden !== "undefined") {
visibilityState = "mozVisibilityState";
visibilityChange = "mozvisibilitychange";
}
else if (typeof document.msHidden !== "undefined") {
else if (typeof d.msHidden !== "undefined") {
visibilityState = "msVisibilityState";
visibilityChange = "msvisibilitychange";
}
else if (typeof document.webkitHidden !== "undefined") {
else if (typeof d.webkitHidden !== "undefined") {
visibilityState = "webkitVisibilityState";
visibilityChange = "webkitvisibilitychange";
}
Expand Down Expand Up @@ -894,13 +901,13 @@ BOOMR_check_doc_domain();
t_end: undefined,

/**
* URL of boomerang.js. This is only set if using the asynchronous loader snippet.
* URL of boomerang.js.
*
* @type {string}
*
* @memberof BOOMR
*/
url: myurl,
url: "",

/**
* Whether or not Boomerang was loaded after the `onload` event.
Expand Down Expand Up @@ -1741,6 +1748,58 @@ BOOMR_check_doc_domain();
}
// not supported
BOOMR.debug("JSON is not supported");
return "";
},

/**
* Attempt to identify the URL of boomerang itself using multiple methods for cross-browser support
*
* This method uses document.currentScript (which cannot be called from an event handler), script.readyState (IE6-10),
* and the stack property of a caught Error object.
*
* @returns {string} The URL of the currently executing boomerang script.
*/
getMyURL: function() {
// document.currentScript works in all browsers except for IE: https://caniuse.com/#feat=document-currentscript
// #boomr-if-as works in all browsers if the page uses our standard iframe loader
// #boomr-scr-as works in all browsers if the page uses our preloader loader
// BOOMR_script will be undefined on IE for pages that do not use our standard loaders

// Note that we do not use `w.document` or `d` here because we need the current execution context
var BOOMR_script = (document.currentScript || document.getElementById("boomr-if-as") || document.getElementById("boomr-scr-as"));

if (BOOMR_script) {
return BOOMR_script.src;
}

// For IE 6-10 users on pages not using the standard loader, we iterate through all scripts backwards
var scripts = document.getElementsByTagName("script"), i;

// i-- is both a decrement as well as a condition, ie, the loop will terminate when i goes from 0 to -1
for (i = scripts.length; i--;) {
// We stop at the first script that has its readyState set to interactive indicating that it is currently executing
if (scripts[i].readyState === "interactive") {
return scripts[i].src;
}
}

// For IE 11, we throw an Error and inspect its stack property in the catch block
// This also works on IE10, but throwing is disruptive so we try to avoid it and use
// the less disruptive script iterator above
try {
throw new Error();
}
catch (e) {
if ("stack" in e) {
return boomr.utils.arrayFilter(e.stack.split(/\n/), function(l) { return l.match(/https?:\/\//); })[0].replace(/.*(https?:\/\/.+):\d+:\d+\D*$/m, "$1");
}
// FWIW, on IE 8 & 9, the Error object does not contain a stack property, but if you have an uncaught error,
// and a `window.onerror` handler (not using addEventListener), then the second argument to that handler is
// the URL of the script that threw. The handler needs to `return true;` to prevent the default error handler
// This flow is asynchronous though (due to the event handler), so won't work in a function return scenario
// like this (we can't use promises because we would only need this hack in browsers that don't support promises).
}

return "";
}

Expand Down Expand Up @@ -3269,6 +3328,10 @@ BOOMR_check_doc_domain();
/* END_DEBUG */
};

boomr.url = boomr.utils.getMyURL();



delete BOOMR_start;

/**
Expand Down
5 changes: 5 additions & 0 deletions tests/page-templates/03-load-order/00-before-page-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ describe("e2e/03-load-order/00-before-page-load", function() {
var tf = BOOMR.plugins.TestFramework;
var t = BOOMR_test;

it("Should contain BOOMR.url set to boomerang's URL", function() {
assert.isString(BOOMR.url, "is not a string");
assert.match(BOOMR.url, /\/boomerang-latest-debug.js($|\?)/, "does not match: " + BOOMR.url);
});

it("Should pass basic beacon validation", function(done) {
t.validateBeaconWasSent(done);
});
Expand Down
5 changes: 5 additions & 0 deletions tests/page-templates/03-load-order/01-after-page-load.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ describe("e2e/03-load-order/01-after-page-load", function() {
var tf = BOOMR.plugins.TestFramework;
var t = BOOMR_test;

it("Should contain BOOMR.url set to boomerang's URL", function() {
assert.isString(BOOMR.url, "is not a string");
assert.match(BOOMR.url, /\/boomerang-latest-debug.js($|\?)/, "does not match: " + BOOMR.url);
});

it("Should pass basic beacon validation", function(done) {
t.validateBeaconWasSent(done);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,11 @@ describe("e2e/03-load-order/02-after-page-load-tag-manager", function() {
var tf = BOOMR.plugins.TestFramework;
var t = BOOMR_test;

it("Should contain BOOMR.url set to boomerang's URL", function() {
assert.isString(BOOMR.url, "is not a string");
assert.match(BOOMR.url, /\/boomerang-latest-debug.js($|\?|&)/, "does not match: " + BOOMR.url);
});

it("Should pass basic beacon validation", function(done) {
t.validateBeaconWasSent(done);
});
Expand Down
4 changes: 4 additions & 0 deletions tests/unit/01-basic.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,8 @@ describe("BOOMR exports", function() {
it("Should have an existing BOOMR.plugins Object", function(){
assert.isObject(BOOMR.plugins);
});

it("Should have an existing BOOMR.url value", function(){
assert.isString(BOOMR.url);
});
});

0 comments on commit bda14f5

Please sign in to comment.