Ожидание завершения загрузки дочернего окна

Есть ли легкий крюк для обнаружения того, что окно, открытое script, закончило загрузку? В принципе, я хочу эквивалент hook onLoad(), но я не могу установить его напрямую - предположим, что дочерний документ является данным, и я не могу на самом деле поместить в него какой-либо код.

Например, скажем, у меня есть следующие два файла:

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>

Поскольку установка location.href не блокируется, w.doSomething() еще не определен, а вызов doSomething() взорван. Как определить, что ребенок закончил загрузку?

Ответы

Ответ 1

Это работает, если местоположение вновь открытого окна имеет одинаковое происхождение:

var w = window.open('child.html')
w.addEventListener('load', w.doSomething, true); 

Ответ 2

как насчет

parent.html:

<html>
<head>
<title>Parent</title>
</head>
<script type="text/javascript">
  var w;
  function loadChild() {
    w = window.open();
    w.location.href="child.html";
    // like this (with jquery)
    $(w).ready(function()
    {
      w.doSomething();
    });
  } 
</script>
</html>
<body>
  I am a parent window. <a href="javascript:loadChild()">Click me</a>.
</body>

Ответ 3

Принятый ответ не решает исходную проблему:

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

Чтобы решить эту проблему, нам нужно немного больше узнать о том, как происходит загрузка страницы в фоновом режиме. На самом деле это что-то асинхронное, поэтому, когда вы пишете w.location.href="child.html"; и вызовите w.doSomething(); сразу после этого он не будет ждать загрузки новой страницы. Хотя разработчики браузеров довольно хорошо решили загрузку iframes, это не относится к дочерним окнам. Для этого не существует API, поэтому все, что вы можете сделать, это написать какой-то обходной путь. То, что вы можете сделать, это использовать правильную функцию defer из прослушивателя события unload дочернего окна:

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

Как обычно при разработке на стороне клиента, каждый из браузеров работает совершенно по-разному.

enter image description here

Лучшее решение - использовать функцию отсрочки, которая вызывает функцию обратного вызова, когда документ дочернего окна находится в готовом состоянии загрузки, где вы можете добавить обработчик загрузки. Если он вызывает обратный вызов до этого, то в текущих браузерах новый документ еще не создан, поэтому обработчик загрузки будет добавлен к старому документу, который впоследствии будет заменен новым документом, и, следовательно, обработчик загрузки будет Потерянный. Единственным исключением является Firefox, потому что этот браузер сохраняет обработчики загрузки, добавленные в окно. Если браузер вызывает отложенный обратный вызов после загрузки readyState, то в некоторых из текущих браузеров может быть задержка даже более 2500 мсек после того, как страница действительно загружена. Я понятия не имею, почему некоторые браузеры имеют такие огромные задержки из-за задержек при загрузке документов дочерних окон. Я искал некоторое время, но не нашел никакого ответа. Некоторые браузеры не имеют отсрочки "загрузки", поэтому все, что вы можете сделать, это использовать "отсроченную" отсрочку и надеяться на лучшее. Согласно результатам моего теста, решение на основе MessageChannel является лучшим мультибраузерным средством "загрузки":

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

Так что вы можете сделать что-то вроде:

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();
    });
});

Если вы хотите поддерживать Safari, вы должны использовать pagehide вместо unload. Событие pagehide поддерживается в IE 11, поэтому, если вы хотите поддерживать даже более старые браузеры, вы должны использовать как unload, так и pagehide и запускать defer только с одним из них, если оба доступны.

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();
});

Если Promises и асинхронные функции поддерживаются, то вы можете использовать что-то вроде следующего:

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

но это другая история...