レンダリングによって form タグが消えた話


はじめに

初めまして。
昨年10月より株式会社ファンデリーに入社しました、毎川です。

入社してから早いもので3ヶ月が経ちました。
本記事では、エンジニアとして業務に携わる中で遭遇した
HTML のレンダリング挙動が原因の不具合についてまとめます。

同じような現象で悩む方の参考になれば幸いです。


目次

  • 経緯
  • 不可解な現象
  • 原因調査
  • 原因の特定
  • 解決方法
  • まとめ

経緯

とある案件で発生していた不具合を解消するため、
「値を nil にするボタン」を一覧画面に追加する対応を行っていました。

詳細な業務ロジックは割愛しますが、
td タグの中に以下のようなボタンを設置しました。

<tr>
  <td class="action-cell">
    <form action="/items/1/remove" method="post" class="remove-form">
      <button type="submit" class="btn-remove">解除</button>
    </form>
  </td>
</tr>

処理を実装し、動作確認を行ったところ——
2行目以降は正常に動作するのに、1行目だけ反応しない
という不可解な現象が発生しました。


不可解な現象

ボタンはクリックでき、見た目も他の行と同じです。
しかし、1行目のボタンだけは何度押してもリクエストが送信されません。

そこで Chrome の DevTools を確認しました。

すると、Elements タブ上では次のような DOM 構造になっていました。

<!-- 1行目 -->
<tr>
  <td class="action-cell">
    <!-- 本来ここにあるはずの form が存在しない -->
    <button type="submit" class="btn-remove">解除</button>
  </td>
</tr>

<!-- 2行目以降 -->
<tr>
  <td class="action-cell">
    <form action="/items/2/remove" method="post" class="remove-form">
      <button type="submit" class="btn-remove">解除</button>
    </form>
  </td>
</tr>

最初の1行目だけ、form タグが DOM から消えていたのです。
当然、form が存在しないためリクエストも送信されません。


原因調査

コード上はすべての行で同じ partial を使っており、
HTML の記述にも誤りはありません。

「なぜ 1 行目だけ?」という疑問が解消できず、
原因調査に時間を要しました。


原因の特定

調査を進める中で、そもそも DevTools で見ているものは何か
という点に立ち返りました。

DevTools の Elements タブに表示されているのは、

ブラウザが HTML を解析・修正した レンダリング後の DOM

です。

さらに調べると、HTML の仕様上、

  • <form> タグの中に <form> を入れ子にすることは 禁止
  • このような構造がある場合、ブラウザは HTML を自動的に修正する

というルールがあることが分かりました。

改めてコードを確認すると、
自分が編集していた partial は すでに form タグで囲まれた状態で render されていた ことが判明しました。

つまり、

  • 外側ですでに <form> が存在
  • その内側でさらに <form> を定義
  • HTML の仕様違反が発生
  • Chrome がレンダリング時に DOM を補正
  • 結果として 最初の form だけが DOM から消えた

という流れでした。


解決方法

対応として、button_to(= form を生成)をやめ、
link_to に変更しました。

<%= link_to "解除", item_path(@item),
      data: {
        turbo_method: :delete,
        turbo_confirm: "本当に解除しますか?"
      } %>

これにより a タグとして出力され、
form の入れ子構造が解消され、不具合は無事解決しました。


まとめ

今回の経験から得た学びは以下の2点です。

  • HTML では form タグの入れ子構造は許可されていない
  • ブラウザはレンダリング時に壊れた HTML を自動修正する

Chrome の賢さに助けられる場面も多い一方で、
その挙動を知らないと原因特定が難しくなるケースもあると感じました。

今回の出来事を通して、
レンダリングの「便利さ」と「怖さ」の両方を実感しました。

今後は HTML の仕様とブラウザの挙動を意識しながら、
より安全な実装を心がけていきたいと思います。


参考文献


現在デザイン・システム室では、新しいメンバーを募集しています。
少しでも興味を持たれた方は、ぜひご応募ください。
皆様からのご応募、心よりお待ちしております。