Ensuring that onLoad of an IFRAME doesn't execute too soon - javascript

I just discovered that the source of my issues is that the parent HTML makes calls to controls in a child IFRAME but is too quick about that and, sometimes, the super onLoad attempts to do so before the sub onLoad had a chance to add stuff to the DOM.
What can I do about it?
I've tried to set up some kind of feed-back from onLoad in the child. Failed miserably with so many strange errors that they can be summarized by dude, just please don't.
I've tried to set up a delayer, which is ugly of epic proportions and not 100% reliable.
EDIT:
In onload I do this:
var stuff = getReferenceToStuff();
var someDiv = stuff.contentWindow.document.getElementById("someDiv");
someDiv.className = "classy";
The problem is that sometimes the reference someDiv is null and sometimes (often when I reload the page by F5), it points to the correct element. I know it's because the contents of the IFRAME are a bit slower.
So my questions is this. How can I ensure that onload is postponed until the embedded IFRAME component's onload ensures that it's been loaded and all the components are there?

The onLoad event isn't always working correctly on document. It works correctly on each element though.
var iframes = document.getElementsByTagName('iframe'),
counter = 0,
max = iframes.length;
[].forEach.call(iframes, iLoaded);
function iLoaded() {
if (++counter === max) {
callback();
}
}
function callback() {
// All the iframes are loaded
}

