Skip to content

Commit

Permalink
Disable MutationObserver in IE 11 due to browser bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
querymetrics authored and nicjansma committed Apr 4, 2018
1 parent fc44746 commit a99f09d
Show file tree
Hide file tree
Showing 59 changed files with 499 additions and 342 deletions.
20 changes: 19 additions & 1 deletion boomerang.js
Original file line number Diff line number Diff line change
Expand Up @@ -1453,6 +1453,24 @@ BOOMR_check_doc_domain();
}
},

/**
* MutationObserver feature detection
*
* @returns {boolean} Returns true if MutationObserver is supported.
* Always returns false for IE 11 due several bugs in it's implementation that MS flagged as Won't Fix.
* In IE11, XHR responseXML might be malformed if MO is enabled (where extra newlines get added in nodes with UTF-8 content).
* Another IE 11 MO bug can cause the process to crash when certain mutations occur.
* For the process crash issue, see https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/8137215/ and
* https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/15167323/
*
* @memberof BOOMR.utils
*/
isMutationObserverSupported: function() {
// We can only detect IE 11 bugs by UA sniffing.
var ie11 = (w && w.navigator && w.navigator.userAgent && w.navigator.userAgent.match(/Trident.*rv[ :]*11\./));
return (!ie11 && w && w.MutationObserver && typeof w.MutationObserver === "function");
},

/**
* The callback function may return a falsy value to disconnect the
* observer after it returns, or a truthy value to keep watching for
Expand Down Expand Up @@ -1492,7 +1510,7 @@ BOOMR_check_doc_domain();
addObserver: function(el, config, timeout, callback, callback_data, callback_ctx) {
var o = {observer: null, timer: null};

if (!BOOMR.window || !BOOMR.window.MutationObserver || !callback || !el) {
if (!this.isMutationObserverSupported() || !callback || !el) {
return null;
}

Expand Down
34 changes: 5 additions & 29 deletions plugins/auto-xhr.js
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@
* [MutationObserver]{@link https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver}
* and [XMLHttpRequest]{@link https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest}.
*
* We will not use MutationObserver in IE 11 due to several browser bugs.
* See {@link BOOMR.utils.isMutationObserverSupported} for details.
*
* ## Excluding Certain Requests From Instrumentation
*
* Whenever Boomerang intercepts an `XMLHttpRequest`, it will check if that request
Expand Down Expand Up @@ -222,8 +225,7 @@
singlePageApp = false,
autoXhrEnabled = false,
alwaysSendXhr = false,
readyStateMap = ["uninitialized", "open", "responseStart", "domInteractive", "responseEnd"],
ie10or11;
readyStateMap = ["uninitialized", "open", "responseStart", "domInteractive", "responseEnd"];

/**
* Single Page Applications get an additional timeout for all XHR Requests to settle in.
Expand Down Expand Up @@ -315,14 +317,6 @@
return;
}

// User-agent sniff IE 10 and IE 11 to apply a workaround for an XHR bug (see below when
// this variable is used). We can only detect this bug by UA sniffing. IE 11 requires a
// different way of detection than IE 11.
ie10or11 = (w.navigator && w.navigator.appVersion && w.navigator.appVersion.indexOf("MSIE 10") !== -1) ||
(w.navigator && w.navigator.userAgent && w.navigator.userAgent.match(/Trident.*rv[ :]*11\./));



function log(msg) {
BOOMR.debug(msg, "AutoXHR");
}
Expand Down Expand Up @@ -1546,19 +1540,6 @@
if (ename === "readystatechange") {
resource.timing[readyStateMap[req.readyState]] = BOOMR.now();

// For IE 10 and 11, we need to turn off the MutationObserver before responseXML
// is first referenced, otherwise responseXML might be malformed due to a browser
// bug (where extra newlines get added in nodes with UTF-8 content)
if (impl.ie1011fix && ie10or11 && req.readyState === 4) {
MutationHandler.pause();

// this reference to responseXML with MO off is enough to ensure the browser
// bug is not triggered
var nop = req.responseXML;

MutationHandler.resume();
}

// Listen here as well, as DOM changes might happen on other listeners
// of readyState = 4 (complete), and we want to make sure we've
// started the addEvent() if so. Only listen if the status is non-zero,
Expand Down Expand Up @@ -1702,16 +1683,13 @@
* Container for AutoXHR plugin Closure specific state configuration data
*
* @property {string[]} spaBackendResources Default resources to count as Back-End during a SPA nav
* @property {boolean} ie1011fix If true, the MutationObserver will be paused on
* IE10/11 to avoid delayed processing, see {@link ProxyXHRImplementation#addListener} for more info
* @property {FilterObject[]} filters Array of {@link FilterObject} that is used to apply filters on XHR Requests
* @property {boolean} initialized Set to true after the first run of
* @property {string[]} addedVars Vars added to the next beacon only
* {@link BOOMR.plugins.AutoXHR#init}
*/
impl = {
spaBackEndResources: SPA_RESOURCES_BACK_END,
ie1011fix: true,
excludeFilters: [],
initialized: false,
addedVars: [],
Expand Down Expand Up @@ -1788,8 +1766,6 @@
* @param {boolean} [config.instrument_xhr] Whether or not to instrument XHR
* @param {string[]} [config.AutoXHR.spaBackEndResources] Default resources to count as
* Back-End during a SPA nav
* @param {boolean} [config.AutoXHR.ie1011fix] If true, the MutationObserver will be paused on
* IE10/11 to avoid delayed processing
* @param {boolean} [config.AutoXHR.alwaysSendXhr] Whether or not to send XHR
* beacons for every XHR.
*
Expand All @@ -1808,7 +1784,7 @@
a = BOOMR.window.document.createElement("A");

// gather config and config overrides
BOOMR.utils.pluginConfig(impl, config, "AutoXHR", ["spaBackEndResources", "ie1011fix"]);
BOOMR.utils.pluginConfig(impl, config, "AutoXHR", ["spaBackEndResources"]);

BOOMR.instrumentXHR = instrumentXHR;
BOOMR.uninstrumentXHR = uninstrumentXHR;
Expand Down
6 changes: 5 additions & 1 deletion tests/boomerang-test-framework.js
Original file line number Diff line number Diff line change
Expand Up @@ -407,7 +407,11 @@
};

