Last active
January 3, 2024 20:40
-
-
Save bluesmoon/675c37368ddd58dfa5f578f1e5a59778 to your computer and use it in GitHub Desktop.
A CSP compliant non-blocking script loader
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(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