Dec 06, 2018

XML関連技術に基づくクロスプラットフォームなアプリ開発基盤を夢見たロストテクノロジー:XPCOM, XUL, XBL, GRE, そしてXULRunner

ロストテクノロジーAdvent Calendar 5日目に予告無しで飛び入り参加のPiroと申します。

アンテナの高い皆さんにとって、開発環境の重要なファクターであるテキストエディタの最低ラインはATOMかVisualStudio Codeでしょうか。では、これらがElectronというChromiumベースの実行環境上で動作するHTML/CSS/JavaScriptで書かれたアプリであるという話を聞いたことはないでしょうか。

Webベースの技術で実用的なローカルアプリを作れるなんて素晴らしい! そこに目をつけるとはさすがGutHub! と思いますか? しかし実際の所は、そういう試み自体は過去から何度もあったのです。WebkitベースでFlashも取り入れて当時のWebのリッチな体験をそのままローカルに持ってこようとしたAdobe AIR、HTMLとJScript/VBScriptのミックスでWindowsローカルアプリを作ろうとしたHTML Application(.hta)。この記事で語るXULRunnerも、そんな試みの中の一つです。

 

ここで、標題に登場するそれぞれの名前の関係性を整理しましょう。

ご存じの方もいるかと思いますが、FirefoxはChrome(Chromium)が採用するBlinkとも、その元となったWebkitとも出自の異なる、<ruby>Gecko<rt>ゲッコ</rt></ruby>(ヤモリの意)というレンダリングエンジンを使っています。そのGeckoをクロスプラットフォームの実装として成立させるために、各プラットフォームや、それどころか実装言語の差すらも横断して、どのプラットフォームでも・どの言語でも共通の呼び出し規約でコンポーネントを呼び出せるようにする仕組み、あるいはその仕組みに則って開発されたコンポーネント群そのものが、<ruby>XPCOM<rt>エックスピーコム</rt></ruby>です。

Geckoの採用が決定されるより前のNetscape製品は各プラットフォームでコードの共通化が充分に図られてはいませんでした。Geckoは当時としては高速なレンダリングエンジンでしたので、当時はXML華やかなりし頃、「それならアプリのGUIも全部XMLで書いて見た目はCSSで制御して動作はJavaScriptで書けばいいじゃん!」と思い切ってGUIの記述に特化して作られたXML応用言語が、Extensible User-interface Language即ち<ruby>XUL<rt>ズール</rt></ruby>です。

ちなみに、同じような発想で.NETアプリのGUIを記述するためにMicrosoftによって作られたのが<ruby>XAML<rt>ザムル</rt></ruby>なのですが、それはまた別のお話。

XULは便利な仕組みでしたが、XULに機能を追加するにはGecko自体に手を入れる必要があります。でも表面的な見た目や動作のみの話であれば、基本的なXULの機能さえあれば、後はCSSとJavaScriptである程度までは実装できるはず。そうして考えられた、XML形式でShadow DOMや追加メソッド、プロパティなどを定義する仕組みが、Extensibke Binding Language即ち<ruby>XBL<rt>エックスビーエル</rt></ruby>です。

ちなみに、それをHTMLに対して行なう物として策定されたクロスブラウザな仕様がWeb Componentsですが、それもまた別の話。

Geckoを他のアプリケーションに組み込んで使うために、Geckoとその関連モジュールを再配布可能なパッケージとしてまとめた物が、Gecko Runtime Environment即ち<ruby>GRE<rt>ジーアールイー</rt></ruby>でした。

また、GREはGeckoをコンポーネントとして使うランタイムですが、これをさらに発展させて、XULベースのアプリケーションを実行する環境としてGREとXBLで書かれたXULベースの多彩なウィジェット(コンポーネント)群とを併せてパッケージにした物も作られました。それが<ruby>XULRunner<rt>ズールランナー</rt></ruby>でした。

 

