Skip to content

Commit

Permalink
Workaround for document.write changing readyState after onload
Browse files Browse the repository at this point in the history
  • Loading branch information
querymetrics authored and nicjansma committed Apr 4, 2018
1 parent 9f1f513 commit 2822a79
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 18 deletions.
48 changes: 30 additions & 18 deletions boomerang.js
Original file line number Diff line number Diff line change
Expand Up @@ -1105,18 +1105,10 @@ BOOMR_check_doc_domain();

// The developer can override onload by setting autorun to false
if (!impl.onloadfired && (config.autorun === undefined || config.autorun !== false)) {
if (d.readyState && d.readyState === "complete") {
if (BOOMR.hasBrowserOnloadFired()) {
BOOMR.loadedLate = true;
this.setImmediate(BOOMR.page_ready_autorun, null, null, BOOMR);
}
else {
if (w.onpagehide || w.onpagehide === null) {
BOOMR.utils.addListener(w, "pageshow", BOOMR.page_ready_autorun);
}
else {
BOOMR.utils.addListener(w, "load", BOOMR.page_ready_autorun);
}
}
BOOMR.attach_page_ready(BOOMR.page_ready_autorun);
}

BOOMR.utils.addListener(w, "DOMContentLoaded", function() { impl.fireEvent("dom_loaded"); });
Expand Down Expand Up @@ -1179,16 +1171,19 @@ BOOMR_check_doc_domain();
},

/**
* Attach a callback to the onload event if the onload has not
* been fired yet
* Attach a callback to the `pageshow` or `onload` event if `onload` has not
* been fired otherwise queue it to run immediately
*
* @param {function} cb - Callback to run when onload fires or page is visible (pageshow)
* @param {function} cb - Callback to run when `onload` fires or page is visible (`pageshow`)
*/
attach_page_ready: function(cb) {
if (d.readyState && d.readyState === "complete") {
if (BOOMR.hasBrowserOnloadFired()) {
this.setImmediate(cb, null, null, BOOMR);
}
else {
// Use `pageshow` if available since it will fire even if page came from a back-forward page cache.
// Browsers that support `pageshow` will not fire `onload` if navigation was through a back/forward button
// and the page was retrieved from back-forward cache.
if (w.onpagehide || w.onpagehide === null) {
BOOMR.utils.addListener(w, "pageshow", cb);
}
Expand Down Expand Up @@ -1239,6 +1234,24 @@ BOOMR_check_doc_domain();
return this;
},

/**
* Determines whether or not the page's `onload` event has fired
*
* @returns {boolean} True if page's onload was called
*/
hasBrowserOnloadFired: function() {
var p = BOOMR.getPerformance();
// if the document is `complete` then the `onload` event has already occurred, we'll fire the callback immediately.
// When `document.write` is used to replace the contents of the page and inject boomerang, the document `readyState`
// will go from `complete` back to `loading` and then to `complete` again. The second transition to `complete`
// doesn't fire a second `pageshow` event in some browsers (e.g. Safari). We need to check if
// `performance.timing.loadEventStart` or `BOOMR_onload` has occurred to detect this scenario. Will not work for
// older Safari that doesn't have NavTiming
return ((d.readyState && d.readyState === "complete") ||
(p && p.timing && p.timing.loadEventStart > 0) ||
w.BOOMR_onload > 0);
},

/**
* Determines whether or not the page's `onload` event has fired, or
* if `autorun` is false, whether `BOOMR.page_ready()` was called.
Expand Down Expand Up @@ -2088,12 +2101,11 @@ BOOMR_check_doc_domain();
* supported or if the entry doesn't exist
*/
getResourceTiming: function(url, sort) {
var entries;
var entries, p = BOOMR.getPerformance();

try {
if (BOOMR.getPerformance() &&
typeof BOOMR.getPerformance().getEntriesByName === "function") {
entries = BOOMR.getPerformance().getEntriesByName(url);
if (p && typeof p.getEntriesByName === "function") {
entries = p.getEntriesByName(url);
if (entries && entries.length) {
if (typeof sort === "function") {
entries.sort(sort);
Expand Down
48 changes: 48 additions & 0 deletions tests/page-templates/06-bugs/122358-document-write.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!DOCTYPE html>
<!--[if IE 8]>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 1.0 Transitional//EN" "http://www.w3.org/TR/html1/DTD/html1-transitional.dtd">
<![endif]-->

<!--
When document.write is used to replace the contents of the page and inject boomerang, the document readyState
will go from "complete" back to "loading" and then to "complete" again. The second transition to "complete"
doesn't fire a second pageshow event in some browsers (e.g. Safari). Boomerang needs to check if performance.timing.loadEventStart has occurred
to detect this scenario.
-->
<html>
<head>
</head>
<body onload="setTimeout(onLoad, 100);">

<!-- the textarea contains the html that we'll use with document.write to replace the page -->
<textarea id="newdocument">
<%= header %>
<script src="122358-document-write.js" type="text/javascript"></script>
<%= boomerangSnippet %>
<script>
BOOMR_test.init({
testAfterOnBeacon: 1,
onBoomerangLoaded: function() {
setTimeout(function() {
document.close();
// readyState will return to "complete" without firing a second pageshow event in Safari
}, 100);
}
});
</script>
<div id="content"></div>
<%= footer %>
</textarea>

<script>
function onLoad() {
// replace our page
var newdocument = document.getElementById("newdocument").value;
document.open();
document.write(newdocument);
// wait until boomerang has loaded before closing the document so that it detects a "loading" readyState
}
</script>
</body>
</html>

11 changes: 11 additions & 0 deletions tests/page-templates/06-bugs/122358-document-write.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*eslint-env mocha*/
/*global assert*/

describe("e2e/06-bugs/122358-document-write", function() {
var tf = BOOMR.plugins.TestFramework;
var t = BOOMR_test;

it("Should have sent a beacon", function(done) {
t.ensureBeaconCount(done, 1);
});
});

0 comments on commit 2822a79

Please sign in to comment.