t.isMutationObserverSupported = function() {
return (window.MutationObserver && typeof window.MutationObserver === "function");
var w = window;
// Use the same logic as BOOM.utils.isMutationObserverSupported.
// Boomerang will not use MO in IE 11 due to browser bugs
var ie11 = (w && w.navigator && w.navigator.userAgent && w.navigator.userAgent.match(/Trident.*rv[ :]*11\./));
return (!ie11 && w && w.MutationObserver && typeof w.MutationObserver === "function");
};

t.validateBeaconWasXhr = function(done) {
Expand Down
8 changes: 4 additions & 4 deletions tests/page-templates/05-angular/03-ng-app.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,26 +18,26 @@ describe("e2e/05-angular/03-ng-app", function() {
});

it("Should take as long as the longest img load (if MutationObserver and NavigationTiming are supported)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
t.validateBeaconWasSentAfter(0, "img.jpg", 100, 3000, 30000, true);
}
});

it("Should not have a load time (if MutationObserver is supported but NavigationTiming is not)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.lastBeacon();
assert.equal(b.t_done, undefined);
}
});

it("Should take as long as the XHRs (if MutationObserver is not supported but NavigationTiming is)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
t.validateBeaconWasSentAfter(0, "widgets.json", 100, 0, 30000, true);
}
});

it("Shouldn't have a load time (if MutationObserver and NavigationTiming are not supported)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.lastBeacon();
assert.equal(b.t_done, undefined);
assert.equal(b["rt.start"], "none");
Expand Down
16 changes: 8 additions & 8 deletions tests/page-templates/05-angular/102-ui-router.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,26 @@ describe("e2e/05-angular/102-ui-router", function() {
});

it("Should take as long as the longest img load (if MutationObserver and NavigationTiming are supported)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
t.validateBeaconWasSentAfter(0, "img.jpg&id=home", 500, 3000, 30000, 0);
}
});

