前端开发各种cross之cross domain(二)

这是我三年前写的blog,原文在这里

在上一篇关于跨域获取数据中,苦逼的前端开发工程师,刚才解决了一个cross domain的问题,还没有来得及沉浸在其中喜悦之际,又迎来了另外一个cross domain的问题:邮箱出现双滚动啦。

在页面中当需要加载外域app的iframe时候,最容易出现跨frame的cross domain问题,比如刚才那前端开发工程师遇到的双滚动的问题,在页面中放入一个外域的iframe,当iframe的高度大于iframe的页面的body高度时,就会出现滚动,这时候加上本身页面已经有一条滚动,那就出现经典的双滚动问题,如果iframe里面又嵌套另一个外域iframe,那可能会出现三滚动,继续嵌套..继续滚动..继续套..继续滚…,之所以出现这个问题,是因为外域的iframe不能直接调用:

parent.document.getElementById("iframe_id").style.height =   document.body.offsetHeight + "px"

这里就需要跨iframe进行cross domain,有以下提到的两个方式。
以下的例子会以这几个页面作为例子:
页面a:

http://www.a.com/a.htm

页面b:

http://www.b.com/b.htm

a的内容:

<iframe src="http://www.b.com/b.htm" id="ifrm_b"></iframe>

html5的postMessage方式

首先介绍一个浏览器原生的跨域调用方式,在支持html5的高级浏览器,支持这种方式:

oWin.postMessage(oMessage, sTargetDomain);
  • oWin为需要跨域调用的window对象
  • oMessage为传送的数据
  • sTargetDomain是跨域的frame的域

假如页面a跨域页面b,那么在页面a上调用以下进行跨域:

1
2
3
4
5
try{
document.getElementById("ifrm_b").contentWindow.postMessage(JSON.stringify({value:"this is cross call by PostMessage."}), "http://www.b.com");
}catch(e){
alert("请使用以下或者更高版本浏览器:\nIE8+, Firefox 3, Opera 9, Chrome 3和 Safari 4!");
}

然后在b,需要加一个window的message事件监听

1
2
3
4
fAddEvent(window, "message", function (o) {
var oMessage = o.data;// data属性就是postMessage的oMessage参数
alert(oMessage.value); // 这时候会显示:this is cross call by PostMessage.
});

这样就完成了跨frame的跨域通讯

  • 优点:完美的跨frame跨域调用
  • 缺点:只支持html5的浏览器

代理iframe方式

使用代理iframe的方式有两种,一种通过window.name方式跨域调用,一种是通过url参数的方式传递调用,不过两种方式的调用原理都是创建一个隐藏的iframe,iframe的url指向需要跨域的域名的一个代理页面,然后通过这个代理的iframe,和跨域的iframe通讯,因为这时代理的iframe和跨域的iframe完全同域,就可以畅通无阻进行。
在上面的例子加多一个代理页面c:
代理iframe c:http://www.b.com/c.htm

  • 在页面a加入以下函数:
1
2
3
4
5
6
7
8
9
10
11
12
function fCrossByName(sDomain, oData) {
var oIframe = document.getElementById("ifrmCross");
if(oIframe){
oIframe.parentNode.removeChild(oIframe);
}
oIframe = document.createElement("IFRAME");
oIframe.style.display = "none";
oIframe.id = "ifrmCross";
oIframe.name = JSON.stringify(oData);
document.body.appendChild(oIframe);
oIframe.src = "http://" + sDomain + "/c.htm";
}
  • 然后在页面a就可以进行跨域:
1
2
3
4
5
fCrossByName("www.b.com", {
value:'this is cross call by iframe',
func : "fCrossByNameCall", // 要跨域调用的函数
win : "ifrm_b"
});
  • 然后在代理iframe加入以下代码:
1
2
3
4
var oData = !window.name?null:(new Function('return '+window.name))();
if(oData.win && oData.func){
var oResult = (oData.win == "top" ? top : parent[oData.win])[oData.func](oData);
}

这样就可以实现整个的跨域调用,通过url参数方式跨域调用,和这个类似,只是需要将数据放到代理iframe的url参数上,而不是name。
下面这个页面demo显示这三种方式的调用(因为没加JSON的转换js包,需要使用支持内置JSON对象的浏览器运行..):
http://mimg.163.com/demo/crossdomain_test.htm

  • 优点:没有浏览器兼容问题
  • 缺点:因为要部署代理iframe的文件,所以比较麻烦,整个流程也比较复杂,如果要实现和html5的postMessage兼容的接口,需要做大量封装,就会更加造成更加复杂

总结:在邮箱实际使用过程中,当只是简单的跨域调用比如前面提到的解决双滚动问题,一般可以简单的使用第二种方式,但是如果需要复杂的双向互相调用,就需要支持html5的浏览器调用postMessage,不支持的需要第二种方法实现兼容。