モダンブラウザでAppletを扱うときに知っておくこと

2009.04.07 / develop

[09/04/07 16:00 追記] embedでの呼び出し結果の表に誤りがあったので訂正しました。

世間ではiPhone OS3.0で騒がれていますが、そんな中メインストリームとは逆行してJava Appletについていろいろ調べていました。

情報が少なすぎる

世間的にはJava Appletの話なんて枯れすぎてる話題なので、いくら調べても2000年過ぎの情報ばかりが大半です。「ただしこの方法ではNetscape4.0以上の環境では。。。」とか言われても困るわけです。今どきのWebアプリケーションらしくJavaScriptと連携させるにはどうすればいいんでしょうか。そもそもappletのロード方法1つとってもSafariやChromeなんかのモダンブラウザに対応したロード方法とかまったくわかりません。あとJava Runtimeのインストールチェックなんてどうすればいいのでしょうか??疑問は尽きません。

そこで、今回これらの情報、

  1. JavaScriptからAppletを呼び出す方法
  2. AppletからJavaScriptを呼び出す方法
  3. モダンブラウザに適したAppletのロード方法

について調べてみたのでまとめておきたいと思います。

Java Runtime のバージョンチェック

そもそもAppletを実行できる環境であるかどうか、を判定する必要がありますが次のようなロジックで確認をすることが可能になります。

  1. Applet側でRuntimeのバージョン情報をpublicなフィールドに格納しておく
  2. JavaScriptから1.のフィールドを参照する
  3. 参照に失敗、または参照先の値が不正な場合はRuntimeがない、と判断。参照先に値があった場合はその値がバージョン情報

次に、JavaScriptとの連携を考えるわけですが、ここでは「JavaScriptからAppletのメソッドを呼び出す」「AppletからJavaScriptの関数を呼び出す」の2パターンがあります。まず、前者の方から考えていきましょう。

AppletからJavaScriptを呼び出す

こちらは非常にシンプルで「document.{$AppletをロードしたHTML要素のid}.{$appletにおけるpublic修飾子のメソッド、またはフィールド}」でアクセスすることができます。たとえば次のようなAppletがあるとします。

  import java.applet.Applet;
  
  public class VMInfo extends Applet {
    public String jreVersion = "";
    public void init() {
      this.jreVersion = System.getProperty("java.version");
    }
  }
  

また、次のようにappletタグでappletをロードしておくとします。

  <applet name="app" code="VMInfo" mayscript="true" archive="plugin.jar" width="430" height="200"></applet>
  

このとき、JavaScriptからAppletのjreVersionフィールドにアクセスする場合、

  document.app.jreVersion
  

で、アクセスが可能です。ここでのappはapplet要素のname属性値になります。非常に簡単ですね。

また、FlashのExternalInterface経由でJavaScriptからFlashのメソッドにアクセスする場合、IE系はdocument経由、それ以外のブラウザはwindow経由でFlashの参照を取得するなど、参照の取得方法は異なります。ところが、Appletの場合はIEでもFirefoxでもChromeでもすべて同じ「document経由」で参照を取得する、というのは1つのポイントになります。

AppletからJavaScriptを呼び出す

先ほどとは逆に、JavaからJavaScriptのメソッドを呼び出す方法について考えてみます。方法としては、

  1. JSObjectを利用する方法
  2. 共通 DOM API を介してアクセスする方法

の2種類の方法が存在します。ここでは単純な1の方法を紹介します。

JSObjectクラスのメソッド群を利用すると、HTML ページの DOM へのアクセスが容易になります。JSObjectを利用する場合、次のjarファイルをクラスパスに通しておく必要があります。

{$jdk}\jre\lib\plugin.jar

plugin.jarにクラスパスを通すと、netscape.javascript.JSObject を利用することが可能になります。JSObjectはstaticメソッドでglobalなwindowオブジェクトの参照を取得します。windowオブジェクトの参照を取得すると、あとは任意のJavaScriptのコードをeval()で実行することが可能になります。たとえばwindow.alert()を呼び出す場合は次のようなコードになります。

  import java.applet.Applet;
  import netscape.javascript.JSObject;
  
  public class VMInfo extends Applet {
     public void init() {
    JSObject win = JSObject.getWindow(this);
    String jsContext = "alert()";
    win.eval(jsContext);
     }
  }

