インフライト添付アップロードの中止処理を追加

basecamp/lexxy

アップロード中の添付ファイルが削除された際に、バックグラウンドで走り続けていた XMLHttpRequest を即座に中止する機構が Lexxy に導入されました。これにより不要なネットワーク通信とリソース消費が防がれ、Docs & Files と同等のユーザー体験が実現します。

背景

Lexxy では、添付ファイルのアップロードを Active Storage の直接アップロード機構で行っていますが、アップロードノードが破棄された場合でもリクエストは残存し、完了かタイムアウトまで待機していました。この挙動は、ユーザーが添付を削除した瞬間にネットワークアクセスが続く点で Docs & Files と動作が不一致でした。

この問題は、アップロードノードが削除されたことを検知できても、実際の XMLHttpRequest オブジェクトへの参照が失われていたために中止できなかったことが原因です。そこで、アップロードリクエストをキーで管理し、破棄時に明示的に abort できる仕組みを追加することが決定されました。

技術的な変更

UploadRequests クラスの導入

新規ファイル src/editor/attachments/upload_requests.jsUploadRequests クラスが追加されました。内部では Map を用いて key → XMLHttpRequest のペアを保持し、track, forget, abort, clear の4メソッドでライフサイクルを管理します。

export class UploadRequests {
  #requestsByKey = new Map()

  track(key, request) { this.#requestsByKey.set(key, request) }
  forget(key) { this.#requestsByKey.delete(key) }
  abort(key) {
    const request = this.#requestsByKey.get(key)
    if (request) {
      this.#requestsByKey.delete(key)
      request.abort()
    }
  }
  clear() { this.#requestsByKey.clear() }
}

Editor 要素への保持

src/elements/editor.js にプライベートフィールド #uploadRequests が追加され、コンストラクタで new UploadRequests() によりインスタンス化されます。uploadRequests ゲッターで外部から取得できるようにし、リセット時に clear() が呼び出されるよう #reset メソッドに追記しました。

  #uploadRequests

  get uploadRequests() { return this.#uploadRequests }

  connectedCallback() {
    // 省略
    this.#uploadRequests = new UploadRequests()
    // 省略
  }

  #reset() {
    // 省略
    this.#uploadRequests?.clear()
    // 省略
  }

アップロードノードからのトラッキング

src/nodes/action_text_attachment_upload_node.js#rememberUploadRequest#forgetUploadRequest のプライベートメソッドが追加され、directUploadWillStoreFileWithXHR デリゲートで取得した requestthis.#editorElement.uploadRequests.track(this.getKey(), request) に渡して追跡します。アップロード完了やエラー時に forget が呼び出され、メモリリークを防止します。

  #rememberUploadRequest(request) {
    this.#editorElement.uploadRequests.track(this.getKey(), request)
  }

  #forgetUploadRequest() {
    this.#editorElement.uploadRequests.forget(this.getKey())
  }

添付ノード破棄時の abort 呼び出し

src/extensions/attachments_extension.js#handleUploadMutations において、mutation === "destroyed" のケースで this.editorElement.uploadRequests.abort(key) が実行されます。これにより、削除された添付に対応するリクエストが即座に中止されます。

  for (const [ key, mutation ] of mutations) {
    if (mutation === "created") {
      this.#uploadsCount++
    } else if (mutation === "destroyed") {
      this.#uploadsCount--
      this.editorElement.uploadRequests.abort(key)
    }
  }

テストの追加

ユニットテスト として test/javascript/unit/editor/attachments/upload_requests.test.js が追加され、track, abort, forget の正しい振る舞いが検証されています。Playwright 回帰テスト test/browser/tests/attachments/upload_abort_on_delete.test.js では、Active Storage のディスクアップロードを保持した状態で添付を削除し、リクエストが aborted として報告されることを確認しています。

設計判断

キー駆動のリクエスト管理

破棄対象ノードは DOM ミューテーションから得られる唯一のハンドルである「キー」のみで管理できるため、UploadRequests はキーを基点にリクエストを追跡します。この設計は追加のデータ構造を最小限に抑え、エディタインスタンスごとに独立した管理領域を提供します。

Editor 要素への責務委譲

UploadRequests のインスタンスは LexicalEditorElement に保持され、エディタのライフサイクル(接続、リセット)と同期して初期化・クリアされます。これにより、エディタが破棄された際に残存リクエストが残らないことが保証されます。

アップロードノード側の最小侵入

ActionTextAttachmentUploadNode は既存のアップロードロジックに対し、リクエスト取得後に track、完了後に forget を呼び出すだけの追加コードで済みます。これにより、既存のアップロードフローを改変せずに中止機構を組み込むことができ、後方互換性が保たれます。

まとめ

本 PR は、Lexxy の添付アップロードに対し「削除時にリクエストを即時中止」する機構を導入しました。キー駆動の UploadRequests によりリクエストを安全に管理し、エディタとノードのライフサイクルに合わせてトラッキングとクリーンアップを行います。結果として不要なネットワーク通信が防がれ、Docs & Files と同様のユーザー体験が実現されました。

記事メタデータ

Generated by:
gpt-oss-120b for DiffDaily
LLM Trace:
3535447b

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

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
gpt-oss-120b for DiffDaily

Review Criteria:

記事構成 ✓ PASS

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

リード文、背景、技術的変更、まとめが明確に配置されており、全体が「総論→各論→結論」の流れになっている。設計判断は任意要素として適切に配置。

カスタムMarkdown構文 ⚠ WARNING

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

コードブロックのファイル名付きシンタックスハイライトは正しい形式 (```javascript:src/... )。しかし PR リンクが [PR #1134](URL) となっており、要件の [#1134](URL) 形式になっていない。リンク記法の形式が不完全だが、内容の理解に支障はない。

対象読者への適合性 ✓ PASS

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

専門的な用語や実装詳細に焦点が当たっており、エンジニア向けに適切なレベルで書かれている。初心者向けの過度な説明はない。

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

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

各セクションは総論パラグラフ→各論パラグラフ→結論パラグラフの構成が守られ、段落はトピックセンテンスで始まり1段落1トピックで記述されている。段落長も適切で、空行で区切られている。

Diff内容との照合 ✓ PASS

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

記事内のコードブロックは提供されたDiffと完全に一致しており、ファイル名・変更点ともに正確に反映されている。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

XMLHttpRequest、UploadRequests、track/forget/abort などの用語は PR で使用されたものと一致し、誤用は見られない。

説明の技術的正確性 ✓ PASS

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

技術的な説明は PR の "What" と "How" に沿っており、根拠が明確。誤った因果関係や不正確な主張はない。

事実の突合 ✓ PASS

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

記事の全主張は PR の説明と Diff に裏付けられている。不要な推測や外部情報は含まれていない。

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

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

PR 番号 #1134 が正しく記載されているほか、数値や固有名詞の誤りはない。

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

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

記事タイトルは PR タイトル「Abort in‑flight uploads when their attachment is removed」を日本語で的確に要約しており、一致している。

外部知識の正確性 ✓ PASS

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

バージョンサポートやリリース日程など、PR に記載されていない外部知識は記事に含まれていない。

時間表現の正確性 ✓ PASS

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

時間表現は "即座に" といった表現のみで、PR の記述と矛盾しない。