【问题标题】:JavaScript close window when opener is closed in IE在 IE 中关闭打开器时 JavaScript 关闭窗口
【发布时间】:2009-09-01 19:31:41
【问题描述】:

对于我的 Web 应用程序,我需要在父窗口关闭时关闭子窗口。 “关闭”是指浏览器窗口实际上已关闭,而不仅仅是导航到新页面。

我已经看过“How can I close the child window if the parent window is closed?”问题,但我的问题是它的扩展。该问题的答案解决了在父级的任何卸载事件上关闭子窗口的问题。但是卸载!=关闭(IMO);只需单击链接即可触发卸载事件。

由于 JS 中没有“onclose”事件,我决定最好的方法是在子级的父级卸载事件 setTimeout 上查看它的父级是否仍然存在,如果不存在则关闭:

var w = window.open("", "Logger", "height=480,width=640,resizeable,scrollbars=yes");
if (w) {
  JSEvents.on(window,'unload',function(){
    if (w && !w.closed) {
      w.setTimeout(function(){
        //IE this==w.opener
        if (!w.opener || w.opener.closed) {
          w.close();
        }
      },500);
    }
  });
}

但是,我相信我已经非常明确地表明,在 IE(7) 中,您不能在父窗口或子窗口的卸载事件期间使用 setTimeout。在上面的示例中,this == w.opener 在 setTimeout 匿名函数内部。此测试从不产生警报:

JSEvents.on(window, 'unload', function(){
  window.setTimeout(function(){alert('HERE');},500);
});

没有setTimeout 的直接alert 将产生警报。

我可以使用从父母那里为孩子设置 setTimeout 的技巧吗?

我可以使用另一种检测父级何时关闭的方法吗?

在 FF 中做起来要容易得多,所以我专注于让它在 IE 下工作。

【问题讨论】:

  • 我发现在 IE 中,如果页面正在转换,document.activeElement.toString() 是一个 URL,但如果关闭,[object] 是一个 URL。结合我上面的 setTimeout 方法,我相信我可以实现我的目标。如果这可行,我将在下面发布我的解决方案。但这需要大量测试。

标签: javascript internet-explorer


【解决方案1】:

我可以使用从父母那里为孩子设置 setTimeout 的技巧吗?

您不能使用 IE 中父级的代码来执行此操作。当 IE 关闭一个窗口时,您从其中的代码中定义的成员消失了,并且对这些成员的引用(例如孩子的超时指向您的函数)仍然悬空。根据您拥有的 IE 版本,可能什么都不会发生,或者您可能会收到“无法从已释放的脚本中执行代码”错误。

可以在孩子的内心做到这一点。父级可以在子级 onunload 上设置一个标志(例如 w.parentUnloaded=true),子级上的 setInterval 轮询器可以检查该标志并自行关闭 —

if (window.parentUnloaded && (!window.opener || window.opener.closed))

这是一个 IE 错误吗?嗯......当然,其他浏览器对卸载脚本的反应不同。但是没有标准说明这里应该发生什么。即使在同一个浏览器系列中,行为也会随着浏览器的更新而改变,以避免跨上下文脚本问题。

有了这样的事情和事件时间问题(*),跨窗口脚本编写比看起来要困难得多。通常最好避免;如果您可以将您的“弹出窗口”放在主页的 div 中,通常最好这样做。

(*: 在某些情况下(**) 可以在一个窗口中触发事件并在另一个窗口中的 JavaScript 仍在运行过程中执行。因此窗口 'a' 可以调用窗口 'b 上的方法' 并在窗口“b”中的其他代码仍在进行中时执行。如果它们是在正常的 JavaScript 假设下编写的,即一次只有一个执行线程处于活动状态,这可能会极大地混淆窗口“b”中的脚本. 这就是为什么我建议在 child 中使用 poller 而不是让 parent 显式调用 child。将来我们将使用 HTML5 的 postMessage 方法来避免这些问题。)

(**: 你可以很好地争辩说这不应该发生,这当然很奇怪,但它确实发生在许多浏览器中,特别是当涉及模态对话或某些版本的 IE Sun Java 插件在使用。)

