たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
Bug 1500479が解決され、Firefox 65以降のバージョンで、WebExtensionsのタブ関連APIに以下の変更が入る事になりました。
tabs.onActivated
のリスナに通知されるactiveInfo
に、previousTabId
というプロパティが追加されました。tabs.get()
やtabs.query()
などで取得されるタブのオブジェクトにsuccessorTabId
というプロパティが加わりました。
tabs.update()
で変更可能です。tabs.onUpdated
では通知されません。tabs.onHighlighted
のような専用のリスナもありませんので、変更を動的に検知する方法はありません。successorTabId
をまとめて変更するtabs.moveInSuccession()
メソッドが追加されました。Firefoxでアクティブな(現在の)タブをCtrl-Wなどで閉じると、右隣や左隣、あるいはそのタブを開いた親のタブにフォーカスを切り替えるようになっています。上記の新機能は、この挙動に介入するための物です。「successor」とは「後継者」という意味で、つまり、アクティブなタブを閉じられた後に次にフォーカスされるタブを指定する仕組みという事になります。
今までは、WebExtensionsのAPI経由でこの挙動に介入する方法がなかったため、例えば「タブを閉じたら、必ずそのタブの直前に見ていたタブにフォーカスを移す」というような事をアドオンで実現しようとすると、
という手順を踏む必要がありました。これは見た目に美しくない(一瞬無関係のタブがフォーカスされてしまう)のもさることながら、現在のFirefoxの初期設定である「Ctrl-Tab/Ctrl-Shift-Tabで最近フォーカスされた順にタブのフォーカスを切り替える」という挙動にも悪影響を与えます。
拙作アドオンのツリー型タブも、ツリーの最後の子を閉じたら、右隣のタブ(=下にある別のツリーの親タブ)ではなく1つ前の兄弟タブまたは親タブにフォーカスを移すという機能があり、これを実現するにあたっては前述の点がずっと未解決のままでした。今回のAPI追加によって、ようやくこの問題を解決する目処が立ったと言えそうです。
Here is the English version of this article.
朗報があります。2016年6月にWebExtensionsへ一本化の方針が示された時に出した要望であるBug 1280347 - Add ability to provide custom HTML elements working as alias of existing Firefox UI items, especially tabsが、最近ようやく解決されました。
何故これが朗報なのでしょうか? アドオンのXULからWebExtensionsへの移行の話をおさらいしてみましょう。
Here is the English version of this article. 7月に英語で書いた物の日本語版です。Qiitaにもクロスポストしています。
ツリー型タブをXULからWebExtensionsに移植した時の話で、アドオン同士の連携が取りづらくなる事への懸念について書きました。この点について現時点での知見をまとめておきます。
Tokyo WebExtensions Meetup #3で、標題の通りの発表をしました。スライドはQiitaにありますが、こちらにもクロスポストしておきます。
タブの複数選択機能が入った事で「選択」という言葉が多義的になってしまったので、まずその点を整理します。 WebExtensions APIにおいては、「選択」という言葉で表されうる状態に以下の2つがあります。
「selected tab」という表現は紛らわしいので、このエントリでは使いません。
Firefox 63でabout:config
でbrowser.tabs.multiselect
をtrue
にすると試せます。Chromeでも同じ操作ができます。
WebExtensionsベースのアドオンにとっては、このタブの複数選択機能に対して2つの関わり方があります。
それぞれ順番に紹介します。 なお、基本的にはChromeの拡張機能でも同様のAPIが使えます。
As I described at the previous article, you can provide more useful and usable context menu for your addon on Firefox 64 and later, if it is focused to control tabs or bookmarks. The previous article described basics of new APIs, but it looked too complex because there are various usecases. So this article aims to describe how to provide context menu simply for different cases:
All following examples assume that your addon named "Bucket" provides ability to send tabs to an online bucket, like the "Pocket".
(Note that this article describes about an improvement on Firefox 64, and Firefox ESR60 is out of target.)
Good news! An old feature proposal filed at the time Mozilla announced that XUL become deprecated and WebExtensions become the next main line has became fixed: Bug 1280347 - Add ability to provide custom HTML elements working as alias of existing Firefox UI items, especially tabs.
Why it is a news for me? Let's look around the short history of addon migration from XUL to WebExtensions.
When I migrated my addon Tree Style Tab from XUL to WebExtensions, I wrote some concerns about communication between addons. Let's share my knowledge around the topic.
Recently I got requests for migration of "2 Pane Bookmarks" to Firefox 57 and later. However, I won't do that. And there are more addons won't be migrated by my hand.
This article describes "why I won't", for example the case of "2 Pane Bookmark".
Because old XUL addons were actually dynamic-applied patches, they could recycle Firefox's internal implementation regardless they were not public APIs. Basically "2 Pane Bookmarks" was built on such characteristics of XUL addons.
Actually, it recycled Firefox's internal codes around bookmark trees and just added two changes: "filter to show only folder tree" and "filter to show bookmark items in the current folder". That was all of the addon did - I didn't have to make more effort. Moreover, to be honest, I started the project from just one reason: "Oh, interestedly it is easy to do! I try to do that!" - yes, just for curious.
On the other hand, WebExtensions addon cannot do that. If I migrate the addon to WebExtensions, I will need to implement everything from scratch: listing bookmarks, implementing drag-and-drop, context menu on bookmarks, commands in the context menu, and more. It is very tiresome.
As I described above, I started "2 Pane Bookmarks" project just because it was easy to do. However, now it is not easy but very tiresome. If I did the migration, there were any other reason.
Basically I have very few time to develop addons in my private time, because I have to spent much time to write/draw technical comic for periodically-issued magazine. By these reasons, sorry but the addon "2 Pane Bookmarks" will never migrated by me.
Still there are some more unmigrated addons I depended on:
I'm very sorry but addons not listed here won't be migrated by my hand. I hope that someone who really want develops successor version of them.
There is the English version of this article.
2ペインブックマークをFirefox 57以降に対応して欲しいという要望を頂くのですが、残念ながら結論を先に言えば、僕の手による2ペインブックマークのWebExtensions移植はまず行われないと思って頂く他ありません。同様に、他にもいくつかのアドオンについて僕の手による移植は期待できない状況です。
以下、2ペインブックマークの場合を例にとって、何故僕の手による移植が期待できないかを説明します。
Firefoxの内部に食い込む従来型アドオンでは、そのような仕組みならではの特徴として、「Firefox本体が使っている仕組みを(APIとして整備されていないものでも)勝手に流用できる」というメリットがありました。
2ペインブックマークは元々、その特徴を最大限に活用して開発されたアドオンでした。 実際、Firefox本体のブックマークツリーのためのコードをほぼそのまま流用し、そこに「フォルダだけ表示するための絞り込み」と「選択されたフォルダの中身だけを表示するための絞り込み」という一手間を加えることによって、それ以外の事をまったくしなくても最小限の労力で実現できる、という背景があったことから成立していました。 というか、「簡単にできるのならやってみよう」というのが動機で作り始めたと言っても過言ではないです。
他方、WebExtensionsではそのような事はできません。ブックマークの列挙、表示、ドラッグ&ドロップ時の動作(ブックマークのツリー上に何かがドラッグ&ドロップされた時の対応も含む)、右クリックされた時のメニューそのもの、その中身となる各種のコマンド、などなど、ブックマークに関わるすべてをゼロから作り上げなくてはなりません。 つまり、実質的には「ブックマークパネルを再実装する」という事になります。これは大変な労力です。
(ちなみに、そういうわけなので、既にWebExtensions移植済みのツリー型タブのバージョン2.0以降も、Firefoxのタブバーに等しい物を、そこで表示されるコンテキストメニューやそれに関わるAPIも含めて、ひたすら愚直に再実装しているという事になります。
上述したとおり、2ペインブックマークは「簡単にできるのならやろう」という動機で始めたと言っていいプロジェクトです。 簡単にはできない事が明白である以上、最大の動機がそこにはありません。 それでもなお僕がアドオンのWebExtensions版を開発するとしたら、それ以外の動機が必要です。
そもそも漫画の作業のためにプライベートの時間をあまり割けない状況下で、それを押してでもやる動機が無い以上、僕の手による移植が行われることはまず期待できない、というのが現状での結論ということになります。
今の所、僕が手がけたアドオンの中でまだWebExtensionsになっていないもののうち、自分自身が強く依存していた物は、以下の通りです。
すみませんが、これら以外の物については、必要としていて作る動機がある方に後継版開発の希望を託したいと思っています。
Firefox 57で従来型のアドオンが使えなくなるという事で進めているアドオンのWebExtensions移行作業ですが、ツリー型タブ、マルチプルタブハンドラに続いて、テキストリンクもWebExtensionsに移行しました。移行後の最初のバージョンは6.0.0で、そこから若干の修正を施した最新版は6.0.1となっています。
テキストリンクは、テストケースの各種の例のようにWebページ中に普通のテキストとして書かれたURI文字列を、ダブルクリックするだけでリンクのように読み込めるようにするという物です。最盛期には10万を超えるユーザーに使われていた事もあった、僕がMozilla Add-onsに登録していたアドオンの中では統計上最もユーザー数の多「かった」アドオンです。
個人的にもこれが無いとイラッとする場面が結構あって地味に依存度が高かったので、いずれWebExtensions移行はするつもりでいたのですが、先月末にMozilla Corporationのアドオンコミュニティマネージャーの方(昨年のMozLondonにアテンドしてくださった方)から「そろそろFirefox 57リリースが近付いてるけど、テキストリンクの移行はどんな感じ?(大意)」というメールを頂いてしまい、顔を合わせた事のある人から直々にせっつかれてはやらないわけにはいくまい……と、予定を繰り上げて移行に着手したのでした。
メールをもらってからリリースまでに要したのがちょうど1週間で、これまでの2例の難航度合いに比べるとびっくりするぐらいあっさり完了した感があります。数を重ねてきてだんだん「WebExtensionsのやり方」に慣れてきたということでしょうか。
実を言うと、テキストリンクは1年ちょっと前にWebExtensions移行に挑戦してみた事があったのですが、その時は途中ですっかり投げ出してしまっていたのでした。今回は、この時の成果物をまるっきり無視しての再挑戦での成功と相成りました。
1回挫折した物が仕切り直して成功したという典型的な例なので、ここでは「何故前回は失敗し、今回は成功したか」という観点から語ってみようと思います。
過去の挑戦時の方針は、端的に言えば「最低限の手直しでの移行」ということになります。
テキストリンクは初版が2004年という非常に歴史の古いアドオンで、Firefoxの仕様変更に対し、その都度手直しでの追従を繰り返してきました。最初のバージョンからバージョン4までの間、テキストリンクはすべての処理が特権領域内で完結する仕様でした。バージョン5でのe10s(マルチプロセス)への対応にあたって、DOM RangeなどのWebページの生のDOMに触らないといけない「コンテントプロセス側で実行しなくてはならない処理」を分割するという変更を行いましたが、本質的な設計はずっと変化していなかったと言えます。
その延長線上にあったこの時の進め方は、WebExtensions化の作業用にブランチを切って、元のコードを「バックグラウンドスクリプト用」や「コンテントスクリプト用」といったフォルダに移動し、XPCOMやXULに依存していた部分を除去していく形でそれぞれのソースの中身を破壊的に変更していく、というものでした。
旧版テキストリンクは、選択範囲(※カーソルも選択範囲の一種なのです)からのURI検出や、ユーザーの操作からタブで開くかウィンドウで開くかといった判断を行う部分など、主要な処理のほとんどがコンテントプロセス側で動作する設計でした。chromeプロセス側は、コンテントプロセスからの指示に従って実際にタブやウィンドウを開くだけという感じです。
また、それとは別にコンテキストメニュー周りの実装がchromeプロセス側にあり、こちらはユーザーがコンテキストメニューを開いたタイミングで処理を開始して、コンテントプロセスに処理を依頼してはその結果を受け取って……という事を繰り返すようになっていました。
これをそっくりそのままWebExtensionsに持っていこうとすると、主要な処理はすべてコンテントスクリプトに移植することになります。しかし、WebExtensionsではコンテントスクリプトでできる事の範囲が大きく制限されています。そこで「コンテントスクリプトではこの機能が動かないのでバックグラウンドスクリプトに移す」という事をその都度やっていると、キリが無い上に、場当たり的な切り貼りの繰り返しでコードはどんどんカオスになっていきます。
また、従来のアドオンでの「コンテントプロセスで動作するスクリプト」とWebExtensionsの「コンテントスクリプト」には大きな違いがあります。それは、従来のコンテントプロセス用スクリプトは「1つのプロセスにつき1つのインスタンスが作られる」のに対し、コンテントスクリプトはタブ内のフレームの数だけインスタンスが作られるという点です。プロセスの数だけインスタンスが増えるという時点でいかがな物かという感じですが、フレームの数だけ増えるとなると、さすがに主要な機能をそこで動かすのは非効率的です。
このほか、コンテキストメニュー項目の動的な更新についての知見がなかったために、選択範囲からのURI文字列の検出結果を受けてメニュー項目を制御するやり方が分からなかった事や、XPCOMとXULがあることを前提として書かれたコードを総点検してXPCOMフリーに書き直すにあたって、代替手段が見つからなかった処理がいくつかあった(WebExtensionsでも最新のWeb技術でも機能が提供されていない部分があった)事など、数々の困難に嫌気がさし、また「シス管系女子」の連載の〆切が迫っていた事も相まって、道半ばで移行作業を放棄してしまったのでした。
TSTの移行に際しての振り返りで「直接Firefoxの中身をいじくり回していたレガシーなアドオンを『若干手直ししてFirefox 57でも使えるようにする』ということは不可能」と書きましたが、この時の失敗はまさにこれが原因であったと総括できます。例えて言うなら、従来バージョンから持ち越した既存の実装という大荷物を背負って道を歩いていて、道のど真ん中にある車止めに荷物が引っかかってしまい先に進めず、「こんなもんどけてやる!」とばかりに軽く考えて邪魔な車止めを取り除こうとして、でも地面にしっかりコンクリートで固定されていて蹴っても叩いても動きやしなくて、その埒の明かなさと背負っている荷物の重みとのダブルパンチで心が折れてしまった……という感じでしょうか。
今回の再挑戦では、TSTやMTHのWebExtensions移行で成功をもたらした「旧版の動作をリファレンスとして、同等のユーザー体験をもたらす新しいWebExtensionsベースのアドオンを新規に開発する(つもりで臨む)」というやり方を取りました。具体的には以下の順で作業を進めました。
この説明にも表れている通り、今バージョンのほとんどのコードはスクラッチで書いた物で、旧版からコードを引き継いだのは「文字列の中からURIらしき部分を検出する」部分だけです。以下、一般的な話は省略してテキストリンクに固有の事情だった部分に絞って詳しく述べます。
URI文字列を検出するにあたっては、window.getSelection()
で取得できるRangeのtoString()
の結果ではなく、「ページ中で目に見えているとおりのテキスト」を対象にマッチングを行わなくてはいけません。というのも、RangeのtoString()
はその範囲にあるテキストノードの内容を単純に連結した結果を返す物なので、親要素が非表示であるテキストノードがある場合や、CSSのdisplay:block
などで見た目上改行されているだけの部分がある場合に、「見た目上は改行されているように見えるのにデータ上は改行が存在しないので、複数の行が連結されて見える(そのためURI文字列の検出結果がおかしくなる)」という事が起こるからです。
旧版では、Webページをプレーンテキスト形式で保存する場面や、ThunderbirdでHTMLメールとして編集された本文からプレーンテキストの本文を生成する場面などで使われるnsDocumentEncoderというXPCOMコンポーネント(@mozilla.org/layout/documentEncoder;1?type=text/plain
)を使ってこれを一発で行えていたのですが、WebExtensionsでは当然使えません。相当する機能を提供して欲しいというbugはずいぶん前に立てたのですが、今に至るまで具体的な実装はまだなされていないままなので、諦めて自力で実装してみました。
詳しい事は当該処理のソースを見てもらうのが一番早いのですが、簡単にいうと、DOM2 TraversalのTreeWalkerを使って泥臭い事をやっています。TreeWalkerはnextNode()
とpreviousNode()
を使うとDOMツリー上の全ノードをツリー構造を無視して文書順で参照するイテレータとして使えるので、CSSのセレクタやXPathでは条件にできない生のDOMの情報を条件とした絞り込みを行っています。具体的には、まずNodeFilter.SHOW_ELEMENT | NodeFilter.SHOW_TEXT
でざっくりテキストノードと要素ノードに絞り込んで、さらにそのノードの祖先に非表示の要素ノードがないかをwindow.getComputedStyle()
で順にチェックして、確かに可視状態だと確認できたノードだけを取り出しています。その後、テキストノードであればnodeVaue
を、要素ノードでdisplay:inline
以外の物があれば適宜改行文字を「表示されているテキスト」として取り出しています。
以上のようなやり方で、一応実用に耐える精度で「見た目通りのテキストを抽出する」を実現できたのですが、想像の及んでいなかったエッジケースなどで想定外の結果になる可能性は依然としてありそうな気がしています。nsDocumentEncoder相当の機能が実現されるまでは、まだまだ悩みは尽きない感じです。
与えられた文字列からURIを検出する処理は、今回の移行で唯一、旧バージョンからほとんどそのままコードを引き継いだ箇所です。
この処理は、データ量にして最終的に完成したWebExtensions版テキストリンクの全体の1/3を占める巨大なモジュールなのですが、やっている事は至って単純で、「相対パスも許容するのか?」「全角英数字も許容するのか?」等の条件に基づいてURIらしき文字列にマッチする正規表現を組み立てるだけの物です。相対パスを絶対パスに解決する部分などでXPCOMコンポーネントの機能を使っている部分はありましたが、全体の中ではそういった箇所はごく一部に限られており、ほとんどは単純な文字列処理だけで構成されていました。
また、元々自動テストを容易にするためにモジュール間の結合を弱めていた結果、機能を使うのに必要なパラメータはほとんどすべて外部から与えられるようになっており、旧テキストリンクの実装の中ではここだけ突出して浮いているようにも感じられるほどでした。
そういう事情があったため、このモジュールについてはほとんど苦労する事なくほぼそのままの形でWebExtensions版に組み込む事ができました。
旧版テキストリンクでコンテントプロセス側に大部分の処理があったのは、「URI文字列として検出した部分を新しい選択範囲にする」という挙動を実現したかったからでした。これをやるには、Rangeを対象に文字列を検索してヒット箇所をRangeで返すというAPIが不可欠です。
旧版ではnsFindというXPCOMコンポーネント(@mozilla.org/embedcomp/rangefind;1
)を使ってこれを実現していましたが、当然ですがこれはWebExtensionsでは使えません。また、W3Cで提案されていたらしいRangeFinderというAPIが実装されていればそれで代替できたのですが、こちらは残念ながら未実装です。という具合で代わりの手段がなかったという事が、過去の移行失敗時に移行を断念した理由の1つでもありました。
幸い、最近になってWebExtensionsにbrowser.find
というAPIが追加され、Firefox 57ではRangeFinder的な事ができるようになっています。なので今回はこれを使ってみる事にしました。
RangeFinderを使う時にネックになるのが、DOM Rangeの取り扱いです。コンテントスクリプトとバックグラウンドスクリプトの間ではJSON文字列に変換できるデータしか交換できないのですが、browser.find
はコンテントスクリプトでは動作しないためバックグラウンドスクリプトで呼び出す必要があり、しかし選択範囲の操作はコンテントスクリプト側でやらなくてはならない……ということで、どうやってDOM Rangeの情報を双方の間で受け渡すかが問題となります。
この事について何かヒントはないかとbrowser.find.find()
の説明を読み進めてみた所、まさにこの通りの事をやる例が載っていました。このAPIはrangeData
という形式で検索結果のRangeをシリアライズして返すようになっており、その情報に基づいてコンテントスクリプト側でRangeを組み立てれば良いようです。DOM Rangeをシリアライズして受け渡す事自体は容易に考えつくものの、どういう考え方でシリアライズするかという事についてはいろいろな考え方が可能であったため、自分で考えていたらそれだけでまた時間がかかってしまっていたでしょう。「既に動いている実装があるのなら、それとフォーマットを揃える」という具合に考える手間を省けたのは助かりました。
ただ、実際にbrowser.find.find()
を使ってみるとメチャクチャ遅いです。それもそのはず、このAPIは検索対象の範囲をRangeで絞り込む事ができなくて、実行すると必ずページ全体を対象に検索し、しかもすべてのヒット箇所を列挙してくるのです。検出対象のURIの数が増えれば増えるほど実行回数が増え、ページが長くなればなるほど1回あたりの処理時間が増えるという感じで、テストケースのページ全体からURIとしてマッチした箇所をRandeで取り出すのにトータルで10秒とかそのくらいかかってしまいました。なので最終的には、バージョン6.0.1の時点では「選択範囲が変化したらまずbrowser.find.find()
無しでURI文字列の検出だけを行い、それらを開くとかコピーするとかの操作が行われた時点で初めてbrowser.find.find()
を実行してRangeを取り出す」という風にして、日常的な操作であるテキストの範囲選択とコンテキストメニューの操作に悪影響が出ないようにしています。
Firefoxの各種メニューに項目を追加するためのAPIであるbrowser.menus
(browser.contextMenus
)では、menus.ContextType
のいずれかを伴って定義したメニュー項目については、その指定にマッチする場面でのみ表示されるようになります。その中にselection
というコンテキストもあり、テキストリンクの「選択範囲内のURIを待て馬手開く」系の機能はそのようにして登録しています。
しかし、ここに1つ問題があります。このコンテキストでは「テキストを範囲選択しているかどうか」しか考慮されず、「その中にどういう内容が含まれているか」までを判断材料とする事ができないのです。
テキストリンクは選択範囲のURI文字列を開く物ですから、選択範囲にURIらしき文字列が無い時にまでメニュー項目が表示されるのは混乱の元です。そこで旧版ではコンテキストメニューが開かれる瞬間に発火するpopupshowing
イベントを検知してメニュー項目の表示・非表示を切り替えていたのですが(これ自体はXULで作られたアドオンの一般的な作法です)、他方、WebExtensionsのbrowser.menus
で監視できるイベントには「メニューが開かれた時」に発火されるイベントがありません。これじゃあ動的にメニューの表示・非表示を切り替えられないじゃないか!ということで、これも過去の移行失敗時の躓きポイントの1つとなっていました。
現時点に至るまでpopupshowing
を代替できるイベント型はまだ実装されていないままなのですが、この回避策はTSTやMTHのWebExtensions移行を進める中で知らず知らずのうちに気が付いていました。要するに、「コンテキストメニューが表示されようとしているその時にメニューを更新する」のではなく、「コンテキストメニューの内容に影響を与える変化が起こったまさにその時に先行してメニュー項目を変化させておく(メニューが開かれる時には、既に定義済みのメニュー項目が表示されるだけになる)」という事なのです。その後実際にコンテキストメニューが開かれなければこの時の処理はまるっきり無駄になってしまいますが、これ以外に方法がない以上は仕方ありません。
WebExtensions版テキストリンクでもこの方針に則って、「selectionchange
イベントをコンテントスクリプトで監視し、変化があったらその時点で選択範囲からのURI文字列の検出を開始する指示をバックグラウンドスクリプトに送る」→「バックグラウンドスクリプトでコンテキストメニューの内容を更新する」という事を行うようにしました。バージョン6.0.0の時点では前述のbrowser.find.find()
がメチャクチャ遅いという問題に引きずられて、範囲選択から数秒待たないとコンテキストメニューの内容が更新されないという状況でしたが、バージョン6.0.1で範囲選択時のURI検出にはbrowser.find.find()
をバイパスした簡易的な処理を使うようにした結果、概ね一瞬でメニューを更新できるようになり、実用上ほとんどの場合では「しばらくお待ち下さい」的なメッセージを目にする事はなくなりました。
この件を通じて、コンテキストメニューでAPIで用意されているコンテキスト以上にきめ細かい判断を行いたい場合に、WebExtensionsでのベストプラクティスはレガシーなXULアドオンでのベストプラクティスとはまったく異なるのだ、という事をまざまざと思い知らされたのでした。
旧版テキストリンクでは、<textarea>
などのテキスト入力欄についてもXPCOMコンポーネントの機能を経由して内部的なDOMツリーに触っていました。これは、上記の「検出したURIの箇所を新しい選択範囲にする」という挙動を実現したかったからです。しかしWebExtensionsではその方法が使えず、テキスト入力欄内部のDOMツリーには触れません。
じゃあどうすればいいのかという話なのですが、これについては「無理なものは無理」という事で、テキスト入力欄の選択範囲はその要素ノードのselectionStart
/selectionEnd
/setSelectionRange()
を介して触れる範囲以上の事はしない事にしました。
移行に失敗した時の考え方だと、こういう時も何か対策はないかと手を尽くして、それでも機能が見つからなくて仕方なくBugを立ててそれが解決されるのを待つ、という事になっていたでしょうが、今回は「完全でなくても今できる範囲でできる事だけをする」という方針だったため、この点はリリースをブロックする要因にはなりませんでした。
はじめからやり直したい症候群や、それに類する言葉を見かける事があります。プログラミングの世界においても、メジャー製品の大規模アップデートで「ここらで一区切り付けて、最初から作り直してもっとスッキリした設計にしよう」みたいな事をやろうとして、収拾がつかなくなり失敗に終わってしまい、最終的に旧版からの小規模な修正のみに留めるしかなかった……みたいな事はたまにあります。そういった事例をいくつか見聞きして、また自分自身もそういった無謀な挑戦をしようとして挫折するという経験を何度かした結果、「スクラップ&ビルドですべてが上手くいくようになるというのは甘い幻想で、実際には地道な改良をコツコツ続けていくしか無い」という教訓が、自分の中である種の呪いのように刻み込まれていました。
しかし実際には、その「教訓」に従って進めようとした前回の移行は失敗に終わり、大部分でスクラッチに近いレベルでの作り直しとなった今回の移行の方が成功するという皮肉な結果となりました。ただし、今回スクラッチせず引き継いだ部分まで作り直していたら、この移行は再び失敗に終わっていた事でしょう。
今まさに自分が直面している問題がどちらのケースなのか、を適切に見極めるのは難しい事です。考える事を放棄して当て推量で雑な判断をすれば、痛い目を見ることになるのは自分自身です。確実な成功に繋げるためには、そこで使おうとしている・使わなければならない技術の特性と限界、現行技術とのギャップの大きさ、取り組んでいる問題の複雑さなど、様々な要因を考慮に入れた総合的な判断を、プログラム全体やモジュール単位で下す必要がある、という当たり前といえば当たり前の事を、自分は今回の移行を通じて実感した次第です。