Fetch API (Blink) のソースコードを眺めてみる

こんにちは。Lighthouse Studio の yoko です。普段お世話になっているブラウザの内部実装を少し眺めて見ました。

今回は Fetch API のソースコードを見ていきます。途中で力尽きましたが、Fetch API の実装を追うための雰囲気だけ感じてもらえれば嬉しいです。

今回の Fetch API は、 Chromium のレンダリングエンジンである Blink の実装を眺めていきます。

Chromium とは

https://www.chromium.org/Home/

Google が主導して開発されているオープンソースのブラウザ。Chrome 含め様々なブラウザのベースになっています。

Chromium で使われているレンダリングエンジンです。

https://www.chromium.org/blink/

レンダリングエンジンは実際に Web ページを描画する仕組みで、主に今回見ていく Blink や WebKit があります。

ちなみに Chromium は Blink を使っていますが、Safari は WebKit を使用しています。

Blink の全体の動きについては以下のドキュメントにまとまっています。

docs.google.com

Fetch APIとは

Fetch API とはリソースを取得するための API です。以下のようにすると、url 先のリソースを取得することができます。

const response = await fetch(url)

Fetch Standard

Fetch API のコードを眺める

Blink のソースコードを眺めていると、Fetch API の実装らしきものを発見。

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/fetch/

これらのファイル群の分類は以下です。

  • *.idl: Blink の interface を V8 (JavaScript のエンジン) にバインドするための定義。Blink IDL で定義されている
    • 詳細: Web IDL in Blink
    • 補足: 違いもあるが、Blink IDL は Web IDL とほぼ同じ文法らしい
  • *.cc: C++ の実装
  • *.h: C++ の interface の定義

idl をまず確認してから実装ファイルを読むのが良さそうですね。 ここからは重要そうなファイルを順に見ていきます。

window_fetch.idl

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/fetch/window_fetch.idl

[
    ImplementedAs=GlobalFetch
] partial interface Window {
    [CallWith=ScriptState, NewObject, RaisesException] Promise<Response> fetch(RequestInfo input, optional RequestInit init = {});
};

Blink IDL で書かれています。これは Web API の window interface に対して fetch という operation を定義していますね。

また、この IDL の実装は GlobalFetch というものであることも判明しました。

再掲 Blink IDL: Web IDL in Blink

global_fetch.cc

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/fetch/global_fetch.cc

ここには fetch() と fetchLater() という API の実装が含まれています。今回は fetch() だけ見ていきます。

エントリーとなるメソッドのコードは以下です。

ScriptPromise<Response> GlobalFetch::fetch(ScriptState* script_state,
                                           LocalDOMWindow& window,
                                           const V8RequestInfo* input,
                                           const RequestInit* init,
                                           ExceptionState& exception_state) {
  UseCounter::Count(window.GetExecutionContext(), WebFeature::kFetch);
  if (!window.GetFrame()) {
    exception_state.ThrowTypeError("The global scope is shutting down.");
    return EmptyPromise();
  }
  return ScopedFetcher::From(window)->Fetch(script_state, input, init,
                                            exception_state);
}

内容を見ると return で記述されている Fetch メソッドで詳細な実装がされていることが分かります。 また、上部の UseCounter がよく分からなかったので調べた結果、以下の情報を発見しました。

あまり理解できませんでしたが、UMA (User Metrics Analysis)のために必要らしいです。

次に本命の Fetch を見ていきます。 Fetch の重要な処理を抜粋すると以下です。

  ScriptPromise<Response> Fetch(ScriptState* script_state,
                                const V8RequestInfo* input,
                                const RequestInit* init,
                                ExceptionState& exception_state) override 

....
Request* r = Request::Create(script_state, input, init, exception_state);

...

FetchRequestData* request_data = r->PassRequestData(script_state, exception_state);

...

   auto promise = fetch_manager_->Fetch(script_state, request_data,
                                         r->signal(), exception_state);
    if (exception_state.HadException())
      return EmptyPromise();

    return promise;

}
  1. Request オブジェクトを作成
  2. fetch した request のデータを取得
  3. fetch_manager というクラスで promise を作成

という流れのようですね。fetch_manager を見ていきます。

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/fetch/fetch_manager.cc

ScriptPromise<Response> FetchManager::Fetch(ScriptState* script_state,
                                            FetchRequestData* request,
                                            AbortSignal* signal,
                                            ExceptionState& exception_state) {
  DCHECK(signal);
  if (signal->aborted()) {
    return ScriptPromise<Response>::Reject(script_state,
                                           signal->reason(script_state));
  }

  request->SetDestination(network::mojom::RequestDestination::kEmpty);

  auto* resolver = MakeGarbageCollected<ScriptPromiseResolver<Response>>(
      script_state, exception_state.GetContext());
  auto promise = resolver->Promise();

  auto* loader = MakeGarbageCollected<Loader>(
      GetExecutionContext(), this, resolver, request, script_state, signal);
  loaders_.insert(loader);
  // TODO(ricea): Reject the Response body with AbortError, not TypeError.
  loader->Start(exception_state);
  return promise;
}

この MakeGarbageCollected で Loader を GC の管理下に置くようです。 MakeGarbageCollected の interface は以下。

https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/platform/heap/garbage_collected.h;l=48?q=MakeGarbageCollected&sq=&ss=chromium%2Fchromium%2Fsrc

これは Oilpan という GC を使っているようでした。

Oilpan - Blink GC

chromium.googlesource.com

ここで力尽きたので、 GC の管理下に置かれた Loader の処理については別の機会で見ます。 恐らくここが Fetch API の核心だと思われるので、続きは Part2 をお待ちください。

補足: fetchLater

ブラウザのタブが閉じられたなどに遅延的に fetch を実行できる API

fetchLater() API

便利そうだが、まだ未対応なブラウザがありプロダクションでは使わない方が良さそうです。

終わりに

今回は chromium のレンダリングエンジンである Blink (Fetch API) の中身を眺めました。正直理解できない部分が多くありましたが、普段使っているブラウザの内部実装というだけで面白かったです。今後は Fetch API だけではなく、レンダリング全体の実装を見ていきたいです。