在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運行時發生了異常。
SecurityError: Error #2060: 安全沙箱沖突:ExternalInterface 調用者 http://pnq.cc/temp/test-dmm-crssdmn.swf 不能訪問 http://q.pnq.cc/works/test/test-dmm-crssmn.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也一樣)。延遲一點初始化就可以得到正確的數值了。
目前我還沒有發現比延遲初始化更好的解決方案,如果你有更好的辦法,歡迎交流!