Detect failure to load contents of an iframe

JavascriptHtmlFirefox AddonFramesXulrunner

Javascript Problem Overview


I can detect when the content of an iframe has loaded using the load event. Unfortunately, for my purposes, there are two problems with this:

  • If there is an error loading the page (404/500, etc), the load event is never fired.
  • If some images or other dependencies failed to load, the load event is fired as usual.

Is there some way I can reliably determine if either of the above errors occurred?

I'm writing a semi-web semi-desktop application based on Mozilla/XULRunner, so solutions that only work in Mozilla are welcome.

Javascript Solutions


Solution 1 - Javascript

If you have control over the iframe page (and the pages are on the same domain name), a strategy could be as follows:

  • In the parent document, initialize a variable var iFrameLoaded = false;
  • When the iframe document is loaded, set this variable in the parent to true calling from the iframe document a parent's function (setIFrameLoaded(); for example).
  • check the iFrameLoaded flag using the timer object (set the timer to your preferred timeout limit) - if the flag is still false you can tell that the iframe was not regularly loaded.

I hope this helps.

Solution 2 - Javascript

I had this problem recently and had to resort to setting up a Javascript Polling action on the Parent Page (that contains the IFRAME tag). This JavaScript function checks the IFRAME's contents for explicit elements that should only exist in a GOOD response. This assumes of course that you don't have to deal with violating the "same origin policy."

Instead of checking for all possible errors which might be generated from the many different network resources.. I simply checked for the one constant positive Element(s) that I know should be in a good response.

After a pre-determined time and/or # of failed attempts to detect the expected Element(s), the JavaScript modifies the IFRAME's SRC attribute (to request from my Servlet) a User Friendly Error Page as opposed to displaying the typical HTTP ERROR message. The JavaScript could also just as easily modify the SRC attribute to make an entirely different request.

function checkForContents(){
var contents=document.getElementById('myiframe').contentWindow.document
if(contents){
	alert('found contents of myiframe:' + contents);
	if(contents.documentElement){
		if(contents.documentElement.innerHTML){
			alert("Found contents: " +contents.documentElement.innerHTML);
			if(contents.documentElement.innerHTML.indexOf("FIND_ME") > -1){
				openMediumWindow("woot.html", "mypopup");
			}
		}
	}
}

}

Solution 3 - Javascript

This is a very late answer, but I will leave it to someone who needs it.

Task: load iframe cross-origin content, emit onLoaded on success and onError on load error.

This is the most cross browsers origin independent solution I could develop. But first of all I will briefly tell about other approaches I had and why they are bad.

1. iframe That was a little shock for me, that iframe only has onload event and it is called on load and on error, no way to know it is error or not.

2. performance.getEntriesByType('resource'). This method returns loaded resources. Sounds like what we need. But what a shame, firefox always adds Resource in resources array no matter it is loaded or failed. No way to know by Resource instance was it success. As usual. By the way, this method does not work in ios<11.

3. script I tried to load html using <script> tag. Emits onload and onerror correctly, sadly, only in Chrome.

And when I was ready to give up, my elder collegue told me about html4 tag <object>. It is like <iframe> tag except it has fallbacks when content is not loaded. That sounds like what we are need! Sadly it is not as easy as it sounds.

CODE SECTION

var obj = document.createElement('object');

// we need to specify a callback (i will mention why later)
obj.innerHTML = '<div style="height:5px"><div/>'; // fallback

obj.style.display = 'block'; // so height=5px will work
obj.style.visibility = 'hidden'; // to hide before loaded

obj.data = src;

After this we can set some attributes to <object> like we'd wanted to do with iframe. The only difference, we should use <params>, not attributes, but their names and values are identical.

for (var prop in params) {
    if (params.hasOwnProperty(prop)) {
        var param = document.createElement('param');
        param.name = prop;
        param.value = params[prop];

        obj.appendChild(param);
    }
}

Now, the hard part. Like many same-like elements, <object> doesn't have specs for callbacks, so each browser behaves differently.

  • Chrome. On error and on load emits load event.
  • Firefox. Emits load and error correctly.
  • Safari. Emits nothing....

Seems like no different from iframe, getEntriesByType, script.... But, we have native browser fallback! So, because we set fallback (innerHtml) directly, we can tell if <object> is loaded or not