it("Should not have a load time (if MutationObserver is supported but NavigationTiming is not)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.beacons[0];
assert.equal(b.t_done, undefined);
}
});

it("Should take as long as the XHRs (if MutationObserver is not supported but NavigationTiming is)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
t.validateBeaconWasSentAfter(0, "widgets.json", 500, 0, 30000, false);
}
});

it("Shouldn't have a load time (if MutationObserver and NavigationTiming are not supported)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.beacons[0];
assert.equal(b.t_done, undefined);
assert.equal(b["rt.start"], "none");
Expand All @@ -68,15 +68,15 @@ describe("e2e/05-angular/102-ui-router", function() {
});

it("Should have sent the second beacon with a timestamp of at least 1 second (if MutationObserver is supported)", function() {
if (window.MutationObserver) {
if (t.isMutationObserverSupported()) {
// because of the widget IMG delaying 1 second
var b = tf.beacons[1];
assert.operator(b.t_done, ">=", 1000);
}
});

it("Should have sent the second beacon with a timestamp of at least 1 millisecond (if MutationObserver is not supported)", function() {
if (typeof window.MutationObserver === "undefined") {
if (!t.isMutationObserverSupported()) {
// because of the widget IMG delaying 1 second but we couldn't track it because no MO support
var b = tf.beacons[1];
assert.operator(b.t_done, ">=", 0);
Expand All @@ -92,14 +92,14 @@ describe("e2e/05-angular/102-ui-router", function() {
});

it("Should have sent the third with a timestamp of at least 3 seconds (if MutationObserver is supported)", function() {
if (window.MutationObserver) {
if (t.isMutationObserverSupported()) {
var b = tf.beacons[2];
assert.operator(b.t_done, ">=", 3000);
}
});

it("Should have sent the third with a timestamp of under 1 second (if MutationObserver is not supported)", function() {
if (!window.MutationObserver) {
if (!t.isMutationObserverSupported()) {
var b = tf.beacons[2];
assert.operator(b.t_done, "<=", 1000);
}
Expand Down
16 changes: 8 additions & 8 deletions tests/page-templates/05-angular/108-location-change-only.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,43 +55,43 @@ describe("e2e/05-angular/108-location-change-only", function() {
});

it("Should have a load time (if MutationObserver and NavigationTiming are supported)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
var b = tf.beacons[0];
assert.isDefined(b.t_done);
}
});

it("Should not have a load time (if MutationObserver is supported but NavigationTiming is not)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.beacons[0];
assert.equal(b.t_done, undefined);
}
});

it("Should take as long as the XHRs (if MutationObserver is not supported but NavigationTiming is)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
t.validateBeaconWasSentAfter(0, "widgets.json", 500, 0, 30000, false);
}
});

it("Shouldn't have a load time (if MutationObserver and NavigationTiming are not supported)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.beacons[0];
assert.equal(b.t_done, undefined);
assert.equal(b["rt.start"], "none");
}
});

it("Should have a t_resp of the root page (if MutationObserver and NavigationTiming are supported)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
var pt = window.performance.timing;
var b = tf.beacons[0];
assert.equal(b.t_resp, pt.responseStart - pt.navigationStart);
}
});

