たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
sp.13b【ゲスト: pupupopo88】楽しいよちよちRubyistがコミュニティに貢献する理由 | しがないラジオの中でKPTの導入の話があった。KPTとは、今取り組んでいることに対して何かのタイミング(定期的に、が基本)でKeep(よかったこと、今後も続けること)・Problem(よくなかったこと、改善が必要なこと)・Try(改善案、Problemを取り除くための次の取り組み)の3つの観点で振り返りを行う手法のことだけど、これを使って会社の中で物事を改善していくにあたって「問題・対・私達」という姿勢が大事という事が話されていた。それで思い至った話なんだけど。
そもそもなんで敢えて「問題・対・私達、という姿勢で取り組まないといけない」なんて事を言わないといけないのか。言い換えれば、なぜ敢えてそういう風に言っていかないと、我々は、すぐに「えっ困ってる? それ君個人の問題だよね、俺ら関係ないよね?」と考えてしまうのか。
我々って誰だ。KPTでの改善が必要なくらいに、問題が解決されないままになってしまっている集団に属している人々、だ。なぜ問題が解決されないままになってるのか。誰もが自分で責任を引き受ける事から逃げ続けているからだ。自分で責任を引き受けるとはどういうことだ、「それ君個人の責任だよね」と言うことと何が違うのか。まったく違う。「それは君個人の責任じゃないね。そういう事が発生してしまう状況を放置してきた我々(その一員である自分)の責任だね。だから私もこの問題の解決に取り組まないといけないね」、こういう姿勢が要るってことだ。
「問題・対・私達」という考え方で問題に取り組むためには、今まで個人に責任を押しつけて自分は無関係だと無視を決め込んでいた自分を改めないといけない。そういう駄目だった自分がいたという事実に向き合わないといけない。
しかしなぜこうも、我々は問題を個人の責任に帰結しようとしてしまうのだろう。僕は、「そういう教育を受けてきたから」「大人がそうしているのを幼い時分から見てきて、そうするのが当たり前と学んできたから」なのではないかと思っている。
こういう話題になったときに僕が決まって思い出すのは、幼い頃の「言い訳するな、他人のせいにするな」と「怒ら」れ「反省を強いられた」体験だ。いや、事実としてそうだったとは限らなくて、主観的にはそう感じられたという事なんだけど。いちいちハッキリ詳細に覚えているわけではないけど、自分の思う正義とか道理とかがあって、それに反した事を頭ごなしに押しつけられて、弁解の余地も与えられなかった、そんな体験の記憶がある。ちゃんと自分が悪かったと納得していたら、こういう記憶の残り方はしなかったのではないだろうか。納得できないままただ反省の言葉を述べることを強いられた、という終わり方だったからこそ、そう記憶しているのではないだろうか。
僕が度々紹介している本で、反省させると犯罪者になります(著:岡本茂樹)という本がある。というか、「反省」というフレーズが関わるような場面での僕の考えや発言のかなりの部分はこの本の影響を受けている。刑務所で犯罪者の更生に関わっていたという人の書いた本なんだけど、「形式的な反省を重視して、本人の納得を軽視する」事は何の解決にも改善にもならない、なぜなら本人が納得しないまま反省のポーズを取ることだけを求めると、処世術としてポーズを取ることだけを覚えるからだ、というような事が書かれている。
そこから考えを発展・一般化させると、こういう事が言えるのではないだろうか。
形式的な反省を強いることは、問題の本質と向き合わずにただその場をやり過ごす(反省のポーズを示す)という処世術や、それを良しとする姿勢を養う。
問題の本質と向き合わずやり過ごす最も簡単な方法は、問題の発生をその問題に直面した個人の責任に帰結させること。
問題が解決されることそのものよりも、「責任を認めた個人がいて、その人が反省して以後改めると言った」ということの方にばかり注目する、そうして「やり過ごす」事が重なっていく。
その結果として、様々な問題が解決されないまま放置され続け、深刻化していくのではないか。
仕事の現場以外での社会の問題、例えば生活保護受給者の自己責任論も、ワープア派遣労働者の自己責任論も、この延長線上にあるのではないか。
べつに、何でもかんでも組織や社会のせいにして個人の責任をうやむやにせよと言いたいわけではない。問題の本質を深掘りしていくと、組織やシステムのせいである場合も、個人のせいである場合も、どちらもありうると思ってる。大事なのは、「個人のせいにすればいい」あるいは「システムのせいにすればいい」のどちらか片方に決めつけてしまうという思考停止、をしないこと、ちゃんと問題の本質を理解すること、だと思う。
人には防衛機制という物がある。本当に自分に責任がある事でも、まずは責任転嫁して「自分のせいじゃない」と考える傾向がある。そのため、認知を歪めて世界を自分に都合のいいように解釈してしまう。そしてこれは、問題を起こした人本人だけでなく、その人に関わる周囲の人にも同じ事が言える。その問題が引き起こされた原因はもしかしたら、自分(周囲の人)が属しているシステムの方にあって、その人本人はたまたまその皺寄せが表出するときに居合わせただけなのかもしれない(し、そうでないかもしれない)。問題の本質に近付くためには、双方の対話を通じて慎重に認知の歪みの存在を炙り出して、それを取り除いていかないといけない。どちらか片方でもその姿勢が欠けていれば、途端に本質から遠ざかってしまう。
本質に辿り着いていないままの状態でする議論も対策も、すべて無意味だと僕は思う。本質と離れた状態のままで議論しても、本質が分かっていない分、必ずその結論には歪みが生まれる。プログラミングで言えば、ロクな調査もしないでworkaroundを重ねるのと同じだ(こういうのをカーゴ・カルト・プログラミングと呼ぶという事を僕は最近知った)。問題の真の解決を図るためには、物事や世界を良くしていくためには、問題の本質と徹底的に向き合う事から逃げてはいけない、と僕は思う。
偏見、決めつけ、思い込み、認知の歪み、自分はそういった物に影響され間違った事を度々してしまう残念で平凡な人であるという事を、歳を重ねるごとに思い知らされる感じがある。「できた人」「有能な人」では決してないから、「そういう駄目さをカバーしてちゃんと問題の本質に近付くために」いろいろ工夫して仕組みを作っていかないといけない、そんな気がしている。
僕が常々「正確な説明」とか「分かりやすい説明」とかを心がけているのは、そういう思いが根底にあるからなのではないかと思う。本質の理解を避けて表面的な丸暗記だけに留めてしまうと、応用が利かず、教わったたった一つのやり方を、本来適用するべきでない場面にまで適用してしまい、事態を悪化させてしまう危険がある。そう思っているから、僕はシス管系女子ではできる限り「おまじない」的な思考停止を促すような説明を避けているつもりだ。
別の事例で、僕が公開しているコードに寄せられたプルリクエスト に対して、たった1行だけの変更を入れるか入れないかでコメントのやり取りを2~3往復要したというのもあった。Closeする事だけに着目すれば「まずマージして、その後勝手に直す」という対応を取る所だろうけれども、相手が「これは不備ではなく意図的なものだ」という説明をしていたので、これは双方の意図に食い違いがあるのではないかと思い、改めて自分の意図を丁寧に説明して、相手の誤解が解けた事をもって初めてマージするという事をした。余計な手間をかける事に何の意味があるのかと思う人もいるだろうけれども、自分の方がむしろ間違っている可能性もあると考えると、その可能性を排除しないまま自分のやり方を押し通す事が最良とは思えなかった。
こういうのは、難易度が高い事というよりはとても面倒な事で、色々な事に忙殺されているとなおざりにされがちなのだろう。教育の現場なんてその最たる例で、一人一人の児童に寄り添って丁寧にやってたら成り立たないのだろう、だから前述の僕の嫌な記憶は「学校」とセットになっているんだと思う。
状況が許す限り、僕は問題の本質の理解を蔑ろにしたくないし、それと表裏一体で、本質の理解を蔑ろにしなくても済むように、本質にきちんと向き合える状況を維持していきたいものです。
「マンガで分かるGit」等で知られる湊川あい氏が転職を決意したきっかが、Webデザイナーとして就職した会社で人手不足のため裏方から事務まで担当していた時に、本分であるはずの「Webデザイナー」ではなく「細々としたことをしてくれている人」と当時の上司から社外の人に紹介された事だった、というのは何度か語られている事だ。なりたい自分になれていないという事実を突きつけられ、現実を直視し、なりたい自分になるための覚悟を決めたのだという。
この話を聞いて「そりゃひどい。本分じゃないことをあれこれやらせておいて、その上、肩書きとしてすら本分が何であるかを認めないとは。」と思い自分の事のように憤慨したのは事実なんだけど、しかしその一方で、仮に自分が「細々としたことをしてくれている人」と紹介されたとして、同じように感じるのだろうか? というと、どうもそこまでのショックを感じないのではないかという気がしている。
というのも、就職活動の時期に至ってすら自分は「何になりたいのか」がハッキリしておらず、何なら「雇ってくれるならどこでも行きます! 仕事をやらせてもらえるなら何でもします!」くらいの雑な考え方しかできていなかった(今になって思えば、最初の会社の社長はよくそんな僕を雇ってくれたものだと思う)。それに実際入社が決まって以降も、開発業務ではなく広報のお手伝いのようなデザイン業務(チラシ・ポスター製作、Webサイト制作)をずっとしていたし、日経LinuxにOpenOffice.orgの解説記事を連載して挿絵も自分で描いてたし、OOoの解説本は中身は書かなかったけど装丁をやったし、東京に来てからも週の半分くらいはMozilla Japanでマーケティング活動のお手伝いでチラシやポスターや紙袋を作ったり、動画のディレクションをしたり、配布するCD-ROMに入れるFirefox Portableの起動用スクリプトを作ったり、フォクすけをデザインしたりテキストを書いたりアドオンも書いたり、自社にいるときも名刺やチラシを製作したりWebサイトも制作したり、Railsを使った開発案件もやったしSambaのサポート案件で客先を訪問して本番環境でトラブったりもしたし、あとFirefoxやThunderbirdの導入サポート案件やインシデントサポートもやってたし……とにかくもう何でも、やるように言われた事はやってきたので。要求された仕事をこなすことが社会人としての自分の価値、こなせなければ価値は無い、と思っていたから、それを不思議に思うこともなかった。
プライベートでも、最初の趣味は絵を描く事だけだったけれども、中学でプログラミングをやり始め、高校では漫画研究部で漫画やイラストを描きつつWeb標準に傾倒してCSSでのレイアウトに凝り、大学ではWeb標準技術をきっかけとしてMozillaに傾倒しそれまで以上にプログラミングに本格的にのめり込んでいき……と、やる事が一定していなかった。自分の中での「自分は何者である」という肩書きを、僕は原点がそうだったからという理由で「絵描きです」と思っていたけれど、気がつけばいつの間にかプログラミングの比重の方がずっと大きくなっていて、絵も元々細かい描き込みを面倒臭がるような所から「絵を描くのが好き」というわけではなかったようだという事が露呈してきていて、改めて考えてもますます自分が何者か分からない状況になってしまっていて。
突き詰めると僕は、人に誉めてもらえるなら何でもやる見境のない人間であるし、自分が気に入らないと思ったら領分を踏み越えて余計な手出し口出しをしてしまう傲慢な人間であるという事なのだと思う。やったことを「誉めて」もらえるのならどう呼ばれても構わなくて、「この立場として誉められたい、他の立場として誉められたくない」という事の拘りがないのだとも思う。ITコミュニティでアイドル扱いでチヤホヤされる人に対しても、羨ましさを覚えこそすれ、「ああいう風には扱われたくない」とは実際の所思っていない。何なら、自分がイケメンであってアイドル扱いでチヤホヤされるならそういうのも良いなとすら思う。
そんな生き方をしているから、僕は「こういう人です」と一言で言える肩書きを持てていない、迷子になってしまっている。
あるいは別の角度から言うと、僕は自分で肩書きを自称するのを恐れているのだとも思う。「Webデザイナーです」「プログラマーです」「漫画家です」と名乗ったら、その瞬間から「あいつWebデザイナーって名乗ってるけどデザインセンスねえな」「あいつプログラマーって名乗ってるけど全然技術力ないな」「あいつ漫画家って名乗ってるけど全然おもんないし絵も下手だな」というような人格を傷付ける揶揄から逃れられなくなる。それが怖いから肩書きを自分からは名乗れずにいる、という部分は間違い無くあると思う。
そういう恐怖があるから、「自分の職業は戸倉綾です」というような「誰でもない自分」宣言すらもできない。自分の生き方という物にすらもそこまでの強い信念や自身を持てていない、という事実に向き合わざるを得なくなる、それを恐れて逃げ続けているという事なのだと思う。
あとは、ほら、なんだ、その、自称より他称の肩書きの方が欲しいんすよ多分。口コミで評判が広まって客の絶えない店と、広告バンバン打ちまくって知名度上げて客の絶えない店と、どっちがおいしそうに思う?って話ですよ。口コミで評判が広まって勝手に「プログラマー」とか「漫画家」とか呼んでもらえるようになる、そういうのに憧れてるんすよ。 自分で名乗ったらその責任は自分に返ってくるけど、他称だったら自分に責任無いじゃないすか。ねえ?
という事を考えると、どうも、僕の肩書きとして適切なのは、「誉められたがりのくせに覚悟の足らないゲス」とかそんな所っぽい感じです。
肩書きがハッキリしてなくてもそのこと自体に不満を感じていないのは、作った・やった成果そのものまで否定される訳ではないからなんじゃないか、と思ってる。成果を知られてないからもっと知られたいとか、成果に対する評価が聞こえてこないから寂しいとか、絵が下手だとかスキルが低くて作った物がヘボいとか、そういう所での辛さはあるけれども、描いた絵はなくならないし作ったプログラムもなくならない、作った物・やった事が無かった事にはなってないからなんじゃないかな、って。
何年に出版されたと奥付に記されている本が国会図書館に所蔵されているとか、何年に開始されたプロジェクトのGitリポジトリがGitHubにあるとか、成果があったという事実やその証拠が世界のあちらこちらに痕跡として残っている以上、無かった事になるというのはまず無いだろうと思えているので、それで僕は今、精神的な安定を得られているということなのかもしれない。
だから、「お前は何もやってない」とか「お前は絵を描いてない」とか「お前はプログラムを作ってない」とかそういう風に言われたとしたら、その時の方が僕は強く動揺してしまう。実際、「マンガで技術解説してる誰々さんすげえ! 新しい! こんなの他に無い!」と他の人がパイオニアとして賞賛されているのを見ると「うっ……僕も何年も前から連載してるんすけど……」と辛い気持ちになるし、その誰々さんの口から「Piroさんて人も漫画で技術解説してる」と言ってもらえたら、それだけで(その事がその誰々さんの読者層の人達に認知されないままであったとしても)溜飲が下がる。本の表紙に名前が載ってても奥付に名前が載ってなかったらものすごく不愉快な気分になる(実はシス管系女子の1冊目の初版がそうだったのです……何らかのミスでそうなったと聞いています)。
自分が思ってる事実の認識と、現実に起こった事としての事実とが、ずれてないといいんだけど。そこがずれてしまうと、何でもかんでも「わしが育てた」って言っちゃう頭のおかしい人になってしまうので。そうはならないでいたいですね。
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を立ててそれが解決されるのを待つ、という事になっていたでしょうが、今回は「完全でなくても今できる範囲でできる事だけをする」という方針だったため、この点はリリースをブロックする要因にはなりませんでした。
はじめからやり直したい症候群や、それに類する言葉を見かける事があります。プログラミングの世界においても、メジャー製品の大規模アップデートで「ここらで一区切り付けて、最初から作り直してもっとスッキリした設計にしよう」みたいな事をやろうとして、収拾がつかなくなり失敗に終わってしまい、最終的に旧版からの小規模な修正のみに留めるしかなかった……みたいな事はたまにあります。そういった事例をいくつか見聞きして、また自分自身もそういった無謀な挑戦をしようとして挫折するという経験を何度かした結果、「スクラップ&ビルドですべてが上手くいくようになるというのは甘い幻想で、実際には地道な改良をコツコツ続けていくしか無い」という教訓が、自分の中である種の呪いのように刻み込まれていました。
しかし実際には、その「教訓」に従って進めようとした前回の移行は失敗に終わり、大部分でスクラッチに近いレベルでの作り直しとなった今回の移行の方が成功するという皮肉な結果となりました。ただし、今回スクラッチせず引き継いだ部分まで作り直していたら、この移行は再び失敗に終わっていた事でしょう。
今まさに自分が直面している問題がどちらのケースなのか、を適切に見極めるのは難しい事です。考える事を放棄して当て推量で雑な判断をすれば、痛い目を見ることになるのは自分自身です。確実な成功に繋げるためには、そこで使おうとしている・使わなければならない技術の特性と限界、現行技術とのギャップの大きさ、取り組んでいる問題の複雑さなど、様々な要因を考慮に入れた総合的な判断を、プログラム全体やモジュール単位で下す必要がある、という当たり前といえば当たり前の事を、自分は今回の移行を通じて実感した次第です。
去る2017年10月22日、東京・秋葉原UDXにて開催された技術同人誌オンリーイベント技術書典3に、企業サークル「シス管系女子会」として参加してきました。
シス管系女子の草の根的な営業活動とファンサービス、あと〆切を設けることで何か新作を作るモチベーションにするという目的での参加でしたが、結果のほどを記しておこうと思います。なお、技術書典、技術書典2の結果は過去記事をご参照下さい。
今回の数字は以下の通りでした。
ポスター等は前回の使い回しなので、今回新たにかかった費用はイベント参加費(企業扱い)と新刊コピー本の費用くらいです。
当日は台風直撃かつ衆議院議員選挙とも重なるという、前回・前々回を上回る悪条件でしたが、主催者発表によると来場者数は2700人ほどだったとのことで、来場者数に対するムックの販売実績としては前回を上回る比率となったと言えます。
前回の技術書典2では積極的に声かけを行い、サンプル代わりのリーフレットをチラシのように積極的にばらまいていましたが、今回はそこまでの積極的な声かけはせず、スペース前で足を止めて下さった方に「よかったらこちら無料なのでどうぞお持ち帰り下さい」と勧める程度に留めました。これは、リーフレットを増産していなかったためばらまいていると足りなくなると思ったのと、技術書典2までに参加した人にまた配ってもウザイだけだろうと思ったのとが理由です。
新刊が間に合うかどうかすら危うかったので事前のアピールをほとんどしておらず、会場内でもほとんどずっと座ったままだったので、数字は前回からかなり後退するのではと考えていたのですが、商業出版物と新刊の頒布数的には全然そんなことありませんでした(コピー本に至っては昼過ぎになくなってしまいました)。 近くにキンコーズがあるので追加で刷ってこようかとも思ったのですが、宣伝活動と考えたときにこれ以上お金をかける意味があるのかよく分からなくなってしまったため、そのまま増産無しで閉会まで居座っておりました。
今回、技術書典3というイベント自体の新たな試みとして「立ち読み専用スペース」「戦利品確認用スペース」という物が用意されており(自分は行かずじまい)、サークル参加者は見本誌とは別に本を立ち読み用に提出することでそちらにも置いてもらえるようになっていました。
どれくらい効果があるのか?については未知数の状態でとりあえず提出してみましたが、会場内でスペースまで足を運んで頂いて本をお買い上げ下さった方の中に、「上の立ち読みスペースで読んで気になったので」とおっしゃって下さった方がいらしたので、まったく効果無しということはないようです。
今回のイベント参加に当たり、元々はシス管系女子BEGINSの0.3話として新作を用意しようと思っていたのですが、
ということで、突貫作業でWSLの紹介の特別編を制作し、新刊としてリリースしました。 元々、イベントが終わったら公開するつもりだったのですが、昼過ぎの時点で頒布物の在庫が無くなりそうということが見えてきたため、早々に会場内からTwitterにも投稿しました。
ただいま技術書典3参加中❢ 新作特別編の無料コピー本では、なんとあたしがWindowsでLinuxコマンドを使っちゃう⁉「まんがでわかるWindows Subsystem for Linux シス管系女子」全編をここで公開しちゃいます✨ pic.twitter.com/x8pFX3uW0o
- 利奈みんとbot/マンガでLinux! (@sysadgirl_mint) 2017年10月22日
「まんがでわかるWindows Subsystem for Linux シス管系女子」の続き❗ これでWindowsユーザーのあなたも本編中の便利テクニックを活用できちゃう⁉ (※技術的に不正確な部分があったのを訂正しました💦) pic.twitter.com/bU7CQf3KM9
- 利奈みんとbot/マンガでLinux! (@sysadgirl_mint) 2017年10月22日
コピー本として頒布したバージョンと最初に公開したバージョンでは、最後のページでWSLのことを「準仮想化」と表現していたのですが、Twitter上でご指摘を受け、冷静に考えると間に挟まるレイヤーが薄いという点では準仮想化に似ているけれども仮想マシンがいるわけではないのだから「仮想化」と言うのは間違いだったと思い至り、帰宅後に最終ページの内容を大幅に更新してTwitterに再投稿しました。 結果として、前半4ページ分と後半4ページ分のそれぞれのツイートのうち後半の方が多くRTされるというヘンテコな状況が発生してしまいましたが、それだけWSLと他の競合技術との違い・使い分けに皆さん関心があるということなのでしょう。
また、まったくの副作用として、この話を描くにあたって色々調べ直したことで自分自身のWSLへの理解が深まりました。一応Cygwin・MinGW(とMSYS/MSYS2)・PowerShell・仮想マシンとの違いは何となく分かっていたつもりではあったのですが、漫画にするにあたって「どういう絵にすればより適切な表現になるのか?」を考えたことで、今までよりもそれぞれの違いをより具体的に言い表せるようになったと思っています。技術発表や紹介記事はそれを読むだけよりも自分で書く方が勉強になる、ということを改めて実感しました。
例によってTogetterの当日のまとめ等も見てみたのですが、戦利品報告として写真を公開されている方の戦利品ラインナップを見ると「ものすごいディープな内容・商業誌では扱われていないような内容の技術同人誌」が主で、今回のコピー本が写り込んでいる物はほとんどありませんでした。シス管系女子のような「解説対象が枯れた技術で、且つ、ものすごく技術的に高度な事をやっているわけではない、初級者向け(中級者以上へのステップアップ用)の内容」というのは、やはり、技術書典というイベントに台風の中わざわざ足を運んで戦利品報告までするようなアグレッシブな方にとっては魅力的ではないということなのだと思います。
宣伝活動としては、直接会場でリーチできる層以外に、その外側・イベント外にも拡散されるような場を選ぶのが効率的なのですが、「シス管系女子」というコンテンツにとってのそういう場所は一体どこにあるのか、そもそも「ある」のかどうか、むしろ拡散されるに足るコンテンツとしての価値は本当にあるのか、まだ答えは見つかっていない感じです。
以上の通り、商業出版物の広報活動としては今回もあまりパッとしない結果に終わってしまいましたが、上記のような悪条件下にも関わらず多くの方が来場して下さり、また、シス管系女子会のスペースまでお越し下さり、暖かい声援の言葉をおかけ頂けました。普段Webではあまり感想・反応を見かけないため、このように直接「ちゃんと読まれている」ということを実感できる機会は自分にとって非常に大きな意義があり、他の何にも代えられない喜びです。お声がけ頂いた皆様、本当にありがとうございます。
今回、事前の準備を疎かにしていたため頒布物は無料のコピー本だけだったのですが、次回以降の参加に際してはファンサービス的な面にもっと注力し、グッズ類の制作にも手を広げていきたいと思っております。
ということで、技術書典3のご報告でした。
ツリー型タブの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.