iframe跨域踩坑

11,604

前言

工作中,有些系统是利用的iframe。有一次突然遇到了一个令人费解的跨域报错,没有发请求,却报了“Error:Blocked a frame with origin...from accessing a cross-origin frame”的跨域错误的问题,当时自己一脸懵(恕我无知)……原来是因为自己的一句window.parent.document.scrollTop,万万想不到,iframe之间的通信也会存在跨域问题,赶紧去研究一下。

一.iframe跨域

其实前言的本质,就是因为父、子窗口这两个页面不同源,但是却想在iframe中获取父窗口的dom,造成了跨域问题。

若非同源,会受到三种跨域限制:一是ajax请求限制;二是DOM无法获得;三是Cookie、LocalStorage 和 IndexDB 无法读取。

本文主要讲第二点,不满足同源策略的两个网页是无法拿到对方的DOM的

这里需要特别说明的是:阮一峰老师在文章 浏览器同源政策及其规避方法里说到父窗口想获取子窗口的DOM,因为跨源导致报错。

但是经我这边用chrome操作,并没有报错,是可以获取到dom的,我大胆猜想可能是chrome更新了安全策略吧。

举个栗子:

<!-- 父窗口http://172.18.12.109:8080/#/Parent,里面分别嵌了同源和跨域两个子窗口-->
<template>
  <div class="">
    <div>父窗口</div>
    <iframe src="http://172.18.12.109:8080/#/Child_Same" id="iframe1"></iframe>
    <iframe src="http://172.18.12.109:8082/#/Child_Diff" id="iframe2"></iframe>
  </div>
</template>

<script>
export default {
  name: 'Parent',
  data () {
    return {
    }
  },
  mounted( ) {
    console.log('document.getElementById("iframe1").contentWindow.document',document.getElementById("iframe1").contentWindow.document);
    console.log('document.getElementById("iframe2").contentWindow.document',document.getElementById("iframe2").contentWindow.document);
  }
}
</script>
<!-- 同源子窗口http://172.18.12.109:8080/#/Child_Same-->

<template>
  <div class="">
    <div>同源子窗口</div>
  </div>
</template>

<script>

export default {
  name: 'Child_Same',
  data () {
    return {
      
    }
  },
  mounted() {
    console.log('同源 window.parent.document',window.parent.document);
  }
}
</script>
<!-- 跨域子窗口http://172.18.12.109:8081/#/Child_Diff-->
<template>
  <div class="">
    <div>跨域子窗口</div>
  </div>
</template>

<script>

export default {
  name: 'Child_Diff',
  data () {
    return {
      
    }
  },
  mounted() {
    console.log('跨域 window.parent.document',window.parent.document);
  }
}
</script>

image

父窗口获取子窗口的DOM在同源和跨域下都可以获取;而子窗口获取父窗口的DOM在跨域时会报错,同源可以获取到。

二.解决方法

targetWindow.postMessage(data,targetOrigin,transfer)

这是跨窗口发送消息的方法。

逐一解释一下参数:

targetWindow: 窗口的引用。可以是当前窗口window,也可以是一个窗口获得的其它窗口的引用,可以为:

  • Window.parent
  • HTMLIFrameElement.contentWindow
  • Window.open
  • Window.opener
  • Window.frames +索引值

data: 传递的消息

targetOrigin:接收消息的目标窗口的源(协议 + 域名 + 端口)。可以设为*,表示不限域名,可向所有窗口发送。

transfer : 可选参数

好的,那么如何接收发来的消息呢?

目标窗口对message事件进行监听,即可获得发送过来的消息

window.addEventListener("message", receiveMessage, false);

function receiveMessage(event)
{
  // For Chrome, the origin property is in the event.originalEvent
  // object. 
  // 这里不准确,chrome没有这个属性
  // var origin = event.origin || event.originalEvent.origin; 
  var origin = event.origin
  if (origin !== "http://example.org:8080")
    return;

  // ...
}

所以前言子获取父窗口dom的问题可以这样解决:

//父窗口向iframe发消息
 document.getElementById("iframe").contentWindow.postMessage({scrollTop:document.body.scrollTop},'http://172.18.12.109:8082');
//iframe接收消息
 window.addEventListener('message', (e)=>{
        let origin = event.origin || event.originalEvent.origin; 
        if (origin !== 'http://172.18.12.109:8080') {
          return;  
        }else {
           console.log(e.data);
           //{scrollTop: 0}
        }
      },false);

再举一个工作中遇到的栗子:

在iframe中不同源的子窗口页面间实现跳转

具体如下:

有一个系统,是由提供了整个页面导航栏的父窗口Aiframe嵌入的不同源子窗口组成的。不同一级导航对应不同模块,同一模块下可以通过路由进行页面跳转,而不同模块只能通过url跳转。

现在要实现点击模块B中的一个按钮从B跳转到模块C的一个页面,但是不能新开窗口跳转,还是要在父窗口里,因为要外部导航栏始终不变,只是内部页面跳转。

因此这里需要用到在子窗口B和父窗口A通信,然后在A中打开C这个窗口。

与前言同样的,此处为父向子发送消息,子窗口接收消息。

示例代码如下:

<!-- 父窗口http://172.18.12.109:8080/#/Parent-->
<template>
  <div class="">
    <div>外部父窗口</div>
    <!-- 跨域 -->
    <iframe src="http://172.18.12.109:8081/#/Child_Diff1" id="iframe1"></iframe>
  </div>
</template>

<script>
export default {
  name: 'Parent',
  data () {
    return {
    }
  },
  mounted( ) {
  //目标父窗口接收子窗口发送的消息
     window.addEventListener('message', (e)=>{
        let origin = event.origin || event.originalEvent.origin; 
        if (origin !== 'http://172.18.12.109:8082') {
          return;  
        }else {
        //更换iframe的src,实现iframe页面跳转
          document.getElementById("iframe1").src=e.data.link;
        }
      },false);
    }
  }
}
</script>
<!-- 内部跨域子窗口1http://172.18.12.109:8081/#/Child_Diff1-->
<template>
  <div class="">
    <div>内部跨域子窗口1</div>
    <button @click="onClick">点击向父窗口发送消息</button>
  </div>
</template>

<script>
export default {
  name: 'Child_Diff1',
  data () {
    return {
      
    }
  },
  methods: {
    onClick () {
      let message =  {type: 'open', link:'http://172.18.12.109:8082/#/Child_Diff2'};
      //子窗口向父窗口发送消息,消息中包含我们想跳转的链接
      window.parent.postMessage(message,'http://172.18.12.109:8080');
    }
  }
}
</script>

实现效果:

image

因此,最终可实现: 点击跨域子窗口1的按钮,向父窗口发送消息,告诉父窗口要跳转的跨域子窗口2的链接,父窗口接收到消息后,更改iframe src的url,即可实现不用打开新页面,iframe子窗口的变化。