たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
標題の通り、ツリー型タブが、Firefoxのアドオン管理画面にオススメとして表示されるようになりました(メールインタビューでやり取りしたScottさんが教えてくれた)。一定期間で入れ替わるやつなのでそのうちまたどっか行くと思うけど、記念に記録を残しておきます。
最初に書いておくと、このエントリはただの自慢です。こういう所に注目してくれる人はどこにもいないので、自分で解説して「僕TUEEEEEE」とアピるという物です。
統計データ上ではFirefoxの全ユーザーの中でアドオンを使っている人は意外と多くなくて、せっかくアドオンがいろいろあるのだから認知を向上させたい、というのがMozilla的には一つの課題のようです。2016年のAllHandsで既に、「アドオンをインストールするまでのハードルを下げたり、アドオンのインストールというよりも機能のON/OFFのように見えるようにしたりするといいのでは?」という方向でリニューアルを進めているということが通達されていて、現在のMozilla Add-onsやアドオン管理画面はその時見たモックほぼそのままのデザインが踏襲されています。
その時見た「機能のON/OFF」の選択肢の中に自分が作ったアドオン、特に「ツリー型タブ」が登場しているというのが、何とも感慨深いです。
というのも、WebExtensions版を作った時の苦労話の冒頭に名前が出てくるTBEより若干マシになってはいたものの、ツリー型タブもアドオンとしての「お行儀の悪さ」はなかなかのものでした。Firefox本体の関数をガンガン置き換えるわ、eval()
で関数の行単位でモンキーパッチを当てまくるわ……なのでMozilla Add-onsのレビューポリシー的に「一般向けのアドオン」としては認められず、サイト上の検索結果等には表れない「開発者向けアドオン」の地位に長く留まっていました。TBEやツリー型タブのせいで(互換性を壊すとクレームがMozillaの方に来るから)Firefox本体の大規模な改修が阻害されていた、という部分ももしかしたらあったかもしれず、恨まれたり疎まれたりしていてもおかしくないくらいです。
そういう背景を持つツリー型タブが今、Mozilla Add-onsのバリデーションでエラーも警告も一切無く、Firefoxを使っている人がアドオン管理画面を開いたら目に入る位置に、広告ブロック等の桁違いのメジャーな選択肢に紛れて表れている現状を見ると、「ずいぶん立派になったもんだ……」と思うわけです。
ところで、苦労話に書いた通り、ツリー型タブのWebExtensions版は同様のWebExtensionsベースの縦長タブバー型アドオンとしては最後発と言っていいくらいに遅れて登場しました。いま自分が把握してるだけでも、サイドバー内に縦型のタブバー風UIを表示するアドオンは他にもこれだけあります。
それらを差し置いて何故ツリー型タブがおすすめになっているのでしょうか。選考過程は関知していないのであくまで憶測ですが、「これらの中ではツリー型タブが一番クソ真面目にFirefoxのタブバーを真似てるから」ではないだろうか、と自分は考えています。
実際に自分自身で上記の縦型タブバーアドオンをいくつかを試してみると、「みんな意外と適当だな……?」という印象を受けました。例えば以下のような具合です。
ツリー型タブはどうかというと、基本的に見た目も挙動も全部Firefoxのタブに合わせるようにしています。技術的に不可能な事以外は、Firefoxのタブバーでできることは全部ツリー型タブでも同じようにできるようにする、という方針です。何故かというと、平たく言えば「そうじゃないと自分が使えないから・使う気にならないから」です。だってイヤじゃないですか、たかが個人開発のアドオンごときのために今まで覚えた物全部捨てて一から順応しなきゃいけないなんて。
もう少し格好を付けて言うと、度々述べていますが、UIの実装時には既存の何かに合わせるのが鉄則で、必要のない中途半端な自己主張はユーザーにとっては邪魔でしかない、と僕は考えています。コンテキストメニュー項目の並び順にせよミュート状態の表示の仕方にせよ、「Firefoxのタブと違う」という事それ自体が学習コストを引き上げ、使い勝手を悪くします。メニュー項目の並び順などはその典型で、人は普段よく使うメニュー項目を「だいたいここら辺」というような視覚的な位置で記憶しているようで、並び順が違うとそれだけでもう目当ての項目を見つけられなくなります。まったく違うならそれはそれで覚え直せばいいのですが、中途半端に似ていると、両方の記憶が混ざって余計に悲惨な事になります。似せるなら半端に似せないで完コピする、「俺の考えた最強のUI」を披露したいというエゴは捨てる、というのが、既存の何かに対する「改良版」と言える物を作る時に必要な心がけだ、と僕は思っています。
こういう所って、頑張れば頑張るほど違和感が無くなって既存の物に溶け込んでいってマジで誰にも気付いてもらえなくて報われなくて、それだけならまだしも、こういう事を頑張る事の価値自体が認識されなくなって誰もこういう所を頑張ろうとしなくなるんじゃないかということを心配してるので、ダサいとは思いつつ折に触れてアピっておく次第です。
また話は変わりますが、Google Chrome向けにもツリー型タブの代替になりそうな拡張機能はいくつかあるものの、どちらも自分にとっては「コレジャナイ」感が強くて、Chromeへの移行を考える材料にはなりませんでした。
どのあたりが「コレジャナイ」なのかは上手く言語化できないのですが、上記のような点が理由なんじゃないかなあとなんとなく思っています。Chrome全然使ってないから憶測ですが。
今見てみたところ、これを書いている現時点でのTabs Outlinerのユーザー数は13~14万人くらい、ツリー型タブのユーザー数は12万人くらいらしいです。Firefoxより圧倒的にユーザー数が多いはずのChrome用拡張機能ですから、ユーザー数に何倍も差があってもおかしくなさそうですが、実際にはこのように案外健闘しているようだというのが興味深いです。元々こういうタイプのUIに順応できる人やこういうUIを求めている人の絶対数がそのくらいという事なんでしょうか? とりあえず今のところは、「Chromeユーザーの中でこれらの拡張機能を使ってる人の割合より、Firefoxユーザーの中でツリー型タブを使ってる人の割合の方が多いという事は、ツリー型タブの方がユーザーに選ばれているという事に違いない!」と勝手に自慢しておこうと思います。
という、最初から最後まで自慢でしかないエントリなのでした。
Firefox 57がリリースされた前後から、アドオンが使えなくなってクソだとか改悪だとかの感想を目にする機会があって、気分が滅入る事が度々ありました。
自分自身は、これは必要な事だったと思っていますし、絶望は1年ほど前に嫌というほどし尽くして、今はもう「で、どうやって乗り越えるか」というフェーズに気持ちがすっかり移ってしまっているため、それらの後ろ向きなコメントには正直な所「えっまだそんなこと言ってるの……」という感想を抱いています。そんな後ろ向きな事を言っていないでもっと生産的な事をしましょうよ、と思わずにはおれません。
しかし同時に、自分自身も他の分野ではエンドユーザーとして後ろ向きな選択をしている場面は多々あり、同意する部分が無いとも言えません。というか今絶望している人達と同じような事をつい1年から2年ほど前には自分も言っていた訳で、それを思い起こす度に、し尽くして乗り越えたはずの絶望が何度も蘇ってきて、「何故自分がこんな理不尽な思いをせねばならん(かった)のだ?」と憤りの感情が頭をもたげてくるのは否めません。
そういう自分の中での混乱を鎮めるために、エンプティ・チェアを2~3個置いて自分の思う所と結論に至るまでの経緯を各立場から辿り直し、考えを整理してみる事にしました。
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を立ててそれが解決されるのを待つ、という事になっていたでしょうが、今回は「完全でなくても今できる範囲でできる事だけをする」という方針だったため、この点はリリースをブロックする要因にはなりませんでした。
はじめからやり直したい症候群や、それに類する言葉を見かける事があります。プログラミングの世界においても、メジャー製品の大規模アップデートで「ここらで一区切り付けて、最初から作り直してもっとスッキリした設計にしよう」みたいな事をやろうとして、収拾がつかなくなり失敗に終わってしまい、最終的に旧版からの小規模な修正のみに留めるしかなかった……みたいな事はたまにあります。そういった事例をいくつか見聞きして、また自分自身もそういった無謀な挑戦をしようとして挫折するという経験を何度かした結果、「スクラップ&ビルドですべてが上手くいくようになるというのは甘い幻想で、実際には地道な改良をコツコツ続けていくしか無い」という教訓が、自分の中である種の呪いのように刻み込まれていました。
しかし実際には、その「教訓」に従って進めようとした前回の移行は失敗に終わり、大部分でスクラッチに近いレベルでの作り直しとなった今回の移行の方が成功するという皮肉な結果となりました。ただし、今回スクラッチせず引き継いだ部分まで作り直していたら、この移行は再び失敗に終わっていた事でしょう。
今まさに自分が直面している問題がどちらのケースなのか、を適切に見極めるのは難しい事です。考える事を放棄して当て推量で雑な判断をすれば、痛い目を見ることになるのは自分自身です。確実な成功に繋げるためには、そこで使おうとしている・使わなければならない技術の特性と限界、現行技術とのギャップの大きさ、取り組んでいる問題の複雑さなど、様々な要因を考慮に入れた総合的な判断を、プログラム全体やモジュール単位で下す必要がある、という当たり前といえば当たり前の事を、自分は今回の移行を通じて実感した次第です。
ツリー型タブのWebExtensions移行の後半作業と並行して進めてたマルチプルタブハンドラのWE移行ですが、こちらも一区切りとしてバージョン2.0をリリースしました。こちらはTSTほどには語る内容が無いので手短に。
マルチプルタブハンドラ(以下、MTH)は元々、「そこにあるFirefoxのタブを雑にドラッグして選択したらメニューが出てきて『で、この選択したタブをどうしたい?』と次のアクションを訊ねてくる」という、操作対象を起点とした操作、本来の意味での「オブジェクト指向」らしい操作というコンセプトに基づくアドオンでした。そのコンセプトを具現化するために、レガシー版ではタブの上でのクリックやドラッグどいった操作をハンドルし、時にFirefox本体の挙動をキャンセルして、「タブをドラッグして選択」という挙動を実現していました。
ところが、WebExtensionsではFirefoxのタブの上での生のイベントをハンドルすることが許されていません。そういうAPIが欲しいという要望は挙がっていましたが、WebExtensions APIのコンセプトに反するということでWONTFIXになっています。よって今後もそういう事ができるようにはならないでしょう。
ただ、WE版TSTは専用のサイドバーを提供していて、その中で発生するイベントを扱う事に関してであれば自分の裁量で好きなようにAPIを提供できます。 なので、WE版MTHは当初は「TSTに完全に依存したアドオン」として開発を始め、TSTにAPIを実装してはMTHでそれを使ってみるという形で両者の開発を並行して進めていたのでした(TSTのAPI整備とその動作検証のための体のいい実験台になってもらったとも言えます)。 その過程で、ツールバーボタンから開いたパネル内であればドラッグ操作でのタブの選択もできなくはないと思い立ったため、TST完全依存のアドオンではなく一応単体でもそれなりに機能するアドオンとしてリリースする方針に切り替えて不足部分を整備し、この度ようやくリリースに至った次第です。
そういう経緯なので、本来意図していたMTHのユーザー体験は、WE版においてはTSTとの併用時においてのみ実現されています。単体のWE版MTHはあくまで仮の姿で、TSTと併用した時にのみ真価が発揮されるとは、なんとも中二病くさい仕様ですね。
ところで、Firefoxにはずいぶん前から(具体的には8年前から)タブを複数選択する機能の実装提案がなされています。 この機能がいつまで経っても実装される様子がないので、渋々MTHを今までメンテナンスしてきた、という部分も実を言うとあるのですが、なんとここに来てそれなりの優先度で解決(実装)に向けて動き始めてきたようです。 もっと早くやってくれていれば、MTHはWE版を開発せずそのまま開発終了にできたかもしれなかったのに……
ともあれ、将来のどこかの時点でMTHがお役御免となるのなら、それに越したことはありません。 その日が来るまでの間は、できる限りメンテナンスを続けていこうと思っています。
以上、MTHのWE版リリースの裏事情でした。
Here is the English version of this article. このエントリはQiitaとのクロスポストです。
2017年の8月下旬に思い立って、ツリー型タブのWebExtensions版を作り始め、去る9月26日にバージョン2.0としてリリースしました。
重い腰を上げて取り組む気になれたのは、必須と目していたAPIが一通り実装されてきて、Firefox 57でようやく技術的に作れる目処が立ってきたからでした。 関係者の皆さんの尽力に改めて感謝の意を表明します。
やっている事自体はそう難しい話ではなく、技術的に目新しいトピックは無いのですが、主に歴史的資料としてレガシーなアドオンの移行の一事例の記録を残しておこうと思います。
I started to develop WebExtensions-based version of the Tree Style Tab at late August 2017, and released as the version 2.0 at 26th November.
The largest reason why I did it is: many numbers of new WebExteisons APIs I required are landed to Firefox 57. Thank you developers for their great effort.
There is no technical novelty topics, but I wrote this as a historical document: a migration story of a very legacy addon.
When I start Firefox, tree of tabs and favicons are always lost. Even if I reorganize tree of tabs again, they are flattened after every restart.
Firefoxを起動すると、必ずタブのツリーとタブのアイコンが失われます。ツリーを作り直しても、再起動する度にツリーが失われてすべてのタブが同階層に表示されてしまいます。
Please press Ctrl(Command)-Shift-J to open the "Browser Console" - one of developer tools of Firefox. Is any error message like this reported?: NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE)
[nsISerializationHelper.deserializeObject] Utils.jsm:94
Screenshot:
Then, the problem may be caused by invalid information stored in your session data. Try following steps to cleanup your session data:
var TabStateCache = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm',{}).TabStateCache;
Array.forEach(
gBrowser.browsers,
(browser)=>
TabStateCache.update(browser, {
iconLoadingPrincipal : null
})
);
"OK"
Ctrl(Command)-Shit-Jを押して、Firefoxの開発ツールの1つである「ブラウザコンソール」を開いて下さい。以下のようなエラーメッセージが出力されていませんか?:NS_ERROR_ILLEGAL_VALUE: Component returned failure code: 0x80070057 (NS_ERROR_ILLEGAL_VALUE)
[nsISerializationHelper.deserializeObject] Utils.jsm:94
スクリーンショット:
もしこのエラーが出ているなら、問題はセッション情報の中に混入してしまった不正なデータが原因で引き起こされている可能性があります。以下の手順でセッション情報をクリーンアップしてみて下さい。
var TabStateCache = Components.utils.import('resource:///modules/sessionstore/SessionStore.jsm',{}).TabStateCache;
Array.forEach(
gBrowser.browsers,
(browser)=>
TabStateCache.update(browser, {
iconLoadingPrincipal : null
})
);
"OK"
Recently I wrote a blog entry for developers of addons for Firefox, who are planning to migrate his/her XUL-based addon to WebExtensions. This is the full version which includes some side topics not related to WebExtensions. I hope this helps you to migrate your ancient addon to WebExtensions.