A CSP compliant non-blocking script loader
<script id="nb-loader-script"> | |
(function(url) { | |
// document.currentScript works on most browsers, but not all | |
var where = document.currentScript || document.getElementById("nb-loader-script"), | |
promoted = false, | |
LOADER_TIMEOUT = 3000, | |
IDPREFIX = "__nb-script"; | |
// function to promote a preload link node to an async script node | |
function promote() { | |
var s; | |
s = document.createElement("script"); | |
s.id = IDPREFIX + "-async"; | |
s.src = url; | |
where.parentNode.appendChild(s); | |
promoted = true; | |
} | |
// function to load script in an iframe on browsers that don't support preload hints | |
function iframe_loader() { | |
promoted = true; | |
var win, doc, dom, s, bootstrap, iframe = document.createElement("iframe"); | |
// IE6, which does not support CSP, treats about:blank as insecure content, so we'd have to use javascript:void(0) there | |
// In browsers that do support CSP, javascript:void(0) is considered unsafe inline JavaScript, so we prefer about:blank | |
iframe.src = "about:blank"; | |
// We set title and role appropriately to play nicely with screen readers and other assistive technologies | |
iframe.title = ""; | |
iframe.role = "presentation"; | |
s = (iframe.frameElement || iframe).style; | |
s.width = 0; s.height = 0; s.border = 0; s.display = "none"; | |
where.parentNode.insertBefore(iframe, where); | |
try { | |
win = iframe.contentWindow; | |
doc = win.document.open(); | |
} | |
catch (e) { | |
// document.domain has been changed and we're on an old version of IE, so we got an access denied. | |
// Note: the only browsers that have this problem also do not have CSP support. | |
// Get document.domain of the parent window | |
dom = document.domain; | |
// Set the src of the iframe to a JavaScript URL that will immediately set its document.domain to match the parent. | |
// This lets us access the iframe document long enough to inject our script. | |
// Our script may need to do more domain massaging later. | |
iframe.src = "javascript:var d=document.open();d.domain='" + dom + "';void(0);"; | |
win = iframe.contentWindow; | |
doc = win.document.open(); | |
} | |
bootstrap = function() { | |
// This code runs inside the iframe | |
var js = doc.createElement("script"); | |
js.id = IDPREFIX + "-iframe-async"; | |
js.src = url; | |
doc.body.appendChild(js); | |
}; | |
try { | |
win._l = bootstrap | |
if (win.addEventListener) { | |
win.addEventListener("load", win._l, false); | |
} | |
else if (win.attachEvent) { | |
win.attachEvent("onload", win._l); | |
} | |
} | |
catch (f) { | |
// unsafe version for IE8 compatability | |
// If document.domain has changed, we can't use win, but we can use doc | |
doc._l = function() { | |
if (dom) { | |
this.domain = dom; | |
} | |
bootstrap(); | |
} | |
doc.write('<body onload="document._l();">'); | |
} | |
doc.close(); | |
} | |
// We first check to see if the browser supports preload hints via a link element | |
var l = document.createElement("link"); | |
if (l.relList && typeof l.relList.supports === "function" && l.relList.supports("preload") && ("as" in l)) { | |
l.href = url; | |
l.rel = "preload"; | |
l.as = "script"; | |
// If the link successfully preloads our script, we'll promote it to a script node. | |
l.addEventListener("load", promote); | |
// If the preload fails or times out, we'll fallback to the iframe loader | |
l.addEventListener("error", iframe_loader); | |
setTimeout(function() { | |
if (!promoted) { | |
iframe_loader(); | |
} | |
}, LOADER_TIMEOUT); | |
where.parentNode.appendChild(l); | |
} | |
else { | |
// If preload hints aren't supported, then fallback to the iframe loader | |
iframe_loader(); | |
} | |
})("https://your.script.url/goes/here.js"); | |
</script> |
(function _check_doc_domain(domain) { | |
// This snippet tries to walk document.domain in case the parent frame changed it after our iframe was created and the script was loaded | |
/*eslint no-unused-vars:0*/ | |
var test; | |
if (!window) { | |
return; | |
} | |
// If domain is not passed in, then this is a global call | |
// domain is only passed in if we call ourselves, so we | |
// skip the frame check at that point | |
if (typeof domain === "undefined") { | |
// If we're running in the main window, then we don't need this | |
if (window.parent === window || !document.getElementById("__nb-script-iframe-async")) { | |
return;// true; // nothing to do | |
} | |
try { | |
// If document.domain is changed during page load (from www.blah.com to blah.com, for example), | |
// window.parent.window.location.href throws "Permission Denied" in IE. | |
// Resetting the inner domain to match the outer makes location accessible once again | |
if (window.document.domain !== window.parent.window.document.domain) { | |
window.document.domain = window.parent.window.document.domain; | |
} | |
} | |
catch (err) { | |
// We could log this, but nothing else to do | |
} | |
} | |
domain = document.domain; | |
if (domain.indexOf(".") === -1) { | |
// we've reached the top level domain | |
return;// false; // not okay, but we did our best | |
} | |
// 1. Test without setting document.domain | |
try { | |
test = window.parent.document; | |
return;// test !== undefined; // all okay | |
} | |
// 2. Test with document.domain | |
catch (err) { | |
document.domain = domain; | |
} | |
try { | |
test = window.parent.document; | |
return;// test !== undefined; // all okay | |
} | |
// 3. Strip off leading part and try again | |
catch (err) { | |
domain = domain.replace(/^[\w\-]+\./, ""); | |
} | |
_check_doc_domain(domain); | |
})(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment