(原著:David Teller, 2020年8月20日、CC BY-NC 4.0で公開されている内容の全訳。自サイトとのクロスポストです。)
要約:Firefoxはかつて、XULとXPCOMに基づく偉大な拡張機能の仕組みを持っていました。この仕組みは長い間私達によく尽くしてくれました。しかし、Firefox開発者と拡張機能開発者の両方にとって、メンテナンスコストは増大し続けるばかりでした。ある面では、増大していくコストは、Firefoxをセキュアにしたり、高速化したり、新しい事を試したりするための努力を、少しずつ破壊していきました。また別の面では、増大していくコストはアドオン開発者のコミュニティを少しずつ破壊していきました。最終的に、古いアドオンの仕組みを守ろうとして数年を過ごした後、Mozillaは、この拡張機能の仕組みを廃止し、それより拡張性は劣るもののメンテナンスしやすいWebExtensions APIに置き換えるという、難しい決断をしました。この選択のおかげで、Firefox開発者は再び、セキュリティや安全性やスピードを改善するために必要な変更を行えるようになったのです。
ここ数日、私はFirefoxのユーザーと会話して、2020年8月のMozillaによるレイオフの結果に関する噂と事実を区別しようとしていました。その過程で何度か持ち出されたのが、Firefox Quantumへの移行におけるXULベースのアドオンの廃止のことでした。私は非常に驚きました。もう何年も前に起こったことについて、この選択に感情を害されたと感じている人が、コミュニティにまだいるということにです。
そして、誰かがredditで指摘していたとおり、私は、何故XULベースのアドオンの廃止以外に選択の余地が無かったかについて、私達が深いところを説明する機会をまだ持っていなかったということに気付きました。
そこで、アドオンとGeckoの内部事情の話に飛び込む準備ができている人向けに、これを機にもう少し詳しい話をしてみようと思います。
見境のないアドオン、Jetpack、そしてWebExtensionsについて
非常に長い間、Firefoxは「コアは非常に小さく、すべてはその上で拡張機能として実装されている」という構成を取っていました。拡張機能の多くはC++で、他はJavaScriptで書かれて、多くはXULというインターフェース言語とXBLというバインディング言語に関するものでした。C++とJavaScriptのコードは、XPCOMと呼ばれた技術の恩恵を受けて接続されていました。拡張機能の開発者がFirefoxをカスタマイズしたいと望んだときにはいつでも、Firefoxに力を与えるために用意された物と全く同じ部品を、カスタマイズのために使うことができました。それはシンプルでありながら非常に強力な仕組みでした。
Session Restore(クラッシュしたような場合でも、最後にあなたが見ていた状態でFirefoxを再開することを可能にする技術)やFind Bar1、その他の機能がFirefoxにおいて当初どのように実装されていたかは、このように説明できます。これが、FirefoxとThunderbirdに力を与えている技術です。Songbird(オープンソースのiTunes対抗アプリ)やInstantbird(チャットクライアント)のようなツールも、こうして作られました。また、これは私が大昔にどのようにしてFirefoxを電子書籍リーダーへとカスタマイズしたか、そして、無数にあるFirefoxアドオンがどのように開発されていたかの説明でもあります。
多くの人がこの拡張機能の仕組みを「XULベースのアドオン」、もしくは「XPCOMベースのアドオン」と呼んでおり、この記事でも私はこれらの用語を使います。しかし、私は時にこれを、いくつかの理由から「見境のない拡張機能の仕組み(Promiscuous Extension Mechanism)」と呼んだ方がいいと考えています:
- あっという間に、アドオン開発者が実現したことのすべてが、他のアドオンやFirefoxを含むシステム内の他のあらゆる物を壊し得ました。そして、それを防ぐ手立ては何もないという場合が度々ありました。
- 同様に、Firefox開発者がしたあらゆることが、アドオンを壊し得ました。それを防ぐ手立てもありませんでした。
- また、Google Chromeに追いつくべく、高速化や安定化、セキュリティの向上などのためにFirefoxで必要とされた変更の中には、多くのアドオンを直ちに、長期的には恐らくすべてのアドオンを、壊してしまうようなものもありました。
- ああ、ちなみに、アドオンは何でもできたので、あなたが口座を持っている銀行のフリをすることから、パスワードの情報を盗み出すことまで、OSに対してあらゆることを非常に簡単にできる余地がありました。
註:この記事に寄せられたコメントを見ていると、明らかにセキュリティに無頓着な人がいるようでしたが、付け加えさせてもらうと、セキュアであることは、Mozillaが生まれた当初の頃から本当に、本当に大事です。アドオンでもその点は変わらず、セキュアでないということは、ユーザーのパスワードを盗んで銀行のアカウントを不正使用するような実際の攻撃手順が、いずれ出現するようになることを意味します――そして、そのような攻撃手順はすぐに闇市場を介して出回り、あちこちで使われるようになります。Firefoxの開発者は、コードレビュー、保護的なプログラミング、クラッシュ原因の調査、いくつかの種類のサンドボックス化、静的解析、メモリ安全な言語など、様々な手段でこのような危機と日々戦い続けています。従って、Mozillaにとっては、ある機能がセキュリティに関する我々の取り組みを妨げるのであれば、私達は常に機能の便利さよりもセキュリティの方を選びます。
これらの点の詳細はまた後で語りましょう。今のところは、「このような状況は看過できない」とFirefox開発者たちが長らく(少なくとも2010年頃から)はっきり言っていた、ということだけ述べておきます。そういうわけで、MozillaはFirefox Jetpackと呼ばれる代替策を考え出したのでした。
Firefox Jetpack2は、Firefoxを拡張するためのまったく異なる方法で、以前のものよりはるかにクリーンでした。Jetpackには、ついに権限(パーミッション)の仕組み(FirefoxがFirefoxと呼ばれるようになるより前から提案されていて、実装が非常に難しいと一般的に言われていたもの)が導入されました。特別なことをしなくても、アドオンは他のアドオンやFirefoxを壊すことはできず(Observerサービスを使うことで実はまだそのようなことができたと記憶していますが、非常に困難でした)、非同期プログラミングを多用し(これは体感パフォーマンスの向上に大きく寄与しました)、そして有限のAPIだけが提供されていたおかげで自動テストが可能でした。これは、Firefox開発者がアドオンを壊してしまっても、そのことを迅速に検知し修正できたということを意味します! それはとても大きな前進でした。より限定されたAPI3という代償はありましたが、多くの場合で、このトレードオフには価値があると思われました。
残念ながら後に、Jetpackの設計と、Firefoxで必要に迫られて行われたいくつかの大きな変更との間の、予期されない非互換があったことが分かりました。非互換の全貌については私はあまり詳しくないですが、これは、Jetpackを断念しなくてはならないことを意味していました。代わりに私達が導入したのが、WebExtensionsです。全体的に、WebExtensionsはJetpackベースのアドオンが目指していたものと似ており、Jetpack同様に限られたAPIを持っていて、ChromiumベースのブラウザーとFirefoxの両方で動かせるというオマケもありました。
あなたが非常に高度なAPIを必要としていた場合、見境のない拡張機能の仕組みからJetpackやWebExtensionsへ移行できるとは限りませんでしたが、しかし多くの拡張機能にとっては、この移行は単純でした――私の個人的な体験では、楽しいものですらありました。
FirefoxはFirefox Quantumに間に合わせるようにWebExtensionsを投入しました。それが見境のないアドオンの仕組みが壊される予定のタイミングだったからです。
今この段階で、私達は歴史的な全体像を把握しました。あなたはもう、より技術的に詳しい所に飛び込む準備ができています。それでは、見境のない拡張機能の仕組みからWebExtensionsへの移行によって、どんな問題が解決されたのかを、徹底的に説明しましょう。
XPCOMについて語ろう!
どのように始まったか
XPCOM、Xross-Platform Component Object Model(プラットフォーム横断のコンポーネントモデル)はおそらく、私達のJavaScript仮想マシンであるSpiderMonkeyと並んで、Firefoxのコアが何であるかを最もよく説明する機能です(Geckoのより深い部分を知っている人向けに言うと、私はXPConnectとCycle Collector4もXPCOMの一部として数えています)。
XPCOMは、コードを2つの言語で書き、それぞれの間で互いに呼び出し合うことを可能にする技術です。Firefoxの内部は、JavaScriptのコードを呼ぶC++のコードと、C++のコードを呼ぶJavaScriptのコードでいっぱいです。大昔には、ここにPythonと.Netを加える計画もありました。これは極めて複雑な機械部品です。というのも、各言語はデータ型の定義も(64bit整数をJavaScriptでどう表現すればいい? JavaScriptの例外はC++ではどう扱う?)、メモリーの管理モデルも(C++で実装された箇所のオブジェクトへの参照を持つJavaScriptのオブジェクトをどう扱うか? C++の実装はそのオブジェクトをdelete
でメモリ上から消去するかもしれません)、並列処理のモデルも(C++のスレッドがすべてを共有するのに対し、JavaScriptのWorkerは何も共有しません)、互いに共通していないからです。
Gecko自体は元々、無数のXPCOMコンポーネントに基づいて設計されていました。それらはC++またはJavaScriptによって実装され、個別にテストされ、動的に接続されたり切断されたり置き換えられたりしながら、実際に動作していました。さらに言うと、XPCOMアーキテクチャは、当時可能だったものに比べてよりクリーンなC++の書き方をするために作られ、多数のプラットフォームで動作し、JavaScriptでコードを書くことの利便性と、C++がもたらす理論上の最高速度との組み合わせを可能としています。
XPCOMコンポーネントを記述するには、典型的な場合ではまずインターフェースを定義し5、次にC++またはJavaScriptのいずれかで(あるいは最近はRustで、もうすぐWasmでも)実装を記述します。いくつかのテンプレートが必要ですが、ともかくそれで動きます。
初期のFirefox開発者がプラットフォームを拡張機能に解放すると決めたとき、XPCOMはただちにアドオンの基礎技術に採用されました。Firefoxはアドオンの作者に対して単に、そのコードを通じてあらゆる所に機能を追加することを許可しただけでしたが、これによって彼らは、思うままに振るうことのできる、とてつもない力を手に入れたのでした。
そして(私自身を含む)アドオン開発者は、確かに自由に開発をし、大いに楽しみました!
…不変のXPCOMの時代
残念なことに、問題は少しずつ段階的に始まりました。
あなたが大規模なアプリケーションを開発するとき、バグの修正や新機能の追加のために、あるいは性能改善のために、何かを変更しないといけないでしょう。XPCOMの世界では、これはXPCOMコンポーネントの変更を意味します。時にはコンポーネントに新機能が追加され、時にはより良い設計に改められたために古い機能が削除されます。
XPCOMベースの拡張機能の仕組みの最初の時代では、このような変更は時に禁止されていました。アドオンに使われているXPCOMコンポーネントがあった場合、互換性を損なうやり方での変更は単純に不可能でした。これはアドオン開発者にとっては素晴らしいことでしたが、すぐにFirefox開発者にとっては悪夢となりました。あらゆる個々の変更は、外部的(Web開発者向け)にも内部的(アドオン開発者向け)にも互換性を保つように行う必要がありました。これは、nsI何々
のような各XPCOMコンポーネントに、そのより良い実装であるnsI何々2
がついて回ることを意味しました。――そして、この両者はお互いに併存したまま動作する必要がありました。また、XPCOMベースのアドオンがあらゆる既存のXPCOMコンポーネントを置き換えることができたのも、Firefox開発者にとって取り扱いがより複雑なケースでした。言うまでもなく、これはFirefoxのクラッシュ原因の調査が困難になる形でFirefoxを壊すのに、非常に有効な方法でした。
これが意味したのは、新機能や現在の機能に行われた改善についてだけでなく、過去の/非推奨の機能や、もう何年も前に使われなくなった単純に古いやり方についてもチェックが必要なために、開発スピードがどんどん遅くなっていくということでした。当面の間は、この開発税は許容可能でした。結局の所、当時のFirefoxの主要な競争相手といえば、より深刻な設計上の問題を抱えていたInternet Explorerでしたし、明らかに多くの無数のオープンソースのコントリビューターの手助けがありました。また、Webで要求される機能の種類も小規模でした。
…すべてのループにアドオン開発者を取り込んだ時代
しかし、Webが発展するにつれて、このような選択がいくつかの問題を――特に性能上の問題を――単純に修正不可能としていたと明らかになってきました。具体例としては、2008年頃のある時点において、Firefoxの開発者は、プラットフォームに単純に非常に多くのXPCOMコンポーネントがあるという事実が、性能に相当な悪影響を与えていると気付きました。XPCOMコンポーネントがJITとC++コンパイラによる最適化されたコードの両方の動作を妨げ、また、XPCOMコンポーネント間でのデータ変換の必要が頻繁に生じすぎていたからです。そのため、性能に致命的な影響を与える箇所をXPCOMコンポーネント無しで書き直すというdeCOMtamination6が開始されました。それはアドオンが動かなくなることを意味していました。
XPCOMベースの拡張機能の仕組みの第2の時代では、Firefox開発者にはXPCOMコンポーネントを削除したり作り直したりすることが許されるようになりましたが、アドオン開発者と連絡を取って、彼らのアドオンの改修方法をも検討しました。これは開発の障壁を取り除きましたが、開発税は、どういうわけかさらに高騰していきました。このやり方では時に、単純な改善であっても、その投入前に外部の開発者と数週間に及ぶ協議が必要となることを意味していたからです。そのため、アドオン開発者によっては、彼らのアドオンを何度も何度も作り直す必要に迫られるなど、アドオン開発税もまた高騰していきました。いくつかの事例では、Firefox開発者とアドオン開発者は非常に良い関係を築くことができ、Firefox内で使われるAPIをアドオン開発者が設計する場合もありましたが、別の事例では、アドオン開発者はメンテナンスの負担に疲弊し、挫折して、立ち上がったばかりのChromeのエコシステムに乗り換えることもありました。
…Snappyの時代
この時期に、MozillaはChromeに真面目に注意を払い始めるようになりました。ChromeはFirefoxと比べて非常に異なった設計方針で始まっていました:
- この時点で、Chromeは大量のメモリやシステムのリソースを消費することに頓着していませんでした。
- Chromeは多くのプロセスを使っており、この設計によって高いセキュリティと応答性を実現していました。
- Chromeはアドオン用のAPI無しで始まりました。これは、Chrome開発者が開発税を払うことなく彼らの求めに応じてあらゆるものを作り直して問題を回避できることを意味していました。
- Chromeが独自の拡張機能の仕組みを導入するにあたって、彼らは多くの場合でバックエンドでの変更に関係なく維持できる適切なAPIを導入しました。
- また、ほとんどすべてのベンチマークにおいてChromeは当初Firefoxよりも低速で、体感速度を高める非常に多くの設計上のトリックに頼っていました。――そしてユーザーはそれを愛しました。
Firefoxをマルチプロセスの設計に切り替える必要があることは、Mozillaにとって、もう何年も明らかでした。事実、Chrome 1.0が明らかになった頃には、マルチプロセス化されたFirefoxの実演デモが出回っていました。この計画はElectrolysis、略してe10sと呼ばれていました。これについてはまた後で語ります。
この時点でMozillaは、私達のユーザーがその時点で使っていた環境に積まれているよりもはるかに多くのメモリーを要求するe10sを一時中断し、Snappyと呼ばれる新しい計画に集中する決定をしました(打ち明けると、私はSnappy計画の開発者の一人でした)。Snappyは、Chromeがやっていたのと同じ設計上のトリックを使って、うまく行けばすべてを作り直すことなしに、Firefoxの体感速度を向上する計画でした。
FirefoxがChromeに比べて遅いと感じられる理由は、私達が非常に多くのことをシングルスレッドで行っていたからでした。Firefoxがファイルをディスクに書き出すときには、これが表示の更新を妨げて、秒間の描画フレーム数(FPS)の数値が低下していました。クラッシュからの復帰用にタブの情報を収集して保存するときや、Cookieを消去するとき、ほかにも様々な場合で同様でした。
これを解決するために私達がとった方法は、以下の2つでした:
- 可能な場合には、コードをメインスレッドで実行する代わりに、別スレッドで実行するようにしました。
- それができない場合、数ミリ秒以内に完了できると保証できるくらいの細かさに処理を分割して、メインスレッド上で断続的に実行するようにしました。
どちらの解決策も、FPSの向上に寄与し、ユーザーのクリック操作にもすぐ応答できるようになりました。これはユーザーインターフェースの体感速度の向上を意味しました。前者はOSの支援を得られる利点がありましたが、後者はかなりの量の性能測定と微調整を必要としました。どちらの解決策も、デバッグしにくいことで忌み嫌われている並列処理の問題に突然直面することになったため、やり遂げるのは困難でした。また、どちらの解決策も、アドオン開発者にとっても大変なもので、機能全体を同期処理から非同期処理に変更する必要があり、ときにはアドオン全体を最初から書き直す必要すらありました。
私にとってこれは思い出深い出来事です。Irakli、Paolo、そして私自身(他にも関わっていた人のことを忘れていたらすみません)が、Promise
、今ではasync function
としても知られるものをFirefoxのコードベースに導入した時期だからです。これらの実験(この話題での唯一の実験というわけでは決してなかった)は、後にこれらの機能をWebに導入するための叩き台としての役割を果たしました。より短期的な話としては、それらはFirefox開発者とアドオン開発者の両方にとって、非同期のコードを書くことをより容易にしました。これらの(そして他のまだ標準化されていない技術も交えた)改善にも関わらず、非同期のコードを書いたりデバッグしたりすることは今なお非常に複雑です。
そういうわけで、それはあっという間に本当に複雑なものとなり、またもアドオン開発者にとって新たなメンテナンス税となりました。拡張機能の拡張性に関して言うと、私達がコードをメインスレッドから別スレッドに移していったことは、状況をより悪くさえしました。というのも、それらは典型的には、JavaScriptのスレッドからは完全に分離されたC++のスレッドに移されたからです。XPCOMコンポーネントはそこかしこで姿を消していき、アドオン開発者にとっては、拡張機能で使える強力な機能が失われていきました。
私はこの段階が、多くのアドオン開発者がメンテナンス税について真剣に不満を述べるようになり始め、あるいは多くの場合に、彼らがただアドオンの更新をしなくなっていった段階だったと考えています。彼らの判断は妥当でした。一人のアドオン開発者として、その時にはもう、私は自作アドオンのメンテナンスを長期間諦めたままになっていました。有り体に言ってクソほど時間を浪費させられるからです。このメンテナンス税によって、多くのアドオン開発者が燃え尽きていきました。
そうしてMozillaは、Firefox開発者とアドオン開発者の双方にとって税負担を大幅に軽減してくれるような、アドオンを書くための新しい方法探しに、真剣に取り組むようになりました。この時点で、解決策はJetpackと目されており、それは実によくできていました! しばらくの間、よりクリーンなJetpackと、より古い見境のないモデルという、2つの拡張機能の仕組みが共存していました。
…ElectrolysisとQuantumの時代
Snappy計画は私達をかなり遠くまで連れて行ってくれましたが、Firefoxの悩みのすべてを解決するには至りませんでした。
前述した通り、Firefox開発者は長い間に渡って、いずれはマルチプロセスの設計に移行する必要があると悟っていました。これは安全性の面でもセキュリティ面でもより望ましく、画面の描画をよりスムーズに保つことをさらに容易にし、より自然なものに思えました。その時にはすでに、MozillaはFirefoxのマルチプロセス化の実験にいくらかの時間取り組んでいましたが、2つの要因によって、マルチプロセス化されたFirefox(別名、Electrolysisあるいはe10s)への移行は妨げられていました:
- マルチプロセスは非常に多くのRAMを必要とするという事実。
- マルチプロセスに対応するためには個々のアドオンを完全に書き直す必要があり、一部は移植がまったく不可能であるという事実。
RAMの値下がりとメモリ使用量の最適化(Memshrink計画)によって、1つ目の問題は段階的に致命的ではなくなっていきました。それに対して、2つ目の問題は解決不可能なままでした。
Webページの内容に影響する必要がある、単純なアドオンの場合を考えてみましょう。例えば、表示のコントラストを高めるために設計されたアドオンがあるとします。e10s以前は、いくつかのJavaScriptのコードがFirefoxのメインウィンドウ内に存在していて、それらは個々のWebページのDOMを直接的に操作でき、非常に容易に書くことができました。e10s以降は、このようなアドオンはプロセス境界をまたいで動作するように書き直される必要がありました。親プロセスはメッセージ交換を通じて子プロセスと対話することだけが可能で、メッセージの処理やOSの都合(例えばクラッシュ)、あるいはユーザーの都合(タブを閉じるなど)によって、子プロセスはあらゆるタイミングで停止する可能性がありました。このようなアドオンはe10sへ移行可能でした。なぜなら、Webページの内容を処理するプロセスにおいてもJavaScriptは実行可能で、e10sチームはアドオンに対して、メッセージ送受信やアドオンが提供するスクリプトをコンテントプロセス側に読み込ませるためのAPIを提供していたからです。
しかし、すべてのプロセスがアドオンと良好に動作できるわけではありませんでした。具体的には、Flashプラグインに起因する忌まわしいクラッシュからFirefoxを保護するための専用プロセスや、GPUの相互作用専用のプロセスには、JavaScript仮想マシンがありませんでした。これはすべて、性能と安全性のためという(良い)理由のためでした――そして、これらのプロセスにJavaScript仮想マシンを追加すると複雑になりすぎるという事実もありました。
そしてまだ、Mozillaには他の選択肢はありませんでした。MozillaがElectrolysisを本番投入していなかった間の日々において、Chromeは設計的にも安全性でもセキュリティでも、端的に優れていました。残念なことに、MozillaのElectrolysisの取り組みは数年来遅延していました。1つには、そのことが問題にはならないほどにFirefoxが充分優れていると長い間錯覚させるベンチマーク結果があり、もう1つは、Firefoxに先立ってFirefoxOSでElectrolysisを試しに使ってみると我々が決定していたからでした。しかし最大の理由は、アドオンを失いたくなかったからでした。
MozillaがついにElectrolysisへの移行に取り組むようになったとき、何人かのアドオン開発者は、その作業のために多大な時間を投じて彼らのアドオンを移植しましたが、多くの人はそうしませんでした。彼らのアドオンは移植が不可能だったか、または、多くのケースで、アドオンのメンテナンス税のために私達はそれらを失ったのでした。また、さらに残念なことに、Electrolysisに対応するよう移植されたアドオンですらも、この記事で言及されている他の様々な理由によって、壊れていきました。
最終的に、MozillaはWebExtensionsの導入と、Quantum計画の一環として、e10sへの飛躍をようやく決定しました。開発に費やされた年月は失われ、二度と取り戻せませんでした。
性能は向上しました。アドオンは書き直される必要があり、アドオンにできることは不可逆的に減らされました。XPCOMベースの拡張機能の仕組みはほぼ廃止されました(内部的にはまだ使われています)。
…未来はRust、Wasm、Fission…
現在、私達はQuantum以後の時代を生きています。可能な限り、新しい機能はRustで実装されています。私が知る限りWasmで実装された機能はまだ投入されていませんが、それは明確にロードマップの中に含まれています。
当初、RustはXPCOMとの相性が良くありませんでした。Rustは他の言語とのやり取りのために、よく動く異なる仕組みを持っていますが、XPCOMとネイティブには対話できません。XPCOMのコードをRustで書いたり使ったりはできて、多大な努力なしでもそれが可能な段階に少しずつ達しつつありますが、FirefoxにおけるRustで書かれた既存のコードの多くの部分では、それをするのは非常に大変でした。従って、XPCOMベースの拡張機能の仕組みを残していたとしても、Rustで実装された機能のほとんどは、私達がそのために明示的にAPIを――おそらくはWebExtensions APIとして――公開しない限り、アドオンからは利用できなかったでしょう。
GeckoでのWasmの使用について私はあまり把握していませんが、それと似たような事が起こると思われます。最初の時点ではXPCOMはなく、明示的に決定が下った場合にのみ、後々段階的にいくつかのXPCOM対応が行われるでしょう。WebExtensions APIとして姿を現すのは、さらにその先のことです。
また、Electrolysis計画の次にはFission計画があります。これはFirefoxのもう1つの主要なリファクタリング作業で、Electrolysisで取られた方向性をさらに推し進め、安全性とセキュリティのためにFirefoxをさらに細かいプロセスに分割し、そしてできれば性能向上にも繋げるものです。これはXPCOMには直接的な影響はありませんが、Electrolysis計画のために移植された、XPCOMベースの技術を使うすべてのアドオンについて、再び全面的な書き直しが必要になることを意味します。
これらのすべての理由が、XPCOMベースのアドオンを延命するという選択7で起こり得たことの裏付けで、それは良くてもせいぜい、技術的な苦痛を不必要に長引かせることになっていたでしょう。しかし、結論は既に述べたとおりです。
XULの問題
ちょっと待って、それで全部ではありません! ここまでXPCOMについてだけ語ってきましたが、XPCOMはFirefoxアドオンの背後にある技術の半分しか表していません。
どのように始まったか
XUL、XML User Interface Language(XMLによるUI言語)は、Firefoxが切り拓いた革新の1つでした。それは史上初の、実用に足る、UIのための宣言的な言語でした。想像してみて下さい。ボタンを置き、それをCSSで装飾して、少しのJavaScriptで接続し、アクセシビリティのためのメタ情報を付与して、各言語に翻訳し、設定を保存できる…… 既存のインターフェースに容易に部品を追加できるXULオーバーレイと呼ばれた仕組みもまた素晴らしいものでした。実際に、メニューを拡張したり新しいボタンを追加したりするのに、他のドキュメントを変更する必要はありませんでした。
Mozillaの存在したほとんどの期間、これはFirefoxのUIを記述する方法でした――そしてもちろん、アドオンのUIを記述する方法もでもありました。それはまるで魔法のようにはたらきました。GtkやWin32、SwingでUIを記述することにうんざりしていたアドオン開発者として私は、これらのツールキットで達成できたどんなことよりも、はるかに優れたユーザー体験を初めて開発でき、それに加えて、圧倒的に時間がかかりませんでした。アプリをビルドし直さなくても、リロードするだけでどのように見えるかを確認できましたし、私のコードはメモリ安全な言語で書かれていたため、GtkやWin32とは異なり、クラッシュを恐れる必要もありませんでした。
もしこれが、適切なライブラリやフレームワークを伴ったHTML5(そしておそらくはElectron)によく似ていると思うなら、その認識はまったく正しいです。XULはWebの仕様が宙ぶらりんの状態で足踏みしていたHTML4の時代に開発され、主に文書用ではなくアプリケーションに特化した、HTMLの後継言語として設計されました。約20年前、MozillaはXULRunnerの最初のバージョンをリリースしました。それは基本的には、HTMLの代わりにXULを(XULの中にHTMLを挿入することもできましたが)用いた、Electronの初期バージョンだったと言えます。
… XULのいくつかの問題
もしあなたがXULベースのアドオンを書いたことがあるなら、物事を壊さないようにするのは非常に難しいとすぐに気付いたでしょう。複数のアドオンがUIの同じ場所を変更した場合、おかしな結果が起こりますし、複数のアドオンが偶然同じ名前の関数や、既存の関数と同じ名前を挿入した場合には、あらゆる種類の破壊が引き起こされます。
同様に、悪意あるアドオンを書くのも非常に容易です。パスワード入力時を含めて、ユーザーが押したすべてのキーのログを簡単に取れますし、そもそも、パスワードマネージャにパッチを当ててすべてのパスワードをどこかのWebサイトに送ることだって容易にできます。はっきり言っておくと、これはSFの話ではありません。バレずに逃げおおせた拡張機能の例を私は知りませんが、まったく無関係の製品である、重要な機能の制御を奪ってユーザーのプライバシーを侵害する、不可視のFirefoxアドオンを、黙ってインストールする物は実際にありました(名前は挙げませんが、少なくともそのうち2つは非常に人気のあるアドオンでした)。
セキュリティ向上のための提案がなされたこともありましたが、私達がXUL(とXPCOM)を使い続ける限り、悪意あるアドオンがあらゆることをやりたいようにするのを防ぐ手立てが無いのは、まったく明らかでした。
…HTML5の時代
HTML5の作業が始まると、MozillaはXULの機能をそこに持ちよることに熱心に取り組みました。HTML5は段階的に、ストレージ、編集可能なコンテンツ、ドラッグ&ドロップ、(XULにおいてXBLとして知られていた)コンポーネント化の仕組み、ブラウズ履歴の操作、音声の再生、暗号化……そして、アクセシビリティと国際化対応を実装するためのクライアントライブラリへの充分な支援と、機能を獲得していきました。
HTML5はまだXULの全機能を備えているわけではありませんが、ついには、XULに対応するためにGeckoに実装されていた多くの機能が、GeckoでのHTML5対応のためにも実装し直される段階に達しました。HTML5で実装される必要があるすべての新機能について、アドオン開発者がそれらをXULと組み合わせて使っても大丈夫なように、XULのいかなる既存の機能も壊さないよう実装することの手間は、XPCOMの場合と同様に開発税と化しました。
これはGeckoの開発スピードをかなり低下させ、XULベースのアドオンを突然動かなくしてしまうようなバグの件数も増加していきました。それに従って、アドオン開発者にとってのメンテナンス税も高騰していきました。
この時点ですでに、MozillaはXULの改善の中止と、むしろHTML5の開発に注力するという決定を、ずいぶん前に下していました。多くの場面でHTML5の性能はXULのそれより優れたものとなり、Firefoxの開発者達はコンポーネント群をXULからHTML5へ移植する作業を始めました。その結果は、(新しい設計ゆえに)より良く、(GeckoがHTML5向けに最適化されたために)高速で、しかしXULベースのアドオンによる拡張はより困難になるというものでした。また、Firefoxのフロントエンドの開発者を雇って訓練することもより容易になりました。突如として、彼らは自身の持つHTML5の知識とHTML5のフレームワークをFirefoxの中で使えるようになったからです。
Jetpackが最初に登場したのは、だいたいこの時期のことでした。Jetpackはアドオン作者に対し、彼らの拡張機能をXULではなくHTML5で書くことを可能にしました。これは多くのアドオン作者にとっても、彼らのHTML5の知識とフレームワークを活用できるため、望ましいことでした。もちろん、HTML5にはXULの機能がまだいくつか欠けていたので、これは万人向けではありませんでした。
…Servoに向かっての前進
並行して、MozillaにおいてServoレンダリングエンジンの作業が始まりました。このエンジンは完全な機能を備えてはいませんでした(現在もそうです)が、非常に良くできたデモンストレーションは、いつかServo(または少なくともその一部分)が、より読みやすく、変更しやすく、安全で、はるかに高速なコードによって、どのようにGecko(または少なくともその一部分)を置き換えることができるかを示しました。
もちろん、落とし穴もありました。とりわけ、MozillaがXULへの投資をやめると決定してからずいぶん経っていたので、ServoチームにはXULを再実装するリソースがありませんでした。いずれGecko(またはその一部)をServoで置き換えられるようにするには、MozillaはまずFirefoxのUIをHTML5に移行する必要がありました。
Firefoxにおいて、この取り組みはうまく回っています。XULのコード量の削減は、Geckoが簡潔になることによる開発税の削減を意味しており、Gecko開発者に対して、HTML5のすべての速度の改善を可能にします。それはまた、UIとアドオンの開発者が、より最適化された技術や、既存のWeb技術のライブラリやフレームワークを使えることも意味します。
繰り返しになりますが、XULをUIに使うことは次第に意味をなさなくなっていて、XULベースのアドオンで得られるものの数も減少しているのです。
…Electrolysis、Quantum、Fissionの時代
その後、Quantum計画が登場しました。前述したように、MozillaがElectrolysis無しでFirefoxをリリースしていた日々は、FirefoxがChromeに対してセキュリティやクラッシュ耐性の点で見劣りすることを余儀なくされていた日々でした。
XULが設計されたとき、マルチスレッドはまだ研究段階の話題でした。LinuxとSystem 7(macOSの祖先)はまだ正式なマルチスレッドをサポートしておらず、学術研究の場以外では、ユーザー向けのアプリケーションに何らかの形で並列処理の必要性が生じると真剣に考えていた人は、ほとんどいませんでした。そのためXULはシングルプロセスかつシングルスレッドを前提に設計されていました。
FirefoxにElectrolysisを実装する時期がきたとき、XULの機能の多くは使えませんでした。マルチプロセスのパラダイムに対応した新バージョンのXULを設計することもできましたが、それは開発税を再び増加させていたでしょうし、アドオン開発者のメンテナンス作業が減ることもなく、それどころか、彼らのアドオンを新しいXULに移植する必要もあったでしょう。そのため、Electrolysisを投入するに当たって書き直す必要が生じた機能は、HTML5で書き直すという選択がなされました。
Electrolysis計画の後にFission計画が現れました。FissonのためにFirefoxのコードをどのように作り直す必要があるかを見てみると、Fission計画にはXUL38が必要だったでしょうし、少なくとも、すべてのアドオンがまた動作しなくなる可能性が非常に高かったです。
XULがFirefoxやGeckoから完全に削除されることは、まだ数年の間はないでしょうが、XULの時代は公式にもう終わりました。繰り返しになりますが、XULベースの拡張機能の仕組みを延命する選択は、技術的な苦痛を不必要に長引かせることになったでしょう。そして、実際の結果は既に述べたとおりです。
今はどうなっている?
さて、Firefoxアドオンの開発者にとって、現在および予測できる未来の選択肢はWebExtensionsと呼ばれています。
設計上WebExtensionsは、見境のない拡張機能の仕組みに比べると、機能がより限定されていますが、設計上より良く動きます。
Firefoxのコード全体の代わりに、WebExtensions APIの互換性だけを保てばよくなったので、ほとんどのFirefox開発税は姿を消しました。WebExtensions APIが安定しているので(残念ながらいくつか例外はありましたが)、ほとんどのメンテナンス税も姿を消しました。より単純で使いやすくなり、アドオン開発者はコードをFirefoxとChromiumu用アドオンの間で共有できるようになり、さらには、デスクトップ環境とモバイル環境の両方で問題無く動作する拡張機能をより簡単に作れるようになりました。
主要な欠点は、もちろん、WebExtensionsがアドオン開発者に対して、見境のない拡張機能の仕組みほどの強力なカスタマイズ性を提供していないということです。アドオン開発者が必要とするカスタマイズ性のすべてが、いずれWebExtensions APIに追加されることに私は期待していますが、実現のためにはより多くの開発リソースが必要で、それは私達に顕著に不足しているものです。
前述したあらゆる取り組みが、アドオン開発者とアドオン利用者にコストを強いたことは承知していますが、Mozillaが行ったこれらの選択が、Firefoxを守りChromeに対する競争力を維持するために必要なものだったと、あなたに納得してもらえれば、私としては幸いです。
編集履歴(訳註:原文に対するもの)
- XULオーバーレイについて追記。(Robert Kaiserによる提案)
- かつてアドオンがXPCOMコンポーネントを置き換えることが可能だったことに言及。(glandiumによる提案)
- 私達だけがPromiseなどの実装に取り組んでいたわけではなかったことを明記。
- 「Chromeに張り合える」という表現を「Chromeのように高速で、安定していて、安全な」のように改めた。
- XPCOM側のElectrolysisへの変換を明記。
- アドオン開発者がメンテナンス作業にうんざりしていたことを明記。
- 起こっていたセキュリティ上の問題について明記。
- セキュリティとは何かを明記。
- 要約を追加。
以上、Mozillaの開発者の方によって書かれた記事を訳してみました。Mozillaの重鎮だったroc氏がChromeの発展を横目で見ていた当時の心境を吐露した記事や、WebExtensionsの導入作業を主導したAndy McKay氏の記事も併せて読むと、Mozilla内の状況が色々見えてきて面白いかもしれません。
この記事だけでは「従来路線でいく選択が何故ありえなかったのか?」ということを読み取れなかった方は、この記事のコメント欄に簡単なまとめを書いてみたので、そちらも併せて参照することをお勧めします。
TSTをWebExtensionsに移行したときの苦労話などで、外部の協力者・アドオン開発者として知っていたことを自分も表面的に書いてみてはいましたが、ここまで幅広く語った記事は、確かに過去に読んだことがなかったと思います。Firefoxがまだ人気があった頃に、その時点までの情報をおさらいした記事は度々見かけた気がしますが、そういう記事では当然それ以後の情報はカバーされていないので、最近までの情報を網羅したまとめ記事には大きな意義があると思います。
個人的な感想としては、悪く言えば「言い訳に終始している」ともとれる内容と感じられ、今でもこの記事でいう「見境のない拡張機能の仕組み」にこだわっている人達のわだかまりの解消は繋がらないだろうと思いました。実際、元記事のコメント欄には、かなり昔のFirefoxからフォークしたPale Moonの維持に関わっている人のコメントなど、結構な長文が投稿されていました。
本件について僕自身は、端的には「WebExtensions化は遅きに失した」と考えています。Firefoxが大人気になって、アドオンが大量に作られて、それへの互換性維持のためにFirefox本体が身動きを取れなくなってしまうより前に、もっと早い時点で大鉈を振るうべきだったのでしょう。ただ、それは今だから言えることで、当時ここまで予測しきっていた人は、Mozilla内にもそんなにいなかったのではないでしょうか。Chromeがより良い拡張機能向けのAPIを最初から整備できたのは、プロジェクト立ち上げ期の開発者たちがFirefoxを作ったまさにその人達で、Firefoxが当時陥っていた状況から多くのことを学べたからだったという面は大きいと思います。
古びていくコードの損切りの判断について、どのようなタイミングで行うと良かったのか、どこまで引っ張ると手遅れになるのか、といったことを考える上で参考にするといいんじゃないかなと思いました。
2020年8月27日追記:当初この記事の末尾に個人的な主張を付記していましたが、翻訳記事の末尾に付け足すのは我田引水だったため、別記事に分けました。
-
訳註:ページ内検索のこと。 ↩
-
訳註:後に「Add-on SDK」と呼ばれるようになった、アドオン開発のためのフレームワーク。 ↩
-
訳註:できることの少なさ。 ↩
-
訳註:ガーベジコレクションのための仕組み。 ↩
-
訳註:リンク先はフォーカス制御に関わるXPCOMコンポーネントのインターフェース定義の例。 ↩
-
訳註:「decontamination(汚染除去、除染)」と「COM」をかけた言葉。 ↩
-
訳註:原文では「XULベースのアドオンの廃止という選択(the choice get rid of XPCOM-based addons)」となっているが、後段の同様の記述から、誤記ではないかと思われたため、このように訳した。 ↩
-
訳註:元のXULをXUL1、Electrolysis対応になったXULをXUL2と呼んだとしての仮称。 ↩