HTMLインポート時のホワイトスペーステキストノードによるクラッシュを修正

basecamp/lexxy

メールHTMLに頻出する <div> 要素間の改行文字が引き起こす Lexical error #282 を、ルートレベルのホワイトスペース専用テキストノードをフィルタリングすることで解消しました。

背景

HEYやTutaなどのメールサービスが出力するHTMLには、<div> 要素間にインデントや改行を含むフォーマット用ホワイトスペースが含まれています。このようなHTML(#812)をLexxyエディタに読み込むと、改行文字 \n がルートレベルの TextNode として生成され、Lexicalのルートノードに直接追加できないことでクラッシュしていました。

問題を再現するHTMLは次のような構造です:

<div>First paragraph.</div>
<div><br></div>
<div>Second paragraph.</div>

この \n$generateNodesFromDOM によってルートレベルの TextNode に変換され、root.append() の段階で Uncaught Error: Minified Lexical error #282 をスローしていました。同じ内容を <p> タグで記述した場合には発生せず、<div><p> の処理に一貫性がないことも問題でした。

技術的な変更

src/elements/editor.js の HTMLインポートパイプラインに、ホワイトスペース専用ノードを除去するフィルタステップが追加されました。

変更前:

return nodes
  .map(this.#wrapTextNode)
  .map(this.#unwrapDecoratorNode)

変更後:

return nodes
  .filter(this.#isNotWhitespaceOnlyNode)
  .map(this.#wrapTextNode)
  .map(this.#unwrapDecoratorNode)

フィルタ関数 #isNotWhitespaceOnlyNode は2つのケースを除外します。まず $isLineBreakNodetrue のノードは無条件に除去します。次に $isTextNode かつ node.getTextContent().trim() === "" となる空白のみのテキストノードも除去します。これらはHTMLソースのフォーマット由来のアーティファクトであり、ルートノードへの追加もセマンティクスも持たないと明示的にコメントで説明されています。

また $isLineBreakNode を利用するため、importに $isLineBreakNode が追加されています。テストは test/browser/tests/editor/load_html.test.js に追加され、改行入りの <div> HTMLを読み込んでもクラッシュせず、段落テキストが正しく表示されることを検証しています。

設計判断

フィルタをパイプラインの先頭に配置する方針が選ばれました。#wrapTextNode はテキストノードを <p> でラップする処理ですが、ホワイトスペース専用のテキストノードをラップしても意味のない空段落が生成されます。先にフィルタリングすることで、後続の変換処理が意味のあるノードだけを扱えるようになっています。

<p> では発生しなかった問題が <div> で発生していた理由は、既存の処理パスの差異にあります。今回の修正はその差異を解消するのではなく、ルートレベルへの不正なノード追加そのものを上流で遮断するアプローチです。ホワイトスペースノードはセマンティクスを持たないという前提を #isNotWhitespaceOnlyNode に集約したことで、将来的に除外条件を追加する際の変更箇所も明確になっています。

まとめ

HTMLインポートパイプラインへのフィルタ追加という最小限の変更で、メールHTML由来のフォーマット用ホワイトスペースに起因するクラッシュを根本から解消しました。<div><p> の処理の一貫性も確保され、エディタの入力データの品質に依存した脆弱性が取り除かれています。

記事メタデータ

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

この記事は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リンク記法の正確性

ファイル名付きシンタックスハイライト、Issue/PR番号のリンク記法など、全てのカスタムMarkdown構文が正しく使用されています。

対象読者への適合性 ✓ PASS

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

Lexicalフレームワークに関する専門用語が適切に使用されており、専門知識を持つエンジニアという対象読者に適合した内容です。

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

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

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

Diff内容との照合 ✓ PASS

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

記事内で引用されているコードは、提供されたDiff情報と完全に一致しています。追加された関数やテストファイル名も正確です。

技術用語の正確性 ✓ PASS

技術用語の正確な使用

「Lexical error #282」「TextNode」「ルートノード」など、PRや関連技術で使われる用語が正確に使用されています。

説明の技術的正確性 ✓ PASS

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

クラッシュの原因、フィルタリング処理のロジック、設計判断の背景など、全ての技術的説明がコードの変更内容と整合しており、論理的かつ正確です。

事実の突合 ✓ PASS

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

記事内の主張はすべてPRのDescription、Diff、関連Issue(#812)の情報で裏付けられており、ハルシネーションは見られません。

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

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

PR番号(#867)、Issue番号(#812)、エラー番号(#282)など、記事に含まれるすべての数値・固有名詞が正確です。

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

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

記事のタイトルはPRのタイトル「Fix Lexical error when loading HTML with whitespace between div elements」の内容を的確に要約し、日本語として自然に表現しています。

外部知識の正確性 ✓ PASS

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

記事に含まれる情報はPRまたはPRに紐づくIssueに記載されている内容に限定されており、根拠のない外部知識の追加はありません。

時間表現の正確性 ✓ PASS

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

「クラッシュしていました」などの過去形の表現が適切に使われており、PRによる変更という時間的な文脈が正確に反映されています。