function isReallyLoaded(obj) {
    return obj.offsetHeight !== 5; // fallback height
}
/**
 * Chrome calls always, Firefox on load
 */
obj.onload = function() {
    isReallyLoaded(obj) ? onLoaded() : onError();
};

/**
 * Firefox on error
 */
obj.onerror = function() {
    onError();
};

But what to do with Safari? Good old setTimeout.

var interval = function() {
    if (isLoaded) { // some flag
        return;
    }

    if (hasResult(obj)) {
        if (isReallyLoaded(obj)) {
            onLoaded();
        } else {
            onError();
        }
    }

    setTimeout(interval, 100);
};

function hasResult(obj) {
    return obj.offsetHeight > 0;
}

Yeah.... not so fast. The thing is, <object> when fails has unmentioned in specs behaviour:

  1. Trying to load (size=0)
  2. Fails (size = any) really
  3. Fallback (size = as in innnerHtml)

So, code needs a little enhancement

var interval = function() {
    if (isLoaded) { // some flag
        return;
    }

    if (hasResult(obj)) {
        if (isReallyLoaded(obj)) {
            interval.count++;
            // needs less then 400ms to fallback
            interval.count > 4 && onLoadedResult(obj, onLoaded);
        } else {
            onErrorResult(obj, onError);
        }
    }

    setTimeout(interval, 100);
};
interval.count = 0;

setTimeout(interval, 100);

Well, and to start loading

document.body.appendChild(obj);

That is all. I tried to explain code in every detail, so it may look not so foolish.

P.S. WebDev sucks

Solution 4 - Javascript

I think that the pageshow event is fired for error pages. Or if you're doing this from chrome, then your check your progress listener's request to see if it's an HTTP channel in which case you can retrieve the status code.

As for page dependencies, I think you can only do this from chrome by adding a capturing onerror event listener, and even then it will only find errors in elements, not CSS backgrounds or other images.

Solution 5 - Javascript

Have a id for the top most (body) element in the page that is being loaded in your iframe.

on the Load handler of your iframe, check to see if getElementById() returns a non null value. If it is, iframe has loaded successfully. else it has failed.

in that case, put frame.src="about:blank". Make sure to remove the loadhandler before doing that.

Solution 6 - Javascript

Doesn't answer your question exactly, but my search for an answer brought me here, so I'm posting just in case anyone else had a similar query to me.

It doesn't quite use a load event, but it can detect whether a website is accessible and callable (if it is, then the iFrame, in theory, should load).

At first, I thought to do an AJAX call like everyone else, except that it didn't work for me initially, as I had used jQuery. It works perfectly if you do a XMLHttpRequest:

var url = http://url_to_test.com/
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
    if (this.readyState == 4 && this.status != 200) {
        console.log("iframe failed to load");
    }
};
xhttp.open("GET", url, true);
xhttp.send();

Edit:
So this method works ok, except that it has a lot of false negatives (picks up a lot of stuff that would display in an iframe) due to cross-origin malarky. The way that I got around this was to do a CURL/Web request on a server, and then check the response headers for a) if the website exists, and b) if the headers had set x-frame-options.

This isn't a problem if you run your own webserver, as you can make your own api call for it.

My implementation in node.js:

app.get('/iframetest',function(req,res){ //Call using /iframetest?url=url - needs to be stripped of http:// or https://
   var url = req.query.url; 
    var request = require('https').request({host: url}, function(response){ //This does an https request - require('http') if you want to do a http request
        var headers = response.headers;
        if (typeof headers["x-frame-options"] != 'undefined') {
            res.send(false); //Headers don't allow iframe
        } else {
            res.send(true); //Headers don't disallow iframe
        }
    });
    request.on('error',function(e){
       res.send(false); //website unavailable
    });
    request.end();
});

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionDaniel CassidyView Question on Stackoverflow
Solution 1 - JavascriptsplattneView Answer on Stackoverflow
Solution 2 - JavascriptChrisView Answer on Stackoverflow
Solution 3 - JavascriptДмитрий КиселевView Answer on Stackoverflow
Solution 4 - JavascriptNeilView Answer on Stackoverflow
Solution 5 - JavascriptKasturiView Answer on Stackoverflow
Solution 6 - JavascriptimmanisView Answer on Stackoverflow