プロモートフレーム訪問時のみ previousFrameElement を保持する最適化
Turbo のフレームコントローラは、すべてのフレーム描画でフレーム全体のクローンを作成し続け、不要なメモリ保持とオブザーバー生成を引き起こしていました。この PR は、data-turbo-action が設定されたプロモートフレーム訪問 のみでクローンを取得するよう条件分岐を追加し、メモリ使用量の肥大化を防ぎます。
背景
FrameController#willRenderFrame は従来、currentElement.cloneNode(true) によってフレームの現在サブツリー全体を深くコピーし、this.previousFrameElement に格納していました。コピーは visitCachedSnapshot がキャッシュ復元時にだけ消費し、他の描画パス(src の変更や refresh="morph"、ブロードキャスト駆動のリロード)では決して使用されずに残っていました。その結果、フレームが頻繁にリロードされると、ピン留めされたクローン と 毎回の cloneNode(true) による子フレームの FrameController、AppearanceObserver、IntersectionObserver の生成 が累積し、ヒープスナップショットで多数のオブザーバーが残留していることが確認されました。
技術的な変更
willRenderFrame に this.action が存在する場合のみ クローンを作成する guard を追加しました。this.action は proposeVisitIfNavigatedWithAction が設定するため、プロモートフレーム訪問時にのみ真となります。コード変更は次の通りです。
@@ -299,7 +299,15 @@ export class FrameController {
// Frame renderer delegate
willRenderFrame(currentElement, _newElement) {
- this.previousFrameElement = currentElement.cloneNode(true)
+ // Only clone the frame for promoted frame visits, where visitCachedSnapshot
+ // consumes the clone to restore the frame's contents into the cached page
+ // snapshot (and deletes it). For plain frame renders — src changes without
+ // a data-turbo-action, refresh="morph" reloads, broadcast-driven reloads —
+ // nothing ever consumes or clears the clone, so it would pin a complete
+ // copy of the frame's previous subtree on the controller indefinitely.
+ if (this.action) {
+ this.previousFrameElement = currentElement.cloneNode(true)
+ }
}
visitCachedSnapshot = ({ element }) => {
この guard により、通常のフレーム描画 では previousFrameElement が未設定のまま残り、不要なメモリ保持が回避されます。テストスイートへの影響はなく、autofocus_tests.js に軽微な待機処理が追加されただけで、既存の機能テストはすべて成功しています。
設計判断
今回の修正は 最小限の侵入性 を保ちつつメモリリークを解消することを狙い、this.action という既存フラグを再利用しています。代替案としては visitCachedSnapshot の呼び出し側でクローン生成を制御するか、previousFrameElement の保持自体を廃止する設計が考えられましたが、後者は多数の既存コードへの広範な変更が必要となります。this.action 判定のみを追加することで、プロモート訪問の動作は変わらず、それ以外のケースでの不要なオブジェクト生成が排除され、リグレッションリスクを低減しています。
まとめ
この PR は、Turbo フレームの 不要なクローン保持 をプロモートフレーム訪問時に限定することで、メモリ使用量とオブザーバー生成コストを大幅に削減しました。機能的な挙動は変わらず、既存テストをすべてパスする安全な最小変更です。