要はActionScript3におけるExternalInterface.callのようなもの、というイメージで良いと思います。JSObectのAPIについては、こちらのドキュメント にまとまっているので、さらに詳しく知りたい方はご参照ください。

ここで1つポイントとして、JSObjectを利用してappletとJavaScriptとの連携を行うときは、appletをロードするときのHTML要素に対して「mayscript」属性を追加しておく必要があります。属性値はtrueにでもしておけばいいですが、実際は属性が存在すれば問題は無いようです。このあたりの話は「JavaScriptを使用するアプレットの単体テスト」で述べられていますので、ご参考ください。

ロード方法

さて、appletのロード方法についても、パッと考えただけでもいろんな方法が思いつきます。

  1. appletタグでロード
  2. objectタグでロード
  3. embedタグでロード

Flashのような発想をすると、objectタグとembedタグの組み合わせでロードするのが本筋のような気もしますが、とりあえずこれまでで述べてきたJavaScript連携のコードを含んだAppletのロードを全パターンで試してみました。

対象となるAppletのコードは次のようにしています。 appletがロードされると、JREのバージョン、およびベンダ情報を取得し、JS側のVMInfo.notify()を呼び出します。(Java→JavaScript呼び出しの確認)

  import java.applet.Applet;
  import java.awt.TextField;
  import netscape.javascript.JSObject;
  
  public class VMInfo extends Applet {
      
      public String jreVersion = "";
      public String jreVendor = "";
  
      private TextField versionField;
      private TextField vendorField;
  
      public void init() {
          this.jreVersion = System.getProperty("java.version");
          this.jreVendor = System.getProperty("java.vendor");
          
          this.versionField = new TextField(jreVersion, jreVersion.length());
          this.vendorField = new TextField(jreVendor, jreVendor.length());
          
          add(versionField);
          add(vendorField);
          
          // callback
          JSObject win = JSObject.getWindow(this);
          String jsContext = "VMInfo.notify({\"version\":\"" + jreVersion + "\", \"vendor\":\"" + jreVendor + "\"})";
          win.eval(jsContext);
      }
  }
  
  

検証するJavaScriptの関数VMInfo.norifyは次のように用意しておきます。構造は単純で、Objectを引数に受け取り、alertで確認しています。

.

  <script type="text/javascript">
   //<![CDATA[
          var VMInfo = {
              notify : function(info){
                  var version = info.version || "";
                  var vendor = info.vendor || "";
                  window.alert([version, vendor]);
              }
          };
   //-->
  </script>
  

あわせて、JavaScript→Javaの呼び出しの確認として、次のようにAppletのプロパティを直接参照してみます。

  <script type="text/javascript">
  //<![CDATA[
        alert(document.app.jreVersion);
  //-->
  </script>
  

呼び出し方法は実はこれではダメ

当初、この組み合わせでいろいろ試していたのですが、実はOperaだけJavaScript→Javaの呼び出しで失敗することが多くて(undefinedが返る)、さてどうしたものか、と悩んでいました。それ以外のブラウザでは正確にAppletのプロパティにアクセスできるので、アクセス方法は間違ってることは無さそう。OperaはAppletへのアクセスはサポートしてないのかな、、と思ってたときに、そういえばAJAXまわりの話題でOperaはロードのタイミングが他のブラウザと比べておかしい、みたいな話題を見たことをフと思い出しました。「もしやappletがロードし終わる前にアクセスしようとしてる?」と思い、プロパティへのアクセスをwaitをかけてズラしてみました。

  <script type="text/javascript">
  //<![CDATA[
        var d = document;
        setTimeout(function(){alert(d.app.jreVersion)}, 3000);
  //-->
  </script>
  

ビンゴ!

