テーブルをまたぐ選択範囲での削除操作クラッシュを修正
テーブルの anchor とテーブル外の focus にまたがる選択範囲に対して Backspace を押すと、Lexical error 148 でエディタがクラッシュするバグが修正されました。
背景
Lexical error 148 は「Expected to find a parent TableCellNode」という文脈で発生するエラーで、テーブルセル内を起点とした選択範囲の処理においてテーブルセルの祖先を前提としたコードパスに入ることが原因です。具体的には、テーブルが先頭要素にある状態でCtrl+A(全選択)を行い Backspace を押すと、選択の anchor がテーブル内セルに、focus がテーブル外(後続の段落など)に置かれた非対称な範囲が生成されます。この状態で isTableCellSelected ゲッターが呼ばれると、選択範囲がテーブルをまたいでいるにもかかわらずテーブルセル内の処理ロジックへ進んでしまい、クラッシュが引き起こされていました。
技術的な変更
isTableCellSelected ゲッターに、処理を継続する前に選択範囲の妥当性を検証するガード節が追加されました。
変更前:
get isTableCellSelected() {
return this.nearestNodeOfType(TableCellNode) !== null
}
変更後:
get isTableCellSelected() {
const selection = $getSelection()
const { anchor, focus } = selection
if (!$isRangeSelection(selection) || anchor.key !== focus.key) return false
return this.nearestNodeOfType(TableCellNode) !== null
}
追加されたガード節は2つの条件を || で連結しています。まず $isRangeSelection(selection) で現在の選択が範囲選択であることを確認し、次に anchor.key !== focus.key で anchor と focus が同一ノードにあるかどうかを検証します。どちらかの条件が満たされない場合は即座に false を返し、nearestNodeOfType(TableCellNode) の呼び出しを行いません。これにより、テーブルセルと非テーブルノードにまたがる選択範囲では isTableCellSelected が false を返すようになり、テーブルセル専用の削除ロジックへ誤って流れ込むことを防ぎます。
テスト追加
リグレッション防止のためのブラウザテストが test/browser/tests/tables.test.js に追加されました。テストはテーブルが先頭要素として存在するエディタ状態を作成し、テーブル後の段落にカーソルを置いてから全選択・Backspace という操作手順を再現します。
// Listen for page errors (the bug threw: "Expected to find a parent TableCellNode")
const errors = []
page.on("pageerror", (error) => errors.push(error.message))
await editor.selectAll()
await editor.send("Backspace")
await editor.flush()
// Table should be removed without errors
await expect(editor.content.locator("table")).toHaveCount(0)
expect(errors.filter((e) => e.includes("#148"))).toHaveLength(0)
page.on("pageerror", ...) でランタイムエラーを収集し、操作後にエラーリストへの #148 の混入がないことをアサートする構造により、テーブル削除の成功とクラッシュ不在の両方を検証しています。
設計判断
ガード節を isTableCellSelected ゲッター自体に配置するアプローチが採用されました。選択範囲の非対称性チェックを呼び出し側に委ねるのではなく、プロパティが常に安全な値を返す責務を持つ設計です。anchor.key !== focus.key という条件は、選択の両端が同じノードに収まっている場合のみテーブルセル判定を続行させる意図を明示しており、将来の呼び出し元の増加に対しても防御的に機能します。
まとめ
isTableCellSelected に2行のガード節を追加するだけで、テーブルをまたぐ選択範囲という境界条件でのクラッシュを解消した修正です。変更箇所を最小限に絞りつつ、ブラウザテストでリグレッションを明示的に検証する形でその意図を記録に残している点も、保守性の観点から参考になります。