Chromium Code Reading: Experimentalな機能の実装(WebPのAcceptヘッダ送出)

はじめに

Google+を見ていたら、面白そうなポストがありました。
https://plus.google.com/100132233764003563318/posts/KRU6nxp7LXG

Canaryビルドに新しいExperimentalな機能が追加されました。簡単に言うと、画像のリクエスト時にHTTPリクエストのAcceptヘッダにWebP画像をサポートしていることをWebサーバに伝える仕組みです。

今回Canaryに追加されたこの機能は、差分量も小さく、Experimentalな機能の追加方法を学ぶのに最適と思いました。

以下が対象のコードです。

  • Issue 14273007: Add a switch to Chromium to enable/disable 'image/webp' accept header.

https://codereview.chromium.org/14273007/

  • Issue 13814024: Add a runtime flag in WebRuntimeFeatures to enable 'image/webp' accept header

https://codereview.chromium.org/13814024/

chrome://flags

ChromeのURLバーから chrome://flags にアクセスすると、Experimentalな機能のON/OFFができます(以下、スイッチと呼びます)。
最新のChromiumでは以下のような画面が表示されます。


スイッチの定義

chrome://flags で表示されるスイッチの説明文はgenerated_resources.grdに定義されています。

src/chrome/app/generated_resources.grd

      <message name="IDS_FLAGS_ENABLE_WEBP_IN_ACCEPT_HEADER_NAME" desc="Title for the flag to enable 'image/webp' in accept header.">
        Enable 'image/webp' accept header
      </message>
      <message name="IDS_FLAGS_ENABLE_WEBP_IN_ACCEPT_HEADER_DESCRIPTION" desc="Description for the flag to enable 'image/webp' in accept header.">
        Enables 'image/webp' accept header in HTTP requests for images, to denote WebP image support.
      </message>

スイッチの特徴がkExperiments定数内に定義されます。
スイッチの説明文、対応OS、スイッチの種類(SINGLE_VALUEはON/OFFのみのスイッチ)など。

const Experiment kExperiments[] = {
[...]
  {
    "enable-webp-in-accept-header",
    IDS_FLAGS_ENABLE_WEBP_IN_ACCEPT_HEADER_NAME,
    IDS_FLAGS_ENABLE_WEBP_IN_ACCEPT_HEADER_DESCRIPTION,
    kOsAll,
    SINGLE_VALUE_TYPE(switches::kEnableWebPInAcceptHeader)
  },
};

初期化

スイッチのデフォルト値はfalseになっていました。

bool RuntimeEnabledFeatures::isWebPInAcceptHeaderEnabled = false;

Blinkの初期化時に、chrome://flagsで設定されたスイッチの状態取得とランタイム機能郡(WebRuntimeFeatures)にスイッチ状態をセットします。

void RenderThreadImpl::EnsureWebKitInitialized() {
[...]
  WebRuntimeFeatures::enableWebPInAcceptHeader(
      command_line.HasSwitch(switches::kEnableWebPInAcceptHeader));
[...]
}

WebRuntimeFeaturesからRuntimeEnabledFeaturesに委譲されています。

