この日記はMozillaのプロダクトへの貢献者としての私の成果を中心に、気になったバグやWeb界隈の話題について書いていますが、 断り書きがある場合を除き、いかなる団体のオフィシャルな見解ではありません。あくまでも個人的なものです。 Mozilla Foundation、Mozilla Corporation、及び関連企業の公式情報ではないことに注意してください。

現在、XHTML 1.0 (もどき)から、HTML5なコンテンツに修正中です。古い日記は修正が完了していませんので表示が崩れます。 順次、修正していく予定ですのでしばらくお待ちください。

もずはっく日記(2018年10月)

2018年10月23日

Re: noteと"contenteditable"|ct8ker|note
初回投稿日時: 2018年10月23日23時41分53秒
最終更新日時: 2018年10月24日01時06分35秒
カテゴリ: Editor Firefox
SNS: (list)

こういう形式でblogの記事にblog(もどき)で返すのもめちゃくちゃ久しぶりだなぁと思いつつ、アウトプットしていかないといけないなという動機から書いてみました。

滅多に更新しなくなったし、メディア等への露出もすっかり無くなったので、この日記をはじめて見る人のために書いておくと、私は、MozillaでFirefoxのcontenteditableの実装部分を含む"Editor"モジュールのモジュールオーナーをやってます。では早速……

contenteditable属性はガワであるdivに付与し、内部に各段落要素(h3、p、blockquote、pre、figure)が存在するという形です。(省略)

(省略)

  • ガワdiv直下にテキストは書けない

contenteditable等でツリーの一部、もしくは全部が編集可能になっている場合、そのフォーカスを受けるであろう編集可能なルート要素をediting hostと言います。contenteditable属性で編集可能にした場合はそれを指定した要素(かつ、親が編集不能の場合に限る)、designModeプロパティを設定してドキュメント全体を編集可能にした場合はルート要素になります(だいたい<html>要素)。

用語を使わないと文章が煩雑になるのでとりあえず、細かいところからですが。

・blockquoteの中で改段落
Chrome、Safariでは次段落はblockquoteになります。

(画像省略)

Firefoxはどうなるのか。こうなります。

(画像省略)

改段落になりません。段落内改行になります。pre段落に入力した時と同じような挙動です。ではどうやって改段落すればよいか。
分かりません!

正直、ジェネレーションギャップを感じたところです。ここの解説は簡単です。HTML 4.01では<blockquote>要素の直下にブロックレベル要素しか置けなかったためです。例えば、

<blockquote>
  <p>
    ここは引用された段落です。
  </p>
</blockquote>

といった感じで、HTML 4.01では引用されるテキストの意味も引用する必要がありました。そのため、<blockquote>の直下に直接テキストノードや<span>要素等を配置してしまうと、各ブラウザ共にデフォルトのparagraph separatorを生成して新しい段落を作るか、<br>を挿入して御茶を濁すか、というブラウザの判断に依存する状況を作ってしまうので結果がブラウザ依存になってしまうのです。

DOCTYPEを見ればHTML5だって分かるんだからブラウザもそう動けば良いじゃないかというお叱りが聞こえて来そうです。ごもっともです。私もそう思います。でもできないんです。その理由は、既に存在している、変更に弱いWebアプリが壊れてしまう可能性があるからです。

少なくないWebアプリは正しいfeature detectionもしていなければ、結果を観察して挙動を調整したりしていません。どうしているのかというと、UA Stringから見て、各ブラウザの動作が変わることを想定せず、決め打ちで動いてるのです。ですので、元の動作を前提とするアプリが壊れてしまう可能性がある以上、変更にはよほどのメリットが無ければブラウザ側で「良きに計らう」ことはできないのです。

・H3の中で改段落
ChromeとSafariの挙動(他2つは置いときましょう…)から、どうやら今の段落と同じ要素が作られるのではないかなという予想が立ちます。
H3が出来そうな気がしてきますよね。
しかし、そうは問屋が卸さない。

(画像省略)

改段落は出来て、何かしらの段落が出来ました。
ただ、そこに入力してみると明らかに見出しH3ではない。
ではpになったのか?残念ながらそうでもないのです。