HTMLベースのWebアプリでは、(Web Componentsを使わないなら)例えば「ulliの入れ子になった構造を階層型メニューに見立てる」というような形で、元々はドキュメントを記述するために考えられた技術であるHTMLをGUIの表現に当てはめるという工程が必要になります。

でもXULはGUIの記述用に作られた言語なので、そのような見立ては必要ありません。実際に、Firefox 65のタブのコンテキストメニューを定義している部分を見てみると、それが分かります。

    <menupopup id="tabContextMenu"
               onpopupshowing="if (event.target == this) TabContextMenu.updateContextMenu(this);"
               onpopuphidden="if (event.target == this) TabContextMenu.contextTab = null;">
      <menuitem id="context_reloadTab" label="&reloadTab.label;" accesskey="&reloadTab.accesskey;"
                oncommand="gBrowser.reloadTab(TabContextMenu.contextTab);"/>
      ...
      <menuitem id="context_duplicateTabs" label="&duplicateTabs.label;"
                accesskey="&duplicateTabs.accesskey;"
                oncommand="TabContextMenu.duplicateSelectedTabs();"/>
      <menuseparator/>
      <menuitem id="context_selectAllTabs" label="&selectAllTabs.label;" accesskey="&selectAllTabs.accesskey;"
                oncommand="gBrowser.selectAllTabs();"/>
      ...
      <menu id="context_moveTabOptions"
            multiselectcontextlabel="&moveSelectedTabOptions.label;"
            multiselectcontextaccesskey="&moveSelectedTabOptions.accesskey;"
            nonmultiselectcontextlabel="&moveTabOptions.label;"
            nonmultiselectcontextaccesskey="&moveTabOptions.accesskey;">
        <menupopup id="moveTabOptionsMenu">
          <menuitem id="context_moveToStart"
                    label="&moveToStart.label;"
                    accesskey="&moveToStart.accesskey;"
                    tbattr="tabbrowser-multiple"
                    oncommand="gBrowser.moveTabsToStart(TabContextMenu.contextTab);"/>
          <menuitem id="context_moveToEnd"
                    label="&moveToEnd.label;"
                    accesskey="&moveToEnd.accesskey;"
                    tbattr="tabbrowser-multiple"
                    oncommand="gBrowser.moveTabsToEnd(TabContextMenu.contextTab);"/>
          <menuitem id="context_openTabInWindow" label="&moveToNewWindow.label;"
                    accesskey="&moveToNewWindow.accesskey;"
                    tbattr="tabbrowser-multiple"
                    oncommand="gBrowser.replaceTabsWithWindow(TabContextMenu.contextTab);"/>
        </menupopup>
      </menu>
      ...

メニューを定義したければmenuと書けば良く、セパレータを置きたければmenuseparatorと書けば良い。メニューが操作された時のハンドリングも、onclickonkeydownをそれぞれ書かなくても、より抽象的なレベルのイベントのハンドラであるoncommandだけを書いておけば後は基盤層がその時の実行プラットフォームに合わせて適切に処理してくれる。クロスプラットフォームなGUIアプリを開発するのに実に都合が良いです。

また、XMLであるという性質がフル活用されているのも見所です。使用環境の言語に合わせたメニューのラベル文字列は、

<!ENTITY  reloadTab.label                    "Reload Tab">
<!ENTITY  reloadTab.accesskey                "R">

<!ENTITY  reloadTab.label                "タブを再読み込み">
<!ENTITY  reloadTab.accesskey                "R">

といった要領でXMLのDTD形式で定義された物をエンティティ参照の形で埋め込んでいます。chrome://browser/locale/browser.dtd という論理的なURLに対する物理的なURLの割り当てを変えれば、それだけで表示ロケールを実行時に切り替えられるというわけです。

もちろん、XMLなのでXML名前空間を使って他のXMLアプリケーションを埋め込む事も普通にできます。実際に、Firefox 52の頃のbrowser.xulには、タブの角を丸めるためのマスクを定義するSVGの要素が埋め込まれていました。

