はじめに
インラインスクリプト(htmlのscriptタグに書かれたjavascript)をデバッグするには、サーバに配置したhtmlファイルなどを書き換える必要があります。
例えば、このようにして。
つまり、手の届くところにhtmlファイルがないとデバッグできません。
まさにこのデバッグできない状況に追い込まれました。
そうしたときにどのようにしてインラインスクリプトのデバッグを実現するのか?
これについて、私が実際に使った方法を書き残しておきます。
本文の手順1と2については状況によっては不要もしくは代替手順が必要です。
例えば、htmlのデバッグしたいscriptタグ内にベタ書きでスクリプトが記述されている場合は不要です(その場合の代替手順については本文の「手順」節の次節にて提案します)。
しかし、今回私が遭遇したケースのように、jsがどこかのタイミングで対象scriptタグ内に外部jsファイルを書き込んでいる場合は、手順1と2との両方を行うことになるかと思います。
本文
手順
- デバッグ対象のjs本体ファイルをGETしている現場を押さえる
- fiddlerカスタムスクリプトを用いてjsファイルを書き換える
- ブレークポイントを仕掛けてデバッグする
1. デバッグ対象のjs本体ファイルをGETしている現場を押さえる
主にこの段階では、Firefoxの開発ツールを使います(他ブラウザでは細かい手順が違っても、やることは同じかと思います)。
まず、デバッグ対象のjsファイルAを入手する必要があります。
どうすればよいでしょうか。
例えば、ページ内スクリプトが外部ファイルであるAをダウンロードして、それをscriptタグとしてhtmlに挿入している場合を考えてみます。
この場合、Aをロードしたタイミングまでに、Aをどこかからダウンロードしているはずです。
そのダウンロードを観測するために、ブラウザ開発ツールのネットワークタブを観ることが有効と思います。
ただし、単純にネットワークタブを見ても、多くのjsファイルをダウンロードしているために、どの.jsファイルがAなのかわからないことがあります。
どうすればよいでしょうか。
色々方法はあるかと思いますが、一つの方法として下記のようなものがあります。
Aをロードしてscriptタグを挿入する瞬間は、htmlタグ内のDOM要素が変更されるはずです。
なので、htmlタグ内の要素が変更されたとき発生するイベントに対して、ブレークポイントを仕掛けます。
具体的に、どう仕掛けるかというと、開発者ツールのインスペクタタブから、デバッグ対象scriptタグの親タグを右クリックして、「ブレークポイントを設定」>「サブツリーの変更」
を選択することで可能です。
これでDOM要素の変更時にデバッガが機能し、ページ内スクリプトの処理を一時停止することができます。
この一時停止したときのメソッドのコールスタックをデバッガで確認すると、このメソッドの呼び出し元をたどることができます。
例えば、呼び出し元をたどって下記のようなコードがあったとしたら、それが怪しいのでそこにブレークポイントを仕掛けてみたりします。
このように試行錯誤して、scriptタグの挿入直前にブレークポイントを仕掛けます。
var sc = dc.createElement("script");
sc.innerHTML = ev.responseText;
dc.documentElement.appendChild(sc);
上記の箇所にブレークポイントを仕掛けて、ブラウザを再読み込みして止まったところで、実際に変数scの値をデバッガで覗いてみます。
その内容が目的のAと同じであれば、evにjsファイルのリクエストのレスポンスが入っているのだろうと察しがつきます。
あとは、evの中身が実際に格納されるところにブレークポイントを仕掛けます。
ブラウザを再読み込みすると、ブレークポイントで処理が止まります。
このときのネットワークタブを開けば、Aをダウンロードしているリクエストとレスポンスを見つけるのは容易かと思います。
そのレスポンスの中身をコピペすれば、お目当てのデバッグ対象Aが手に入ったことになります。
加えて、次の工程で必要となるので、このリクエスト先のURLについてもメモしておきます。
2. fiddlerカスタムスクリプトを用いてjsファイルを書き換える
次に、Fiddlerのカスタムスクリプトを用いて、先ほどメモしたURLからのレスポンスであるA.jsを書き換えます。
カスタムスクリプトの使い方は、私がここに簡単に記述しておきました。
上記リンク先のOnBeforeResponse内に処理を記述するやり方で、Aを書き換えます。
例えば、下記のようなスクリプトを書きます。
if ( oSession.host == "example.com" && oSession.uriContains("A.js")){ // メモしたURLの、host名とjsファイル名でリクエストを特定する
// リクエストのレスポンスbodyを文字列に変換する
oSession.utilDecodeResponse();
var oBody = System.Text.Encoding.UTF8.GetString(oSession.responseBodyBytes);
// レスポンスbodyにブラウザ開発ツールで使うおまじないを追記する
oSession.utilSetResponseBody(oBody + " //# sourceURL=modA.js");
}
スクリプトを保存後、再度ブラウザで対象ページを再読み込みしてFiddlerを通したリクエストを送ります。
そうするとFiddlerがBodyを書き換えたレスポンスをブラウザに返し、ブラウザがそれを読みこみます。
ここで、上記サンプルに書いたおまじないが効くことになります。
3. ブレークポイントを仕掛けてデバッグする
おまじないによって、デバッガータブ内のsource list pane
に、modA.jsが配置されます。
(この挙動の詳細は例えば公式ドキュメントを参考にどうぞ。)
modA.jsを手作業で探すの大変かと思うので、デバッガータブにてCtrl + P
を押してファイル名で検索することができます。
発見出来たら、modA.jsのデバッグしたい箇所に自由にブレークポイントを仕掛けることができます。
以上の手順で、手元にないインラインスクリプトをデバッグすることが実現できました。
代替手順
上記1,2の工程でjsファイルを入手して書き換えましたが、これの代わりにhtmlファイルを入手して書き換えます。
たとえば、htmlファイルは、開発者ツールのネットワークタブ使って簡単に入手できると思います。
あとは、レスポンスBodyの書き換えですが、これまた色々なやり方があると思います。
たとえば、先のカスタムスクリプト内で、</script>
タグをforループで検索して、それを //# sourceURL=modA.js </script>
と置換する処理を書くのも手かと思います。
ちなみに、開発者ツールのインスペクタタブで直接htmlをいじってscriptタグ内を書き換えておまじないを書いても機能しません。なぜかは知りません。
↑ もっと簡単な代替手順に気づきました。
下記のようなスクリプトをコンソールで走らせると、デバッガータブ内のsource list pane
に、modA.jsが配置されます。
let newelm = document.createElement('script');
newelm.innerHTML = document.querySelector("デバッグ対象scriptタグを指すCSSセレクタ").innerHTML + " //# sourceURL=modA.js"
document.querySelector("デバッグ対象scriptタグを指すCSSセレクタ").replaceWith(newelm);
ちなみにCSSセレクタは開発ツールのインスペクタタブで、デバッグ対象scriptタグを右クリックしてコピー > CSSセレクタ
を選べばコピーできます。
この代替手順をGreaseMonkeyなどで自動化すればよりデバッグがはかどりそうです。
あとがき
こういった煩雑なことを実現する手段は後になって忘れがちなので書き起こしておきました。
今後またこの手段を使うことがあれば、次はサンプルをcodesandbox.ioなどで書いてみようかと思います。
一応最後に、もしかしたら居るかもしれない読者の方へ、この方法を使う上での注意点を伝えさせていただきます。
この方法は、デバッグ先コードの管理者の承認がある状態で使うことを想定し、そのような状況下で私自身使っています。
承認が無い状態でこうしたスクリプトの書き換えをするのは、もしかしたら好ましくないことではないかと思います。
この方法はそもそもあまり詳しく検証していないので、想定外の動作の原因ともなりかねません。
よくよくリスクを検討した上で参考にしていただければ幸いです。