window.postMessage() 方法可以安全地实现跨域通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为https),端口号(443为https的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。
一、安装live-server
要想实现跨窗口通信,必须要在服务器上运行,直接用浏览器打开HTML文件只能处理单个文件,窗口之间无法通信。
npm install -g live-server
使用命令live-server
进行启动。
安装live-server,在任意位置启动服务器。
在服务器中启动之后,会看见文档中多了一个script标签。这段代码是由live-server插入的。当运行在后台的live-server检测到文件变化,就会通过websockt向网页发送“reload”消息,从而可以实现浏览器中的网页总是实时的响应文件的变更。
-
// <![CDATA[ <-- For SVG support
-
if (
'WebSocket'
in
window) {
-
(
function() {
-
function refreshCSS() {
-
var sheets = [].slice.call(
document.getElementsByTagName(
"link"));
-
var head =
document.getElementsByTagName(
"head")[
0];
-
for (
var i =
0; i < sheets.length; ++i) {
-
var elem = sheets[i];
-
head.removeChild(elem);
-
var rel = elem.rel;
-
if (elem.href &&
typeof rel !=
"string" || rel.length ==
0 || rel.toLowerCase() ==
"stylesheet") {
-
var url = elem.href.replace(
/(&|\?)_cacheOverride=\d+/,
'');
-
elem.href = url + (url.indexOf(
'?') >=
0 ?
'&' :
'?') +
'_cacheOverride=' + (
new
Date().valueOf());
-
}
-
head.appendChild(elem);
-
}
-
}
-
var protocol =
window.location.protocol ===
'http:' ?
'ws://' :
'wss://';
-
var address = protocol +
window.location.host +
window.location.pathname +
'/ws';
-
var socket =
new WebSocket(address);
-
socket.onmessage =
function(msg) {
-
if (msg.data ==
'reload')
window.location.reload();
-
else
if (msg.data ==
'refreshcss') refreshCSS();
-
};
-
console.log(
'Live reload enabled.');
-
})();
-
}
-
// ]]>
二、基础知识
MessageEvent有以下几个属性:
- data:从其他window中传递过来的对象
- origin:调用 postMessage 时消息发送方窗口的 origin
- source:对发送消息的窗口对象的引用; 您可以使用此来在具有不同origin的两个窗口之间建立双向通信。
在发送数据窗口执行:otherWindow.postMessage(msg,origin)
- otherWindow:表示接受数据的窗口的window对象,包括iframe的子窗口和通过window.open打开的新窗口。
- msg表示要发送的数据,包扩字符串和对象(ie9以下不支持,可以利用字符串和json互换)。
- origin表示接收的域名。
三、最简单的一个demo
父窗口打开一个子窗口,然后询问子窗口:“吃饭了吗”,子窗口回复父窗口:“吃了”
father.html
-
<html>
-
-
<body>
-
-
</body>
-
<script>
-
window.addEventListener("message", function(e) {
-
document.querySelector("body").appendChild(document.createTextNode('son say: ' + e.data))
-
})
-
var son = window.open("son.html")
-
son.onload = function() {//必须得要等到儿子加载完成才可以说话
-
son.postMessage("吃饭了吗", location.href)
-
}
-
</script>
-
-
</html>
-
son.html
-
<html>
-
-
<body>
-
-
</body>
-
<script>
-
window.addEventListener("message", function() {
-
console.log(event)
-
document.querySelector("body").appendChild(document.createTextNode("father say: " + event.data))
-
event.source.postMessage("吃了", event.origin)
-
})
-
</script>
-
-
</html>
四、一个网页聊天系统
father.html
-
<html>
-
-
<head>
-
<style>
-
textarea,
-
input {
-
width: 80%;
-
font-size: 20px;
-
font-family: "Consolas";
-
}
-
-
textarea {
-
height: 80%;
-
}
-
-
input {
-
height: 10%;
-
}
-
</style>
-
</head>
-
-
<body>
-
<div style="text-align:center">
-
<textarea readonly></textarea>
-
<input type="text" style="margin-top:10px" onkeydown="keydown()">
-
</div>
-
</body>
-
<script>
-
function $(sel) {
-
return document.querySelector(sel)
-
}
-
window.addEventListener("message", function() {
-
$("textarea").value += "\nson say: " + event.data
-
})
-
var son = window.open("myson.html")
-
-
function keydown() { //这里不需要传递参数,直接使用event就可以
-
if (event.keyCode == 13) {
-
son.postMessage($("input").value, location.href)
-
$("textarea").value += "\n我说:" + $("input").value
-
$("input").value = ""
-
event.preventDefault
-
}
-
}
-
</script>
-
-
</html>
myson.html
-
<html>
-
-
<head>
-
<style>
-
textarea,
-
input {
-
width: 80%;
-
font-size: 20px;
-
font-family: "Consolas";
-
}
-
-
textarea {
-
height: 80%;
-
}
-
-
input {
-
height: 10%;
-
}
-
</style>
-
</head>
-
-
<body>
-
-
<div style="text-align:center">
-
<textarea readonly></textarea>
-
<input type="text" style="margin-top:10px" onkeydown="keydown()">
-
</div>
-
</body>
-
<script>
-
var father = null
-
window.addEventListener("message", function() {
-
$("textarea").value += "\nfather say: " + event.data
-
if (father == null) {
-
father = {
-
source: event.source,
-
origin: event.origin
-
}
-
}
-
})
-
-
function $(sel) {
-
return document.querySelector(sel)
-
}
-
-
function keydown() { //这里不需要传递参数,直接使用event就可以
-
if (event.keyCode == 13) {
-
father.source.postMessage($("input").value, location.href)
-
$("textarea").value += "\n我说:" + $("input").value
-
$("input").value = ""
-
event.preventDefault
-
}
-
}
-
</script>
-
-
</html>
五、最后一个demo
-
<html>
-
-
<head>
-
<meta charset="UTF-8">
-
</head>
-
-
<body>
-
<input type="button" value="Open Window" onclick="openWin()" />
-
</body>
-
<script>
-
window.addEventListener("message", function(evt) {
-
var ele = document.createElement("pre")
-
ele.innerText = "son say:" + JSON.stringify(evt.data)
-
document.querySelector("body").appendChild(ele)
-
})
-
-
var popupwin = window.open("son.html");
-
//onload只能执行一次,也就是如果子窗口有onload事件,可能会覆盖。
-
popupwin.onload = function(e) {
-
var params = "天下大势为我所控"
-
var origin = location.href
-
popupwin.postMessage(params, origin);
-
}
-
popupwin.onunload = function(e) {
-
var ele = document.createElement("h1")
-
ele.innerText = "儿子最后说:" + popupwin.returnValue
-
document.querySelector("body").appendChild(ele)
-
}
-
</script>
-
-
</html>
son.html
-
<html>
-
-
<head>
-
<title>popup window</title>
-
</head>
-
-
<body>
-
<button onclick="closeWin()">点我返回</button>
-
<div id="show"></div>
-
</body>
-
<script>
-
function closeWin() {
-
window.returnValue = "这是返回值";
-
window.close();
-
}
-
//HTML DOM fully loaded, and fired window.onload later.
-
document.onreadystatechange = function() {
-
if (document.readyState === 'complete') {
-
window.addEventListener('message', function(event) {
-
document.querySelector("#show").appendChild(document.createTextNode("father say:" + e.data))
-
event.source.postMessage("what's the fuck", event.origin)
-
});
-
}
-
};
-
</script>
-
-
</html>
六、安全问题
如果您不希望从其他网站接收message,请不要为message事件添加任何事件侦听器。
如果您确实希望从其他网站接收message,请始终使用origin和source属性验证发件人的身份。
当您使用postMessage将数据发送到其他窗口时,始终指定精确的目标origin,而不是*。
参考资料
https://developer.mozilla.org/zh-CN/docs/Web/API/Window/postMessage