浏览器父子窗口间通讯

父子窗口通讯需求背景

最近在实现一个关联谷歌帐户的需求。看到网上的大部分涉及前端方面的实现都有这么个OAuth标准下的流程:javascript

  • 打开一个子窗口
  • 在子窗口重定向到受权页
  • 用户点击受权按钮
  • 用户受权后受权页会重定向到一个默认的或者用户自定义的uri。
  • 受权完成

通常在受权页被受权后子窗口(受权窗口)都会被关闭。浏览器的每一个窗口是禁止关闭当前窗口的,只能关闭由当前窗口打开的其余窗口。有图有真相:html

clipboard.png

因此想要关闭子窗口就须要父窗口来操做。而什么时候让父窗口关闭子窗口就须要子窗口在完成用户受权后通知父窗口来关闭本身。这就涉及到父子间窗口的通讯。前端

父子间窗口通讯分两种状况java

父子窗口同源

浏览器的同源策略还没了解到就请自行Google了。
按照OAuth流程是不会出现父子窗口同源的现象。可是这里也作一下总结。后端

父窗口向子窗口通讯

子窗口是由父窗口建立的。父窗口能够在打开子窗口后获取到子窗口的引用,经过这个引用能够触发子窗口的方法以此向子窗口传递消息浏览器

// parent code
let child_window_handle = null;
$('#open-child-win-btn').on('click', () => {
    child_window_handle = window.open('target_url.html', '_blank', 'width=700, height=500, left=200');
})

这个时候有一个子窗口的句柄了(handler)。
而子窗口的页面下有以下方法网络

// child code
function ProcessParentMsg(msg) {
    // do something with the msg
}

父窗口只须要在调用子窗口的对应方法就能够和子窗口完成通讯异步

// parent code
child_window_handle.ProcessParentMsg('msg_form_parent_window');

子窗口向父窗口通讯

子窗口能够经过window对象的opener属性访问到父窗口。而且调用父窗口的方法来完成向上通讯。post

// child code
window.opener.ProcessChildMsg();
// parent code
function ProcessChildMsg(msg) {
    // do something with msg
}

父子窗口同源的状况下,父窗口是能够很大程度的控制子窗口的。除了能够触发子窗口的方法,也能够监听子窗口的事件,onbeforeunloadonresize, focus等等, 可是父子窗口不一样源的状况下。父窗口没法执行子窗口下的方法,也没法监听窗口下的事件。以前设想的关闭子窗口的实现方式是在父窗口得到子窗口的句柄而后监听子窗口的onload,onload以后就调用父窗口的用于关闭子窗口的方法。显然这只能在同源的状况下发生了。url

父子窗口不一样源

这种状况下父子窗口要通讯就须要借助HTML5的message passing功能了。

父窗口向子窗口通讯

直接看示例😄,
在父窗口中向子窗口派发消息

// parent window
let child_window_handle = window.open('child_target.html', '_blank', 'width=700, height=500');

child_window_handle.postMessage('Msg to the child window', '*');

在子窗口下监听消息

// child window
window.addEventListener('message', (e) => {
    ProcessParentMsg(e.data);
});

function ProcessParentMsg(msg) {
    // do something with the msg
}

子窗口向父窗口通讯

// child window
window.opener.postMessage("Message to parent", "*");
// parent window
window.addEventListener('message', function(e) {
    ProcessChildMsg(e.data);
}, false);

function processChildMsg() {
    //  do something with the message
}

总结

当我在实现点击按钮打开受权窗口的时候一直出现窗口被拦截的提示,没法直接打开受权弹窗口。这是由于点击window.open这个操做是在异步操做的回调里面执行的。默认这种状况下浏览器都会拦截这个新窗口,除非用户设定对这个域名容许任何弹窗。

clipboard.png

stackoverflow上能够看到这个解释

The general rule is that popup blockers will engage if window.open or similar is invoked from javascript that is not invoked by direct user action. That is, you can call window.open in response to a button click without getting hit by the popup blocker, but if you put the same code in a timer event it will be blocked. Depth of call chain is also a factor - some older browsers only look at the immediate caller, newer browsers can backtrack a little to see if the caller's caller was a mouse click etc. Keep it as shallow as you can to avoid the popup blockers.

起先当我点击按钮的时候我先去经过网络请求接口获取受权页的链接。在异步回调里获取到了受权页连接。此时再去用window.open去打开这个连接。这个不是 direct user action。即便能够相信也是一个比较差的用户体验,由于形成了延迟。因此修改后的方案就是用户点击了关联按钮立刻打开一个blank窗口。同时异步去获取受权页连接。获取后reload打开的受权窗口的地址为获取到的链接。这就不会致使 popup blocked 的现象发生了。

描述到这里可能若是没有受权页开发经验的人可能仍是没法理解是怎么关闭子页面的。当用户打开受权页后,用户点击受权按钮

clipboard.png

此时页面会跳转到一个用户指定的uri。若是未指定的话,会直接显示authorize code在窗口中,这一般不是咱们想要的。咱们须要用这个受权码去换取token,token是真正能够登陆用户帐户的临时凭证。因此一般是用户指定一个uri,这个uri能够是一个后端接口,受权窗口被用户受权后会以querystring的形式带上code的参数跳转到咱们提供的uri。这个时候后端接口能够获取到受权码去执行换token的操做。以后接口返回一个text/html的response,response 返回的内容大体以下:

<html>
    <head>
    <meta charset="UTF-8"/>
        <title>test</title>
    </head>
    <body>
    帐号关联成功
    </body>
    <script>
    window.opener.postMessage('close_child_window', '*');
    </script>
</html>
相关文章
相关标签/搜索