跨域访问-iframe之间通信

简介:

在项目中,经常会使用到 iframe,把其它域名的内容嵌入到页面中,这对于我们来说是个很方便的方法,但是,有时候,无可避免需要多个iframe间或者iframe与主页面之间进行通信,比如交换数据或者触发一系列事件。

本文将重点介绍如何在跨域的情况下进行iframe通信。首先,我们了解一下在同域情况下,iframe的通信方法。

frame同域通信

假设,www.a.com域的main.jsp需要与子页面sub_1.jsp的互相访问,实现的功能如下:

  • (1)main.jsp通过iframe加载sub_1.jsp
  • (2)加载sub_1.jsp完成后,主页面通过JS读取子页面的标题显示到主页面的 p 标签,另外,调用子页面提供的方法 fun()
  • (3)子页面读取主页面的标题显示到 span 标签
  • (4)点击子页面的按钮,调用主页面的 fun() 方法

清单:main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>我是主页标题</title>
    <script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>

<p style="font-weight:bold;" id="sub_title">子页面加载完成后,将在此处显示子页面title</p>

<iframe width="500" height="300" id="frame"></iframe>

<p>
    <button onclick="loadFrame('sub_1.jsp');">load sub page</button>
</p>

<script type="text/javascript">

    function loadFrame(page) {

        var $frame = $("#frame");
        $frame.attr("src", page); //加载页面

        $frame.one("load", function () {
            var subWin = document.getElementById("frame").contentWindow; //获取子窗口的window对象

            $("#sub_title").html(subWin.document.title);

            subWin.fun();
        });
    }

    /**
     * 提供给子页面调用的函数
     * @param arg
     */
    function fun(arg) {
        alert("main页面的fun方法被frame页面调用,参数为: " + arg);
    }

</script>
</body>
</html>

清单:sub_1.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>sub_1:这是第一个子页面的标题</title>
    <script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>
这是第一个子页面
<button onclick="callparent();">调用父页面方法</button>

<div>
    父页面的标题是:<span id="parent_title"></span>
</div>

<script type="text/javascript">
    /**
     * 调用父页面的fun方法
     * @param arg
     */
    function callparent() {
        var pwin = window.parent; //获取父页面的window对象

//        var pwin = window.top; //获取顶层父页面的window对象

        pwin.fun(123456)
    }

    /**
     * 供父页面调用
     */
    function fun() {
        console.log("子页面的方法fun()被调用了。")
    }

    $(function () {
        //把父页面的title设置到p标签

        var pwin = window.parent; //获取父页面的window对象

        $('#parent_title').html(pwin.document.title)
    });
</script>
</body>
</html>

frame跨子域通信

如果上面的sub_1.jsp页面放在sub.a.com,同样通过www.a.com的main.jsp的iframe载入sub_1.jsp

 <%--跨资源访问,--%>
 <button onclick="loadFrame('http://sub.a.com/sub_2.jsp');">load sub page</button>

这时,通过浏览器的控制台后台,可以观察到报错。

Uncaught DOMException: Blocked a frame with origin "http://www.a.com" from accessing a cross-origin frame.

例如下面这种跨子域的操作是不允许的:

 var pwin = window.parent; //获取父页面的window对象

 $('#parent_title').html(pwin.document.title)

解决方法: 把主页的域和子页面的域设置为同一个二级域名下,比如a.com,它们之间就可以访问了:

(1)在main.jsp加上js代码

document.domain = "a.com"; //提升为二级域名

(2)在sub_1.jsp加上js代码

document.domain = "a.com"; //提升为二级域名

frame跨全域通信

html5中提供了window.postMessage这么一种用于安全的使用跨源通信的方法,可以实现跨文本档、多窗口、跨域消息传递。

postMessage(data,origin)方法接受两个参数:

(1)data:要传递的数据,html5规范中提到该参数可以是JavaScript的任意基本类型或可复制的对象,然而并不是所有浏览器都做到了这点儿,部分浏览器只能处理字符串参数,所以我们在传递参数的时候需要使用JSON.stringify()方法对对象参数序列化,在低版本IE中引用json2.js可以实现类似效果。

(2)origin:字符串参数,指明目标窗口的源,协议+主机+端口号[+URL],URL会被忽略,所以可以不写,这个参数是为了安全考虑,postMessage()方法只会将message传递给指定窗口,当然如果愿意也可以建参数设置为"*",这样可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

接下来,我们完成一个简单的示例,熟悉原理:子页面(www.b.com)发送子页面的标题到父页面(www.a.com),父页面接收参数,赋值到付页面的HTML文档中。

