Chromeのサービスモデルについて
Chrome Service Model - Google Docsを読んでChromeのサービス指向モデルとは何か理解を深める。
図はAIに生成してもらった各サービスを象徴するロボット
解決したい問題
- コンポーネントの一貫性が低い
- プラットフォームやブラウザの機能は Blink (
third_party/blink
)や、他のディレクトリ(chrome|components|content)/(browser|renderer)
にあり、散らばってしまっている。一見無関係に見える機能が間接的に依存していて、再利用やセキュリティ、安定性を目的とした個別のサービス、コードに分離できない
- プラットフォームやブラウザの機能は Blink (
- 重複/不必要な複雑性
- ブラウザプロセスにある多くの機能固有のIPCのはより小さい共通サービスAPIのセットで置き換え可能
- 例えば、pepper, content, extensions は、パーミッション要求、通知、クォータ、ディスクアクセス、リソースロードの処理のために似たようなコードを持っている
- 各機能が独自のバインディングレイヤーを実装している(e.g. Extensions, WebUI)
- 共通の操作(ネットワーク、設定など)は呼び出すコードのプロセスによってアクセス方法が違う
- 拡張性の低さ
- 柔軟なAPIやモジュール性が不足しているため、実験や新機能の追加のためにブラウザに直接機能を実装しなければいけないため、複雑さやコードの肥大化を招いてしまう。あるいは(実験や新機能追加が)完全に禁止されているものもある(Flutter, Dart, Cardboard)[1]。
- 漏洩した抽象化
- インターフェースの実装方法が同じでないプラットフォームではコードの再利用が難しくなる(iOSのWKWebViewとWebContentsなど)[2]
- WebContentsのようなコア抽象化機能は、ホストされるレンダラーがBlinkのインスタンスであると仮定しているので、PDFやBlimpのような機能には不要なオーバーヘッドが追加される
- プラットフォームが違うプロセスにサービスを移動させたり(Android では gpu のコードのパスが違う)、セキュリティや安定性の改善が難しい(e.g. ネットワークとプロフィールプロセス)
これには2つの理由がある。ひとつめは、コードベースがシンプルにそういうことが起こらないように設計されてなかった。明らかなニーズが発生するまでは過剰に設計しすぎないように意識的に選択をしていた。ふたつめは、新機能追加や、プラットフォームのサポートを急ぐあまり、レイヤリングを見直す時間がなかった。
MojoやMandolineから得た教訓と長期的なリファクタリングへのコミットメントによって、アーキテクチャをよりモジュール的で柔軟な設計に移行する機会を得た。
Overview
サービスモデルに変更することでこれらの問題を解決する。
現在Chromeはたくさんのプラットフォームをホストしている(web platform, Pepper plugins, extensions, NaCl, Mojo Apps, Chrome Apps, Android Apps)が、こういうものはレイヤードモデルを使うのが一般的(OSなど)。最終的なゴールは共通のサービスのセットがあり、その上に他のサービスや機能を重ねられるようにすること。Appendixを見るとわかりやすそう。
従来のブラウザプロセスにはUI、ネットワーク、プロフィール、デバイス、機能の役割をする実装があるなど、他のプロセスからも利用できる機能が実装されているが、これをAfterの形に分離する。
Afterの形では、様々なサービスがプロセスとして存在しており、複数のプロセスからプロセス間通信を通して利用できるようになっている。例えば、ネットワークサービスはブラウザプロセスからも利用されているし、Blinkプロセス(レンダラープロセス?)からも利用されている。このように共通の処理をサービスという形で分離することで再利用性や拡張性が高く、プロセスが分離されているので安全であったり、安定性も高い設計になっている。
デバイスのメモリが厳しく、プロセスをたくさん生成できないようなデバイスでは従来どおり、ブラウザプロセスでサービスを動かすこともできる。
Mojoは呼び出し元がどのプロセス/モジュール/言語かによらず重複した実装を避けてコードの再利用を可能にする。
現行(2016年当時)のシステムのIPCを確認すると、初期セット―ネットワーク、設定、レベルDB、同期、通知、パーミッション、クォータ、クリップボード、pub/subなど―の共有サービスが存在する。これらのサービスを作る際には、現状のコードでこれらの新しいサービスを使う使うように変換をする必要がある。
同様に、ブラウザーの機能を露出(expse)する際には、Mojoインターフェースを介してAPIを露出するべきである。適切なセキュリティチェックをした上で、別のプロセスや言語からアクセス可能にするべきである。これはあるfeatureのために、いくつかのAPIをサポートする必要性を避けられることになる[1]。
これは異なる実装を使えるようにすることも可能にする―例えばChromeチームや他のGoogleのチームが新機能をブラウザに開発する探索をする際に―。レンダリングエンジンと相互にやり取りする場所ではBlinkやiOS、コンテンツハンドラー(以下で述べる)で実装されている透過的なMojoインターフェースを使う。
-
ここはどういう意味なんだろう ↩︎
プロジェクト
ここからはサービス化で何をするべきなのかが記載されている。
-
複数の機能で利用されたり、その上で機能が開発されるような"Foundation"サービスを作る[1]。これは機能を変換したり、簡素化するようにニーズに沿って駆動されるべきである。どのコードでどういうサービスが使えるかをまとめたのがこのスプレッドシート。サービスを作る際には、ゴールはより多くの現状実用的なコードを共通の機能を使うように変換することであるべきだ。最初に取り組むべきは次のコンポーネントだ。
- UI (rendering & input) (mus)
- Network (as a rough sketch, see Mandoline’s mojo/services/network/public/interfaces/)
- File (as a rough sketch, see Mandoline’s components/filesystem/public/interfaces/)
- LevelDB
- Synchronization
- Pub/Sub
- Preference
- Permission
- Clipboard
- Notifications
- Tracing
- WebView (navigation, frame tree)
-
used by と built uponのニュアンスの違いはわかるような、分からないような ↩︎
- ブラウザやレンダラーにかかわるWeb Platform実装をレンダラープロセスに個別に実装されるように変換する。具体的にはレイヤーリングを使ってそれらの実装がコアサービスを使うように。例えば次のようなWeb Platform実装がある。
- Local Storage: detailed design
- Session Storage
- IndexedDB
- FileSystem
- ブラウザやレンダラープロセスにかかるブラウザのコンポーネントたちを、Mojoインターフェースを通してHTMLエンジンを呼び出すようにリファクタする
- すべてのレイヤードコンポーネントでiOS/contentを取り扱う余分なレイヤーが削除されてから開始する[1]
- 残りのブラウザの機能(i.e. iOSと共有していないChromeの機能)を完了させる
-
iOSのWebKitを取り扱う上で結構余分なコードがあったんだろうなと推測 ↩︎
コードの場所
//services
に存在する。抽出されたライブラリのコードは別の適切な場所(src/components, src/ui, etc.)に移動されるべき。
トラッキングバグ
Scope & Non-Goals
- サービス化はすべてのChromeの機能が他のサービスと通信するときにMojoを使うように努力させることを目的とはしていないし、すべての機能やインターフェースを各々のプロセスに分解することは目的とはしていない。GeckoでやられていたXPCOMを複製したい訳では無い。
代わりに、こう考えることができる。次の観点からこのプロジェクトはChrome IPC から Mojoへの変換の拡張だと。
- いくつかのChrome IPCインターフェースの実装に存在する一般的なユースケースは、低レベルのよく設計された汎用インターフェースとして公開され、機能固有のコードがクライアントに移動できるようになっている。
- これらのインターフェースがモジュール化されているため、呼び出しコードを更新することなく、別のプロセスに実装を移すことができる。
さらに、Mojoを、単純な関数呼び出しやPostTask()のような明白なテクニックの代わりにMojoを使うことをデフォルトにするのではなく、コードの一部を分離する必要があると考えられる場合に使用するツールと考えるべきです[1]。
-
なんでもMojoにすればいいというわけではないということか ↩︎
Appendix A: Future projects
このプロジェクトによるChromeのアーキテクチャの改善は私達が何年も臨んでいたものだ。しかし、それはいまのところ実装するのは複雑過ぎた。いくつかの例を示すと:
- サンドボックス化された別プロセスでネットワークコードを実行する。 Windowsの場合、LSPなどを介して外部ソフトウェアにフックされるため、これは安定性に役立つだろう... このコードをサンドボックス化することもできる(レンダラーほど強力ではないが、サンドボックスのないブラウザよりはましだ)。 これは段階的に行うことができ、各レベルがそれぞれの利点を提供し、最終的に別のプロセスになります:
- すべてのコードが同じ API を介してネットワーク リクエストを行うように統一します(つまり、呼び出し元のコードがレンダラーまたはブラウザ プロセスに関係なく)。 これにより、コードが簡素化され、Blimpのようなプロジェクトがhttpやソケットリクエストなどの実装を簡単にオーバーライドできるようになります。
- 同期的にネットワーク・レイヤーをフックするブラウザ・プロセスのコードは、非同期APIを使用する必要があります。 これにより、ネットワーク・ライブラリーへのパブリック・インターフェースがすっきりする。
- ネットワーク・コードを別プロセスに移す。 これにより、安定性とセキュリティが向上します。 また、Chromeがすべて動作していなくても、他の機能からネットワークリクエストを行うことができるようになります。
- Mojo ShellのコンテンツハンドラコンセプトをChromeに実装する。 これにより、コードの他のサブシステムを変更することなく、新しい機能/実験でコンテンツを表示したり、入力イベントを取得したりできるようになります。 ほとんどの場合、Mojo UI Serviceを使用するChromeに依存します。
- プロファイルディレクトリにのみアクセスできるサンドボックス化されたプロセスで、プロファイルごとのファイルサービスを実行する。 そのため、ディスクにアクセスするコードのほとんどは、他のディレクトリを読み書きするエクスプロイトから保護されます。 ファイルサービスのインスタンスは複数必要で、そのうちの1つは、ファイルシステムを必要とするブラウザのコードのために、ファイルシステム全体にアクセスできる。 ステップ:
- ファイルサービスを使うための子プロセスにあるファイルシステムアクセスを変換する
- ブラウザにあるファイルアクセス(我々制御下のコードにある)をファイルサービスをうまく使うように変換する
- ブラウザがシャットダウンしたあとでブラウザの小さいパーツを実行する。現状はすべてのコードが依存しているので不可能だ。サービスモデルでは、いくつかの必要なサービスを実行することが可能になる。例えば例として
- ARCランタイム(Chromeとは分割されたプロセスにある)はいくつかのChromeのサービスを必要とする。第一歩として、手動でサービスをインターフェースでラップする。第二歩として、ARCをChromeなしで実行できるようにするためには、すべてのサービスがChromeなしで動くようにする。
- デスクトップにおいて、我々はブラウザが閉じていても通知を見せたい[1]
-
これがあると、よりネイティブアプリに近づけそう。PWAが2015年とかなので、そのときに出てきた課題感なのかな ↩︎
Appendix B: FAQ
Q これは異なるサービスを異なるディヴィジョンごとにサポートする必要があることを意味しますか?外部AuthorsやWebにこれらのサービスを公開することになりますか?
A: すぐには考えていない。最終的には、サービス化が完了したときに、インターフェースがstableになったときに、どれをサポートしたいのかを考えはじめられるはずだ。そのときに全部流して、サービスサービスをアップデートするアイデアを考えることができる。異なるスケジュールや他のチームに公開するかどうかなど。
Q: Web Platformの実装をcontent/rendererをBlinkへ移動するリファクタリングとどれぐらい関係していますか?同様に、より特殊なAPIをBlinkへ移動させることなど。
A: 両方のプロジェクトはお互いに有益であるべきで、他のプロジェクトを妨げることはないでしょう。
ウェブプラットフォームの機能をレンダラー内部でのみ実行するように変更すると、そのコードをすべて Blink に移動するのが簡単になります(コンテンツ依存関係がなくなるため)。
translateのようなブラウザー機能が、汎用APIではなくBlink専用のAPIを使うように変更されれば、レンダラー側のtranslateコードとBlink間のAPIサーフェスが減る。 そうなれば、このプロジェクトでは、ブラウザのプロセスからMojoを通じて新しい特定のAPIを呼び出すことが容易になる。