エラーページの「Copy as text」でHTMLエスケープ文字が混入するバグを修正
Railsの開発用エラーページにある「Copy as text」ボタンで、スタックトレースをコピーした際にHTMLエスケープ文字(' など)が混入するバグが修正されました。テキストの格納要素を <script> から <textarea> に変更することで、ブラウザによる自動エスケープを回避しています。
背景
「Copy as text」ボタンは、開発中に発生した例外のスタックトレースをクリップボードにコピーするための機能です。しかし、テキストの格納に <script type="text/plain"> タグを使用していたことで、HTMLエンティティへの自動変換が発生していました。
PR本文に示された具体例では、コピーされたテキストに以下のような文字化けが含まれていました:
app/controllers/admin/assets_uploads_controller.rb:73:in 'Admin::AssetsUploadsController#resource_params'
シングルクォート ' が ' に変換されており、このままコードエディタや検索ツールに貼り付けても正しく機能しません。
技術的な変更
テキストの格納要素を <script type="text/plain"> から <textarea hidden> に変更し、JavaScriptでの取得プロパティを .textContent から .value に切り替えました。
変更前:
<script type="text/plain" id="exception-message-for-copy"><%= @exception_message_for_copy %></script>
変更後:
<textarea hidden id="exception-message-for-copy"><%= @exception_message_for_copy %></textarea>
JavaScript側では、テキストの読み出し方法も合わせて変更されています。
変更前:
const text = document.getElementById("exception-message-for-copy").textContent;
変更後:
const text = document.getElementById("exception-message-for-copy").value;
textContent はDOM上のテキストノードを取得するため、HTMLエンティティとしてエスケープされた文字列(' など)をそのまま返します。一方、<textarea> の .value プロパティはフォーム入力値として管理されるため、HTMLエンティティが自動的にデコードされた文字列を返します。この違いが修正の核心です。
テストも同様に更新されており、<script type="text/plain"> を対象とした正規表現マッチが <textarea hidden> を対象としたものに置き換えられています。また、XHRリクエスト時にコピーボタンが表示されないことを確認するテストに、<textarea> タグも存在しないことの検証が追加されました。
設計判断
<textarea hidden> という要素の選択 は、HTMLの仕様に則った適切な判断です。<textarea> はフォームコントロールとして、その内容をプレーンテキストとして保持します。hidden 属性を付与することでUIには表示されず、スクリーンリーダーなどの支援技術への影響も最小限に抑えられています。
<script type="text/plain"> はブラウザがJavaScriptとして実行しないことを保証するものの、DOM上はHTMLコンテキストで解釈されるため、ERBの <%= %> で挿入されたコンテンツはHTMLエンコードされた状態で格納されます。<textarea> では同じERBの出力がフォームの値として格納されるため、.value 経由で取得した際には元の文字列が復元されます。
まとめ
本PRは、要素の種類とJavaScriptプロパティの選択が持つHTMLエスケープ挙動の違いに起因するバグを、最小限のコード変更で解消しています。<script> から <textarea> への変更は単なる代替ではなく、テキストを「HTMLコンテンツ」ではなく「フォーム値」として扱うという意味的な変更であり、ブラウザの仕様を正しく活用した修正といえます。