等待子窗口加载完成

IT技术 javascript
2021-03-02 09:39:02

是否有一个简单的钩子来检测脚本打开的窗口是否已完成加载?基本上,我想要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()调用会爆炸。我怎样才能检测到孩子已经完成加载?

3个回答

如果新打开的窗口的位置是同源的,这会起作用:

var w = window.open('child.html')
w.addEventListener('load', w.doSomething, true); 
我忘了补充一点addEventLister,IE 中没有正确支持,所以使用像 gabtub 建议的 jQuery(或另一个修复浏览器不兼容事件处理的 JS 框架)可能是一个好主意......
2021-04-21 09:39:02
可能是这样,是的。我相信file:///浏览器中 URL的安全策略非常严格的(在某些情况下,只是被破坏了)。使用合适的 Web 服务器尝试一下,看看是否可以解决问题。此外,尝试检查 IE/Chrome 中的控制台是否有任何错误消息。
2021-04-27 09:39:02
应该是。在浏览器中,同源基本上意味着目标必须在同一主机和同一协议上你是否将它与 混合http://
2021-05-04 09:39:02
不混合,我猜 Chrome 只是不认为两个file:///URL 是同源的?我根本无法让 IE7 在本地文件上运行脚本......可能我应该在正在运行的服务器上尝试这个。
2021-05-04 09:39:02
你能澄清“同源”吗?它似乎不适用于file:///我本地机器上的URL,但也许这是安全问题?
2021-05-08 09:39:02

接受的答案并没有解决原来的问题:

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

为了解决这个问题,我们需要更多地了解页面加载是如何在后台进行的。实际上它是异步的,因此当您编写w.location.href="child.html";w.doSomething();立即调用,它不会等到新页面加载。虽然浏览器开发人员很好地解决了 iframe 的加载问题,但对于子窗口却并非如此。没有用于此的 API,因此您所能做的就是编写某种解决方法。您可以做的是从子窗口的卸载事件侦听器中使用正确的延迟函数:

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

像往常一样,客户端 js 开发,每个浏览器的工作方式完全不同。

在此处输入图片说明

最好的解决方案是使用 defer 函数,它会在子窗口的文档处于加载就绪状态时调用回调,您可以在其中添加加载处理程序。如果在此之前调用回调,那么在当前浏览器中,新文档尚未创建,因此加载处理程序将添加到旧文档中,稍后将被新文档替换,因此加载处理程序将迷路了。唯一的例外是 Firefox,因为该浏览器将加载处理程序添加到窗口中。如果浏览器在加载 readyState 后调用 defer 回调,那么在当前的某些浏览器中,页面实际加载后甚至可能会有超过 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的支持,所以如果你想支持更老的浏览器,那么你必须同时使用卸载和pagehide,只有其中一人开始延迟如果两者都可用。

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 和 async 函数,那么您可以使用以下内容:

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

但这是一个不同的故事......

@Nenri 对于每个跨帧跨域请求也是如此。因此,例如,如果www.example.com打开sub.example.com一个新的窗口,那么你将无法访问该窗口,并等待页面加载。只有禁用浏览器安全和隔离才能解决这个问题,但这只是 Chromium 的一个功能,它用于测试,而不是用于生产。通常您所能做的就是使用postMessage并要求子窗口自行关闭。所以我会推荐你​​的情况。
2021-04-20 09:39:02
@Nenri yw ........ :-) 请注意,您无法通过提供错误地址来访问浏览器错误页面,例如 Chrome 错误页面。它们与打开页面的来源不同。没有解决方法afaik。我正在使用 window.open 和 Karma 进行 e2e 测试项目,但因此我不得不部分停止。github.com/inf3rno/e2e我可能稍后再开始。
2021-05-08 09:39:02
就我而言,打开的弹出窗口来自其他来源,但它是静态的,永远不会更改为我无法编辑的内容,所以我没有这个问题,但谢谢你告诉我那些:-) 我在等明天给这个答案一些赏金代表。
2021-05-11 09:39:02
你救了我的一天谢谢!我正在寻找一种从弹出窗口中引发的事件中关闭弹出窗口的方法,但我总是收到警告,说“只有创建窗口的脚本才能关闭它”。但是这段代码允许我从 opener 中放置一个 eventListener !所以,对于每个寻找方法的人来说,这就是要走的路
2021-05-12 09:39:02
@Nenri 在不同的浏览器中尝试一下,尤其是在最近的 Chrome 中。childWindow.document如果在子窗口中打开不同的源,则无法访问那应该会产生安全错误。请注意,我说的是这种起源:en.wikipedia.org/wiki/Same-origin_policy如果您不想访问它,只需关闭它,那么简单的 defer 就可以了,但不要使用document.readyStateand在这种情况下的事件侦听器。
2021-05-15 09:39:02

怎么样

父.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>
如果不使用 JS 框架,最好使用 gunderwonder 的解决方案。正如他所说,IE 不支持“addEventListener”,但您可以进行浏览器检查,并且在 IE 的情况下,使用微软的添加事件侦听器的实现 => "w.attachEvent('onload',doSomething);"
2021-04-15 09:39:02
oop,交叉评论。是的,jQuery,这可以解释为什么我还没有看到它。看起来确实应该有一些方法可以使用开箱即用的 DOM 来做到这一点。
2021-05-07 09:39:02
好像什么都没做 :( 我以前没有见过$操作符(我大部分时间都在 Java 上);它应该做什么?Chrome JavaScript 调试器给了我uncaught exception ReferenceError: $ is not defined.
2021-05-08 09:39:02