elkurin’s blog

銀英伝はいいぞ

ChromiumでFeatureをShipした話

2020年の1月-3月の3ヶ月間、GoogleChromeチームでインターンをしました。Chromeプロダクトでインターンをするのはこれで3回目です。したがって、Chromeにもインターンにも比較的慣れており、仕事のスタートアップとラップアップに限って言えば正社員さんよりも回数をこなしている入社と退職のエリートです。

そんな僕が今年配属されたのはChrome Worker Teamでした。この記事ではインターンで実際に体験させてもらった「ChromiumでFeatureをShipする流れ」について紹介しようと思いますが、その前に今回のターゲットであるWorkerというものについて軽く説明してみます。(詳しい仕組みなどはまた別の記事で書いたり書かなかったりします。)

Workerとは?

Workerとは、別スレッドでスクリプトを処理させるためのAPIです。重そうな処理とか別働隊として稼働し続けてほしい処理などがあるときは、その内容を書いたhoge.jsを用意してnew Worker('hoge.js');などとやると、別スレッドを立ち上げて走ってくれます。

developer.mozilla.org

"Worker"という名前は馴染みがない人が多いと思いますが、みなさんも知らないうちに結構使っているはずです。

例えばGmailYoutubeなどの通知がPCに通知されるのはWorkerの1種であるService Workerのおかげです。Chromeブラウザを使っている方は、ブラウザの右上にあるメニューを開いてMore tools > Task Manager と選択するとChrome内のプロセスが確認できるんですが、例えば僕の画面(下図)だと3行目に"Service Worker: https://twitter.com/sw.js"というのがありますね。これはTwitterがPCに通知を出すスレッドで、Twitterのタブを閉じてもkillされずに独立して動き続けます。ページを開いていないのにちゃんと通知が来るのはこいつのおかげですね。

f:id:elkurin:20200421011906p:plain

TaskManager

今回僕が携わったShared Workerは複数のページからアクセスできるスレッドを作成するAPIです。

new SharedWorker('worker.js', { name: 'worker-name', type: 'module'});

上の赤文字で書いてある部分が別スレッドで走らせたいスクリプトになりますが、現在ここで使えるのはClassic scriptのみです。Classic script?なんじゃそれ?というと、

import 'module.js';

これができないスクリプトのことです。Cで言えば#include "stdio.h"的なやつのことですね。つまり従来のShared Workerでは他のスクリプトを呼び出すimport文が使えなかったわけですが、そこでModule scriptというimport文に対応したscriptを使用できるようにするModule Shared Workerを作るというのが僕のプロジェクトです。

実際に働いていると、実はコーディングをしている時間はそこまでなく、ソフトウェアエンジニアとは英語を書くお仕事なんじゃないかという気持ちでした。specに深入りした複雑な議論を必要とする変更が多く、そのたびにdocumentを書いたり見つけたバグをcrbug.comにレポートしたり書面で議論したりと、書面化能力がかなり身についたと思います。以下は書いたdocumentの一例です。

docs.google.com

 というわけで、本題である「ChromiumでFeatureをShipしてみた」に移りたいと思います。

FeatureをShipするまで

ChromiumでFeatutreをShipする、すなわち機能をリリースするには、結構長くてしっかりした工程があります。ざっと以下のような感じになります。

  1. Design Docを書く
  2. 機能を実装する
  3. テスト(Web Platform Test)を完備する
  4. セキュリティ脆弱性を直す as far as we know
  5. 他のブラウザからのサポートを得る
  6. Intent to Shipを提出してAPI ownersから承認をもらう
  7. リリース!

 それぞれについて体験談を書いていきます。

なんのFeatureを作るのか

ブラウザでFeatureを作る際、(私の知る限りでは)先にWhatWGというWeb標準化団体がつくた規格があり、それに沿うように機能を作っていくことが多いです。今回のプロジェクトも同様で、workerのspecに仕様だけ策定されてまだ実装はされていないModule Shared Workersを作りました。

今回、時間の限られたインターンということで、何を作るかについては最初から与えられていました。

Design Documentを書く

"Shared Workersをモジュール対応させる"と言っても、どういう実装方針がいいのか、どういう関数やクラス、パラメータが必要か考える必要があります。これらをまとめて書き記したものがいわゆるDesign Docです。

Documentを書くのは、今みんなに読ませるためというより、のちの開発者を助けるためだと思っています。似たような機能を作りたいとき、改善・拡張したいとき、バグの直し方が非自明なときなどにDocumentがあるととても幸せだし、逆に誰しもDocumentがなくて「READMEはどこだコラァ!」とキレたことがあるでしょう(?)。そう、つまりDocumentが必要なのは後世のエンジニアたちであり今の俺には必要ない!のでサボりにサボって実装が終わったくらいに書き始めました。

これは僕が書いたDesign Docです。

docs.google.com

 

Featureが動くために必要な実装をする

デザインが決まれば次は実装です。(ちなみに私はデザインが決まる前に実装しちゃいました。てへぺろ☆(・ω<))モジュールをユーザーに使ってもらうためには、そもそもユーザーがモジュールを指定できるようWebAPIを変更しなくてはならないので、ここはエイヤッと実装してしまってから、さてプロジェクト開始です。

機能を作る上で最初に行うのはflagを作ることです。chromiumでは新しい機能を追加する際、それぞれの機能に対応したテストフラグを用意し、そのflagがenableのときだけ機能が実行されるようにします。Behind the flagとか言われるやつですね。機能が完成してstableになるまでこのflagの裏に実装していくことになります。