<?xml version="1.0"?>
...
<window id="main-window"
        xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
        xmlns:svg="http://www.w3.org/2000/svg"
        xmlns:html="http://www.w3.org/1999/xhtml"
        xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
...
        persist="screenX screenY width height sizemode">
...
  <svg:svg height="0">
#include tab-shape.inc.svg
    <svg:clipPath id="urlbar-back-button-clip-path">
#ifndef XP_MACOSX
      <svg:path d="M -9,-4 l 0,1 a 15 15 0 0,1 0,30 l 0,1 l 10000,0 l 0,-32 l -10000,0 z" />
#else
      <svg:path d="M -11,-5 a 16 16 0 0 1 0,34 l 10000,0 l 0,-34 l -10000,0 z"/>
#endif
    </svg:clipPath>
#ifdef XP_WIN
    <svg:clipPath id="urlbar-back-button-clip-path-win10">
      <svg:path d="M -6,-2 l 0,1 a 15 15 0 0,1 0,30 l 0,1 l 10000,0 l 0,-32 l -10000,0 z" />
    </svg:clipPath>
#endif
  </svg:svg>
...
</window>

数式を取り扱うMathMLでも、ドキュメント書きに便利な要素型が揃っているXHTMLでも、XMLアプリケーションなら何でも埋め込めます。

あるXMLドキュメントを一定のルールに基づいて別のXMLドキュメントに変換するXSLTという技術もあり、Geckoはこれにも対応しています。実際に、Google Analytics用のsitemap.xmlのようにスタイル定義がなされていないXMLドキュメントを開いた時の開閉可能なツリー表示は、XSLTでそのように元のドキュメントを変換した結果です。

様々な用途のために設計されたXML応用言語を、XML自体の柔軟性をグルーとして結び付け、それぞれの利点を連携してリッチなGUIアプリを実現する。これは、XMLが夢見た世界の1つの到達点と言えるのではないでしょうか。

こうしたXULベースのアプリケーションはFirefox, Thunderbird, SeaMonkeyという純正のMozilla製品を筆頭に、それ以外にもXULRunnerベースのアプリケーションとして、デスクトップ版Skypeの一時期のバージョンや、メディアプレーヤーのSongbirdなど、いくつかの製品が実際に存在していました。

 

しかし2018年の今振り返ってみると、生き残ったのは結局FirefoxなどのMozilla純正製品(およびそのフォーク版のPale Moon等)だけになってしまいました。何故でしょうか。

理由はいくつも考えられます。まずは何と言っても、覚えなければならない既存技術の種類の多さでしょう。先のメニュー定義の例だけでも、XUL, XML, DTDと3つの知識が必要ですし、外観を変えるにはCSSの、挙動を定義するにはJavaScriptとXPCOMの知識が必要になってきます。「適切な技術を適切なレイヤで個別に定義し、それらを組み合わせる」という性質が仇となり、「どこから勉強すればいい」という見通しがまったく立たない状況が発生してしまっていたのです。様々なXML関連技術とMozilla独自のXUL/XPCOMという膨大な知識体系を丸ごと頭に入れるか、さもなくば諦めるか、という二択を迫られれば、多くの人が諦める方を選ぶのは道理でしょう。

また、実際にFirefoxの基盤レイヤの開発に長く携わっていたroc氏は、このような複雑で拡張可能な仕組みを支える基盤そのものが重荷となって開発の足を引っ張り、性能向上やGecko/Firefoxそのものの新規の開発者の流入を妨げたという面もあったと考えていたようです。

汎用の基盤として整備しても結局使ってくれる人は増えない。誰にも使われない汎用の仕組みをたくさん保持していても、メンテナンスコストが増大する一途で何のメリットもない。アプリケーション開発プラットフォームとして後方互換性を大事にすれば大事にするほど、それは既存の実装の変更を難しくし、そうする間にWebKitには性能面で水を開けられ、人の心もどんどん離れていく。そうした状況下でMozillaは結局、APIのフリーズを全撤回することを発表しました。

アプリケーション開発者にとって、古いAPIにそれ以上変更がないことが確約され、安心してアプリケーション開発に使える事が保証されているという事は、非常に重要です。それが無いアプリケーション開発基盤に基づいた開発は、グラグラ揺れる足場の上で積み木を積むような苦行でしかありません。そういうわけで、サードパーティによるアプリケーション開発基盤としての信頼性を喪失し、(もちろん、明確なプロジェクト終了の発表があったという事もあって、)GREもXULRunnerも今や完全に姿を消してしまいました。

実を言うと、XUL, XBL, XPCOMはまだFirefoxやThunderbirdの中で基盤技術として使われており、完全にロストテクノロジーになったとは言い切れない状況です。しかしながら、Mozilla自身はこれらのXULやXPCOMに基づく一連の技術体系に既に見切りをつけており、メインプロダクトであるFirefoxの中からその痕跡を排除する書き換え作業が着々と進行しています。Firefox 57での従来型アドオンの廃止とWebExtensionsへの完全移行も、その流れの一環にありました。そういった状況を踏まえると、もはやこれらの技術に将来性は無く、新たな採用事例が増える事も望めませんので、ロストテクノロジーと言ってしまって過言ではないでしょう。

 

XML関連技術のレンダリングエンジン上に組み上げられた、XMLとその関連技術の「美しい」連携という、どこかの誰かによる「ぼくのかんがえたさいきょうのアプリ開発のしくみ」という難解な技術体系は、こうして消えゆく定めと相成りました。

そもそもの話をすると、アプリ開発者にとっては、欲しかったのは「クロスプラットフォームなアプリケーション開発が可能となる仕組み」の方であって、「XMLの理想が体現されているかどうか」は全く別の話です。別の方法でクロスプラットフォームなアプリケーション開発が可能なのであれば、XMLの諸々の「かくあるべし」という様式に拘る必要は全く無かった訳です。「顧客が本当に欲しかった物」から離れて、それどころか「開発者が欲しかった物」ですらない、「学者が考える、かくあるべしという形」の方に気を取られてしまった時点で、負けは決まっていたのかもしれません。

とはいえ、「かくあるべし」と定められた仕様を実際に愚直に実装することによって、その非実用性を炙り出す働きをしたという意味では、意義深かったという事は言えるのかもしれません。実際、Firefox開発チームから離れた人達によって作り直されたGoogle Chrome(Chromium)は、「前回の失敗を踏まえて」無駄を削ぎ落とした結果、これだけ人々に愛される製品になったのですから。

そして、そのChromiumに基づいて、Web系エンジニア自身が常日頃から親しんできた物(HTML, CSS, JavaScript)を積み上げた形でアプリケーションを開発できる環境として生まれたElectron、これこそが「顧客が本当に欲しかった物」だという事なのかもしれません。

あるいはもっと単純に、AppleとGoogleという大樹によってもたらされる恩恵にぶら下がる方が得が大きい、と多くの人が判断したという事なのか。

折しもつい最近、Microsoftが自社開発のブラウザエンジンを捨ててChromiumベースに乗り換えるのではという情報が出ました。今となってはElectronの開発元のGitHubもMicrosoft資本の傘下、Microsoft自身もElectronベースで製品を開発している状況では、皆が愛するChromiumへの投資を増やして、誰からも愛されない自社技術に見切りをつけるというのも、十分にあり得る話だなと僕には思えます。技術の中立性・健全性を維持していくためには複数の実装が並立して競争・相互監視する事が大事だとMozillaの中野さんが説かれたのと日をそう開けずに出てきたこの情報に、世の中はとかく「頭の良い人達が思う所の、あるべき方向」には向かわないものなのだなあ、と諦念を抱かずにはおれません。

 

以上、ロストテクノロジーAdvent Calendar 5日目の記事でした。明日はどんなロストテクノロジーが紹介されるのでしょうか、楽しみですね。

エントリを編集します。

wikieditish message: Ready to edit this entry.











拡張機能