Lanston
Lanston

Reputation: 11854

dynamically created iframe triggers onload event twice

I created an iframe dynamically and found that this iframe trigger onload event twice.

var i=0;
frameOnload=function(){
  console.log(i++);  
};

var ifr=document.createElement("iframe");  
ifr.src="javascript:(function(){document.open();document.write('test');document.close();})();";
ifr.onload=frameOnload;  
document.body.appendChild(ifr);

Why i finally is 1?
How to prevent iframe's onload twice instead of pointing onload function to null inside itself?

Upvotes: 45

Views: 38242

Answers (9)

Basit
Basit

Reputation: 297

Solution for Angular

This is happening because The iframe onload event will be triggered twice in webkit browsers ( safari/chrome ), if you attach the onload event BEFORE the iframe is appended to the body.

use AfterViewInit hook to register the load callback

Template:

<iframe
  #iframe
  [src]="src | safe : 'resourceUrl'"
  [height]="height"
  width="100%"
></iframe>

Component:

export class IframeWrapperComponent implements AfterViewInit {
  @Input({ required: true }) src = '';
  @Input({ required: true }) title = '';
  @Input({ required: true }) height = '';

  @ViewChild('iframe') iFrame!: ElementRef<HTMLIFrameElement>;

  ngAfterViewInit(): void {

    if (this.iFrame) {

      this.iFrame.nativeElement.onload = () => {

        console.log('write your onload logic here');

      };
    }
  }
}

Upvotes: 0

Ben P.P. Tung
Ben P.P. Tung

Reputation: 1284

I've also encountered the same problem, but get no answer anywhere, so I tested by myself.

The iframe onload event will be triggered twice in webkit browsers ( safari/chrome ), if you attach the onload event BEFORE the iframe is appended to the body.

So you can prevent iframe onload twice by change your code in the following way.

document.body.appendChild(ifr);
ifr.onload=frameOnload; // attach onload event after the iframe is added to the body

Then, you will only get one onload event, which is the event the document really loaded.

Upvotes: 72

user1786185
user1786185

Reputation: 1

Register for onload event after appending iframe. This solves my problem.
document.body.appendChild(ifr);
ifr.onload=frameOnload;  

E.G:
iframeContainer.appendChild(iframe)
  iframe.onload = function() {
    console.log("iframe onload called")
  iframeContainer.appendChild(iframe)
  iframe.onload = function() {
    console.log("iframe onload called")
  }  
    
  }

Upvotes: 0

Viraj Singh
Viraj Singh

Reputation: 2358

I faced this issue in vue.js 2.6.12
I handled it by checking if event.target.url matches the src url inside the onload function of iframe.

<template>
           <iframe
                @load="onLoad"
                :src="embedUrl"
                ref="cardNumber"
                frameborder="0"
                scrolling="no"
                width="100%"
                height="100%"
                align="left"
            ></iframe>
</template>


<script>
export default {
...,
methods: {
onLoad (event) {
    // considerting iframe to be loaded if the event.target.url matches the src url
    if(event.target.src === this.embedUrl)) {
      // instructions after loading
    } 
   }
 }
}
</script>

Upvotes: 0

leo
leo

Reputation: 8530

Building on @jakub.g's answer:

From inside the frame, the evt.target.src hack does not work. But I found that I could instead check the evt.target.title. The second time DOMContentLoaded is triggered, evt.target points to the document being loaded, so this would work:

document.addEventListener('DOMContentLoaded', function(evt) {
  if (evt.target.title !== myPageTitle) {
    // Work around for Webkit browsers trigging this event twice om iframes
    return
  }
  // DOM content loaded, for real
}

This behavior seems to be consistent even in Internet Explorer (save for old versions without .addEventListener

Upvotes: 0

inon
inon

Reputation: 1772

Here code that works good!

Credit: @Dave Stolie https://coderanch.com/t/443223/languages/Frames-loading-refresh

<html>  
 <head>  
   <script type="text/javascript">  
     function loadFrame()  
     {
        var url="http://www.google.com";
        alert(document.getElementById("theFrame").src);

        if (document.getElementById("theFrame").src != url)
        {
            alert("loading");
            document.getElementById("theFrame").src = url;  
        }
     }  
   </script>  
   </head>  
   <body onLoad="loadFrame();">  
   <iframe id="theFrame" src="x"/>
  </body>  
  </html>

Upvotes: 0

Ophion
Ophion

Reputation: 41

I've had this happen to asynchronously loaded stylesheets, like this one.

<link rel="preload" href="stylesheet.css" as="style" onload="this.rel='stylesheet'">

I find that the simplest solution is to have the event remove itself:

<link rel="preload" href="stylesheet.css" as="style" onload="this.removeAttribute('onload'); this.rel='stylesheet'">

Upvotes: 2

jakub.g
jakub.g

Reputation: 41398

To expand on the top-voted answer: if you can not control when and how things are attached to the DOM -- for instance, when using a framework (we've had this happening in our Angular app) -- you might want to try the solution below.

I did heavy cross-browser testing and I found the following workaround: check the parameter passed to onload callback and inspect evt.target.src in the event listener.

iframe.onload = function(evt) {
    if (evt.target.src != '') {
        // do stuff
    }
}

(if you call global method from HTML, remember to pass event in your HTML markup: <iframe onload="window.globalOnLoad(event)">)

evt.target.src can be be '' (empty string) only in webkit in the first onload call, from my tests.

Study of iframe onload behavior in different situations

iframe.onload = function(evt){
    console.log("frameOnload", ++i);
    console.log("src = '" + evt.target.src + "'");
};

iframe onload behavior with regular URL

// Chrome:  onload 1 src='', onload 2 src=requestedSrc
// IE11:    onload 1 src=requestedSrc
// Firefox: onload 1 src=requestedSrc
document.body.appendChild(iframe);
iframe.src = "http://www.example.org/";

iframe onload behavior with 404 URL

// Chrome:  onload 1 src='', onload 2 src=requestedSrc
// IE11:    onload 1 src=requestedSrc
// Firefox: onload 1 src=requestedSrc
document.body.appendChild(iframe);
iframe.src = "http://www.example.org/404";

iframe onload behavior with non-resolvable (at DNS level) URL

// Chrome:  onload 1 src='', onload 2 src=requestedSrc
// IE11:    onload 1 src=requestedSrc
// Firefox: onload NEVER triggered!
document.body.appendChild(iframe);
iframe.src= 'http://aaaaaaaaaaaaaaaaaaaaaa.example/';

iframe onload behavior with iframe.src explicitly set to about:blank before appending to DOM

// Chrome:  onload 1 src='about:blank', onload 2 src=requestedSrc
// IE11:    onload 1 src=requestedSrc
// Firefox: onload 1 src=requestedSrc
iframe.src = "about:blank";
document.body.appendChild(iframe);
iframe.src = "http://www.example.org/";

iframe onload behavior with iframe.src explicitly set to a javascript expression before appending to DOM

// Chrome:  onload 1 src='javascript:void 0', onload 2 src=requestedSrc
// IE11:    onload 1 src=requestedSrc
// Firefox: onload 1 src=requestedSrc
iframe.src = "javascript:void 0";
document.body.appendChild(iframe);
iframe.src = "http://www.example.org/";

Upvotes: 31

jakub.g
jakub.g

Reputation: 41398

FWIW, while I was able to reproduce double onload in case where iframe.src is javascript:, I was not able to reproduce it with a normal HTTP URL (onload was happening once). However, when you change the order of calls so that iframe.src is set after appending to body, then the double-load happens (again, webkit only):

var i = 0;

var iframe = document.createElement("iframe");
iframe.onload = function(){
  console.log("frameOnload", ++i);
};
document.body.appendChild(iframe);
iframe.src = "http://www.example.org/";
//iframe.src = "http://www.example.org/404";

// double onload!

When I assign src before appending, I get one onload call.

var i = 0;

var iframe = document.createElement("iframe");
iframe.onload = function(){
  console.log("frameOnload", ++i);
};
iframe.src = "http://www.example.org/";
//iframe.src = "http://www.example.org/404";
document.body.appendChild(iframe);

// only one onload

Upvotes: 0

Related Questions