AIRが持つHTMLレンダリングエンジン
さて、今回はAIRが持つHTMLレンダリング機能について解説したい。
当連載の初回でも簡単に説明したとおり、AIRはWebブラウザの機能を内蔵している。おかげで、AIRはFlexによる開発だけではなく、標準的なHTML/JavaScriptを用いてアプリケーションを開発することが可能となっている。そして、Flexをベースとして開発されたアプリケーションであっても、このHTMLレンダリングエンジンを利用してWebページを表示したり、操作したりすることもできる。ちなみに、AIRランタイムに含まれているのはApple SafariブラウザのベースとなっているWebKitというHTMLレンダリングエンジンだ。
このHTMLレンダリング機能を用いると、インターネット上のWebページといえども、アプリケーションの構成部品として自在に取り扱うことが可能となる。この技術を応用したサンプルアプリケーションには、以下のようなものが挙げられる。
「Scout」は、HTMLソースやDOMツリーなど、Webページのありとあらゆる情報を表示し、編集することが可能なブラウザアプリケーションだ。Adobe Labsのサンプル集から入手できる |
「DryerFox」は、ドラム式洗濯機の中でWebページがぐるぐる回るというもの。Doug Schmidt氏のブログから入手可能だ。ただし、Apollo αで動作するようになっているので、AIR βで動作させるにはアプリケーションディスクリプタを書き変える必要がある。 |
今回の記事でも、AIRのHTMLレンダリング機能を理解するのに手頃なサンプルを用意したので、後に紹介する。まずは、基本的なAPIを解説しよう。
HTML関連のAPI
HTML関連のAPIは、それほど数も多くなく、理解は容易だ。とりあえず以下の二つを押さえておけば問題はない。
mx.controls.HTML
flash.html.HTMLControl
mx.controls.HTML
こちらは、HTMLをロードして表示するためのMXMLコンポーネントだ。location
属性にURLを指定するだけで、指定したWebページを表示することができ、表示領域からはみ出る場合は自動的にスクロールバーが表示される。
例えば、HTMLコンポーネントを用いてマイコミジャーナルを表示するのであれば、
<mx:HTML location="http://journal.mycom.co.jp/index.html" width="100%" height="100%" />
たったこれだけだ。上で紹介したサンプルのどちらも、このHTMLコンポーネントを使用して作成されている。
flash.html.HTMLControl
単にWebページを表示するだけではなく、ページ内のDOMやJavaScriptコードを操作したり、Webページの読み込みにおける様々なイベントを捕捉したい場合は、flash.html.HTMLControl
APIに関する知識が必要になる。先に説明したHTML
コンポーネントも、ページのレンダリング自体はHTMLControl
を用いて行っており、HTML
クラスのプロパティ「htmlControl
」でアクセスできる。
このクラスを直接用いた場合、以下のようなコードになるのが典型的だ。
// (1) HTMLControlを生成してプロパティに値をセット
var html:HTMLControl = new HTMLControl();
html.width = 200;
html.height = 150;
// (2) Webページを読み込み
html.load(new URLRequest("http://journal.mycom.co.jp/index.html"));
// (3) Spriteに貼り付け
var sprite:Sprite = new Sprite();
sprite.addChild(html);
(1) HTMLControl
は、普通にnew
してインスタンスを生成し、プロパティをセットできる。
(2) load
メソッドを使用して、Webページの読み込みを行うことができる。引数には、flash.net.URLRequest
を指定する。HTML形式の文字列を引数に取るloadString(htmlContent:String)
メソッドも存在する。
(3) HTMLControl
クラスは、直接Flexコンポーネントに子要素として追加することはできない。この例のように、flash.display.Sprite
やmx.core.UIComponent
に子要素として追加することで、他のFlexコンポーネント上に表示させることができるようになる。
HTMLControlのイベント処理
HTMLControl
クラスは、addEventListener
メソッドを用いて、HTMLレンダリングの際の様々なイベントを処理するリスナを登録することができる。数多くのイベントが存在するが、良く使うのは以下のようなイベントだ。
- Event.COMPLETE - ページの読み込みが完全に完了した
- Event.DOM_INITIALIZE - DOMツリーを生成する直前
- Event.HTML_BOUNDS_CHANGE - HTMLコンテンツの幅や高さが変化した場合
- HTMLUncaughtJavaScriptExceptionEvent.UNCAUGHT_JAVA_SCRIPT_EXCEPTION - 捕捉されていないJavaScript例外が発生した場合
HTMLControlをWebブラウザのように使う
HTMLControl
クラスは、load
メソッドを用いて読み込んだページの履歴を保持しており、一般的なWebブラウザのように「進む」「戻る」などのナビゲーションを行うことが可能だ。
そうした、一般的なWebブラウザが持つナビゲーションと似たようなことを行うためのメソッド・プロパティは以下の通りだ。
- historyLengthプロパティ - 保持している履歴の総数を表す
- historyPositionプロパティ - 履歴内の、どの位置にいるのかを返す。このプロパティに値をセットすることも可能。
- historyBack()メソッド - 履歴を一つ戻る
- historyForward()メソッド - 履歴を一つ進む
- historyGo(steps:int)メソッド - ゼロを履歴内の現在位置とし、指定した履歴のページにジャンプする
- reload()メソッド - 現在のページを読みなおす
- cancelLoad()メソッド - ロードを中止する
読み込んだページのJavaScriptにアクセスする
HTMLControl
でWebページを読み込んだ後、window
プロパティにアクセスすれば、そのページにおけるJavaScriptのwindow
オブジェクトにアクセスすることができる。
html.load(new URLRequest("http://journal.mycom.co.jp/index.html"));
// ドキュメントのタイトルを表示
trace(htmlControl.window.document.title);
サンプルアプリケーションの解説
では、以上の説明を踏まえてサンプルアプリケーションを紹介したい。
今回のサンプルは、上部のテキストフィールドにURLを入力して「Go」ボタンを押すと、Webページが表示されるというものだ。ただこれだけだと面白くないので、表示されたWebページ内のリンクは全て、マウスオーバーするだけでリンク先ページの「サムネイル」が表示されるようにした。
以下に示すのがサンプルの全ソースコードだ。エラー処理を考慮していなかったり、まだ粗削りな部分はあるがご容赦願いたい。今回は、コード中のコメントをもって解説と代えさせていただく。
HTMLControlDemo.mxml
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" creationComplete="init();">
<mx:Script>
<![CDATA[
import flash.html.HTMLControl;
import flash.html.JavaScriptObject;
private const DEFAULT_HOME:String = "http://www.google.com";
// サムネイルに表示するページの幅と高さ
private const THUMBNAIL_WIDTH:uint = 1024;
private const THUMBNAIL_HEIGHT:uint = 800;
// アプリケーションの初期化
private function init():void {
// HTMLコントロールにリスナを登録
htmlPage.htmlControl.addEventListener(Event.LOCATION_CHANGE, onLocationChange);
htmlPage.htmlControl.addEventListener(Event.COMPLETE, onHTMLComplete);
// テキストフィールドにホームページのアドレスをセット
locationInput.text = DEFAULT_HOME;
changeLocation();
}
// HTMLControlのロケーションが変化した際呼び出される
private function onLocationChange(event:Event):void {
var htmlControl:HTMLControl = HTMLControl(event.target);
// テキストフィールドに表示されたURLを変更
locationInput.text = htmlControl.location;
}
// ページの移動を行うメソッド
private function changeLocation():void {
var url:String = locationInput.text;
htmlPage.location = url;
}
// ページ内で、一度表示したサムネイルのキャッシュ
private var thumbnailCache:Object;
// WEBページの読み込みが完了した際呼び出される
private function onHTMLComplete(event:Event):void {
thumbnailCache = {};
var htmlControl:HTMLControl = HTMLControl(event.target);
// JavaScriptオブジェクトにアクセスして、ページ内のリンクを全て取得
var links:JavaScriptObject = htmlControl.window.document.links;
for (var i:uint = 0; i < links.length; i++) {
// リンクにイベントハンドラを登録
links[i].addEventListener("mouseover", createThumbnailLoadFunc(links[i]));
links[i].addEventListener("mouseout", createThumbnailHideFunc(links[i]));
links[i].addEventListener("click", createThumbnailHideFunc(links[i]));
}
}
// サムネイルを非表示にする関数のオブジェクトを返す
private function createThumbnailHideFunc(link:JavaScriptObject):Function {
return function(mouseEvent:JavaScriptObject):void {
trace("hide thumbnail.");
var thumbnail:Sprite = thumbnailCache[link.href];
if (!thumbnail) {
return;
}
htmlPage.removeChild(thumbnail);
}
}
// サムネイルをロードし、表示する関数のオブジェクトを返す
private function createThumbnailLoadFunc(link:JavaScriptObject):Function {
return function(mouseEvent:JavaScriptObject):void {
// URLをキーとしてキャッシュされているサムネイルを取得
var thumbnail:Sprite = thumbnailCache[link.href];
// キャッシュに存在しない場合は、新たにロード
if (!thumbnail) {
thumbnail = new Sprite();
var html:HTMLControl = new HTMLControl();
html.load(new URLRequest(link.href));
// Webページを縮小して表示
html.scaleX = .5;
html.scaleY = .5;
html.width = THUMBNAIL_WIDTH;
html.height = THUMBNAIL_HEIGHT;
thumbnail.addChild(html);
thumbnailCache[link.href] = thumbnail;
}
// マウスポインタの位置に表示
thumbnail.x = mouseEvent.x;
thumbnail.y = mouseEvent.y;
htmlPage.addChild(thumbnail);
};
}
]]>
</mx:Script>
<mx:VBox x="10" y="10" height="100%" width="100%">
<mx:HBox height="20" width="100%">
<mx:TextInput id="locationInput" width="100%"/>
<mx:Button label="Go"/>
</mx:HBox>
<mx:HTML id="htmlPage" width="100%" height="100%" />
</mx:VBox>
</mx:WindowedApplication>