Reputation: 7442
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]);
});
});
});
Upvotes: 2
Views: 4043
Reputation: 151511
I've been successful using a simpler method than what the OP proposes in the self-answer. I produce the URLs using:
new URL(scriptURL, window.location.href).toString();
where scriptURL
is the URL that needs to be fixed to get a proper protocol and window
is the parent of the iframe
element that holds the scripts. This can take care of scenarios that differ from the OPs example URLs: like relative URLs (../foo.js
) or absolute URLs that don't start with a host (/foo.js
). The above code is sufficient in my case.
If I were to replicate the search through the window hierarchy that the OP used, I'd probably do something like the following. This is TypeScript code. Strip out the type annotations to get plain JavaScript.
function url(win: Window, path: string): string {
// We search up the window hierarchy for the first window which uses
// a protocol that starts with "http".
while (true) {
if (win.location.protocol.startsWith("http")) {
// Interpret the path relative to that window's href. So the path
// will acquire the protocol used by the window. And the less we
// specify in `path`, the more it gets from the window. For
// instance, if path is "/foo.js", then the host name will also be
// acquired from the window's location.
return new URL(path, win.location.href).toString();
}
// We searched all the way to the top and found nothing useful.
if (win === win.parent) {
break;
}
win = win.parent;
}
// I've got a big problem on my hands if there's nothing that works.
throw new Error("cannot normalize the URL");
}
I don't have a default return value if the window chain yield nothings useful because that would indicate a much larger issue than the issue of producing URLs. There'd be something wrong elsewhere in my setup.
Upvotes: 0
Reputation: 7442
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;
}
Upvotes: 1
Reputation: 8193
Your code is working fine --> http://plnkr.co/edit/vQGsyD7JxZiDlg6EZvK4?p=preview
Make sure you execute createIFrame on window.onload or DOMContentLoaded.
var scripts = [
'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.1/jquery.js',
'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.2/jquery.js',
'https://cdnjs.cloudflare.com/ajax/libs/jquery/1.11.3/jquery.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);
}
window.onload = function(){
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]);
});
});
});
};
Upvotes: 2