清单:父页面main.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>我是主页标题</title>
    <script type="text/javascript" src="js/jquery.js"></script>
    <script>
        window.onmessage = function (evt) {
            evt = event || evt; //兼容性,获取事件
            console.log(evt.origin); //打印来源
            $("#sub_title").html(evt.data);
    }
    </script>
</head>
<body>

<p style="font-weight:bold;" id="sub_title">子页面加载完成后,将在此处显示子页面title</p>

<iframe width="500" height="300" id="frame"></iframe>

<p>
    <%--跨全域访问--%>
    <button onclick="loadFrame('http://www.a.com/sub_3.jsp');">load sub page</button>
</p>

<script type="text/javascript">

    function loadFrame(page) {
        var $frame = $("#frame");
        $frame.attr("src", page); //加载页面
    }

    function setTitleVal(text) {
        $("#sub_title").html(text);
    }

</script>
</body>
</html>

清单:子页面sub_3.jsp

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8" %>
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>这是第一个子页面的标题</title>
    <script type="text/javascript" src="js/jquery.js"></script>
</head>
<body>

<script type="text/javascript">

    $(function () {
        window.parent.postMessage(document.title,"*"); //window.parent是父页面的window对象
    });
</script>
</body>
</html>

这样就实现了上面所述的功能。既然,已经知道了如何使用postMessage,下面将抽取出来得到一个通用的模块post_msg.js

/**
 * 封装postMessage方法,使发出的信息能被通用的message listener处理
 * @param {Window} targetWindow 目标框架窗口
 * @param {String} cmd  在目标窗口window空间中存在的方法名,消息发送后,目标窗口执行此名称的方法
 * @param {Array} args cmd方法需要的参数,多个参数时使用数组
 * @param {Function} callback 可选的参数,如果希望获得目标窗口的执行结果,使用此参数,结果返回后自动以返回结果为参数调用此回调方法
 */
function sendFrmMsg(targetWindow, cmd, args, callback) {
    var fname;
    if (callback) {
        fname = "uuid" + new Date().getTime(); //生成唯一编码
        window[fname] = callback;
    }

    args = (args instanceof Array) ? args : [args];

    var msg = {
        cmd: cmd,
        args: args,
        returnCmd: fname
    }


    targetWindow.postMessage(JSON.stringify(msg), "*");
}

/**
 * 获取另一个跨域窗口上的变量值
 * @param {Window} targetWindow 目标框架窗口
 * @param {String} varName 待获取值的变量名称
 * @param {Function} callback 获取成功后调用此回调方法处理变量值
 */
function getFrmVarValue(targetWindow, varName, callback) {
    sendFrmMsg(targetWindow, "getOtherFrameVarValue", [varName], callback);
}

/**
 * 给另一窗口设置变量值
 * @param {Window} targetWindow 目标框架窗口
 * @param {String} varName 待设置变量名
 * @param {Object} value 待设置变量值
 */
function setFrmVarValue(targetWindow, varName, value) {
    sendFrmMsg(targetWindow, "setOtherFrameVarValue", [varName, value]);
}

/**
 * 获取窗口变量值
 * @param {String} varName 变量名称
 */
function getOtherFrameVarValue(varName) {
    try {
        eval("var ret = " + varName);
        return ret;
    } catch (e) {
        console.log(e);
    }
}

/**
 * 设置变量值
 * @param {String} varName 变量名称
 * @param {Object} value 变量值
 */
function setOtherFrameVarValue(varName, value) {
    try {
        if (typeof value === "string") { // 字符串类型在拼接表达式时需要加引号
            value = "'" + value + "'";
        }
        eval(varName + "=" + value);
    } catch (e) {
        console.log(e);
    }
}

/**
 * message 事件监听器,自动根据cmd执行
 * @param {Object} evt
 * obj 形式:
 * {
 *     cmd: "目标窗口的function引用名",
 *     args: "参数列表" , 数组形式,
 *     [returnCmd]: "可选的,表示双向调用的回调function引用名,在回调时"
 *  }
 */
window.onmessage = function (evt) {
    evt = evt || event;

    var source = evt.origin;

    try {
        var obj = JSON.parse(evt.data);
        console.log(obj);
    } catch (e) {
        console.log(e);
    }

    if (obj.cmd) {
        // 拼成:setVal(obj.arg0, obj.arg1);
        var cmd = obj.cmd + "(";

        if (obj.args) { //拼接参数
            for (var i = 0; i < obj.args.length; i++) {
                obj["arg" + i] = obj.args[i];
                if (i > 0) {
                    cmd += ",";
                }
                cmd += "obj.arg" + i;
            }
        }

        cmd += ")";
        // 以上代码完成后,如obj.cmd="fun",则拼接字符串如下:fun(obj.arg1, obj.arg2);
        // 在通过eval执行时,各参数即obj.arg1等已绑定到obj对象上,所以取的是传递过来的参数数组值
        try {
            var ret = eval(cmd);
            if (obj.returnCmd) { //把结果返回给源
                evt.source.postMessage(JSON.stringify({
                    cmd: obj.returnCmd,
                    args: [ret]
                }), evt.origin);
            }
        } catch (e) {
            if (console) console.log(e);
        }
    }
}

