ExternalInterfaceでは対象swfをonLoad以降にロードしてはダメ

2008.11.19 / actionscript

何かとはまりやすいJavaScript-ActionScript連携のExternalInterface。以前にもこんなエントリ書いてました。

今回、また新たにハマりポイントがあったのでメモしておきます。

IEでswfの参照が取得できない(nullが返る)

通常、JSからASの関数をコールする場合は

  <object width="320" height="240" align="middle" classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" id="externalpl">
  <param value="sameDomain" name="allowScriptAccess"/>
  <param value="true" name="allowFullScreen"/>
  <param value="/swf/hoge.swf" name="movie"/>
  <param value="high" name="quality"/>
  <param value="#111111" name="bgcolor"/>
  <embed width="320" height="240" align="middle" 
  pluginspage="http://www.macromedia.com/go/getflashplayer" type="application/x-shockwave-flash" 
  allowscriptaccess="sameDomain" allowfullscreen="true" bgcolor="#111111" quality="high" 
  src="/swf/hoge.swf" name="externalpl"/>
  </object>
  

と、hoge.swfがある場合にJS側で

  var player = document.all? window['externalpl'] : document['externalpl'];
  

と、すると

player.hello();

なんかが実行できます。ただし、あらかじめhoge.swf側で

  ExternalInterface.addCallback('hello', this._hello);
  function _hello() : void
  {
    // hogehoge 
  }
  

などとしておく必要があります。

今回のハマりは、このJSからASの関数を呼び出すときに、IEの時の場合のみASの関数が実行できない、詳しく言うとASの関数を実行するためのFlashの(関数)オブジェクト参照を取得できず、nullが返ってくるという問題がありました。逆にFirefox, Safariなどではちゃんとplayerの参照が取得できて(正常に参照が取得できると、関数オブジェクトが取得できます)、helloメソッドの実行もうまくいくわけです。

よかれと思ったことが仇に

今回、というか僕は常々FlashVarsで値を渡しやすくするために、swfのタグの書き出しはHTMLを直接書くのではなくて、ラップするJavaScriptの関数を作って、それ経由で書き出すようにしています。いわゆるSWFObjectを利用してもいいのですが、たかだかタグ書き出しのためだけにライブラリをロードするのも通信がバカバカしいので自作することが多いです。たとえば

      var param = {
          type : 'windows', 
          name : 'vista'
      };
      var o = new OS(param);
      o.start();
  

な感じでJSのOS#startメソッドを実行するとFlashVarsでtype=windows&name=vistaの値を渡してobject/embedタグが書き出される、のような感じです。

こうしておくと書き出し方法をdocument.writeのように愚直に書き出すこともできるし、swfのサイズが大きい場合にHTMLがロードし終わった後にinnerHTML差し替えによって遅延書き出し方法を取る事も出来ます。このように書き出し方法やそのタイミングを自分の好みで変えられる上に、後者のような方法だとページ全体の体感速度を上げることもできて非常に便利なのです。ただし、そこに罠があったのです。。

swfをonLoad以降に書き出すとマズい

今回のケースではjQueryを使って、DOMContentLoaded/onLoadのタイミングでswfを動的に挿入させるようにしていました。要するにこんなかんじ。

  $(function(){
      var param = {
          type : 'windows', 
          name : 'vista'
      };
      var o = new OS(param, "container");
      o.start();  
  });
  

知っている方も多いかと思いますが、jQueryでは関数を$()で囲む事でDOMContentLoaded(画像のロードを待たない、純粋なDOM Tree構築後のタイミング。ただしDOMContentLoadedをサポートしていない場合はonLoadのタイミング。)で処理を実行させることができます。このタイミングで指定ID要素(=上の例だと“#container”)に対してinnerHTMLにobject/embed要素書き換えでswfをロードさせるようにしました。FirefoxやSafariはこれで何ごともなく動くのですが、IEだと動作しません。

では、どうすればいいか?というと、object/embed要素の書き出し方法をbody.onload時ではなく、単純にXHTML上にdocument.writeでobject/embed要素書き出しに修正すると動きました。

どうやら

この結果から推測するに「IEはbody.onload時(DOMContentLoaded時)において存在しないswfの参照は取得することができない」ということが言えそうです。つまり、ExternalInterfaceを利用したい場合は、対象swfはDOMContentLoaded以降にロードされるのではなく、DOMContentLoaded以前にロードされておく必要がある、ということですね。もっと言うと、IEでwindow[‘swf_id’]で参照するためのDOM Treeの参照(またはその複製?)がDOMContentLoaded以降に更新されていないように思われます。

知っておけばハマらないのですが気づかなければなかなか抜け出せない罠でした。しっかしExternalInterfaceの罠の多さはほんと異常です。。それもIEでハマることがやたら多い印象。このあたり、IE8になると少しは解消されたりするんでしょうか??