Ответ 1
Это работает, если местоположение вновь открытого окна имеет одинаковое происхождение:
var w = window.open('child.html')
w.addEventListener('load', w.doSomething, true);
Есть ли легкий крюк для обнаружения того, что окно, открытое 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()
взорван. Как определить, что ребенок закончил загрузку?
Это работает, если местоположение вновь открытого окна имеет одинаковое происхождение:
var w = window.open('child.html')
w.addEventListener('load', w.doSomething, true);
как насчет
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>
Принятый ответ не решает исходную проблему:
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();
});
});
Как обычно при разработке на стороне клиента, каждый из браузеров работает совершенно по-разному.
Лучшее решение - использовать функцию отсрочки, которая вызывает функцию обратного вызова, когда документ дочернего окна находится в готовом состоянии загрузки, где вы можете добавить обработчик загрузки. Если он вызывает обратный вызов до этого, то в текущих браузерах новый документ еще не создан, поэтому обработчик загрузки будет добавлен к старому документу, который впоследствии будет заменен новым документом, и, следовательно, обработчик загрузки будет Потерянный. Единственным исключением является 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();
но это другая история...