it("Should have a t_page of total - t_resp (if MutationObserver and NavigationTiming are supported)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
var pt = window.performance.timing;
var b = tf.beacons[0];
assert.equal(b.t_page, b.t_done - b.t_resp);
Expand All @@ -107,7 +107,7 @@ describe("e2e/05-angular/108-location-change-only", function() {
});

it("Should have sent the second beacon with a timestamp of ~0 seconds (if MutationObserver is supported)", function() {
if (window.MutationObserver) {
if (t.isMutationObserverSupported()) {
var b = tf.beacons[1];
assert.closeTo(b.t_done, 0, 50);
}
Expand All @@ -122,7 +122,7 @@ describe("e2e/05-angular/108-location-change-only", function() {
});

it("Should have sent the third with a timestamp of at around 0 seconds (if MutationObserver is supported)", function() {
if (window.MutationObserver) {
if (t.isMutationObserverSupported()) {
var b = tf.beacons[2];
assert.closeTo(b.t_done, 0, 50);
}
Expand Down
6 changes: 4 additions & 2 deletions tests/page-templates/05-angular/109-multiple-iframes.html
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
<%= boomerangScript %>
<script src="109-multiple-iframes.js" type="text/javascript"></script>

<iframe src="support/simple-spa-page.html"></iframe>
<iframe src="support/simple-spa-page.html"></iframe>
<iframe src="support/simple-spa-page.html?id=1"></iframe>
<iframe src="support/simple-spa-page.html?id=2"></iframe>
<script type="text/javascript">
BOOMR_test.init({
testAfterOnBeacon: true,
Expand All @@ -15,4 +15,6 @@
});
</script>

<!-- delay onload so that the iframes have time to run their boomerangs -->
<img src="/delay?delay=2000&amp;file=/assets/img.jpg" style="width: 100px" />
<%= footer %>
16 changes: 8 additions & 8 deletions tests/page-templates/05-angular/113-late-locationchangestart.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,26 +33,26 @@ describe("e2e/05-angular/113-late-locationchangestart", function() {
});

it("Should take as long as the longest img load (if MutationObserver and NavigationTiming are supported)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
t.validateBeaconWasSentAfter(0, "img.jpg&id=home", 500, 3000, 30000, 0);
}
});

it("Should not have a load time (if MutationObserver is supported but NavigationTiming is not)", function() {
if (window.MutationObserver && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.beacons[0];
assert.equal(b.t_done, undefined);
}
});

it("Should take as long as the XHRs (if MutationObserver is not supported but NavigationTiming is)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() !== "undefined") {
t.validateBeaconWasSentAfter(0, "widgets.json", 500, 0, 30000, false);
}
});

it("Shouldn't have a load time (if MutationObserver and NavigationTiming are not supported)", function() {
if (typeof window.MutationObserver === "undefined" && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
if (!t.isMutationObserverSupported() && typeof BOOMR.plugins.RT.navigationStart() === "undefined") {
var b = tf.beacons[0];
assert.equal(b.t_done, undefined);
assert.equal(b["rt.start"], "none");
Expand All @@ -68,15 +68,15 @@ describe("e2e/05-angular/113-late-locationchangestart", function() {
});

it("Should have sent the second beacon with a timestamp of at least 1 second (if MutationObserver is supported)", function() {
if (window.MutationObserver) {
if (t.isMutationObserverSupported()) {
// because of the widget IMG delaying 1 second
var b = tf.beacons[1];
assert.operator(b.t_done, ">=", 1000);
}
});

it("Should have sent the second beacon with a timestamp of at least 1 millisecond (if MutationObserver is not supported)", function() {
if (typeof window.MutationObserver === "undefined") {
if (!t.isMutationObserverSupported()) {
// because of the widget IMG delaying 1 second but we couldn't track it because no MO support
var b = tf.beacons[1];
assert.operator(b.t_done, ">=", 0);
Expand All @@ -92,14 +92,14 @@ describe("e2e/05-angular/113-late-locationchangestart", function() {
});

it("Should have sent the third with a timestamp of at least 3 seconds (if MutationObserver is supported)", function() {
if (window.MutationObserver) {
if (t.isMutationObserverSupported()) {
var b = tf.beacons[2];
assert.operator(b.t_done, ">=", 3000);
}
});

it("Should have sent the third with a timestamp of under 1 second (if MutationObserver is not supported)", function() {
if (!window.MutationObserver) {
if (!t.isMutationObserverSupported()) {
var b = tf.beacons[2];
assert.operator(b.t_done, "<=", 1000);
}
Expand Down
Loading

0 comments on commit a99f09d

Please sign in to comment.