script要素のdefer属性周辺のBlink(Chromium)の実装の調査

script要素にdefer属性を設定すると、ドキュメントのパース完了後にスクリプトが実行される。スクリプトの実行によってパーサがブロックされないので最初の表示が早くなるという利点がある(いまだったら、パース完了後ではなく、その都度非同期で実行するasync属性を使うべきだが)。async, defer属性周辺の実装が気になったので、defer属性のパースとスクリプトの実行までを辿ってみたときのメモ。

ドキュメントをパースする過程でscript要素がみつかると、HTMLScriptRunner::runScript( https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/html/parser/HTMLScriptRunner.cpp&l=329&cl=GROK&rcl=1442849530 )を呼ぶ。

この中で呼んでいるScriptLoader::prepareScriptメソッドで、設定されている属性やパーサの状態に応じて諸々の準備をおこなっている。 defer属性が設定されている場合は、 https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/dom/ScriptLoader.cpp&l=242&cl=GROK&rcl=1442849530 の条件式

client->hasSourceAttribute() && client->deferAttributeValue() && m_parserInserted && !client->asyncAttributeValue()

がtrueになり、m_willExecuteWhenDocumentFinishedParsingフィールドにtrueを代入する。
補足だが、!client->asyncAttributeValue()という節からasync, defer両方の属性が設定された場合はasyncが有効になることがコードで示されている。

ScriptLoader::prepareScriptメソッドに戻る。 前に説明したScriptLoader::prepareScriptメソッドの実行により https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/html/parser/HTMLScriptRunner.cpp&l=357&cl=GROK&rcl=1442849530scriptLoader->willExecuteWhenDocumentFinishedParsing() は真になり、HTMLScriptRunner::requestDeferredScriptメソッドを呼び出す。

このメソッドでは、両端キュー m_scriptsToExecuteAfterParsingに、ロード・実行されるであろうスクリプトを扱うオブジェクトを追加する。

パースの流れはここまで。

次はパーサの実行終了からスクリプトの実行までを追う。 パーサの実行が終了するタイミングで、 HTMLDocumentParser::prepareToStopParsingメソッド https://code.google.com/p/chromium/codesearch#chromium/src/third_party/WebKit/Source/core/html/parser/HTMLDocumentParser.cpp&rcl=1442849530&l=249 が呼ばれる。
その中から、 HTMLDocumentParser::attemptToRunDeferredScriptsAndEndメソッド、 HTMLScriptRunner::executeScriptsWaitingForParsingメソッドと順にメソッドが呼ばれていき、最終的にHTMLScriptRunner::executePendingScriptAndDispatchEventメソッドスクリプトが実行される。