您不能在应用程序关闭后留下弹出窗口;只是不礼貌。

有些人会说首先打开弹出窗口是不礼貌的。 ;-)

在我看来,在离开父窗口时关闭任何子窗口是有意义的,无论用户是关闭窗口,还是只是导航回他的主页,书签,或键入地址什么的。如果我试图将应用程序“重置”到开始状态,我个人可能也希望在刷新时丢失子窗口。

如果您有多个文档,父级将在这些文档之间导航,它们都是同一个应用程序的一部分,并且不应该关闭子级,那么您自己就真的很难! :-) 但是,您可以调整上面的“子窗口 if”方法来尝试嗅探 opener.location 并查看它是否在您的应用程序中以决定是否关闭。诀窍是,如果打开器已导航到不同的域,则访问将引发安全异常,因此您必须将位置访问包装在 try...catch 块中,如果打开器位置也会关闭窗口不可读。

bucabay 写道(和 Anthony 类似的话):

一旦您刷新或关闭窗口,浏览器就会认为该窗口已关闭。所以就孩子而言,一旦你刷新父母,它的开启者就消失了。

这是非常明智和合乎逻辑的。浏览器可能应该每个文档都有一个这样的“窗口”。但是尝试一下 - 他们没有。在 IE/FF/Op/Saf/ 中,通过刷新打开器,子弹出窗口保留对其打开器的访问权限(并且,只要该打开器是相同安全上下文中的文档,打开器的内容)铬。

【讨论】:

  • 这对我的问题非常有帮助,谢谢。不幸的是,我的 Web 应用程序是 Web 开发人员在他们的网站上使用的东西。该工具只能将内容插入现有页面,不能创建新页面。这就是为什么我的弹出窗口(这是开发人员在使用应用程序时所做的事件/操作的日志)使用空白页面的原因。通过在页面转换之间保持窗口打开,可以为他们的工作会话维护历史日志。棘手的一点是,如果他们关闭父窗口,但将子窗口留在日志周围,则不会重置并变得混乱。
【解决方案2】:

在弹出窗口中:(如果打开器关闭或更改域,则此方法有效)

 var int = window.setInterval(function(){
   // On opener domain change, all browsers throw an error. Lets use that error to our advantage using try/catch:
   try
     {
    if(opener && typeof opener.document != 'undefined')
    {     
     // Adding this variable fixes IE8. Why? Because F U thats why.
     var openerRef = window.opener.location.host;
    }
    else{
     // Loads the survey when opener is closed
     window.location = 'exit-survey.jsp';
    }
     }
   // Loads the survey if an error throws (error throws when opener changes domain)
   catch(err)
     {
    window.location = 'exit-survey.jsp';
     }
  }, 500);
})

