Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Waiting for child window loading to complete

Tags:

javascript

Is there an easy hook for detecting that a window opened by a script has finished loading? Basically, I want the equivalent of the onLoad() hook, but I can't set it directly -- assume that the child document is a given and I can't actually put any code of my own in it.

For instance, say I have the following two files:

parent.html:

<html>
  <head>
    <title>Parent</title>
  </head>
  <script type="text/javascript">
    var w;
    function loadChild() {
      w = window.open();
      w.location.href="child.html";
      // block until child has finished loading... how?
      w.doSomething();
    } 
  </script>
</html>
<body>
  I am a parent window. <a href="javascript:loadChild()">Click me</a>.
</body>

child.html:

<html>
  <head>
    <title>Child</title>
  </head>
  <script type="text/javascript">
    function doSomething() {
      alert("Hi there");
    }
  </script>
</html>
<body>
  I am a child window
</body>

Since setting location.href is non-blocking, w.doSomething() isn't defined yet and the doSomething() call blows up. How can I detect that the child has finished loading?

like image 420
David Moles Avatar asked Sep 03 '09 07:09

David Moles


2 Answers

This works if the location of the newly opened window is same-origin:

var w = window.open('child.html')
w.addEventListener('load', w.doSomething, true); 
like image 96
Øystein Riiser Gundersen Avatar answered Oct 17 '22 04:10

Øystein Riiser Gundersen


The accepted answer does not solve the original problem:

  w = window.open();
  w.location.href="child.html";
  // block until child has finished loading... how?
  w.doSomething();

In order to solve this we need to know a little bit more about how page loading goes in the background. Actually it is something asynchronous, so when you write w.location.href="child.html"; and call the w.doSomething(); immediately after that, it won't wait until the new page is loaded. While browser developers solved the loading of the iframes pretty well, the same is not true for child windows. There is no API for that, so all you can do is writing some kind of workaround. What you can do is using the proper defer function from the unload event listener of the child window:

w.addEventListener("unload", function (){
    defer(function (){
        w.doSomething();
    });
});

As usual by client side js development, each of the browsers work completely differently.

enter image description here

The best solution is using a defer function, which calls the callback when the document of the child window is in a loading readyState, where you can add a load handler. If it calls the callback before that, then in the current browsers, the new document is not yet created, so the load handler will be added to the old document, which will be later replaced by the new document, and hence the load handler will be lost. The only exception is Firefox, because that browser keeps the load handlers added to the window. If the browser calls the defer callback after the loading readyState, then in some of the current browsers there can be even more than 2500 msec delay after the page is actually loaded. I have no clue why some of the browsers have such huge delays by some of the defers by child window document loading. I searched for a while, but did not find any answer. Some of the browsers don't have any "loading" defer, so by those all you can do is using a "delayed" defer, and hope for the best. According to my test results a MessageChannel based solution is the best multi-browser "loading" defer:

function defer (callback) {
    var channel = new MessageChannel();
    channel.port1.onmessage = function (e) {
        callback();
    };
    channel.port2.postMessage(null);
}

So you can do something like:

w.addEventListener("unload", function (){
    // note: Safari supports pagehide only
    defer(function (){
        if (w.document.readyState === "loading")
            w.addEventListener("load", function (){
                w.doSomething();
            });
        else
            w.doSomething();
    });
});

If you want to support Safari, then you should use pagehide instead of unload. The pagehide event is supported from IE 11, so if you want to support even older browsers, then you must use both unload and pagehide and start the defer only with one of them if both are available.

var awaitLoad = function (win, cb){
    var wasCalled = false;
    function unloadListener(){
        if (wasCalled)
            return;
        wasCalled = true;
        win.removeEventListener("unload", unloadListener);
        win.removeEventListener("pagehide", unloadListener);
        // Firefox keeps window event listeners for multiple page loads
        defer(function (){
            win.document.readyState;
            // IE sometimes throws security error if not accessed 2 times
            if (win.document.readyState === "loading")
                win.addEventListener("load", function loadListener(){
                    win.removeEventListener("load", loadListener);
                    cb();
                });
            else
                cb();
        });
    };
    win.addEventListener("unload", unloadListener);
    win.addEventListener("pagehide", unloadListener);
    // Safari does not support unload
});


w = window.open();
w.location.href="child.html";
awaitLoad(w, function (){
    w.doSomething();
});

If Promises and async functions are supported, then you can use something like the following:

w = window.open();
await load(w, "child.html");
w.doSomething();

but that is a different story...

like image 9
inf3rno Avatar answered Oct 17 '22 02:10

inf3rno