テックタッチアドベントカレンダー19日目の記事です。
CSE(カスタマーサクセスエンジニア)の @teru (現在育休1ヶ月目)です。毎年アドベントカレンダーの記事を書くたびに役割が変わっていますが、テックタッチに入社してからはフロントエンドエンジニア→SET→CSE(←今ここ)で元気に働いています。最近の趣味は先月に生まれた0ヶ月の娘の泣き方を真似しながら抱っこしてあやすことです。えっえっえっ、と小刻みに泣くのでなんとなくクセになります。
この記事では、ブラウザのページやブラウザ拡張/コードスニペットで動作する JavaScript を調査する際の Chrome DevTools の活用方法について紹介をします。
- CSE の業務と開発者ツール
- ページとブラウザ拡張/コードスニペットのスクリプトを調査する
- 開発者ツールの活用例
- 終わりに
CSE の業務と開発者ツール
CSE チームは、所属はプロダクト開発サイドでありつつも、ビジネスサイドの CSM チームと密接に連携しながら、顧客の技術的な課題の解決を行うことを主な業務としています。「顧客の環境でプロダクトが想定通りに利用できない」という問い合わせを受けた際に、プロダクトの利用状況/ブラウザの仕様/OS・デバイス・ネットワーク環境などを調査し、現在の状態や解決策を顧客へ伝えることで問題を解消できるよう支援を行っています。
プロダクトが利用される環境は顧客によって様々であり、どのような状況でプロダクトが動作しているかを正確に調査する必要があります。この調査において、ブラウザの開発者ツールを利用することで様々な情報を集めることができます。
ページとブラウザ拡張/コードスニペットのスクリプトを調査する
テックタッチではブラウザ拡張やコードスニペットで実行する JavaScript によりプロダクトの主な機能を提供しています。このため、プロダクトに関して依頼される技術調査の案件について、次のような傾向があります。
- 問い合わせの事象(例: 意図した動作をしない、動作が遅いなど)が発生するタイミングはページ読み込み完了後、拡張やスニペットが読み込まれてから以降であることが多い。
- 逆を言えば、接続要求〜DOM の読み込み完了(DOMContentLoaded)までのタイムラインが調査対象となることは少ない。
- 特定のイベントやネイティブ関数においてページのスクリプトの影響を受けることがある。
In-app performance に重点を置く
上述の傾向をクリティカルレンダリングパスに照らしてみると、ページから読み込んだ HTML/CSS からパースされた DOM/CSSOM、ページで実行される JavaScript、およびブラウザ拡張/コードスニペットの読み込み完了後に該当する In-app performance (アプリケーション内パフォーマンス)に重点を置くと調査を効率良く行うことができることに気が付きます。
具体的には以下のような点を重点的に確認します。
パースされた DOM/CSSOM:
- 標準的な HTML か、または特殊なタグを利用しているか(iframe, canvas など)
- 要素の階層、位置関係がどうなっているか
- 要素数が多すぎないか
- CSS による表示状態(非表示である、サイズが 0、要素の重なりなど)
- CSS による操作への影響(スクロール可否、ホバー時の挙動)
- [optional] どのような言語/フレームワークで書かれているか(設計思想を元に挙動を推測できることがある)
ページで実行される JavaScript:
- どのようなライブラリを利用しているか(フロントエンド以外の言語・フレームワークを推測する)
- 実行順や async/defer
- ブラウザ拡張/コードスニペットより先、または後に実行されているか
In-app performance のプロダクトの挙動:
- ブラウザ拡張/コードスニペットが読み込まれて実行された時
- タイムアウト、インターバルなど任意の時間経過で実行された時
- クリック、スクロールなど任意のイベントをトリガーして実行された時
- DOM の構造変化など任意の状態遷移をトリガーして実行された時
開発者ツールで見るべき点を絞る
ブラウザ拡張/コードスニペットが影響を及ぼしている(または受けている)In-app performance 内の事象を発見するために、ブラウザの開発者ツールを利用します。基本は Chrome DevTools を利用しますが、再現環境の都合で IE 開発者ツールを利用する場合もあります。ツール内のタブ別に以下のような利用頻度で調査に臨んでいます。
タブ | 利用頻度 | 理由 |
---|---|---|
Network | △ | API に到達できているか、いないか。またはリクエスト内容が正しいか。を見れば良いのである程度分かりやすい |
Elements | ◎ | 第三者サービスの DOM、拡張が生成した DOM、イベントリスナー、要素の状態(階層、可視状態、スタイル、Shadow DOM etc...)など見るべき箇所は多い |
Console | ◎ | 第三者サービスや拡張の info ログ、ワーニング、エラーログなどが出力されていることが多い |
Performance | ○ | 第三者サービスも含めて JavaScript の Call Tree を調べるために活用する |
Performance monitor | △ | パフォーマンス調査時に CPU 使用率、DOM 要素数、JS メモリヒープなどを確認する |
Sources | △ | ログで確認した関数名を検索したり、Performance の Call Tree からジャンプする |
開発者ツールの活用例
開発者ツールのタブ別に活用例を紹介していきます。ツールのタブの並び順ではなく、大まかにですが私がよく調査を行う順で紹介します。
Network タブ
スクリプトファイルや API レスポンスの内容を確認する
API へ期待するリクエスト/レスポンスが送受信できているか。コードスニペットを利用している場合、スクリプトがダウンロードできているか、を Filter やネットワークの種類のトグルで探し、詳細を確認します。リクエストメソッド(GET, POST など)、ステータスコード、ヘッダー、リクエストパラメータ、レスポンス(プレビュー)のデータの内容などから期待するデータの送受信が行われているかを確認します。
Elements タブ(Console ペイン)
コンソールの JavaScript コンテキストを変更する
ブラウザ拡張や iframe 内部の変数やオブジェクトにアクセスしたり、console.log で出力した内容を確認したいことがあります(拡張の chrome API、iframe の document オブジェクトなど)。Console ペインのコンテキストのメニューから選択することで、対象のコンテキストを切り替えることができます。
拡張の background ページを開発者ツールで検証する
Chrome 拡張がバックグラウンドスクリプトを利用している場合は、拡張のページで対象の拡張の バックグラウンド ページ をクリックすることでバックグラウンドの開発者ツールを開くことができます。
Preserve log でログを保持する
Preserve log をオンにすることで、ページを遷移してもログの内容が保持されるようになります。Console タブ(ペイン)および Network タブで利用できます。
$x で XPath 式を評価する
Console ペインで $x('//div')
の記法で XPath 式を評価できます。任意の要素から評価したい場合は $x('//div', document)
と第二引数に要素を指定することもできます。
ページ内の要素に設定されているイベントリスナーを調べる
Elements タブ右ペインの Event Listeners からページ内の要素に設定されているイベントリスナーの一覧を確認することができます。要素を指定していない場合は document、指定している場合はその要素のリスナー一覧が表示されます。Ancestors にチェックをつけることで親要素のリスナーを合わせて列挙することができます。
設定されているリスナーの横にある Remove ボタンを押すことで、リスナーを削除することもできます。ページの挙動がどのリスナーの動作によって実装されているか分からない場合に、アタリをつけてリスナーを削除してみると、実装箇所を発見できることがあります。(ページの挙動を変えてしまうので、通常の動作と大きく乖離してしまわないように注意深く行う必要があります)
リスナーの動作の詳細を確認したい場合には、後述の Event Listener Breakpoints を利用することで任意のイベントをトラップしてリスナーの処理をステップ実行することができます。
他にイベントリスナーの一覧を調べる方法として、Console で getEventListeners()
を実行する方法もあります。
debugger を活用して要素や関数を調べる
Console で debugger
と入力して実行すると、強制的に JavaScript のコードを停止することができます。これを活用して通常では掴みにくい要素を検証したり関数をステップ実行したりすることができるようになります。
mouseover 中のみ表示される要素を調べる
Console で setTimeout(() => {debugger}, 3000)
を実行することで、3秒後にコードを停止できます。これを利用して、例えばマウスカーソルが載っている間のみ表示される要素を表示中にコード停止させて要素を選択できるようにすることができます。
同様に、 Source タブを表示中に F8 を押下することによってもコードを停止させることが可能です。こちらの場合は、次のスクリプトが実行されるまでは停止状態にならないので、必ずしも想定しているコードの実行をトラップできるわけではない点に注意してください。
クリックなどの操作をトリガーにしてコードを停止させる
任意の要素やその親(document など)の複数のイベントが混然としていて、上述のタイムアウトや手動でのコード停止が難しい場合、Console で element.addEventListener('click', () => {debugger})
を実行して任意の操作の後にコードを停止させることができます。click, focus, keyup などいくつかのイベントで登録されているリスナーの処理を順次確認できるので、コードの特定を効果的に行うことができます。
Performance タブ
Performance タブは名前の通りパフォーマンスの計測や調査に向いていますが、記録したタイムラインからネットワークの通信やスクリプトの実行シーケンスを確認することにも役立ちます。
ネットワークの通信とスクリプトの実行シーケンスを見る
Performance タブを選択し、左上の Record ( Ctrl + E (Mac は ⌘ + E) )をクリックすると計測を開始します。その後にストップを押すか再度 Ctrl + E を押すと計測を終了し、結果がペイン内に表示されます。
ペイン上部の Overview セクションに FPS、CPU、NET、スクリーンショットが表示されます。それぞれ負荷やアクセスの状態が時系列でグラフ化されており、マウスカーソルを合わせるとその時点のスクリーンショットを確認できます。デフォルトでは全期間が選択状態になっていますが、カーソルをドラッグすることで任意の期間のみを表示対象に絞ることができます。経過時間や負荷の状態、スクリーンショットを目安に調査したい期間を探します。
その下の Network セクション、Main セクションを開くと、Overview セクションで指定した期間のアクティビティを確認することができます。どのようなネットワークリクエスト/レスポンスがあるか、スクリプトの実行や Rendering, Painting イベントがあるかを時系列と実行スタックで確認できます。
特定の関数、実行時間が長い関数を探す
Main セクションの任意のイベントをクリックすると、Summary タブに処理の統計が表示されます。イベントをクリックしない場合は、Overview セクションで指定した期間内の統計が表示されます。
同列にある Call Tree タブをクリックするとイベント内のアクティビティがツリー表示されます。ツリーは任意のイベント及びそこからコールされる関数のスタック順に階層となっているので、ツリーを開いていくことで実行されている関数を辿ることができます。関数の右には定義元のスクリプト行とリンクが表示されているので、これをクリックして Sources タブで対象の関数にジャンプできます。
同ペインでは Self Time、Total Time などでソートすることもできるので、実行に時間がかかっているイベントや関数を時間の長い順に探していくことができます。
Performance monitor ペイン
パフォーマンスモニターでページの負荷状況を調べる
開発者ツールの右上、メニュー > More tools > Performance monitor からパフォーマンスモニターペインを表示できます。
ページの CPU 使用率、JS ヒープサイズ、DOM 要素数などを時系列のグラフで表示できます。ページ全体や特定の処理の動作が重い場合にデータの推移を見て原因を探すことができます。
Sources タブ
特定のイベントの動作を Event Listener Breakpoints で調べる
Sources タブ右ペインの Event Listener Breakpoints で任意のイベントにチェックを入れることで、そのイベントが発生した時にコードを停止させることができます。これを使って、例えば「ある input 要素で focus イベントが発生した時に実行されるスクリプト処理」をステップ実行してコードを特定することができます。
調査したいイベントを探す手掛かりとして、前述の Elements タブ内 Event Listeners ペインで調査したいイベントリスナーを予め調べておくこともできます。
Call Stack の Async を有効にして非同期なコールスタックを全て表示する
コードの停止中、Call Stack を確認すると実行中の関数に加えて非同期に実行された関数のコールスタックも合わせて表示されます( (async)
が表記されます)。Promise や setTimeout などで実行されたものが対象となりますが、setInterval は対象外です。(Chrome ver.96 で確認)
非同期のコールスタックが表示されない場合、機能がオフになっている可能性があります。開発者ツールで Ctrl + Shift + P (Mac は ⌘ + Shift + P)
→ Capture async stack traces
を入力して選択し、機能をオンにしてください。
終わりに
ブラウザ拡張の検証はベースとなるページとそこにプラスオンで動作する拡張との相互の影響を見ながら実施する必要があり、通常の Web アプリケーション開発と重なる部分/異なる部分を意識することが求められます。通常の Web 開発とはまた異なった開発体験、面白さ(と難しさ)が感じられます。
DevTools についても一定の利用パターンは確立できつつも、まだ活用できていない機能やブラウザのアップデートにより追加・変更される機能、業務として求められる前提や要件が日々変化するので、その時点でのベストプラクティスを日々追従できるよう心掛けたいです。