如何使用? 假如main.jsp中有一个方法setTitleVal(arg)是对HTML标签的赋值,sub_3,jsp需要把子页面的标题的值传到父页面setTitleVal方法中,完成赋值。
(1)需要在main.jsp引入post_msg.js,并且暴露etTitleVal方法
(2)sub_3,jsp引入post_msg.js,调用下面代码即可。

 sendFrmMsg(window.parent, "setTitleVal", document.title);

原文链接

目录
相关文章
|
Web App开发 人工智能 前端开发
【Web API系列】使用getDisplayMedia来实现录屏功能
【Web API系列】使用getDisplayMedia来实现录屏功能
585 0
|
存储 缓存 并行计算
CPU组成元素:运算器+控制器(一)
CPU组成元素:运算器+控制器
4476 0
|
存储 缓存 算法
Linux 的 workqueue 机制浅析
## Intro workqueue 是 Linux 中非常重要的一种异步执行的机制,本文对该机制的各种概念,以及 work 的并行度进行分析,以帮助我们更好地**使用**这一机制;对 workqueue 机制并不陌生的读者也可以直接跳到第四节,即 "Concurrency" 小节,了解 workqueue 机制中 work 的并行度 以 v2.6.36 为界,workqueue 存在两个不
2300 0
Linux 的 workqueue 机制浅析
|
缓存 索引
kibana上执行ES DSL语言查询数据并查看表结构与数据、删除索引、查看文件大小
kibana上执行ES DSL语言查询数据并查看表结构与数据、删除索引、查看文件大小
853 0
|
Prometheus Kubernetes Cloud Native
k8s安装kube-promethues(超详细)
k8s安装kube-promethues(超详细)
1919 0
|
JavaScript 安全 程序员
Vue踩坑-because it violates the following Content Security Policy directive
Vue踩坑-because it violates the following Content Security Policy directive
824 0
|
JSON API 数据安全/隐私保护
阿里云邮件推送邮件发送失败的问题排查解决
阿里云邮件推送服务中邮件发送失败的排查方法包括:确认SMTP设置正确无误;验证发信域名和邮件地址;检查是否超出发送配额;审查邮件内容以确保合规;确保网络连接稳定;利用发送日志诊断具体问题。当阿里云邮件推送服务出现问题时,可考虑使用AOKSend作为替代方案,其配置简单且服务稳定可靠,支持多种配置选项,并提供详尽的文档支持。示例Python代码展示了如何使用AOKSend API发送邮件。这些步骤有助于确保邮件的顺利发送。
|
JSON JavaScript 前端开发
Golang深入浅出之-Go语言JSON处理:编码与解码实战
【4月更文挑战第26天】本文探讨了Go语言中处理JSON的常见问题及解决策略。通过`json.Marshal`和`json.Unmarshal`进行编码和解码,同时指出结构体标签、时间处理、omitempty使用及数组/切片区别等易错点。建议正确使用结构体标签,自定义处理`time.Time`,明智选择omitempty,并理解数组与切片差异。文中提供基础示例及时间类型处理的实战代码,帮助读者掌握JSON操作。
411 1
Golang深入浅出之-Go语言JSON处理:编码与解码实战
|
消息中间件 Java RocketMQ
微服务架构师的福音:深度解析Spring Cloud RocketMQ,打造高可靠消息驱动系统的不二之选!
【8月更文挑战第29天】Spring Cloud RocketMQ结合了Spring Cloud生态与RocketMQ消息中间件的优势,简化了RocketMQ在微服务中的集成,使开发者能更专注业务逻辑。通过配置依赖和连接信息,可轻松搭建消息生产和消费流程,支持消息过滤、转换及分布式事务等功能,确保微服务间解耦的同时,提升了系统的稳定性和效率。掌握其应用,有助于构建复杂分布式系统。
394 0
【sgDragMove】自定义组件:自定义拖拽组件,仅支持拖拽、设置吸附屏幕边界距离。
【sgDragMove】自定义组件:自定义拖拽组件,仅支持拖拽、设置吸附屏幕边界距离。