Alexey Alexey - 29 days ago 10
Javascript Question

Cannot load script into iframe

Test page: https://jsfiddle.net/y25rk55w/

On this test page you can see 3

<iframe>
's embeded into each other. Each
<iframe>
contains a
<script>
tag in it's
<head>
tag.

The problem is: only the
<script>
in the first
<iframe>
will be loaded by the browser. The other two
<script>
tags will be present in the dom but the browser will never even try to load them. The problem is not browser specific, it can be reroduced in chrome, firefox, ie. The problem cannot be fixed by adding timeouts or waiting before appending the scripts. It seems to be important that all the iframes have programatically generated content; if you replace this iframes with iframes with actual src links, the problem will disappear.

The question is: how can I actually load a script into iframes 2 and 3?

Full test code:

// It doesn't matter if the scripts exist or not
// Browser won't try to load them either way
var scripts = [
'//testdomain.test/script1.js',
'//testdomain.test/script2.js',
'//testdomain.test/script3.js'
];

function createIFrame(win, onCreated) {
var iframe = win.document.createElement('iframe');
iframe.onload = function () {
onCreated(iframe);
};
win.document.body.appendChild(iframe);
}

function loadScript(win, url) {
var script = win.document.createElement('script');
script.src = url;
script.onload = function() {
console.log("Script " + url + " is loaded.");
};
win.document.getElementsByTagName('head')[0].appendChild(script);
}

createIFrame(window, function(iframe1) {
loadScript(iframe1.contentWindow, scripts[0]);
createIFrame(iframe1.contentWindow, function (iframe2) {
loadScript(iframe2.contentWindow, scripts[1]);
createIFrame(iframe2.contentWindow, function (iframe3) {
loadScript(iframe3.contentWindow, scripts[2]);
});
});
});

Answer Source

In the question you can see that I was ommiting the protocol:

/* This is valid to omit the http:/https: protocol.
   In that case, browser should automatically append 
   protocol used by the parent page */
var scripts = [
    '//testdomain.test/script1.js',
    '//testdomain.test/script2.js',
    '//testdomain.test/script3.js'
];

The thing is, programatically created iframes have protocol about: (or javascript:, depending on how you create them). I still can't explain why the first script was loading or why the other two scripts were not showing up in the network tab at all, but I guess it's not very important.

The solution: either explicitly use https:// or programatically append protocol using something like the following code:

function appendSchema(win, url) {
    if (url.startsWith('//')) {
        var protocol = 'https:';
        try {
            var wPrev = undefined;
            var wCur = win;
            while (wPrev != wCur) {
                console.log(wCur.location.protocol);
                if (wCur.location.protocol.startsWith("http")) {
                    protocol = wCur.location.protocol;
                    break;
                }
                wPrev = wCur;
                wCur = wCur.parent;
            }
        } catch (e) {
            /* We cannot get protocol of a cross-site iframe.
             * So in case we are inside cross-site iframe, and
             * there are no http/https iframes before it,
             * we will just use https: */
        }
        return protocol + url;
    }
    return url;
}