When using window.onload all content of the body, including content of the iframes and all other resources like images, should be loaded before the onload is fired. However, some browsers have had problems with timing on firing onload, i.e. different browsers trigger the event in different stage of page parsing.
If you're using DOMContentLoaded or jQuery's $(document).ready(), only the HTML of the main page is loaded, but some resources are still under work (including iframe's content loading). I'm not aware what happens if you attach an inline listener for the iframe element itself.
If there's timing problems, maybe not trigger the function needing iframe reference in main window at all. Instead invoke that function in main window in irame's window.onload. But even this won't tackle the problem, if you're using some asynchronous technique to populate the iframe. In this case you need to invoke the function in main window after all requests have been completed.
(Now you maybe also know, what are the codesnippets I'd like to see in your post : ) ).

I have 2 solutions to your problem:
If you are on HTML5, use window.postMessage. Just message from iFrame to the parent in the onload of iFrame. Parent should register handler in <script> tag, that appears before iFrame.
Add a callback function to window in the '' tag before iFrame. This function is called by iFrame when it's load is complete. Here is the basic template.
Here is the sample template:
<script>
window.iframeCallback = function(message) {
// first clear the temp function we added to the window
// It is a bad practice to corrupt the global namespace
delete window.iframeCallback;
// you do your work here
};
</script>
..
..
<!-- iFrame should appear after the above script-->
<iframe/>

Related

Diagnosing body.onload not being called

I have some web pages which rely on body.onload (or, equivalently, window.onload) to set them up properly. Sometimes onload is not being called.
Is there a trick with some web browser (ideally Chrome, where this apparently happens most often) which will tell me what exactly is preventing the page from loading successfully?
Clue: this rarely (maybe even never) happens when I hit F5 to reload the whole page, but more generally it happens if a page has been arrived-at by clicking a link or pasting the url into the address bar. Is there a quirk of onload semantics that might be tripping me up?
N.B. The scripts themselves are not producing any errors in the console.
I think you want window.onload
I have tested such cases, and none of the following will work:
var callback = function() { alert("Body loaded"); };
$("body").load(callback);
document.body.addEventListener("load",callback,false);
However, document.body.onload seems to work fine. Make sure that body is correctly namespaced:
document.body //<body>
body //Reference error
If you're talking about:
<body onload="callback();"></body>
Then go back and review your code, because it should work.
Personally, I suggest using the load event of the window object or a framework's ready event.
//Execute when the window is loaded
var callback = function() {
//Your code goes here...
};
if (window.addEventListener) window.addEventListener("load",callback,false);
else window.attachEvent("load",callback);
With jQuery, you only need the following:
$(window).load(callback);
jQuery's ready event is as follows:
$(document).ready(callback);
//Or just:
$(callback);
MooTools use this:
window.addEvent("domready",callback);
And different libraries all have their own way.

Javascript: possible for parent to kill child iframe if it is stuck in an infinite loop?

I have a page that has an iframe with external content. I don't want infinite loops in the external content to crash my whole page. Is there any way to get around this.
I tried to set something up where the parent postMessages the child iframe every so often and if the child Iframe doesn't respond for too long a time the parent changes the iframes src, but this doesn't seem to work. The parent's setTimeout functions no longer execute once the iframe starts looping. See my code here (note that it will crash your tab if you execute it, open the console before execution to view the logging):
<html>
<head>
</head>
<body>
<script type="text/javascript">
var scr = 'script';
var html = '<html><head><script>\n' +
' window.addEventListener("message", answer, false);' +
' function answer() { console.log("answered"); parent.postMessage(\'hi\', \'*\');}' +
' setTimeout("while(1){console.log(\'in loop\')};", 3000)' +
"</" + scr + "></head><body>IFRAME</body</html>";
var lastAnswer = (new Date()).getTime();
var iframe = document.createElement('iframe');
queryChild();
window.addEventListener("message", receive, false);
function receive() {
lastAnswer = (new Date()).getTime();
console.log('got answer');
}
function queryChild() {
console.log('querying');
if((new Date()).getTime() - lastAnswer > 5000) {
console.log('killing');
iframe.src = '';
} else if(iframe.contentWindow){
iframe.contentWindow.postMessage('hi', '*');
}
setTimeout(queryChild, 2000);
};
document.body.appendChild(iframe);
iframe.contentWindow.document.open();
iframe.contentWindow.document.write(html);
iframe.contentWindow.document.close();
</script>
</body>
</html>
Any suggestions on how to solve this problem?
My experience with this kind of problem is that unless you can access the external code before feeding it to the iframe (either as an URL or via the srcdoc attribute), the loop will completely interrupt any JavaScript execution.
Whatever kind of timeout functionality you implement, it will not be called due to the iframe code execution consuming 100% resources until the browser reports a crash.
Your options are:
Sanitize the code automatically before adding it to the iframe, which proves impractical since there are infinite ways to loop infinitely, and you will not be able to catch them all. You would have to write a scanner script that could detect infinite loops while not crashing in the course of scanning the code.
Use a sandboxing solution like Google Caja to sanitize the code. However, this will change the code structurally if not configured heavily.
In case of an application that has capabilites of creating virtual environments and monitoring them, you could execute the iframe code (let's say on a virtual machine of sorts), check if the process locks up and use that outcome to determine if you can safely set the iframe.src property to your code's URL. This might be the only solution that can guarantee some sort of guarantee that this code will not lock up immediately (however, there are many ways to have race conditions at some later point of execution, so there will not be a sure way to say it will never lock up the browser).
Summary: Unless you can find a way to test the code extensively before showing it in the iframe, you can not guarantee that the iframe code will not lock up the browser tab.
It depends highly on the browser, some browser uses one thread for each page (or iframe), in this case your script cannot be executed until the iframe execution is over (the infinite loop). Some others have one thread per page (or iframe) and maybe you are able to do it.
What I'm sure is than if you expect to support enterprise browsers (like IE8) you can't.
This
console.log('killing');
iframe.src = '';
Will not kill the iframe. according to same origin policy you can not manipulate external domains from your domain. Just changing the src of an iframe doesn't trigger a navigation in the iframe. src will change, but iframe will not get navigated.
If your get the message from inner iframe you should remove the iframe from your document tree and insert a new one in document tree in order to kill the iframe using removeChild
document.body.removeChild(iframe);
document.body.appendChild(newiframe);
Look at this simple demonstration I've created here: http://jsbin.com/avodeb/1/
Try this:
<script>
document.getElementById('iframeID').onload= function() { //When the iframe loads quickly
clearTimeout(killerTimer); // Stop the Killer Timer
};
var killerTimer = setTimeout( function() {
document.getElementById("iframeID").setAttribute("src",""); //Otherwise, kill the iframe
}, 3000 );
</script>

About Window onload, ondomready etc

From Geek Daily :
The onload event does not fire until every last piece of the page is loaded.
Now I have a inframe in my jsf application and I can not replace by any means, this iframe with any other jsf component. I am trying to set the scroll position of the scrollbar resides in that iframe to a particular coordinate when the iframe is loaded. I have tried this:
window.onLoad(onLoad);
function onLoad() {
var frames = window.parent.frames;
var iframe = frames['frameR'];
iframe.contentWindow.scrollTo(0, 200);
}
But it's not working. I placed an alert(iframe) after var iframe = frames['frameR']; and it was displaying a message [object Window] but iframe.contentWindow.scrollTo(0, 200); is not working at all.
Either I am doing something wrong or as all the jsf components are not rendered in the iframe, the iframe's scroll position can be set then.
Is there any way to achieve this functionality?
Is onLoad is the last event before render of all the components?
The method you are looking for is window.onload, note capitalisation.
It is best to set the frame's onload listener from the code going into the iframe. It can be done from the parent document, but if you wait for the main document's load event to make sure the iFrame exists, the iframe's load may have already fired.
You can also use a script element immediately below the iFrame to set it's onload property:
<iframe id="frameR" src="..."></iframe>
<script>
var el = document.getElementById('frameR');
if (el) el.onload = doStuff;
</script>

How to ensure YUI object to be created first

I like YUI sandbox but I have a problem with it. Consider a document with an iframe (for instance ckedior that I included) with an initialization script. It is always executed before the outer document's sections (don't know why).
And I need to call a function, that is initialized in YUI sandbox in the parent document. But it can never be initialized, because the execution starts in the iframe.
var getWordCount;
AUI().ready('aui-node', 'console', function(A) {
getWordCount = function (htmlData) {
var target = A.one('div.my-png-image');
target.one('div:fist-child').set('text', strip(htmlData).trim().split(/\s+/).length);
};
});
Without yui sandbox I'd just declared a function in the parent document, and even tough the execution starts in the iframe, the global function would exist and could be called from the iframe.
The problem might be AUI.ready, as it executes only when the whole HTML is loaded. This means the iFrame would load before the browser triggers the .ready-event.
You could try to test this by changing the AUI event you use to trigger your code, and put the code you want to run before the iFrame-load outside the AUI.ready.
If you need a certain element in dOM to be loaded before running, you could try checking if the element is loaded and then run your script, like in this example.
Could you perhaps create the IFrame from within the AUI().ready?

Detecting the onload event of a window opened with window.open

window.popup = window.open($(this).attr('href'), 'Ad', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0');
$(window.popup).onload = function()
{
alert("Popup has loaded a page");
};
This doesn't work in any browser I've tried it with (IE, Firefox, Chrome). How can I detect when a page is loaded in the window (like an iframe onload)?
var myPopup = window.open(...);
myPopup.addEventListener('load', myFunction, false);
If you care about IE, use the following as the second line instead:
myPopup[myPopup.addEventListener ? 'addEventListener' : 'attachEvent'](
(myPopup.attachEvent ? 'on' : '') + 'load', myFunction, false
);
As you can see, supporting IE is quite cumbersome and should be avoided if possible. I mean, if you need to support IE because of your audience, by all means, do so.
If the pop-up's document is from a different domain, this is simply not possible.
Update April 2015: I was wrong about this: if you own both domains, you can use window.postMessage and the message event in pretty much all browsers that are relevant today.
If not, there's still no way you'll be able to make this work cross-browser without some help from the document being loaded into the pop-up. You need to be able to detect a change in the pop-up that occurs once it has loaded, which could be a variable that JavaScript in the pop-up page sets when it handles its own load event, or if you have some control of it you could add a call to a function in the opener.
As noted at Detecting the onload event of a window opened with window.open, the following solution is ideal:
/* Internet Explorer will throw an error on one of the two statements, Firefox on the other one of the two. */
(function(ow) {
ow.addEventListener("load", function() { alert("loaded"); }, false);
ow.attachEvent("onload", function() { alert("loaded"); }, false);
})(window.open(prompt("Where are you going today?", location.href), "snapDown"));
Other comments and answers perpetrate several erroneous misconceptions as explained below.
The following script demonstrates the fickleness of defining onload. Apply the script to a "fast loading" location for the window being opened, such as one with the file: scheme and compare this to a "slow" location to see the problem: it is possible to see either onload message or none at all (by reloading a loaded page all 3 variations can be seen). It is also assumed that the page being loaded itself does not define an onload event which would compound the problem.
The onload definitions are evidently not "inside pop-up document markup":
var popup = window.open(location.href, "snapDown");
popup.onload = function() { alert("message one"); };
alert("message 1 maybe too soon\n" + popup.onload);
popup.onload = function() { alert("message two"); };
alert("message 2 maybe too late\n" + popup.onload);
What you can do:
open a window with a "foreign" URL
on that window's address bar enter a javascript: URI -- the code will run with the same privileges as the domain of the "foreign" URL
The javascript: URI may need to be bookmarked if typing it in the address bar has no effect (may be the case with some browsers released around 2012)
Thus any page, well almost, irregardless of origin, can be modified like:
if(confirm("wipe out links & anchors?\n" + document.body.innerHTML))
void(document.body.innerHTML=document.body.innerHTML.replace(/<a /g,"< a "))
Well, almost:
jar:file:///usr/lib/firefox/omni.ja!/chrome/toolkit/content/global/aboutSupport.xhtml, Mozilla Firefox's troubleshooting page and other Jar archives are exceptions.
As another example, to routinely disable Google's usurping of target hits, change its rwt function with the following URI:
javascript: void(rwt = function(unusurpURL) { return unusurpURL; })
(Optionally Bookmark the above as e.g. "Spay Google" ("neutralize Google"?)
This bookmark is then clicked before any Google hits are clicked, so bookmarks of any of those hits are clean and not the mongrelized perverted aberrations that Google made of them.
Tests done with Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:11.0) Gecko/20100101 Firefox/11.0 UA string.
It should be noted that addEventListener in Firefox only has a non-standard fourth, boolean parameter, which if true allows untrusted content triggers to be instantiated for foreign pages.
Reference:
element.addEventListener | Document Object Model (DOM) | MDN:
Interaction between privileged and non-privileged pages | Code snippets | MDN:
This did the trick for me; full example:
HTML:
Click for my popup on same domain
Javascript:
(function(){
var doc = document;
jQuery('.import').click(function(e){
e.preventDefault();
window.popup = window.open(jQuery(this).attr('href'), 'importwindow', 'width=500, height=200, top=100, left=200, toolbar=1');
window.popup.onload = function() {
window.popup.onbeforeunload = function(){
doc.location.reload(true); //will refresh page after popup close
}
}
});
})();
onload event handler must be inside popup's HTML <body> markup.
First of all, when your first initial window is loaded, it is cached. Therefore, when creating a new window from the first window, the contents of the new window are not loaded from the server, but are loaded from the cache. Consequently, no onload event occurs when you create the new window.
However, in this case, an onpageshow event occurs. It always occurs after the onload event and even when the page is loaded from cache. Plus, it now supported by all major browsers.
window.popup = window.open($(this).attr('href'), 'Ad', 'left=20,top=20,width=500,height=500,toolbar=1,resizable=0');
$(window.popup).onpageshow = function() {
alert("Popup has loaded a page");
};
The w3school website elaborates more on this:
The onpageshow event is similar to the onload event, except that it occurs after the onload event when the page first loads. Also, the onpageshow event occurs every time the page is loaded, whereas the onload event does not occur when the page is loaded from the cache.
The core problem seems to be you are opening a window to show a page whose content is already cached in the browser. Therefore no loading happens and therefore no load-event happens.
One possibility could be to use the 'pageshow' -event instead, as described in:
https://support.microsoft.com/en-us/help/3011939/onload-event-does-not-occur-when-clicking-the-back-button-to-a-previou
Simple solution:
new_window = window.open(...);
new_window.document.write('<body onload="console.log(1);console.log(2);></body>');

Categories