エラーページの「Copy as text」でHTMLエスケープ文字が混入するバグを修正

rails/rails

Railsの開発用エラーページにある「Copy as text」ボタンで、スタックトレースをコピーした際にHTMLエスケープ文字(&#39; など)が混入するバグが修正されました。テキストの格納要素を <script> から <textarea> に変更することで、ブラウザによる自動エスケープを回避しています。

背景

「Copy as text」ボタンは、開発中に発生した例外のスタックトレースをクリップボードにコピーするための機能です。しかし、テキストの格納に <script type="text/plain"> タグを使用していたことで、HTMLエンティティへの自動変換が発生していました。

PR本文に示された具体例では、コピーされたテキストに以下のような文字化けが含まれていました:

app/controllers/admin/assets_uploads_controller.rb:73:in &#39;Admin::AssetsUploadsController#resource_params&#39;

シングルクォート '&#39; に変換されており、このままコードエディタや検索ツールに貼り付けても正しく機能しません。

技術的な変更

テキストの格納要素を <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エンティティとしてエスケープされた文字列(&#39; など)をそのまま返します。一方、<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コンテンツ」ではなく「フォーム値」として扱うという意味的な変更であり、ブラウザの仕様を正しく活用した修正といえます。

記事メタデータ

Generated by:
Claude Sonnet 4.6 for DiffDaily
LLM Trace:
d6ef0d68

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

記事構成 ✓ PASS

Title, Context, Technical Detailの存在と明確さ

リード文(総論)→背景・技術的変更・設計判断(各論)→まとめ(結論)という3部構成が明確で、すべての必須要素を含んでいます。構成は模範的です。

カスタムMarkdown構文 ✓ PASS

シンタックスハイライト・GitHubリンク記法の正確性

ファイル名付きシンタックスハイライトやGitHubへのPRリンク記法が正しく使用されています。

対象読者への適合性 ✓ PASS

エンジニア向けの適切な技術レベルと表現

Railsの内部実装に関するトピックであり、専門知識を持つエンジニアという対象読者に適した技術レベルと表現で書かれています。

パラグラフ・ライティング ✓ PASS

トピックセンテンス・1段落1トピック・段落長

各セクションが総論→各論で構成され、各段落はトピックセンテンスで始まるなど、パラグラフ・ライティングの原則が守られており、非常に読みやすいです。

Diff内容との照合 ✓ PASS

コードブロックとDiff内容の一致

記事で引用されているコードは、提供されたDiffの内容とファイル名を正確に反映しています。テストコードの変更に関する言及も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「HTMLエンティティ」「textContent」「.value」などの技術用語が文脈に応じて正確に使用されています。

説明の技術的正確性 ✓ PASS

技術的主張の正確性と論理性

`<script>`と`<textarea>`、`.textContent`と`.value`の挙動の違いに関する説明は技術的に正確で、バグの原因と修正のロジックを明確に解説しています。

事実の突合 ✓ PASS

PR情報による主張の裏付け(ハルシネーション検出)

記事内のすべての主張は、PRのDescriptionやDiff内のコード変更から裏付けられています。「設計判断」セクションはPRにない記述ですが、コードの意図を解説する妥当な技術的洞察であり、ハルシネーションではありません。

数値・固有名詞の確認 ✓ PASS

PR番号・コミットID・バージョン等の正確性

PR番号(#57091)やファイルパスなどの固有名詞はすべて正確に記載されています。

タイトル・説明との一致 ✓ PASS

記事タイトル・説明とPR内容の一致

記事のタイトルはPRのタイトル「Fix bug with escaped characters when "Copy as text" is clicked」の内容を忠実に、かつ分かりやすく表現しています。

外部知識の正確性 ✓ PASS

PRに記載のない外部知識(LTS、サポート状況など)の不使用

記事はPR情報とDiffコードの範囲内で記述されており、バージョン情報やリリース予定など、PRに記載のない外部知識の捏造は見られません。

時間表現の正確性 ✓ PASS

時間表現がPR情報と一致しているか

「〜が修正されました」といった過去形の表現が適切に使用されており、マージ済みの変更を解説する記事として時間表現は正確です。