(画像省略)

…divです。
どのブラウザでやってもこうなります。pやblockquoteとの差は何なのか、H3は見出しだから改段落によって複数できるのはおかしいという事なのか、もうこれがcontenteditableの素の仕様であると理解する他ありません。

ブラウザは理路整然とした動作をしていると思います。

"default paragraph separator"という概念を知っていれば簡単な話です。そして、各ブラウザのその初期値は"div"です。これを取得するには以下のようにします。

let defaultParagraphSeparator = document.queryCommandValue("defaultParagraphSeparator");

defaultParagraphSeparatorの値は、"div"もしくは"p"になります。Geckoのみ互換性のために"br"を返すことがあります。

また、元記事のように<p>要素をデフォルトのparagraph separatorとして利用したい場合、明示的に設定することができます。

document.execCommand("defaultParagraphSeparator", false, "p");

Geckoでのみ、"br"も引数として受け入れますが、これは、過去のGeckoの動作に戻すために実装した、従来からあるWebアプリへの逃げ道ですので新規の開発で使うことはないでしょう。

エディタの機能は多岐に渡りますが、まず最も重要な基本機能は、今回挙げたこれら事象を含む、あらゆるテキスト入力操作をクロスブラウザで差異・問題なく行えるようにすること、です。この土台が安定しないと、装飾やembed等の高級機能の実装も覚束ないですから。

Editing APIの仕様草案を書いたり、issueのとりまとめに活躍されているのがInvited ExpertとしてクレジットされているJohannes Wilm氏ですが、彼もブラウザ間の互換性を調整しての標準化は断念しています。前述したように、ブラウザ同士が同じ動作をするように「改善」することによって、既存のWebアプリはたいてい壊れてしまうからです。彼はブラウザ上で動作するリッチテキストエディタを開発しているらしいのですが、その経験からくる結論は「できるだけブラウザに動作させずにDOMを操作する」という感じのことを言ってたように思います。これは、Firefoxという一ブラウザの実装をメンテナンスしている私からしても現実的で的を射ています。同時に、Undo/Redoの自前実装といった、茨の道も当然発生してしまいます。

とりあえず、元記事を書かれた方は、仕様書(もどき)をまず読んでみた方が良いかと思います。用語をご存知なかったことや、APIをご存じなかったことから、この辺の仕様書(もどき)を一度も読まれていないと思われます。といっても、互換性の問題から標準化に失敗しており、草案やそれ以前の状態から抜け出せてない文書たちなので、見つけにくいのも確かです。私もだいたい、巻き込まれて知ったクチなので。ですので、以下にリンクと簡単な説明を添えておきます。一言だけ言っておくと、楽しすぎる地獄へようこそ

The Editing taskforce

各仕様へのリンク集です

execCommand

document.execCommand()の標準化のために書かれ始めたっぽいのですが、各ブラウザの絶望的なまでの動作の違いがリストアップされているだけです。

おそらく、これの結果を見て、標準化が断念されたと思います。ただ、各ブラウザの動きを知る上で貴重な資料となっています。Geckoはすでにいくつかの動作を変更していますが、この文書による予備知識があると理解が簡単かと思います。

ContentEditable

contenteditable属性を拡張して、よくあるパターンに機能削減されたリッチテキストエディタをブラウザ側に提供してもらえたら良いな、という感じの仕様案です。

完全に更新は止まっていると認識しています。逆に言うと、ここに載ってて、特定のブラウザしか実装していない機能に期待したり依存したりしてはいけません。ブラウザ開発者も自分の管理するコードをシンプルにしたいので、互換性がなく、標準に無い機能はあっさり削除される可能性があるからです。

6.6.1 Making document regions editable: The contenteditable content attribute

最新のHTML仕様としてのcontenteditable属性と、それによって得られるであろう結果の定義です。

見ての通り、エディタとしての動作自体は定義する気はありません。

関連するかもしれないエントリ

関連するかもしれないエントリを発見できませんでしたが、無いとは限りません。