このあと、メインの機能であるstatic importとdynamic importをサポートしていくわけですが、気合入れてとすると動きました。機能が使えるかのテストも行い成功、実装完了です!一気に進みましたね。ここまで2週間です。

テストを書く

実装終わったし動いたんだから、あとはちょちょいのちょい。そう思っていた時期が私にもありました。本当の試練はここから始まります。

「テストを書く」とは具体的に何をするのかというと、Web Platform Test (a.k.a WPT)というオープンソースのテストの群れにcommitするということです。Chromeチームの方がWPTの走らせ方を紹介しているので興味のある方は走らせてみてください。

qiita.com

閑話休題Web標準化〜

WPTはChrome専用ではなく、ブラウザを作るすべての人のために用意されたものです。なぜChrome専用のテストを使わないのかというと、ブラウザ開発者は他のブラウザと足並みを揃えるべきだからです。あるブラウザがなんかすごいWebAPIを作ったとしても、デベロッパからしてみれば、他のブラウザで動かないAPIなどごめんである、というわけです。もちろん各々のブラウザで性能や機能が異なっているからこそ様々なブラウザが跋扈しているわけですが、一部のブラウザでしか動かないサイトが量産されてはたまったもんじゃないので、ある程度のWebの標準化は必要視されています。この「標準化」を行っているのがWhatWGという団体で、HTMLやらDOMやらの標準規格を作って、これを守ってねと各ブラウザたちに推奨しています。私が今回お世話になったhtml specfetch specWhatWGによる仕様書です。この仕様書はWhatWGさんたちからただ降ってくるものではなく、様々なbrowser vendorたちが意見を持ち寄って現在進行形で作られている総合知であり、僕もインターン中に何度かspecにcontributeさせてもらいました。以下は記念すべき僕のfirst commitです。


長々と脱線してしまいましたが、WPTはWhatWGが定めたWeb標準を満たしているかを確認するためのテストであり、すべてのブラウザがパスすることが期待されるテストです。しかし当然ながら仕様を定めた瞬間に実装も完了するわけではなく、むしろ仕様の議論を重ね吟味されてから実装する方が賢明でしょう。そしてテストに関してもやはりモノがなければ作るのは難しいです。というわけで、WPTにテストを追加するのも多くの場合最初に機能を実装した人の仕事になります。今回のModule Shared Workerは光栄なことに僕が初の実装者なので、面倒なことに「Web標準を満たしているかを確認するためのテスト」を完備しなくてはなりません。

というわけでたくさんのテストを書きました。来る日も来る日もテストを書きました。何をテストすべきかは簡単な問題ではなかったのでどういうテストが必要なのかを考えて洗い出し、手書きのテストをたくさん書き、組み合わせ爆発するものはテストジェネレーターを書いて、コーナーケースを探して、たくさんのテストを作成しました。

テストが通るようにデバッグ

たくさんのテストを書くと当然たくさんのバグが見つかります。最初から完璧のつもりで実装してますが、なんだかんだバグは出てくるものです。特によくバグが埋まっているのは、HTTPヘッダのセキュリティポリシー関連です。Shared Workerを立ち上げる際にscriptをfetchしさらにその中でscriptをimportしたりsubresourceをfetchしたりというように、Module Shared WorkerはCross-siteの嵐であり、セキュリティ脆弱性の宝庫でした。何が正しいのかが非自明だったので、いろんなspecを追いながら調査していく作業が必要になります。僕はこの手の重箱の隅をつつくような作業が結構好きでした。具体的にどんな感じなのかを知りたい方は、例えばHTTPS stateの継承について僕のメンターさんがまとめてくださっているので読んでみてください。

nhiroki.jp

リリースする前には、少なくとも「漏れちゃいけないデータが漏れる」系のバグは直す必要があり、いろいろなセキュリティバグを発掘しては整地しての繰り返しでした。してこのです。

この話題は結構面白い話が詰まっているので、別の記事で書いたり書かなかったりするつもりです。

Intent to Shipを提出

だいたい機能が動きテストも整備され十分にテストをパスするようになったらChromium committersたちに「こんな機能を実装したいのですがよろしいか?」とIntent to Shipという企画書を提出します。そしてAPI Ownersという偉い人から3つのOKが出たらついにリリースです。

Intent to Shipを監視しておくとどんな機能がくるか追いかけることができるので、興味あったらここで確認してください。Twitterアカウントでも更新されてます。

 

リリース!

最後に、test flagをテスト環境外でもenableすることで、今までflagの裏で実装していた機能がデフォルトで動くようになりました。Feature is shipped by default!祝!

この機能はChrome M82というバージョンでアップデートされる予定でしたが、コロナ関係でM82のリリースは中止されM83に含まれることになりました。Chromium BlogでM83の新機能の中に紹介されています。

f:id:elkurin:20200421122837p:plain

https://blog.chromium.org/

なんかちょっとうれしいですね。

 

最後に

以上、ChromiumでFeatureをShipしてみた、でした。かなりしっかりしていますね。以前もChromiumソースコードに触っていたとは言え、Accessibility Teamではデバッガを作っていてあまりWebとは関係なく、DOM Teamにいたときは木で遊んでいただけなので、最初は無知無知太郎でメンターさんには大変お世話になり、今回のインターンを通してかなりWeb系の造詣が深くなりました。そしてWebセキュリティとWeb標準化に強く興味を持ちました。

インターン期間中、submitしたpatchは58、reportしたcrbugは15、他にもspecにcontributeしたりと、かなり盛り沢山な経験をしました。Chromiumは社にいなくても触れるオープンソースなので、これからも自分で埋め込んだバグを自分でレポートしていきたいと思います(?)。