跨浏览器tab页的通信解决方案尝试
目标
当前页面需要与当前浏览器已打开的的某个tab页通信,完成某些交互。其中,与当前页面待通信的tab页可以是与当前页面同域(相同的协议、域名和端口),也可以是跨域的。
要实现这个特殊的功能,单单使用HTML5的相关特性是无法完成的,需要有更加巧妙的设计。
畅想
现在我们发现下思维,假设多种场景下的解决方案,最终寻找通用解。
case 1
两个需要交互的tab页面具有依赖关系。
如 A页面中通过JavaScript的window.open打开B页面,或者B页面通过iframe嵌入至A页面
,此种情形最简单,可以通过HTML5的 window.postMessage
API完成通信,由于postMessage函数是绑定在 window 全局对象下,因此通信的页面中必须有一个页面(如A页面)可以获取另一个页面(如B页面)的window对象,这样才可以完成单向通信;B页面无需获取A页面的window对象,如果需要B页面对A页面的通信,只需要在B页面侦听message事件,获取事件中传递的source对象,该对象即为A页面window对象的引用:
B页面
window.addEventListner('message',(e)=>{
let {data,source,origin} = e;
source.postMessage('message echo','/');
});
postMessage的第一个参数为消息实体,它是一个结构化对象,即可以通过“JSON.stringify和JSON.parse”函数还原的对象;第二个参数为消息发送范围选择器,设置为“/”意味着只发送消息给同源的页面,设置为“*”则发送全部页面。
case 2
两个打开的页面属于同源范畴。
若要实现两个互不相关的通源tab页面通信,可以使用一种比较巧妙的方式:localstorage。localStorage的存储遵循同源策略,因此同源的两个tab页面可以通过这种共享localStorage的方式实现通信,通过约定localStorage的某一个itemName,基于该key值的内容作为“共享硬盘”方式通信。
不过,如果单纯使用localStorage存储做通信方式会遇到一个问题,就是两个页面把握不准通信时机,如果A页面此刻需要发送给B页面一条消息“hello B”,它会设置localStorage.setItem('message','hello B'),并且采用setTimeout轮训等待B的消息;而B此刻也同样使用setTimeout轮训等待localStorage的message项的变化,当获取到'message'字段时,便取出消息'hello B'。B如果要发消息给A,仍然采用同样套路。
这种方式性能极其低下,需要通信两方不停的监听localStorage某项的变化,及其浪费事件队列处理效率。幸好,HTML5提供了storage事件,通过window对象侦听storage事件,会侦听localStorage对象的变化事件(包括item的添加、修改和删除)。因此,通过事件可以完成高效的通信机制:
A 页面
window.addEventListener("storage", function(ev){
if (ev.key == 'message') {
// removeItem同样触发storage事件,此时ev.newValue为空
if(!ev.newValue)
return;
var message = JSON.parse(ev.newValue);
console.log(message);
}
});
function sendMessage(message){
localStorage.setItem('message',JSON.stringify(message));
localStorage.removeItem('message');
}
// 发送消息给B页面
sendMessage('this is message from A');
B 页面
window.addEventListener("storage", function(ev){
if (ev.key == 'message') {
// removeItem同样触发storage事件,此时ev.newValue为空
if(!ev.newValue)
return;
var message = JSON.parse(ev.newValue);
// 发送消息给A页面
sendMessage('message echo from B');
}
});
function sendMessage(message){
localStorage.setItem('message',JSON.stringify(message));
localStorage.removeItem('message');
}
发送消息采用sendMessage函数,该函数序列化消息,设置为localStorage的message字段值后,删除该message字段。这样做的目的是不污染localStorage空间,但是会造成一个无伤大雅的反作用,即触发两次storage事件,因此我们在storage事件处理函数中做了if(!ev.newValue) return;
判断。
当我们在A页面中执行sendMessage函数,其他同源页面会触发storage事件,而A页面却不会触发storage事件;而且连续发送两次相同的消息也只会触发一次storage事件,如果需要解决这种情况,可以在消息体体内加入时间戳:
sendMessage({
data: 'hello world',
timestamp: Date.now()
});
sendMessage({
data: 'hello world',
timestamp: Date.now()
});
通过这种方式,可以实现同源下的两个tab页通信,兼容性
通过caniuse网站查询storage事件发现,IE的浏览器支持非常的不友好,caniuse使用了“completely wrong”的形容词来表述这一程度。IE10的storage事件会在页面document文档对象构建完成后触发,这在嵌套iframe的页面中造成诸多问题;IE11的storage Event对象却不区分oldValue和newValue值,它们始终存储更新后的值
case 3
两个互不相关的tab页面通信。
这种情况才是最急需解决的问题,如何实现两个没有任何关系的tab页面通信,这需要一些技巧,而且需要有同时修改这两个tab页面的权限,否则根本不可能实现这两个tab页的能力。
在上述条件满足的情况下,我们就可以使用case1 和 case2的技术完成case 3的需求,这需要我们巧妙的结合HTML5 postMessage API 和 storage事件实现这两个毫无关系的tab页面的连通。为此,我想到了iframe,通过在这两个tab页嵌入同一个iframe页实现“桥接”,最终完成通信:
tab A -----> iframe A[bridge.html]
|
|
\|/
iframe B[bridge.html] -----> tab B
单方向的通信原理如上图所示,tab A中嵌入iframe A,tab B中嵌入iframe B,这两个iframe引用相同的页面“bridge.html”。如果tab A发消息给tab B,首先tab A通过postMessage消息发送给iframe A(tab A可以获取到iframe A的window对象iframe.contentWindow);此后iframe A通过storage消息完成与iframe B的通信(由于iframeA 与iframe B同源,因此case 2的通信方式这里可以使用);最终,iframe B同样采用postMessage方式发送消息给tab B(在iframe中通过window.parent引用tab B的window对象)。至此,tab A的消息走通了所有链路,成功抵达tab B。
反方向发送消息同样的道理,这里就不在详细说明。接下来到了 talk is cheap,show me the code 环节:
tab A:
// 向弹出的tab页面发送消息
window.sendMessageToTab = function(data){
// 由于[#J_bridge]iframe页面的源文件在vstudio服务器中,因此postMessage发向“同源”
document.querySelector('#J_bridge').contentWindow.postMessage(JSON.stringify(data),'/');
};
// 接收来自 [#J_bridge]iframe的tab消息
window.addEventListener('message',function(e){
let {data,source,origin} = e;
if(!data)
return;
try{
let info = JSON.parse(JSON.parse(data));
if(info.type == 'BSays'){
console.log('BSay:',info);
}
}catch(e){
}
});
sendMessageToTab({
type: 'ASays',
data: 'hello world, B'
})
bridge.html
window.addEventListener("storage", function(ev){
if (ev.key == 'message') {
window.parent.postMessage(ev.newValue,'*');
}
});
function message_broadcast(message){
localStorage.setItem('message',JSON.stringify(message));
localStorage.removeItem('message');
}
window.addEventListener('message',function(e){
let {data,source,origin} = e;
// 接受到父文档的消息后,广播给其他的同源页面
message_broadcast(data);
});
tab B
window.addEventListener('message',function(e){
let {data,source,origin} = e;
if(!data)
return;
let info = JSON.parse(JSON.parse(data));
if(info.type == 'ASays'){
document.querySelector('#J_bridge').contentWindow.postMessage(JSON.stringify({
type: 'BSays',
data: 'hello world echo from B'
}),'*');
}
});
// tab B主动发送消息给tab A
document.querySelector('button').addEventListener('click',function(){
document.querySelector('#J_bridge').contentWindow.postMessage(JSON.stringify({
type: 'BSays',
data: 'I am B'
}),'*');
})
至此,通过在tab A和tab B中引入“桥接”功能的iframe[bridge.html]页面,实现了两个无关tab页的双向通信,这种实现的技巧性较强。
参考资料
Communication between tabs or windows
跨浏览器tab页的通信解决方案尝试的更多相关文章
- chrome浏览器tab页内存占用变大,网站变慢为哪般?
问题概述: 公司做的是BS应用. 之前我们的后台服务器程序是带状态的,用ehcache存储登录状态:这两天被我改成了redis存储,应用本身不再存储登录状态. 然后自测,我在测试某个很耗时间的网页操作 ...
- Puppeteer笔记(七):Puppeteer切换浏览器TAB页
一.Puppeteer切换浏览器TAB页 1.browser.pages() 二.上手实例Demo 功能测试:打开www.ly.com首页,定位搜索"苏州",获取新打开页面上的搜索 ...
- 浅谈基于WOPI协议实现跨浏览器的Office在线编辑解决方案
如今,基于Web版的Office 在线预览与编辑功能已成为一种趋势,而关于该技术的实现却成为了国内大部份公司的技术挑战,挑战主要存在于两方面: 其一:目前国内乃至微软本身,还没有相对较为完善的解决方案 ...
- React技巧之处理tab页关闭事件
原文链接:https://bobbyhadz.com/blog/react-handle-tab-close-event 作者:Borislav Hadzhiev 正文从这开始~ 总览 在React中 ...
- Web跨浏览器进程通信(Web跨域)
Web跨域已是老生常谈的话题,这次来尝试下跨域浏览器进程之间的通信 —— 这在过去基本依靠网络中转实现 在之前一篇文章里尝试了跨浏览器的数据共享,最后提到使用LocalConnection还可以实 ...
- 跨web浏览器的IC卡读卡器解决方案
BS结构的程序,如果要与IC卡读卡器通信本身就是件不容易解决的事情.微软的activex ocx技术将这种应用限制在IE浏览器上了,不兼容其它的浏览器.而Chrome使用插件也不兼容IE和其他的浏览器 ...
- easyui修复浏览器刷新后,tab页全部关闭的问题
一.问题描述 使用easyui搭建的上左右页面布局,当我们在右侧打开了tab页,发现点击浏览器的刷新按钮后,整个页面会被重新渲染,导致所有打开的tab页都被关闭,回到初始状态的问题. 这个问题虽然不影 ...
- 浏览器,tab页显示隐藏的事件监听--页面可见性
//监听浏览器tab切换,以便在tab切换之后,页面隐藏的时候,把弹幕停止 document.addEventListener('webkitvisibilitychange', function() ...
- 监听浏览器tab选项卡选中事件,点击浏览器tab标签页回调事件,浏览器tab切换监听事件
js事件注册代码: <script> document.addEventListener('visibilitychange',function(){ //浏览器tab切换监听事件 if( ...
随机推荐
- c++ static用法总结【转载】
static关键字是C, C++中都存在的关键字.static从字面理解,是“静态的“的 意思,与此相对应的,应该是“动态的“. static的作用主要有以下3个: 1.扩展生存期: 2.限制作用域: ...
- 一步一步学MySQL-日志文件
错误日志 错误日志不用多说,记录了mysql运行过程中的错误信息,当出现问题时,我们可以通过错误日志查找线索. 慢查询日志 可以通过参数long_query_time来设置时间,当sql语句执行超过指 ...
- Android -- 深入了解自定义属性
1,相信我们写过自定义控件的同学都会有一个疑问,自定义属性到底是怎么工作的,为什么要使用自定义属性呢,接下来结带着大家一起来学习学习,在学习这一篇的时候,可以下看看我的上一篇<从源码的角度一步步 ...
- css 定位功能position
Static 定位 HTML元素的默认值,即没有定位,元素出现在正常的流中.静态定位的元素不会受到top, bottom, left, right影响. 相对定位Relative相对定位元素的定位是相 ...
- bouncycastle 国密SM2 API的使用
摘要:本文不对SM2做过多的介绍,主要介绍java bouncycastle库关于SM2的相关API的使用及注意事项 1. SM2 签名: 注意: 1)签名格式ASN1(描述了一种对数据进行表示.编码 ...
- Alpha版与Beta版
简单说说这两个词的意思,以后会稍加更多的补充. Alpha版意在对少数主要客户和市场进行数量有限的分发,用于演示目的的早期构造.其无意在实际环境中使用.使用Alpha版的所有人员必须了解确切内容和质量 ...
- Android -- AsyncTask源码解析
1,前段时间换工作的时候,关于AsyncTask源码这个点基本上大一点的公司都会问,所以今天就和大家一起来总结总结.本来早就想写这篇文章的,当时写<Android -- 从源码解析Handle+ ...
- CSS3基础(2)—— 文字与字体相关样式、盒子类型、背景与边框相关样式、变形处理、动画功能
一. CSS3 文字与字体相关样式 1. 给文字添加阴影 text-shadow: length length length ccolor; 属性适用于文本阴影,指定了水平阴影,垂直阴影,模糊的距离, ...
- javascript多种方法实现数组去重
先说说这个实例的要求:写一个方法实现数组的去重.(要求:执行方法,传递一个数组,返回去重后的新数组,原数组不变,实现过程中只能用一层循环,双层嵌套循环也可写,只做参考): 先给初学者解释一下什么叫数组 ...
- 绑定微信以及获取openId
由于公司最近在做一个微信公众号的项目,需要获取用户openId,我再一次踏入了微信的坑! 先在这里告诫后来的同志,如果一样要开始做有关微信的东西,最好是有前辈,或者直接看完文档,不懂或者纳闷的地方直接 ...