Home > Latest topics

Latest topics 近況報告

たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。

萌えるふぉくす子さんだば子本制作プロジェクトの動向はもえじら組ブログで。

宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能! シス管系女子って何!? - 「シス管系女子」特設サイト

Page 2/26: 1 2 3 4 5 6 7 8 9 »

WebExtensionsのSuccessor Tabs APIの使い方 - Dec 03, 2018

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経由でこの挙動に介入する方法がなかったため、例えば「タブを閉じたら、必ずそのタブの直前に見ていたタブにフォーカスを移す」というような事をアドオンで実現しようとすると、

  1. Firefoxによって何らかのタブがフォーカスされる。
  2. その後改めて、別のタブにフォーカスし直す。

という手順を踏む必要がありました。これは見た目に美しくない(一瞬無関係のタブがフォーカスされてしまう)のもさることながら、現在のFirefoxの初期設定である「Ctrl-Tab/Ctrl-Shift-Tabで最近フォーカスされた順にタブのフォーカスを切り替える」という挙動にも悪影響を与えます。

拙作アドオンのツリー型タブも、ツリーの最後の子を閉じたら、右隣のタブ(=下にある別のツリーの親タブ)ではなく1つ前の兄弟タブまたは親タブにフォーカスを移すという機能があり、これを実現するにあたっては前述の点がずっと未解決のままでした。今回のAPI追加によって、ようやくこの問題を解決する目処が立ったと言えそうです。

続きを表示する ...

WebExtensionsのコンテキストメニューの新常識 on Firefox 64 - Nov 06, 2018

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への移行の話をおさらいしてみましょう。

続きを表示する ...

WebExtensionsベースのアドオンが他のアドオンと連携するにはどうするのが良いか? - Nov 06, 2018

Here is the English version of this article. 7月に英語で書いた物の日本語版です。Qiitaにもクロスポストしています

ツリー型タブXULからWebExtensionsに移植した時の話で、アドオン同士の連携が取りづらくなる事への懸念について書きました。この点について現時点での知見をまとめておきます。

続きを表示する ...

WebExtensionsでのタブの複数選択APIのつかいかた - Nov 01, 2018

Tokyo WebExtensions Meetup #3で、標題の通りの発表をしました。スライドはQiitaにありますが、こちらにもクロスポストしておきます。

タブの「選択」状態とは?

タブの複数選択機能が入った事で「選択」という言葉が多義的になってしまったので、まずその点を整理します。 WebExtensions APIにおいては、「選択」という言葉で表されうる状態に以下の2つがあります。

  • アクティブ なタブの事
    • active tab というのがWebExtensions APIの語彙上の言い方
    • current tab という言い方も古くからある
    • foreground tab という言い方もある(background tabとの対比)
  • 選択複数選択) 状態にあるタブの事
    • highlighted tab というのがWebExtensions APIの語彙上の言い方
    • multiselected tab という言い方もされる

「selected tab」という表現は紛らわしいので、このエントリでは使いません。

Firefox本体に入ったタブの複数選択機能

Firefox 63でabout:configbrowser.tabs.multiselecttrueにすると試せます。Chromeでも同じ操作ができます。

  • タブの上でShift/Ctrl(Command)-クリックでタブを選択できます。
    • 選択されたタブは、上に青い線(Linux版ではシステムカラーに依存する。UbuntuのAmbientテーマではオレンジ色)が出る。
      (スクリーンショット)
  • タブが選択されていると、コンテキストメニューほか一部の機能が「選択中のタブすべてについてそれをする」ようになります。 (スクリーンショット:メニューのラベルが「Tab*s*」になっている)
    • まだ一部の機能はそのようになっていない。Firefox 64で一通りの機能がすべてタブの複数選択に対応するという事だと予想される。

アドオンからの2つの利用局面

WebExtensionsベースのアドオンにとっては、このタブの複数選択機能に対して2つの関わり方があります。

  • 選択されたタブに何かする
    • タブを処理対象にした便利な機能を提供するアドオンの場合
  • タブを選択する
    • タブを管理する、タブバーの代替となるアドオンの場合

それぞれ順番に紹介します。 なお、基本的にはChromeの拡張機能でも同様のAPIが使えます。

続きを表示する ...