【讨论】:

    【解决方案3】:

    您是否尝试过像这样在打开的窗口中创建函数:

    window.closeWithParent = function() {
      if (!window.opener || window.opener.closed) {
        window.close();
      }
    };
    window.parentClosing = function() {
      window.setTimeout(window.closeWithParent, 500);
    };
    

    然后从您的父窗口:

    JSEvents.on(window,'unload', function() { 
      if (w.parentClosing) w.parentClosing(); 
    });
    

    我不确定,但我认为跨窗口与窗口对象交互可能会导致您看到的问题。此外,通过这种方式(希望)在子窗口范围内调用 setTimeout 而不是正在卸载的父窗口(这会丢失任何超时)。

    【讨论】:

    • 实际上在w.setTimeout 函数this=w.opener 内,所以我认为范围实际上是父窗口,而不是子窗口。子窗口实际上是一个空白窗口,我完全是通过 JS 编写的。没有默认内容,我似乎无法向页面添加
    • Hrm... 我认为让它在加载 JS/CSS 内容的服务器上打开一个“静态页面”会更容易,<body> 仍然可以是空白的,但是你有一个窗口,它也存在于同一个域中(安全问题较少)
    • 不幸的是,我的应用程序的性质是我只能在现有 HTML 中插入一个脚本标签,我不能在域上提供任何新页面。感谢您的帮助
    【解决方案4】:

    问题在于,从浏览器的角度来看,窗口是一个为显示文档内容而创建的概念。当您从该文档导航到另一个文档时,该窗口将关闭并创建一个新窗口。

    浏览器的概念窗口由浏览器拥有的实际客户端窗口托管,并且该客户端窗口可能被重新用于显示后续文档,这实际上与您无关(如果您不介意短语)。

    它是浏览器选择显示内容的方式,具有关闭按钮的窗口可能会被用户点击,这超出了大多数浏览器认为任何宿主文档都需要知道的范围。

    因此,您现在可能发明的任何技巧来规避这一点,如果它确实有效,可能会在更高版本的浏览器中被更严格的安全性所关闭。

    我的建议是放弃这个要求。

    【讨论】:

    • 正确。但是您仍然可以间接检查特定域的窗口是否打开。
    • 如果您想编写一个用户友好的 Web 应用程序,那是应用程序的事情。应用程序关闭后,您不能只留下弹出窗口;只是不礼貌。
    • @Rob:我知道您希望为用户提供最好的体验,但是,浏览器的首要目的是让拥有浏览器的客户可以访问公共网站。这使用户面临的安全风险需要投入大量精力来沙箱化此活动。此要求通常与创建完整的应用程序不一致,这些应用程序的工作方式与对主机具有完全访问权限的其他客户端能够交付。
    • @AnthoneyWJones 有哪些安全问题?那里的每个浏览器都专门允许使用 JS 打开的窗口被父或子的 JS 关闭。这里的问题是我的特定用例,弹出窗口没有 URL。
    • 由于窗口没有 URL,IE 会认为它与打开它的窗口位于不同的域中。在这种情况下,通常 open 将返回 null ,或者至少在尝试操作窗口时会出现权限错误。
    【解决方案5】:

    一旦您刷新或关闭窗口,浏览器就会认为该窗口已关闭。所以就孩子而言,一旦你刷新父母,它的开瓶器就消失了。

    因此,您无法测试窗口是否刚刚刷新,或者使用 JavaScript 引用打开了同一域上的另一个实例。 (如window.opener)

    但是,您可以创建对其他窗口的间接引用并将它们保存在跨窗口的任何浏览器存储中,甚至是服务器存储中。让存储反映窗口的状态将允许其他窗口观察该窗口,即使它们没有参考。

    您可以使用 cookie,或 DOM Storage 等。我有一个使用 cookie 的库(它是在一年前编写的,当时不支持 DOM 存储 - 我认为是 FF2+、IE8+)。如果您想将其视为示例,我可以这样做。

    不管怎样,你能做的就是保留一段代表父窗口的数据。定期更新,并从孩子那里轮询。

    cookie 示例:

    // parent
    setInterval(function() { setCookie('parent_alive', new Date()) }, 1000);
    // child
    setInterval(function() { if (readCookie('parent_alive') < new Date()-5000) window.close() }, 1000)
    

    这里的孩子将在父母不更新cookie“parent_alive”后5秒关闭。主要问题是互联网连接可能会阻止页面加载 5 秒钟,孩子认为它已关闭。所以这是一个平衡的行为。

    请注意,如果您使用会话 cookie,轮询会非常有效,因为它们会保留在内存中。但是,如果您使用持久性 cookie,您可能会遇到很糟糕的磁盘。

    【讨论】:

      【解决方案6】:

      似乎将脚本添加到子窗口范围的正确方法是使用 DOM 创建脚本标记。以下代码用于检查父窗口在 IE 中卸载后是否仍然打开四分之一秒。

      var w = window.open("", "Logger", "height=480,width=640,resizeable,scrollbars=yes");
      if (w) {
        JSEvents.on(window,'unload',function(){
          if (w && !w.closed) {
            var srpt = w.document.createElement('script');
            srpt.type = 'text/javascript';
            srpt.text = 'window.setTimeout(function(){if(!window.opener||window.opener.closed){window.close();}},250);';
            w.document.body.appendChild(srpt);
          }
        });
      }
      

      感谢大家帮助我指出这个方向。解决方案只是弄清楚如何动态插入带有文本内容的新脚本标签,而不是 src

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多