'load' event not firing when iframe is loaded in Chrome
Asked Answered
D

7

37

I am trying to display a 'mask' on my client while a file is dynamically generated server side. Seems like the recommend work around for this (since its not ajax) is to use an iframe and listen from the onload or done event to determine when the file has actually shipped to the client from the server.

here is my angular code:

    var url = // url to my api
    var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
    e.load(function() {
        $scope.$apply(function() {
            $scope.exporting = false;  // this will remove the mask/spinner
        });
    });
    angular.element('body').append(e);

This works great in Firefox but no luck in Chrome. I have also tried to use the onload function:

e.onload = function()  { //unmask here }

But I did not have any luck there either.

Ideas?

Dormie answered 13/12, 2013 at 17:42 Comment(7)
Have you tried adding the onload directly to the iframe's html? I've had some issues with the order Chrome loads elements & associated events in the past.Locoweed
Yup just tried. Still fires in FF but not in Chrome.Dormie
Hmm, what about the url? You're not going cross-domain are you?Locoweed
wonder if its a problem with the headers the server is setting. I am setting Content-Type = 'application/zip', and Content-Disposition = 'attachment; filename=temp.zip. Not really sure what else it could be.Dormie
Here is a plunker to show the problem. Run in FF works just great, run in Chrome not so much: plnkr.co/edit/zkqeAW?p=previewDormie
The plunker works well in Chrome 44+, the url setted to iframe src must have the HTTP headers below: X-Content-Type-Options:nosniff and X-Frame-Options:denyAgreed
@jetma It does not work even after setting the headers as you say.Priorate
M
26

Unfortunately it is not possible to use an iframe's onload event in Chrome if the content is an attachment. This answer may provide you with an idea of how you can work around it.

Marion answered 17/12, 2013 at 1:24 Comment(2)
So long story short is that readystatechange and onload event handlers are not called for downloads in Chrome.Marion
Please refer to the following post for a sample code: stackoverflow.com/questions/12076494/…. It took me a bit of time to figure out that a number of other answers elsewhere, which refer to the use of the onload event, where actually false. It's possibly something to do with the latest web browsers being more aligned with the specification.Unpleasant
L
17

I hate this, but I couldn't find any other way than checking whether it is still loading or not except by checking at intervals.

var timer = setInterval(function () {
    iframe = document.getElementById('iframedownload');
    var iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
    // Check if loading is complete
    if (iframeDoc.readyState == 'complete' || iframeDoc.readyState == 'interactive') {
        loadingOff();
        clearInterval(timer);
        return;
    }
}, 4000);
Lisabethlisan answered 6/5, 2015 at 0:56 Comment(2)
This should be the accepted answer for direct downloads. The only part I would add is a max time so it doesn't sit and wait forever.Redemption
This doesn't work. Even before the server starts sending a response, the iframeDoc.readyState is set to completed.Priorate
D
4

You can do it in another way:

In the main document:

function iframeLoaded() {
     $scope.$apply(function() {
            $scope.exporting = false;  // this will remove the mask/spinner
        });
}

var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element('body').append(e);

In the iframe document (this is, inside the html of the page referenced by url)

    window.onload = function() {
        parent.iframeLoaded();
    }

This will work if the main page, and the page inside the iframe are in the same domain.

Actually, you can access the parent through:

window.parent
parent
//and, if the parent is the top-level document, and not inside another frame
top          
window.top

It's safer to use window.parent since the variables parent and top could be overwritten (usually not intended).

Delvalle answered 17/12, 2013 at 9:21 Comment(0)
E
2

you have to consider 2 points:

1- first of all, if your url has different domain name, it is not possible to do this except when you have access to the other domain to add the Access-Control-Allow-Origin: * header, to fix this go to this link.

2- but if it has the same domain or you have added Access-Control-Allow-Origin: * to the headers of your domain, you can do what you want like this:

var url = // url to my api
var e = angular.element("<iframe style='display:none' src=" + url + "></iframe>");
angular.element(document.body).append(e);
e[0].contentWindow.onload = function() {
    $scope.$apply(function() {
        $scope.exporting = false;  // this will remove the mask/spinner
    });
};

I have done this in all kinds of browsers.

Emetic answered 20/12, 2013 at 17:53 Comment(0)
E
1

I had problems with the iframe taking too long to load. The iframe registered as loaded while the request wasn't handled. I came up with the following solution:

JS

Function:

function iframeReloaded(iframe, callback) {
    let state = iframe.contentDocument.readyState;
    let checkLoad = setInterval(() => {
        if (state !== iframe.contentDocument.readyState) {
            if (iframe.contentDocument.readyState === 'complete') {
                clearInterval(checkLoad);
                callback();
            }
            state = iframe.contentDocument.readyState;
        }
    }, 200)
}

Usage:

iframeReloaded(iframe[0], function () {
    console.log('Reloaded');
})

JQuery

Function:

$.fn.iframeReloaded = function (callback) {
    if (!this.is('iframe')) {
        throw new Error('The element is not an iFrame, please provide the correct element');
    }

    let iframe = this[0];
    let state = iframe.contentDocument.readyState;
    let checkLoad = setInterval(() => {
        if (state !== iframe.contentDocument.readyState) {
            if (iframe.contentDocument.readyState === 'complete') {
                clearInterval(checkLoad);
                callback();
            }
            state = iframe.contentDocument.readyState;
        }
    }, 200)
}

Usage:

iframe.iframeReloaded(function () {
    console.log('Reloaded');
})
Edva answered 23/2, 2021 at 11:23 Comment(0)
L
0

I've just noticed that Chrome is not always firing the load event for the main page so this could have an effect on iframes too as they are basically treated the same way.

Use Dev Tools or the Performance api to check if the load event is being fired at all.

I just checked http://ee.co.uk/ and if you open the console and enter window.performance.timing you'll find the entries for domComplete, loadEventStart and loadEventEnd are 0 - at least at this current time:)

Looks like there is a problem with Chrome here - I've checked it on 2 PCs using the latest version 31.0.1650.63.

Update: checked ee again and load event fired but not on subsequent reloads so this is intermittent and may possibly be related to loading errors on their site. But the load event should fire whatever.

This problem has occurred on 5 or 6 sites for me now in the last day since I noticed my own site monitoring occasionally failed. Only just pinpointed the cause to this. I need some beauty sleep then I'll investigate further when I'm more awake.

Leeth answered 22/12, 2013 at 6:52 Comment(0)
G
0

In case someone has a special case like I did... I used a plugin with Cloudflare names RocketLoader, and its magic made that my event was present of the iframe but never called. I disactivated RocketLoader and it works now.

Gordongordy answered 28/11, 2023 at 16:17 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.