void WebRuntimeFeatures::enableWebPInAcceptHeader(bool enable)
{
    RuntimeEnabledFeatures::setWebPInAcceptHeaderEnabled(enable);
}
class RuntimeEnabledFeatures {
[...]
    static void setWebPInAcceptHeaderEnabled(bool isEnabled) { isWebPInAcceptHeaderEnabled = isEnabled; }
    static bool webPInAcceptHeaderEnabled() { return isWebPInAcceptHeaderEnabled; }

スイッチの利用

ここからは、Experimentalな機能の実装に依存するコードです。

void CachedImage::setCustomAcceptHeader()
{
    if (RuntimeEnabledFeatures::webPInAcceptHeaderEnabled())
        setAccept("image/webp,*/*;q=0.8");
}

余談: バックトレース

バックトレースの取得にcontent shellを使います。content shellとはレンダリングエンジン(Blink)の動作確認に使える軽量ブラウザです。

こんな画面です。質素です。

CachedImage::setCustomAcceptHeaderにブレークポイントを設定して、バックトレースを取ってみます。

と、その前に、content_shellプロジェクトのプロパティからコマンドライン引数に「--single-process」オプションを設定しておきます。
これをしておかないとうまくバックトレースが取れません。

バックトレースをざっくり読み解くと、HTMLのパース(最近HTMLのパースはBackgroundParserでスレッド化された!)、CSSのスタイル解決、画像のロード、キャッシュイメージの生成、を経たことがわかります。

誰が画像を要求したのか、解明してみます。

スタックとレースからStyleResolver::loadPendingImagesを見てみると、CSSのbackground-imageのURLに指定された画像を取得しようとしているようです。

どんな画像?

StyleResolver::loadPendingImageのローカル変数imageValueのm_urlをデバッガで見ると、http://www.google.co.jp/images/srpr/logo4w.png でした。

content shellを起動すると、http://www.google.co.jp/に行くので、Googleトップページのロゴ画像と言うわけです。

>	webkit.dll!WebCore::CachedImage::setCustomAcceptHeader()  行 301	C++
 	webkit.dll!WebCore::CachedImage::CachedImage(const WebCore::ResourceRequest & resourceRequest)  行 62	C++
 	webkit.dll!WebCore::createResource(WebCore::CachedResource::Type type, WebCore::ResourceRequest & request, const WTF::String & charset)  行 73 + 0x29 バイト	C++
 	webkit.dll!WebCore::CachedResourceLoader::loadResource(WebCore::CachedResource::Type type, WebCore::CachedResourceRequest & request, const WTF::String & charset)  行 478 + 0x16 バイト	C++
 	webkit.dll!WebCore::CachedResourceLoader::requestResource(WebCore::CachedResource::Type type, WebCore::CachedResourceRequest & request)  行 405 + 0x20 バイト	C++
 	webkit.dll!WebCore::CachedResourceLoader::requestImage(WebCore::CachedResourceRequest & request)  行 155 + 0x12 バイト	C++
 	webkit.dll!WebCore::CSSImageValue::cachedImage(WebCore::CachedResourceLoader * loader)  行 79 + 0x16 バイト	C++
 	webkit.dll!WebCore::StyleResolver::loadPendingImage(WebCore::StylePendingImage * pendingImage)  行 4248 + 0xc バイト	C++
 	webkit.dll!WebCore::StyleResolver::loadPendingImages()  行 4285 + 0x1b バイト	C++
 	webkit.dll!WebCore::StyleResolver::loadPendingResources()  行 4360	C++
 	webkit.dll!WebCore::StyleResolver::applyMatchedProperties(const WebCore::StyleResolver::MatchResult & matchResult, const WebCore::Element * element)  行 1915	C++
 	webkit.dll!WebCore::StyleResolver::styleForElement(WebCore::Element * element, WebCore::RenderStyle * defaultParent, WebCore::StyleSharingBehavior sharingBehavior, WebCore::RuleMatchingBehavior matchingBehavior, WebCore::RenderRegion * regionForStyling)  行 1010	C++
 	webkit.dll!WebCore::Element::styleForRenderer()  行 1372 + 0x26 バイト	C++
 	webkit.dll!WebCore::NodeRenderingContext::createRendererForElementIfNeeded()  行 247 + 0xc バイト	C++
 	webkit.dll!WebCore::Element::createRendererIfNeeded()  行 1263 + 0x24 バイト	C++
 	webkit.dll!WebCore::Element::attach()  行 1274	C++
 	webkit.dll!WebCore::executeTask(WebCore::HTMLConstructionSiteTask & task)  行 100 + 0x20 バイト	C++
 	webkit.dll!WebCore::HTMLConstructionSite::executeQueuedTasks()  行 143 + 0x12 バイト	C++
 	webkit.dll!WebCore::HTMLTreeBuilder::constructTree(WebCore::AtomicHTMLToken * token)  行 379	C++
 	webkit.dll!WebCore::HTMLDocumentParser::constructTreeFromCompactHTMLToken(const WebCore::CompactHTMLToken & compactToken)  行 617	C++
 	webkit.dll!WebCore::HTMLDocumentParser::processParsedChunkFromBackgroundParser(WTF::PassOwnPtr<WebCore::HTMLDocumentParser::ParsedChunk> popChunk)  行 428	C++
 	webkit.dll!WebCore::HTMLDocumentParser::pumpPendingSpeculations()  行 485	C++
 	webkit.dll!WebCore::HTMLDocumentParser::didReceiveParsedChunkFromBackgroundParser(WTF::PassOwnPtr<WebCore::HTMLDocumentParser::ParsedChunk> chunk)  行 334	C++
 	webkit.dll!WTF::FunctionWrapper<void (__thiscall WebCore::HTMLDocumentParser::*)(WTF::PassOwnPtr<WebCore::HTMLDocumentParser::ParsedChunk>)>::operator()(const WTF::WeakPtr<WebCore::HTMLDocumentParser> & c, WTF::PassOwnPtr<WebCore::HTMLDocumentParser::ParsedChunk> p1)  行 254 + 0x24 バイト	C++
 	webkit.dll!WTF::BoundFunctionImpl<WTF::FunctionWrapper<void (__thiscall WebCore::HTMLDocumentParser::*)(WTF::PassOwnPtr<WebCore::HTMLDocumentParser::ParsedChunk>)>,void __cdecl(WTF::WeakPtr<WebCore::HTMLDocumentParser>,WTF::PassOwnPtr<WebCore::HTMLDocumentParser::ParsedChunk>)>::operator()()  行 523	C++
 	webkit.dll!WTF::Function<void __cdecl(void)>::operator()()  行 704 + 0x1a バイト	C++
 	webkit.dll!WTF::callFunctionObject(void * context)  行 62	C++
 	glue.dll!base::internal::RunnableAdapter<void (__cdecl*)(void *)>::Run(void * const & a1)  行 171 + 0x18 バイト	C++
 	glue.dll!base::internal::InvokeHelper<0,void,base::internal::RunnableAdapter<void (__cdecl*)(void *)>,void __cdecl(void * const &)>::MakeItSo(base::internal::RunnableAdapter<void (__cdecl*)(void *)> runnable, void * const & a1)  行 872	C++
 	glue.dll!base::internal::Invoker<1,base::internal::BindState<base::internal::RunnableAdapter<void (__cdecl*)(void *)>,void __cdecl(void *),void __cdecl(void *)>,void __cdecl(void *)>::Run(base::internal::BindStateBase * base)  行 1173 + 0x19 バイト	C++
 	base.dll!base::Callback<void __cdecl(void)>::Run()  行 396 + 0xe バイト	C++
 	base.dll!base::MessageLoop::RunTask(const base::PendingTask & pending_task)  行 476	C++
 	base.dll!base::MessageLoop::DeferOrRunPendingTask(const base::PendingTask & pending_task)  行 489	C++
 	base.dll!base::MessageLoop::DoWork()  行 669 + 0xc バイト	C++
 	base.dll!base::MessagePumpForUI::DoRunLoop()  行 241 + 0x1d バイト	C++
 	base.dll!base::MessagePumpWin::RunWithDispatcher(base::MessagePump::Delegate * delegate, base::MessagePumpDispatcher * dispatcher)  行 64 + 0xf バイト	C++
 	base.dll!base::MessagePumpWin::Run(base::MessagePump::Delegate * delegate)  行 48 + 0x1c バイト	C++
 	base.dll!base::MessageLoop::RunInternal()  行 431 + 0x29 バイト	C++
 	base.dll!base::MessageLoop::RunHandler()  行 405	C++
 	base.dll!base::RunLoop::Run()  行 46	C++
 	base.dll!base::MessageLoop::Run()  行 312	C++
 	base.dll!base::Thread::Run(base::MessageLoop * message_loop)  行 153	C++
 	base.dll!base::Thread::ThreadMain()  行 197 + 0x16 バイト	C++
 	base.dll!base::`anonymous namespace'::ThreadFunc(void * params)  行 57 + 0xe バイト	C++
 	kernel32.dll!751c33aa() 
void StyleResolver::loadPendingImages()
{
    if (m_state.pendingImageProperties().isEmpty())
        return;

    PendingImagePropertyMap::const_iterator::Keys end = m_state.pendingImageProperties().end().keys();
    for (PendingImagePropertyMap::const_iterator::Keys it = m_state.pendingImageProperties().begin().keys(); it != end; ++it) {
        CSSPropertyID currentProperty = *it;

        switch (currentProperty) {
        case CSSPropertyBackgroundImage: {
            for (FillLayer* backgroundLayer = m_state.style()->accessBackgroundLayers(); backgroundLayer; backgroundLayer = backgroundLayer->next()) {
                if (backgroundLayer->image() && backgroundLayer->image()->isPendingImage())
                    backgroundLayer->setImage(loadPendingImage(static_cast<StylePendingImage*>(backgroundLayer->image())));
            }
            break;
        }
PassRefPtr<StyleImage> StyleResolver::loadPendingImage(StylePendingImage* pendingImage)
{
    CachedResourceLoader* cachedResourceLoader = m_state.document()->cachedResourceLoader();

    if (pendingImage->cssImageValue()) {
        CSSImageValue* imageValue = pendingImage->cssImageValue();
        return imageValue->cachedImage(cachedResourceLoader);
    }