解决Flash和html在多标签浏览器中互访问题

8次阅读

     在 Flash 播放器运行时,将不同来源的资源划分到独立的沙箱 (sandbox) 内,不同沙箱之间不能彼此操作数据 (除非目标沙箱做过一些设置,授权其他沙箱可访问),这就是 Flash 的跨沙箱问题。当 Flash 文件(.swf)  和页面(.html) 不在同一个域名下时,如果不经过 Flash 内部声明 System.allowDomain,html 无法访问 flash 定义的接口; 不经过 html 设置 allowScriptAccess 为’always’,Flash 也无法调用页面上的 js 函数。

那么如果 html 和 flash 都设置了互相可以访问,是否 Flash 和 html 之间就可以互相访问了呢?理论上是的,然而实际上却不是。

在 Chrome、Firefox 等非 IE 浏览器上,互访是没有问题的。在 IE6、IE7、IE8 上也是正常的。但是在傲游、360 浏览器、腾讯浏览器等基于 IE 的多标签浏览器中,刷新页面的时候,Flash 播放器还是会报安全沙箱错误。

比如在某一个页面访问中,使用“基于 IE 的多标签浏览器”访问,你会看到,第一次是正常的,刷新之后就不正常。如果你安装的是 debug 版本的播放器,可以看到 Flash 运行时发生了异常。

解决 Flash 和 html 在多标签浏览器中互访问题

SecurityError: Error #2060:  安全沙箱冲突:ExternalInterface  调用者  http://xxxx.swf< 不能访问  http://yyyy.html。

at flash.external::ExternalInterface$/_initJS()
at flash.external::ExternalInterface$/call()
at Main/start()
at Main/init()

at Main()Flash 的源码:

package{
 import flash.display.Sprite;
 import flash.external.ExternalInterface;
 import flash.system.Security;
 import flash.text.TextField;

 /**
  * Flash 缓存造成的伪沙箱问题演示
  * @author qhwa
  */
 public class Main extends Sprite
 {public function Main():void
  {var tf:TextField = new TextField();
   tf.text = 'flash ready';
   tf.autoSize = 'left';
   addChild(tf);

   // 允许被所有其他沙箱中的 js 或 flash 调用
   Security.allowDomain("*");

   start();}

  private function start():void
  {
   // 在基于 IE 的多标签浏览器中,这里运行时可能出错
   ExternalInterface.call("alert", "Hi, flash is ready!");
   ExternalInterface.addCallback('drawCircle', drawCircle);
  }

  private function drawCircle():void
  {TextField(getChildAt(0)).appendText('nDraw a circle');

   graphics.beginFill(Math.random() * 0xFFFFFF, .5);
   graphics.drawCircle(Math.random() * stage.stageWidth,
    Math.random() * stage.stageHeight,
    50);
   graphics.endFill();}

 }
 }

似乎一旦 swf 是从缓存中读取的,allowScriptAccess 这个配置就不起作用?为了验证是不是缓存引起的,我们每次为 swf 文件地址后面加上随机的数字,发现就不存在上面的问题了。可见这个问题确实是浏览器缓存造成的。

为 swf 文件动态加时间戳或随机数,通过防止缓存可以回避掉这个问题。不过这不是一个很好的方案,因为这会极大增加服务器的压力,并且导致页面加载速度一直都很慢。

不过好消息是,目前有个比这个更好的方案:延迟 Flash 的初始化功能。通过将 Flash 的 ExternalInterface.addCallback 时机延后一些,就可以解决这个问题。

修改一下 Flash 的代码,加一个 setTimeout:

…(略)

public class Main extends Sprite
{public function Main():void
 {…(|> 略)
  //start();
  setTimeout(start, 500);
 }

 …(|> 略)

}
}

那么,延迟多少比较合适呢?如果太多,用户会感觉到明显的延迟; 太少,一些性能较差的电脑上问题依然存在。根据我一年多总结的经验,500ms 是比较合理的数字。目前阿里巴巴中国网站上使用的 Flash 应用程序,如果有需要和 js 通信,都是延迟 500ms 初始化。

顺便说一下,延迟 500ms 还有另外的一个作用。IE6 中,Flash 初始化的时候无法得到  stage.stageWidth 正确的数字,返回是 0(stageHeight 也一样)。延迟一点初始化就可以得到正确的数值了。

目前我还没有发现比延迟初始化更好的解决方案,如果你有更好的办法,欢迎交流!

正文完