setTimeoutでアクセス時間をズラしてあげることで、Operaでもアクセスが可能になりました。やはりOperaはAppletへのプロパティアクセスが他のブラウザよりも早く(?)行われていたみたいです。本当は3000ms決め打ちじゃなくて、一定回数試行した方がいいのですが、とりあえず今回の実験はこの方法を取る事にして、いろんなappletロード方法の組み合わせと比較してみたいと思います。

ちなみに、今回試したブラウザの細かなバージョンについては、IE 7, Firefox 3.0.8, Safari 3.2.2, Opera 10.00 alpha, Chrome 2.0.171.0となっています。すべてWindows XP上での動作確認です。

パターン1 : appletタグでロード

まずは一番シンプルでレガシーな方法。

  <applet name="app" code="VMInfo" mayscript="true" archive="plugin.jar" width="430" height="200"></applet>
  

結果は次の通り。

Browser 表示 JS→Java Java→JS
IE7
Fx3
Safari3
Opera10
Chrome2

全部OK! レガシーな方法はモダンブラウザでもちゃんと動くようです。

パターン2 : objectタグでシンプルにロード

次にobjectタグでロード。ただし、オプション情報はすべてobject要素の属性に設定するシンプルなロード方法にしてみます。

  <object id="app" classid="java:VMInfo" archive="plugin.jar"
  mayscript="true" type="application/x-java-applet" width="230"
  height="100">
  

結果は次の通りとなりました。

Browser 表示 JS→Java Java→JS
IE7 × × ×
Fx3
Safari3 ×
Opera10 ×
Chrome2 × ×

この形式だとIEだと表示すらしてくれませんでした。JSからの呼び出しもなかなかうまくいかない模様です。 むしろシンプルな方法だとobjectタグを利用した場合、IE以外のブラウザでも表示してくれるのは新しい発見でした。

パターン3 : objectタグでparamタグと組み合わせてロード

次にparamタグと組み合わせてobjectタグでロード。

  <object id="app"
  classid="clsid:8AD9C840-044E-11D1-B3E9-00805F499D93" width="230"
  height="100">
  <param name="code" value="VMInfo"
  />
  <param name="archive" value="plugin.jar" />No
  applet
  </object>
  

結果は次の通りとなりました。

Browser 表示 JS→Java Java→JS
IE7
Fx3 × × ×
Safari3 × × ×
Opera10 × × ×
Chrome2 × × ×

これも面白い結果。object/paramの組み合わせ方法はやはりAppletの場合でもIEしか有効では無い模様です。

パターン4 : embedタグでロード

次にパターン2,3とは逆にembedタグのみでロードしてみました。

  <embed code="VMInfo.class" width="230" height="100" name="app"
  type="application/x-java-applet;version=1.6"
  pluginspage="http://java.sun.com/javase/downloads/ea.jsp" />
  

Browser 表示 JS→Java Java→JS
IE7 × × ×
Fx3
Safari3
Opera10 ○(*) ○(*)
Chrome2

こちらは、IE, Opera以外は総じて良い結果になりました。 Operaは、JavaScriptからJavaの呼び出しにおいて、「○」としましたが、不安定なことが多く成功したりしなかったり、ということが繰り返されていたことを注釈として付け加えておきます。

まとめ

モダンブラウザにおいてもJavaScriptからAppletの呼び出し、AppletからJavaScriptの呼び出しなど、言語間の連携は可能であることが確認できました。また、Appletのプログラムは、その呼び出し方法によってブラウザごとでまったく挙動が異なることが分かりました。特に気にしなければappletタグで呼び出すのが最も安定した呼び出し方法で、IEのみでロードをさせたいときはFlashと同じくObject/paramタグの組み合わせで呼び出すのがベストなようです。

さて、今回はここで終わりますが実はappletの呼び出し方法はappletタグでの呼び出しはベストな解ではありません。

  • Java-pluginがインストールされていないときの自動プラグインインストール
  • appletのキャッシュコントロール

などを考えたいときに、もっと凝った方法を取る必要があります。この話は次回に書いてみたいと思います。