I am using an iframe in order to isolate its content's CSS. Which is working great. But, I need the iframe to load an external script, specifically the script I am loading is: https://web.squarecdn.com/v1/square.js
You can see that this external script calls document often. I need it to reference the document of my iframe not the document of the parent HTML.
I have tried this and it does not accomplish this:
const insertScriptToIframe = (doc, target, src, callback) => {
var s = doc.createElement("script");
s.type = "text/javascript";
if(callback) {
if (s.readyState){ //IE
s.onreadystatechange = function(){
if (s.readyState == "loaded" ||
s.readyState == "complete"){
s.onreadystatechange = null;
callback();
}
};
} else { //Others
s.onload = function(){
callback();
};
}
}
s.src = src;
target.appendChild(s);
}
// add web payments script
const createWebPaymentsNode = () => {
const iFrame = document.createElement('iframe');
const context = iFrame.contentDocument;
const frameHead = context.getElementsByTagName('head').item(0);
insertScriptToIframe(context, frameHead, 'https://web.squarecdn.com/v1/square.js');
}
It adds the external script to the iframe. But the external script is referencing the parent document. I know it is doing this because the external script, itself, loads other scripts and iframes that are being placed in the parent HTML, instead of the iframe's.
I need to be able to do this in vanilla js. Thank you
I want to load a external URL in a hidden iframe on my web page. I need to detect once the external URL has finished loading, and then call my Function MyFunct(). If the external URL takes longer than 3 seconds to load I need to timeout and call MyFunct()
Will the following code call my function MyFunct() either once the iframe has loaded or after 3 seconds because of the timeout ?
var iframe = document.getElementById('MyFrame');
iframe.src = "SomeRandonUrl";
iframe.onload = function () {
setTimeout(MyFunct, 3000);
}
You should check if it has loaded in a timeout event like the following.
var iframe = document.getElementById('MyFrame');
iframe.src = "SomeRandonUrl";
iframe.onload = function () {
if (iframe.src !== "about:blank")
iframe.loaded = true; // set that it has loaded if not about:blank
}
setTimeout(function() {
if (!iframe.loaded) { // if undefined or false valued
alert('Timedout');
// and then you can cancel it's loading setting another src for e.g
}
}, 3000);
I need to read the HTML body content (either as DOM or as a raw string) as soon as possible to optimize some loading of further scripts (load script A if content is X, or B if content is Y). Is it possible to avoid waiting for all sync scripts to load (which is a condition for DOM-ready) in order to read the body?
Note: changing scripts to be async is not an option.
Update: I do not control the server, so the optimization cannot be done there.
Perhaps look at the state changes by subscribing to that. Inline comments for each.
// self envoking
(function() {
console.log("self envoking in script");
}());
console.log(document.readyState); // logs "loading" first
window.onload = function() {
console.log('onload'); // I fire later
}
document.addEventListener("DOMContentLoaded", function() {
console.log('DOMContentLoaded');
});
document.addEventListener('readystatechange', function() {
if (document.readyState == "loading") {
//document is loading, does not fire here since no change from "loading" to "loading"
}
if (document.readyState == "interactive") {
//document fully read. fires before DOMContentLoaded
}
if (document.readyState == "complete") {
//document fully read and all resources (like images) loaded. fires after DOMContentLoaded
}
console.log(document.readyState)
});
<script id="myscript">
// self envoking
(function() {
console.log("self envoking with id");
window.customevent = new Event('customload');
// Listen for the event.
document.addEventListener('customload', function(e) {
console.log("customload fired");
}, false);
// another way
var scriptNode = document.createElement('script');
scriptNode.innerHTML = 'console.log("happy happy day inject");';
document.head.appendChild(scriptNode);
}());
</script>
<script id="another">
var thisScriptElement = document.getElementById("another");
// self envoking
(function() {
console.log("Another self envoking with id");
// Dispatch the event.
document.dispatchEvent(customevent);
}());
</script>
I have a page that has a header and sidebar with a right content panel that is an Iframe.
In a page loaded into the right content panel, I am trying to have a clicked link update the Browser URL in the parent window to the URL of the new page that is loaded into the Iframe.
I do not want the actual parent window to reload the URL but simply to update the URL in the address bar.
Something like:
window.history.pushState('obj', 'newtitle', '/bookmarks/list/');
Is this possible from an Iframe?
I was able to accomplish updating the parent windows URL in the address bar using history.pushState by sending the new URL to the parent from the child Iframe window using postMessage and on the parent window listening for this event.
WHen the parent receives the child iframes postMessage event, it updates the URL with pushSTate using the URL passed in that message.
Child Iframe
<script>
// Detect if this page is loaded inside an Iframe window
function inIframe() {
try {
return window.self !== window.top;
} catch (e) {
return true;
}
}
// Detect if the CTRL key is pressed to be used when CTRL+Clicking a link
$(document).keydown(function(event){
if(event.which=="17")
cntrlIsPressed = true;
});
$(document).keyup(function(){
cntrlIsPressed = false;
});
var cntrlIsPressed = false;
// check if page is loaded inside an Iframe?
if(inIframe()){
// is the CTRL key pressed?
if(cntrlIsPressed){
// CTRL key is pressed, so link will open in a new tab/window so no need to append the URL of the link
}else{
// click even on links that are clicked without the CTRL key pressed
$('a').on('click', function() {
// is this link local on the same domain as this page is?
if( window.location.hostname === this.hostname ) {
// new URL with ?sidebar=no appended to the URL of local links that are clicked on inside of an iframe
var linkUrl = $(this).attr('href');
var noSidebarUrl = $(this).attr('href')+'?sidebar=no';
// send URL to parent window
parent.window.postMessage('message-for-parent=' +linkUrl , '*');
alert('load URL with no sidebar: '+noSidebarUrl+' and update URL in arent window to: '+linkUrl);
// load Iframe with clicked on URL content
//document.location.href = url;
//return false;
}
});
}
}
</script>
Parent window
<script>
// parent_on_message(e) will handle the reception of postMessages (a.k.a. cross-document messaging or XDM).
function parent_on_message(e) {
// You really should check origin for security reasons
// https://developer.mozilla.org/en-US/docs/DOM/window.postMessage#Security_concerns
//if (e.origin.search(/^http[s]?:\/\/.*\.localhost/) != -1
// && !($.browser.msie && $.browser.version <= 7)) {
var returned_pair = e.data.split('=');
if (returned_pair.length != 2){
return;
}
if (returned_pair[0] === 'message-for-parent') {
alert(returned_pair[1]);
window.history.pushState('obj', 'newtitle', returned_pair[1]);
}else{
console.log("Parent received invalid message");
}
//}
}
jQuery(document).ready(function($) {
// Setup XDM listener (except for IE < 8)
if (!($.browser.msie && $.browser.version <= 7)) {
// Connect the parent_on_message(e) handler function to the receive postMessage event
if (window.addEventListener){
window.addEventListener("message", parent_on_message, false);
}else{
window.attachEvent("onmessage", parent_on_message);
}
}
});
</script>
Another solution using Window.postMessage().
Iframe:
/test
/test2
<script>
Array.from(document.querySelectorAll('a')).forEach(el => {
el.addEventListener('click', event => {
event.preventDefault();
window.parent.postMessage(this.href, '*');
});
});
</script>
Main page:
Current URL: <div id="current-url"></div>
<iframe src="iframe-url"></iframe>
<script>
const $currentUrl = document.querySelector('#current-url');
$currentUrl.textContent = location.href;
window.addEventListener('message', event => {
history.pushState(null, null, event.data);
$currentUrl.textContent = event.data;
});
</script>
See demo on JS Fiddle.
It seems that $('#someIframe').load(function(){...}) won't fire if it is attached after the iframe has finished loading. Is that correct?
What I'd really like is to have a function that is always called once when or after an iframe has loaded. To make this clearer, here are two cases:
Iframe hasn't loaded yet: run a callback function once it loads.
Iframe has already loaded: run the callback immediately.
How can I do this?
I've banged my head against a wall until I found out what's happening here.
Background information
Using .load() isn't possible if the iframe has already been loaded (event will never fire)
Using .ready() on an iframe element isn't supported (reference) and will call the callback immediately even if the iframe isn't loaded yet
Using postMessage or a calling a container function on load inside the iframe is only possible when having control over it
Using $(window).load() on the container would also wait for other assets to load, like images and other iframes. This is not a solution if you want to wait only for a specific iframe
Checking readyState in Chrome for an alredy fired onload event is meaningless, as Chrome initializes every iframe with an "about:blank" empty page. The readyState of this page may be complete, but it's not the readyState of the page you expect (src attribute).
Solution
The following is necessary:
If the iframe is not loaded yet we can observe the .load() event
If the iframe has been loaded already we need to check the readyState
If the readyState is complete, we can normally assume that the iframe has already been loaded. However, because of the above-named behavior of Chrome we furthermore need to check if it's the readyState of an empty page
If so, we need to observe the readyState in an interval to check if the actual document (related to the src attribute) is complete
I've solved this with the following function. It has been (transpiled to ES5) successfully tested in
Chrome 49
Safari 5
Firefox 45
IE 8, 9, 10, 11
Edge 24
iOS 8.0 ("Safari Mobile")
Android 4.0 ("Browser")
Function taken from jquery.mark
/**
* Will wait for an iframe to be ready
* for DOM manipulation. Just listening for
* the load event will only work if the iframe
* is not already loaded. If so, it is necessary
* to observe the readyState. The issue here is
* that Chrome will initialize iframes with
* "about:blank" and set its readyState to complete.
* So it is furthermore necessary to check if it's
* the readyState of the target document property.
* Errors that may occur when trying to access the iframe
* (Same-Origin-Policy) will be catched and the error
* function will be called.
* #param {jquery} $i - The jQuery iframe element
* #param {function} successFn - The callback on success. Will
* receive the jQuery contents of the iframe as a parameter
* #param {function} errorFn - The callback on error
*/
var onIframeReady = function($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) { // accessing contentWindow failed
errorFn();
}
};
Working example
Consisting of two files (index.html and iframe.html):
index.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Parent</title>
</head>
<body>
<script src="https://code.jquery.com/jquery-1.12.2.min.js"></script>
<script>
$(function() {
/**
* Will wait for an iframe to be ready
* for DOM manipulation. Just listening for
* the load event will only work if the iframe
* is not already loaded. If so, it is necessary
* to observe the readyState. The issue here is
* that Chrome will initialize iframes with
* "about:blank" and set its readyState to complete.
* So it is furthermore necessary to check if it's
* the readyState of the target document property.
* Errors that may occur when trying to access the iframe
* (Same-Origin-Policy) will be catched and the error
* function will be called.
* #param {jquery} $i - The jQuery iframe element
* #param {function} successFn - The callback on success. Will
* receive the jQuery contents of the iframe as a parameter
* #param {function} errorFn - The callback on error
*/
var onIframeReady = function($i, successFn, errorFn) {
try {
const iCon = $i.first()[0].contentWindow,
bl = "about:blank",
compl = "complete";
const callCallback = () => {
try {
const $con = $i.contents();
if($con.length === 0) { // https://git.io/vV8yU
throw new Error("iframe inaccessible");
}
successFn($con);
} catch(e) { // accessing contents failed
errorFn();
}
};
const observeOnload = () => {
$i.on("load.jqueryMark", () => {
try {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href !== bl || src === bl || src === "") {
$i.off("load.jqueryMark");
callCallback();
}
} catch(e) {
errorFn();
}
});
};
if(iCon.document.readyState === compl) {
const src = $i.attr("src").trim(),
href = iCon.location.href;
if(href === bl && src !== bl && src !== "") {
observeOnload();
} else {
callCallback();
}
} else {
observeOnload();
}
} catch(e) { // accessing contentWindow failed
errorFn();
}
};
var $iframe = $("iframe");
onIframeReady($iframe, function($contents) {
console.log("Ready to got");
console.log($contents.find("*"));
}, function() {
console.log("Can not access iframe");
});
});
</script>
<iframe src="iframe.html"></iframe>
</body>
</html>
iframe.html:
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Child</title>
</head>
<body>
<p>Lorem ipsum</p>
</body>
</html>
You can also change the src attribute inside index.html to e.g. "http://example.com/". Just play around with it.
I'd use postMessage. The iframe can assign its own onload event and post to the parent. If there are timing issues just make sure to assign the parent's postMessage handler before creating the iframe.
For this to work the iframe must know the url of the parent, for instance by passing a GET parameter to the iframe.
This function will run your callback function immediately if the iFrame is already loaded or wait until the iFrame is completely loaded before running your callback function.
Just pass in your callback function that you want to run when the iFrame finishes loading and the element to this function:
function iframeReady(callback, iframeElement) {
const iframeWindow = iframeElement.contentWindow;
if ((iframeElement.src == "about:blank" || (iframeElement.src != "about:blank" && iframeWindow.location.href != "about:blank")) && iframeWindow.document.readyState == "complete") {
callback();
} else {
iframeWindow.addEventListener("load", callback);
}
}
This will take care of the most common issues like chrome initializing iframe with about:blank and iFrame not supporting DOMContentLoaded event. See this https://stackoverflow.com/a/69694808/15757382 answer for explanation.
I had the same problem. In my case, I simply checked if the onload function is fired or not.
var iframe = document.getElementById("someIframe");
var loadingStatus = true;
iframe.onload = function () {
loadingStatus = false;
//do whatever you want [in my case I wants to trigger postMessage]
};
if (loadingStatus)
//do whatever you want [in my case I wants to trigger postMessage]
I tried very hard to come to a solution that worked consistently cross browser. IMPORTANT: I was not able to come to such a solution. But here is as far as I got:
// runs a function after an iframe node's content has loaded
// note, this almost certainly won't work for frames loaded from a different domain
// secondary note - this doesn't seem to work for chrome : (
// another note - doesn't seem to work for nodes created dynamically for some reason
function onReady(iframeNode, f) {
var windowDocument = iframeNode[0].contentWindow.document;
var iframeDocument = windowDocument?windowDocument : iframeNode[0].contentWindow.document;
if(iframeDocument.readyState === 'complete') {
f();
} else {
iframeNode.load(function() {
var i = setInterval(function() {
if(iframeDocument.readyState === 'complete') {
f();
clearInterval(i);
}
}, 10);
});
}
}
and I was using it like this:
onReady($("#theIframe"), function() {
try {
var context = modal[0].contentWindow;
var i = setInterval(function() {
if(context.Utils !== undefined && context.$) { // this mess is to attempt to get it to work in firefox
context.$(function() {
var modalHeight = context.someInnerJavascript();
clearInterval(i);
});
}
}, 10);
} catch(e) { // ignore
console.log(e);
}
});
Note that even this does not solve the problem for me. Here are some problems with this solution:
In onReady, for iframes that were added dynamically, iframeDocument.readyState seems to be stuck at "uninitialized" and thus the callback never fires
The whole setup still doesn't seem to work in firefox for some reason. It almost seems like the setInterval function is cleared externally.
Note that some of these problems only happen when there is a lot of other stuff loading on the page, which makes the timing of these things less deterministic.
So if anyone can improve upon this, it would be much appreciated.
Only when the content inside the iframe is loaded innerDoc is true and fires code inside the if.
window.onload = function(){
function manipulateIframe(iframeId, callback) {
var iframe = document.getElementById(iframeId).contentWindow.document;
callback(iframe);
};
manipulateIframe('IFwinEdit_forms_dr4r3_forms_1371601293572', function (iframe) {
console.log(iframe.body);
});};
example
I think you should try using onreadystatechange event.
http://jsfiddle.net/fk8fc/3/
$(function () {
var innerDoc = ($("#if")[0].contentDocument) ? $("#if")[0].contentDocument : $("#if")[0].contentWindow.document;
console.debug(innerDoc);
$("#if").load( function () {
alert("load");
alert(innerDoc.readyState)
});
innerDoc.onreadystatechange = function () {
alert(innerDoc.readyState)
};
setTimeout(innerDoc.onreadystatechange, 5000);
});
EDIT: the context is not what I think it is. you can just check the readyState of iframe document and everything should be fine.
OP: This is a packaged up function I made from the concepts described above:
// runs a function after an iframe node's content has loaded
// note, this almost certainly won't work for frames loaded from a different domain
onReady: function(iframeNode, f) {
var windowDocument = iframeNode[0].contentWindow.document;
var iframeDocument = windowDocument?windowDocument : iframeNode[0].contentWindow.document
if(iframeDocument.readyState === 'complete') {
f();
} else {
iframeNode.load(f);
}
}