Various "custom context menu" usages in Firefox 64 - Oct 26, 2018

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:

  1. Extra context menu items for custom commands, when your addon has no sidebar/popup panel
  2. Extra context menu items for custom commands, grouped under a submenu with a custom label
  3. Context menu dedicated to custom commands on your sidebar/popup panel, and expose them as a submenu on other situations
  4. Context menu dedicated to custom commands only on your sidebar/popup panel
  5. Context menu dedicated to custom commands on your sidebar/popup panel, and expose them as a submenu on other situations, with a custom label
  6. Imitated context menu compatible to Firefox's one on tabs or bookmarks, only on your sidebar/popup panel

All following examples assume that your addon named "Bucket" provides ability to send tabs to an online bucket, like the "Pocket".

続きを表示する ...

An improvement of WebExtensions on Firefox 64 about implicit collaboration of addons - Oct 14, 2018

このエントリの日本語版はこちらから読めます。

(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.

続きを表示する ...

What's the best way to collaborate an WebExtension-based addon with others? - Jul 28, 2018

このエントリの日本語版はこちらから読めます。

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.

続きを表示する ...

Why I won't migrate some addons from XUL to WebExtenions? - Nov 19, 2017

この記事の日本語版があります。

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".

It was easy on legacy XUL addons, but not easy on WebExtensions

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.

(So I had to reimplement the tab bar completely including the context menu itself on tabs, for Tree Style Tab 2.0 and later.)

Is there any reason to do migration overcoming the 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.

  • If I strongly depended the addon, it is the largest reason for migration. Actually, I depended on "Tree Style Tab" and other already migrated addons and I really needed them, thus I migrated them - despite they lost some features from restrictions of WebExtensions APIs. On the other hand, I don't depend on "2 Pane Bookmark" - I ordinarily use "Bookmarks" toolbar button and its dropdown menu.
  • If it is a technically-interesting challenge, I possibly try to do it. However, I'm not interested in WebExtensions-Based 2 Pane Bookmarks - it won't be funny, ther is only tiresome.
  • If it is requested as a business on my employer company, I'll do that. But "2 Pane Bookmarks" seems not for company use, just for private. Sadly no company customer seems to hope its migration.

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.

Conclusion

Still there are some more unmigrated addons I depended on:

  • Second Search: I really want a search box to type arbitrary terms into, and I hope to search it by any web service after the input. Firefox's built-in feature "One-Off Search" does that, but it doesn't list search services I created as bookmarks with keyword. I think I need to provide new search box for bookmarks with keyword.
  • Informational Tab: especially thumbnail in each tab. It is impossible for Firefox's native tabs, so I'll implement it as an addon to extend Tree Style Tab.
  • New Tab from Location Bar: I don't know its technical possibility, but it is not supported by Firefox itself yet and it won't be done in near feature, so I need to try that.

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.

僕自身の手によるWebExtensions版開発が期待できないアドオンについて - Nov 19, 2017

There is the English version of this article.

2ペインブックマークをFirefox 57以降に対応して欲しいという要望を頂くのですが、残念ながら結論を先に言えば、僕の手による2ペインブックマークのWebExtensions移植はまず行われないと思って頂く他ありません。同様に、他にもいくつかのアドオンについて僕の手による移植は期待できない状況です。

以下、2ペインブックマークの場合を例にとって、何故僕の手による移植が期待できないかを説明します。

従来型アドオンでは簡単にできて、WebExtensionsでは簡単にはできない、という事がある

Firefoxの内部に食い込む従来型アドオンでは、そのような仕組みならではの特徴として、「Firefox本体が使っている仕組みを(APIとして整備されていないものでも)勝手に流用できる」というメリットがありました。

2ペインブックマークは元々、その特徴を最大限に活用して開発されたアドオンでした。 実際、Firefox本体のブックマークツリーのためのコードをほぼそのまま流用し、そこに「フォルダだけ表示するための絞り込み」と「選択されたフォルダの中身だけを表示するための絞り込み」という一手間を加えることによって、それ以外の事をまったくしなくても最小限の労力で実現できる、という背景があったことから成立していました。 というか、「簡単にできるのならやってみよう」というのが動機で作り始めたと言っても過言ではないです。

他方、WebExtensionsではそのような事はできません。ブックマークの列挙、表示、ドラッグ&ドロップ時の動作(ブックマークのツリー上に何かがドラッグ&ドロップされた時の対応も含む)、右クリックされた時のメニューそのもの、その中身となる各種のコマンド、などなど、ブックマークに関わるすべてをゼロから作り上げなくてはなりません。 つまり、実質的には「ブックマークパネルを再実装する」という事になります。これは大変な労力です。

(ちなみに、そういうわけなので、既にWebExtensions移植済みのツリー型タブのバージョン2.0以降も、Firefoxのタブバーに等しい物を、そこで表示されるコンテキストメニューやそれに関わるAPIも含めて、ひたすら愚直に再実装しているという事になります。

面倒さを乗り越えて開発するだけの動機の有無

上述したとおり、2ペインブックマークは「簡単にできるのならやろう」という動機で始めたと言っていいプロジェクトです。 簡単にはできない事が明白である以上、最大の動機がそこにはありません。 それでもなお僕がアドオンのWebExtensions版を開発するとしたら、それ以外の動機が必要です。

  • 自分がそれを使っていて強く依存しているかどうか。「ツリー型タブ」をはじめとして既にWebExtensionsに移行済みのアドオンは、自分自身がそれに強く依存しており「自分自身がユーザーとして、無いと困るので」、そのような強い動機から、APIの制限のために移行できない機能がある事を承知の上でWebExtensions版を作成しました。残念ながら2ペインブックマークは自分自身にそこまでの思い入れがありません(自分は普段はツールバー上のブックマークボタンからの一覧表示だけを使っています)。
  • 技術的な挑戦として面白そうかどうか。上述したとおり、従来型アドオンとしての2ペインブックマークは「最小限の労力でこれを実現できる」という事が自分にとっては技術的に面白い部分でしたが、WebExtensions版は「ひたすら面倒なだけで、特に技術的に面白いわけではない」という事が既に分かっています。自分にとってはこれは魅力的に感じられません。
  • お金をもらえる仕事かどうか。自分が使っていなくても、技術的に面白くなくても、会社の仕事として受注したものであればそれはもちろん仕事としてお引き受けします。が、残念ながら拙作のFirefox用のアドオンはそのほとんどが個人利用向けであることから、そういった展開には期待できません。

そもそも漫画の作業のためにプライベートの時間をあまり割けない状況下で、それを押してでもやる動機が無い以上、僕の手による移植が行われることはまず期待できない、というのが現状での結論ということになります。

まとめ

今の所、僕が手がけたアドオンの中でまだWebExtensionsになっていないもののうち、自分自身が強く依存していた物は、以下の通りです。

  • セカンドサーチ(入力欄に思いついた検索語句を入力したら、その後で検索エンジンを選択して検索を実行できる、という機能。Firefoxの検索エンジンについてはOne-Off Searchでそれが可能だが、ブックマークキーワードとして登録した物に対してはそれができないため、ブックマークキーワード用の検索窓を用意する必要があります。)
  • 情報化タブ(の中の、タブのサムネイル表示機能。これはFirefox本体のタブに対しては絶対に実現不可能なので、ツリー型タブと組み合わせて使う想定です。)
  • ロケーションバーから新しいタブを開く(技術的な実現可能性は不明ですが、Firefox本体でこれと同等のことを可能にする設定が実装されるのがまだまだ先の事になりそうなので、それまでの当座を凌ぐために挑戦してみたいと思っています。)

すみませんが、これら以外の物については、必要としていて作る動機がある方に後継版開発の希望を託したいと思っています。

テキストリンクのWebExtensions移行、失敗と成功の分かれ目について - Nov 04, 2017

このエントリはQiitaとのクロスポストです

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ベースのアドオンを新規に開発する(つもりで臨む)」というやり方を取りました。具体的には以下の順で作業を進めました。

  1. 「選択範囲とその前後のテキストの抽出」といった必須の要素技術をコンテントスクリプトで実装する。
  2. 文字列からのURI文字列の検出という、純粋にJavaScriptのコードだけで完結できるモジュールを、従来版から選択的にバックグラウンドスクリプトとして移植する。
  3. ダブルクリック操作をトリガーとして、コンテントスクリプトで可視状態のテキストを抽出し、それをバックグラウンドスクリプトに送ってURIを検出させて開く、という形で最も基本的な機能を実装する。
  4. 開いた後のURI文字列を選択範囲にする、という挙動を実装する。
  5. 選択範囲の変化をトリガーとして、コンテントスクリプトで可視状態のテキストを抽出し、それを材料にバックグラウンドスクリプトでURI文字列の検出とコンテキストメニューの更新を行う、という一連の事を実装する。
  6. テキスト編集領域内での動作に対応する。
  7. 設定画面を実装する。

この説明にも表れている通り、今バージョンのほとんどのコードはスクラッチで書いた物で、旧版からコードを引き継いだのは「文字列の中から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の検出処理

与えられた文字列からURIを検出する処理は、今回の移行で唯一、旧バージョンからほとんどそのままコードを引き継いだ箇所です。

この処理は、データ量にして最終的に完成したWebExtensions版テキストリンクの全体の1/3を占める巨大なモジュールなのですが、やっている事は至って単純で、「相対パスも許容するのか?」「全角英数字も許容するのか?」等の条件に基づいてURIらしき文字列にマッチする正規表現を組み立てるだけの物です。相対パスを絶対パスに解決する部分などでXPCOMコンポーネントの機能を使っている部分はありましたが、全体の中ではそういった箇所はごく一部に限られており、ほとんどは単純な文字列処理だけで構成されていました。

また、元々自動テストを容易にするためにモジュール間の結合を弱めていた結果、機能を使うのに必要なパラメータはほとんどすべて外部から与えられるようになっており、旧テキストリンクの実装の中ではここだけ突出して浮いているようにも感じられるほどでした。

そういう事情があったため、このモジュールについてはほとんど苦労する事なくほぼそのままの形でWebExtensions版に組み込む事ができました。

ダブルクリック操作で開いた後のURI文字列を選択範囲にする

旧版テキストリンクでコンテントプロセス側に大部分の処理があったのは、「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.menusbrowser.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を立ててそれが解決されるのを待つ、という事になっていたでしょうが、今回は「完全でなくても今できる範囲でできる事だけをする」という方針だったため、この点はリリースをブロックする要因にはなりませんでした。

まとめ

はじめからやり直したい症候群や、それに類する言葉を見かける事があります。プログラミングの世界においても、メジャー製品の大規模アップデートで「ここらで一区切り付けて、最初から作り直してもっとスッキリした設計にしよう」みたいな事をやろうとして、収拾がつかなくなり失敗に終わってしまい、最終的に旧版からの小規模な修正のみに留めるしかなかった……みたいな事はたまにあります。そういった事例をいくつか見聞きして、また自分自身もそういった無謀な挑戦をしようとして挫折するという経験を何度かした結果、「スクラップ&ビルドですべてが上手くいくようになるというのは甘い幻想で、実際には地道な改良をコツコツ続けていくしか無い」という教訓が、自分の中である種の呪いのように刻み込まれていました。

しかし実際には、その「教訓」に従って進めようとした前回の移行は失敗に終わり、大部分でスクラッチに近いレベルでの作り直しとなった今回の移行の方が成功するという皮肉な結果となりました。ただし、今回スクラッチせず引き継いだ部分まで作り直していたら、この移行は再び失敗に終わっていた事でしょう。

  • 「ああもうめんどくさい、リセットしちまおう」と乱暴に投げ出してしまわず、小さな改良の積み重ねで乗り切るのが適切な場合
  • 「これは小さな変更で乗り切れるようなレベルの話じゃない」と割り切って、一からやり直す方が適切な場合

今まさに自分が直面している問題がどちらのケースなのか、を適切に見極めるのは難しい事です。考える事を放棄して当て推量で雑な判断をすれば、痛い目を見ることになるのは自分自身です。確実な成功に繋げるためには、そこで使おうとしている・使わなければならない技術の特性と限界、現行技術とのギャップの大きさ、取り組んでいる問題の複雑さなど、様々な要因を考慮に入れた総合的な判断を、プログラム全体やモジュール単位で下す必要がある、という当たり前といえば当たり前の事を、自分は今回の移行を通じて実感した次第です。

Page 2/26: 1 2 3 4 5 6 7 8 9 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき