Home > Latest topics

Latest topics 近況報告

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

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

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

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

Why I don't provide "disabe animation of tab dragging operations" feature for Tree Style Tab? - Nov 06, 2013

This is the English translation of my another entry.

I decided to reject a pull request for TST, adding new secret preference to disable animation effects around drag and drop of tabs, because it contradicts the principle of the TST project. This entry describes why I rejected the pull request.

Why I introduced such an animation effect?

Because it is introduced by Firefox itself. After the animation effect is activated on Firefox, I updated TST to follow it. However, it was unwelcome update for some people and I got many requests like "it is hard to operate tabs with drag-and-drop", "I want an option to disable animation effects during tabs are dragged." Actually, I can see two issues on the issue tracker:

The patch of the pull request adds a secret preference to do it. It is enough small and clear. If I added the option, I wrote just same patch.

But I disagree to merge the pull request, because I think that the approach of the patch is similar to a story: "Firefox's Gecko engine is too buggy and less compatibility to WebKit, so why don't you delete all codes of Gecko and introduce WebKit with Firefox-like UI?" In other words, it is very easy to add new option which is requested by people, however, I'm extremely reluctant to do it beacuse it is opposed to my polify on Tree Style Tab project.

Basic premises and my policies.

Basically, this project depends on Mozilla Firefox project --which is very large and uncontrollable by me-- and it is unavoidable to be tossed up and down by the storm of changes in Firefox. Actually, on my another project, I had to rewrite the addon for new versions of Firefox again and again. I learned through the bitter experience that I should have some strict policies on my addon projects:

  • Don't re-implement a feature included in Firefox itself. For example, Tab Mix Plus has its own session management mechanism, because TMP project is started before the session management feature is introduced by Firefox itself. If the TMP project was started after that, they project team didn't implement such a custom session manager. There is no merit to implement a custom feature which conflicts to Firefox's.

    To reduce maintenance cost, and to keep better compatibility with other addons which are developed based on Firefox's APIs, I think I should update and re-construct my addons for new APIs introduced by Firefox. It is better than I struggle to keep old custom implementations against Firefox's changes.

    • However, sometimes I decide to keep my custom implementations, when it is hard to rewrite codes for Firefox's new API for me.

      For example, TST uses a library "JSDeferred" to process asynchronous operations easily. On the other side, lately Firefox uses Promise and Task for the same purpose. I know I should rewrite TST based on them instead of JSDeferred, but I still don't do it because: 1) TST is strongly designed based on JSDeferred. 2) Promise/Task are not available on the current ESR (Firefox 17). (In other words, I'll merge pull requests to do such a reconstruction, if there is no disadvantage about compatibility with other addons.)

    • When the API of Firefox's library is too untrustworthy for me, I decide to use my custom library based on very low stable APIs.

      For example, Firefox has a system named "preference" which can save/restore users' configurations. Because it is not developer-friendly (ex. there are three deferent types --boolean, integer, and string--, and hard to observe changed configurations dynamically), Firefox provides some libraries like FUEL. But, such libraries are untrustworthy and risky for me because Firefox team sometimes changes those APIs despite they promised those APIs are developer-friendly --obviously they should be stable and safe--. I don't want to spend time to update my codes for such unstable APIs, so I actually use my custom library modules/lib/prefs.js which is based only on very stable low APIs. It is one of reasons why I don't update my codes for Promise/Task yet.

      Anyway, I think that FUEL APIs are untrustworthy because Firefox team created FUEL just for third party add-on developers, not themselves. Because Promise/Task are used everywhere in Firefox, Firefox team will keep them stable for themselves.

  • Don't depend on deprecated features. For example, "E4X" became obsolete on lately Firefox. TST used E4X in some cases, so I had to decide that I keep E4X for TST or I give up. Some projects (not mine) decided to re-implement E4X by themselves, but I decided to give up and rewrite codes without E4X.
  • Don't include features not related to the main concept. The basic concept is: what I want to use, one feature per one addon, and, as minimum as possible.

    In my old blog entry (note: written in Japanese), I told that: features which I never use or unrelated to the main concept may satisfy users in the short term, but it will shorten life of the project in the long term. Basically I develop and publish my addons on GitHub because I need it and I want to keep it available for me. So I don't want to introduce changes which can disrupt the concept.

  • Provide higihly compatible, natural look-and-feel for Firefox's built-in features and other addons. For example, Firefox has a feature "auto hide toolbox" for the fullscreen mode started by F11 key. And, TST also provides "auto hide tab bar" feature. Yes, it is not related to "tree" feature. But if TST doesn't have the feature, you'll see unexpected vertical tab bar in the fullscreen mode. You press F11, you expect that the web page becomes fullscreened, then the vertical tab bar should not appear on the scene.

    This is the main reason why I took much time to update TST to support drag-and-drop animation effects. Before the animation effect is introduced to Firefox, I respected behaviors of drag-and-drop around layers and objects on Adobe Illustrator. This behavior is still available when you drag a link to the tab bar.

Reasons why the pull request is unacceptable for me.

Based on the above policies, I disagree to merge this pull request to TST's master, because:

  • Firefox has no option to disable animation effect of dragging tabs.
    • There are different and large codes for both cases: with and without animation.
    • However, animation-less operations in rare cases seem to be inhospitable (for me), contrastively basic operations with animation are developed actively.
    • So I forecast that codes for animation-less operations can be removed from future versions of Firefox. It is risky that I develop TST strongly based on such a disappearing implementation.
  • Because there is no option to enable/disable animation effects, I think that Firefox team basically designs Firefox to do drag-and-drop operations with animations. Then, I should keep TST along the design of Firefox itself.

    • I believe that animation effects in GUI often provide better user experiences. I'm affirmative about such a policy of Firefox project.
    • Actually disabling animation effects by userChrome.css or other ways sometimes break Firefox itself. For example, the internal operation to finalize closed tabs uses "TransitionEnd" DOM event to trigger itself, but it didn't workcorrectly because the event was never fired if the animation efect was disabled by userChrome.css. (I don't kwnow the bug is already fixed or not.)

      I don't think my addon should be specially hospitable for people who live without animation effects, because Firefox itself disfavors them.

  • I disagree to add new option to disable animation effects, for a request like "it is hard to operate tabs with animation effects." Instead, I think I should improve usability of tab operations with animation effects, to reduce frustrations around such operations.
    • If you don't have to operate tabs manually, you won't suffer from animations. If you have to operate tree of tabs manually to put them as you want, then TST should build tree of tabs automatically more intelligently to set you free from stressful manual operations.
    • If it is hard for you to drop the tab to the favorite position with animation effects, then TST should make it easy, instead of disabling animation effects.
  • Currently I have no plan to use Firefox without animation effects. I don't want to maintain codes for a feature I never use.
  • Of course, disabling animation effects is useful for some people who suffer from visual transitions, about accessibility. However, remember, Firefox itself is not providing ability to disable animations, for such people. And, this addon is "Tree Style Tab." I think TST should not include custom accessibility features which is missing on Firefox itself --it should be done by Firefox project--.
    • If you seriously suffer from the problem, you should file a bug for the issue, induce developers to implement ability to disable animation effects. Or, you should create a new addon which is based on a simple concept: disabling animation effects. (Note, I never develop such an addon because I never use it!) The user script zzzz-removeTabMoveAnimation.uc.js for userChrome.js seems to be written for the purpose.

The conclusion: fork this project freely.

This is just my personal, current opinion. Of course I don't think this is the final truth of the topic. If you have information which can solve my worrying, or if you explain compelling reasons that I should do it, then I possibly merge such a change.

Otherwise, I'm sorry but I never merge such pull requests to my master repository. Then please fork this project, extend, maintain, and release it for people who have same distress - it is my stance on this project. To keep my codes forkable -- this is one of reasons why I distribute all codes of TST under OSS licenses.

ツリー型タブにおける、タブのドラッグ時のアニメーション効果についてのポリシー - Sep 13, 2013

English version of this entry is available.

ツリー型タブに頂いたプルリクエストについて、ポリシー上の理由から取り込めないなあと思ったのだけれども、これまで、参照しやすい形でまとまった文章としてポリシーを表明していなかった気がするで、何故この変更を取り込まないか(タブをドラッグ&ドロップするときのアニメーション効果をOFFにできるようにしないのか)の判断理由を頑張って文章にしてみました。アーカイブとして、少し手直し&追記してこちらにも転記しておきます。

Firefox本体の挙動としてタブのドラッグ&ドロップ操作にアニメーション効果が適用されるようになって以後、ツリー型タブにも、その挙動に合わせるための変更を随時行ってきました。

しかしながら、この変更は万人にとって望ましい結果をもたらしたとは言えず、「タブをドラッグ&ドロップで操作しにくい」「タブのドラッグ&ドロップ操作時のアニメーション効果を無効にできるようにして欲しい」といった意見・要望が何度か寄せられています。今すぐにパッと挙げられるだけでも、Issue Tracker上には以下の2つのIssueがありました。

メールを通じて寄せられた意見も合わせると、無視できない数の人がこの件に関心を持っているようだ、という印象を僕は持っています。

冒頭に挙げたプルリクエストは、ドラッグ&ドロップ時のアニメーションを無効化する設定をツリー型タブに加える、という物でした。変更箇所は最小限で、内容もおかしなことをしている部分はなく、コードとしてはそのまま取り込んで全く違和感のないパッチです。

ですが、自分の認識としては、このパッチで示されている方向での対応というのは言わば、「FirefoxのGeckoエンジンはバグだらけだしWebkitと互換性が低いから、Geckoを全部消して、WebkitにFirefox風のUIを付けた物を次のバージョンのFirefoxにしますよ」というような物なのではないか、と考えています。……という例えは極端なのでもう少し普通の言葉で言い直すと、「確かに皆が望む通りに対処することは簡単なのだけれども、プロジェクトの運営ポリシー的に、この点で皆が望む通りの対処の仕方をする事には非常に慎重である」ということになります。

大前提として、このプロジェクト(ツリー型タブというアドオンの開発プロジェクト)は、Mozilla Firefoxという自分には制御のできない別のプロジェクトに依存している・振り回される事を避けられないプロジェクトである、という事をまず押さえておく必要があります。

ツリー型タブ以外も含めた自作のFirefoxアドオン全体で、Firefoxの仕様変更に追従するために大きな変更が必要だったことが過去に何度かあり、その時の苦い経験から、現在のところ、自分はこれらのアドオン開発のプロジェクトにおいて以下のような方針を立てるようにしています。

  • Firefox本体で実装している物と同じ目的の物は、できる限り、アドオン側で独自に実装し直さない。
    例えばTab Mix Plusはタブのセッション管理機能について、Firefox本体にそのような機能が入る前から開発されてきたという経緯から、Firefox本体のセッション管理機能とは全く別のセッション管理機能を持っています。が、今新たにTab Mix Plusを開発するとしたら、独自の仕組みを持つメリットは全くありません。 メンテナンスコストの削減と、「Firefox本体の機能と親和性が高くなるように開発されている」他のアドオンと併用したときの相互運用性を高く保つという、2つの観点から、独自の仕組みをメンテナンスし続けるよりは、Firefox本体で実装された仕組みにうまく載っかるように改修する道を選ぶべきであると、自分は考えています。
    • ただ、アドオン内の各部分が独自の仕組みに強く依存した設計になっており、Firefox本体で採用された新しい仕組みに基づく形に改修するのが非常に難しい(工数が大きい)場合には、独自の仕組みを保ち続ける選択を取る場合があります。
      例えば、TSTでは非同期な処理と非同期な処理の連携をうまく取るために、JSDeferredというライブラリを使用しています。一方で、最近のFirefoxではPromiseTaskといった仕組みが導入されており、本来であれば、JSDeferredではなくこれらに基づく形で全体を作りなおすべきところです。が、ESR版Firefoxではまだこれらが使えないという事情もあって、移行はできていません。
      (逆に言うと、そのような手のかかる変更に現在のところ自分は着手できていないが、問題視はしているということで、この点を解消するプルリクエストであれば、他のアドオンとの互換性などいくつかの懸念が払拭できていれば、是非とも取り込んでいきたいと思っていると言えます。)
    • Firefox本体で提供されているライブラリのAPIの安定性がいまいち信頼できないという場合、そのより低位の、より変更が少ないと予想されるAPIに基づいて、自分でライブラリを作成して使用しているという場合もあります。
      例えば、Firefoxではユーザ設定を保存する機能としてpreferenceという仕組みがありますが、真偽値・整数値・文字列値という型の違いがあったり、設定の変更を監視するのが面倒だったりということで、より簡単に使えるようにするためのライブラリをFirefox自身がいくつか提供しています。FUELもそのひとつです。しかしながら、それらの「シンタックスシュガー」的な用途で作られたライブラリは、ライブラリ自体のAPIが彼らの都合でコロコロ変えられてしまうリスクが結構あり、それで泣かされるのは非常にアホらしいという認識が自分にはあります。なので、僕は自作のアドオン群では、preferenceを扱う最も低いレイヤのAPIであるnsIPrefBranchを直に操作するライブラリ(modules/lib/prefs.js)を自分で作って使っています。
      PromiseやTaskへの移行に踏み切れていない理由の1つとしては、このような懸念もあります。が、FUELのようなライブラリがFirefox内部でほとんど使われていないために捨てられてしまうリスクがある(彼らは、自分が使う物は大事にメンテナンスしますが、「アドオン作者が使うように」といった感じでお仕着せの便利ライブラリとして用意した物については、彼ら自身が使っていないので冷遇しがちという印象が僕にはあります。)のに比べると、PromiseやTaskはFirefoxの中の深い部分で既に多用され始めているので、今更捨てられてしまうリスクはそれほどには高くないかな……という目算もあります。
  • Firefox本体で廃止が決まっている機能、廃止される見込みがある機能には、なるべく依存しない。
    例えば、記憶に新しいところではE4Xの廃止というトピックがありました。文字列のヒアドキュメント代わりにE4Xを使用している箇所が、E4Xの廃止で動かなくなるという事に対して、E4Xを諦めるか、E4Xを使い続けるのか、という2つの道があり得ました。プロジェクトによっては、E4Xを自前で実装し直すことでそれ以外の箇所の変更を最小限に留めるという道を選んだところもあったようですが、自分は、E4Xを捨てる道を選びました。このあたりの判断については過去に会社のブログで詳しく書いていますので、そちらも見てもらえると幸いです。
  • プロジェクトのコンセプトから外れる事は、なるべくしない。
    コンセプトは「自分が使いたいものを、1機能につき1アドオンとして、最小の形で開発する」ということ。
    過去のエントリで詳しく書いていますが、自分が使わない機能や、コンセプトから外れる機能を盛り込むことは、短期的にはユーザの満足度を高める結果に繋がる可能性がありますが、長期的には、プロジェクトの寿命を縮めるリスクを増やす事だと自分は考えています。GitHub上に置いてある僕のアドオンは、基本的にはどれも「自分が使いたいから・使い続けたいから」開発をしているので、自分が使い続けにくくなる可能性が高まる変更は、なるべく取り込みたくないと考えています。
  • 細かい挙動はFirefox本体の本来の挙動に可能な限り合わせる。違和感が無いように作り込む。
    例えばFirefox本体の機能として、F11キーでフルスクリーン表示に切り替えた時に、ツールバーが画面の外に隠れるというものがあります。ツリー型タブには「タブバーを自動的に隠す」機能がありますが、単機能だシンプル化だと言っておきながら一見するとツリーとは関係ないこのような機能を未だに含め続けているのには、このF11でのフルスクリーン表示時の挙動をFirefox本体の挙動に自然にマッチするようにしたいからというのが大きな動機としてあります。
    タブのドラッグ&ドロップ操作のアニメーションに、ツリー型タブでそれなりの労力を割いて対応している最大の理由も、これです。(Firefoxにタブのドラッグ&ドロップのアニメーションが実装されるまでは、UIが似ているAdobe Illustratorのオブジェクトとレイヤーの操作性を参考に作り込んでいました。これは今でも、タブ以外のオブジェクトをタブバー上にドラッグした時に見ることができます。)

以上を踏まえてこのプルリクエストで触れている問題を考えると、自分は以下の理由から、この変更を行わない方が良いという考えを持っています。

  • Firefox本体に、タブのドラッグ&ドロップ時のアニメーションをOFFにする機能が無い。
    • アニメーションするかしないかでFirefox内でのコードパスが大きく異なり、どちらもそれなりに大きな規模である。
    • が、アニメーションのための開発が活発に行われていたのに対して、アニメーション無しのドラッグ&ドロップについては冷遇されている(気が、自分はする)。
    • ということは、今後、アニメーション無しのドラッグ&ドロップは実装が削除される可能性もある(と、自分は見ている)。 削除される可能性がそれなりにある実装に強く依存してしまうのは、リスクが高い。
  • ON/OFFの設定がないという事は、Firefox的には、タブのドラッグ&ドロップは基本的にアニメーションするものであるというスタンスだと考えられる。であれば、Firefoxに依存するプロジェクトは、Firefoxの方針に従っておくのが得策であろう。
    • アニメーション効果を適用することは、見た目のキレイさだけでなく、使い勝手の向上にも関係するので、自分はそのようなスタンスを取ることには肯定的である。
    • アニメーション効果をユーザが無理矢理無効化すると、正しく動かなくなる、という部分がFirefoxの中にすらある。
      具体的には、タブを閉じる処理は内部でのデストラクタを走らせるトリガーとして「アニメーションが完了した」というイベントを使っており、アニメーションが無効化されているとデストラクタが走らないのでタブがきちんと閉じられないという問題があった(今その問題がどうなっているかは把握していない)。
      Firefox本体の側がアニメーションOFFでの利用をここまで冷遇している以上、そのような使い方をこのアドオンで手厚くサポートする必然性を、自分は感じない。
  • 「アニメーションがあるとタブをドラッグ&ドロップで操作しにくい」、という要望に真正面から「アニメーション効果をOFFにできるようにしました」と応えるよりも、そういう要望が出にくくなるような形での解決を図るべきだと考えている。
    • そもそもドラッグ&ドロップで操作する必然性が薄ければ、問題なくなるのではないか。手動でツリーを編集しなければ意図した通りにならない、という状況があるのなら、ユーザに手動でのツリー編集という手間を強いる方向よりは、ツリー編集しなくてもストレスを感じないように、ユーザの意図を汲んで自動的にいい感じにツリーを構築するようにした方が、より望ましいのではないか。
    • アニメーション有りの状態でドラッグ&ドロップすると狙った位置にドロップしにくい、という問題があるのであれば、それは、アニメーションをOFFにすることで対処するのではなく、アニメーション有りでも狙った位置にドロップできるようにするのが、正しい解決ではないのか。
  • 僕自身は、アニメーションをOFFにして使うつもりがない。自分が使わない機能に対するメンテナンスコストを抱え込みたくない。
  • 視覚的に連続した変化を認識しにくい、アニメーションされた方が却って認識しづらくなる、といった性質あるいは障害を持っている人がいる、という事を考慮すると、アニメーションをOFFにできるようにすることは、アクセシビリティ上意義のある事だと思う。
    が、Firefox本体がアニメーションをOFFにできるようになっていない以上、「タブをツリー表示できるようにする」というコンセプトのこのアドオンが、独自にアクセシビリティ上の配慮を盛り込むのはおかしいのではないか。
    • この点を真面目に考えるのであれば、「FirefoxのBugzillaで問題提起して、Firefox本体の側でアニメーション効果のON/OFFをできるようにする」あるいは、「Firefoxのアニメーション効果を無効にするという1つのコンセプトに則ったアドオンを開発する」という道を選ぶべきだと、自分は思う。(アドオンではないが、userChrome.js用のzzzz-removeTabMoveAnimation.uc.jsというスクリプトは、そのための物と言えそう。)

以上の事は、あくまで現時点での自分の考えがそうであるというだけで、絶対普遍の真理であるとは考えていません。ここに挙げた数々の懸念点を払拭できる材料があったり、あるいは、そのような懸念があってもなおこのようにするべきであるという強い動機があって、僕が同意できるのであれば、変更を取り込むことにはやぶさかでないです。

でも、そうでない限りは、この種の変更については「フォーク版を作ってそっちで自由に開発していって下さい。そして、その機能を必要とする人自身の手でメンテナンスしていって下さい。」というのがこのプロジェクトの方針ということになります。僕がこれらのアドオン群にオープンソースライセンスを適用しているのには、そのような自由を保障しておきたかったからという理由もあります。

冷たいようですが、どこで線を引くのか・何を基準に線を引くのかを熟慮した結果、このような線引きに落ち着いたということで、同様の不満を抱いている方々にはどうかご理解を頂ければ幸いです。

Tabs Outlinerとツリー型タブ - Sep 21, 2012

Tabs OutlinerというGoogle Chrome/Chromium用拡張機能がある。説明をよく読むと、Firefoxでツリー型タブを使っていた人が、Google Chromeに乗り換えるにあたって作った物らしい。

以前試した時は、リンクからタブを開いてもツリーが構築されないというのが「えっ」という感じであんまりよく試さないでアンインストールしちゃったんだけど、今日なんとなくまたインストールしてオプションを開いてみたら「Tree Style Tabs」ってチェックボックスがあって、そこに書いてあった説明を見たら「これONにしたら自動でツリーになるようになる」的な感じっぽかったので試してみたら確かにリンクから開いたタブが勝手に子タブになってくれた。

ということで、ツリー型タブにロックインされてしまってるせいでFirefoxからGoogle Chromeに乗り換えられないという人(時々そういう話を目にするのです)にとってこれはマジに救世主になるかもしれないなと思ってもうちょっとだけ試してみてた。タブの切り替えがダブルクリックじゃないとだめだったり、他の拡張機能との連携はさすがにできないっぽかったり、Google Chrome本体のメニューから終了したらツリー構造が保存されなかったりで、僕自身が乗り換える先としては厳しいなあ……とは感じたけど、そういう所が気にならない人なら、普通に使えるんだと思う。

そんな「Tree Style Tabs」モードなんだけど、チェックボックスの下にすごく長い注意書きが付いてた。僕が読み取れた大意としては、「そういう要望がものっそ沢山寄せられるから機能を付けてるけど、自分(作者)はこのモードはお薦めしない。試してみてもいいけど、このモードだけで使うんじゃなくて、このモードをOFFにしたTabs Outliner本来の状態でも是非使ってみて欲しい。」ということが書いてあるようだった。同様のことがTabs Outlinerの配布ページにも書いてあって、作者の人によると、ツリー表示は情報の一覧性が悪くて、フラットなタブのリストの方が可読性が高いぞ、と。ツリー、ずいぶん嫌われたもんですね……

この見解の違いは、タブとタブのツリーという物をその人がどう捉えているのか? という所から生じているのだと思う。

僕にとって、タブのツリーは(生存期間は短いけれども)ある情報を起点にしてあちこちさまよい歩いた足跡そのものであって、どのページからどのタブへ辿り着いたのかが視覚化されているということが非常に重要だ。僕はその関係性を目で追って「分かれ道になったノードはどれだったっけ」と探している(ちなみに、この時見ているのは主にfaviconと情報化タブが提供するサムネイルで、タブのラベル文字列は注視していない)。1階層のグループ化しかできないPanoramaだけでは不十分なのは、「新しいタブを開いて分かれ道になったノードがどれだったか」という情報が欠落してしまうからだ。

そのくらい憶えておけよって言われるだろうけど、僕はほんとに頭が悪いというか物覚えが悪いというかバカなので、「そのくらい」がもう悲しいくらい絶望的なほどに覚えられない。覚えられないから、そのまま視覚化して置いておく。僕にとってタブのツリーは、脳に収まりきらない情報を置いておく外部記憶になっていると言える。メインメモリの中に情報が乗り切らなくて、画面の中にしか置いておけないのだから、多少アクセス速度が遅いとしても、そこに置いてある情報を毎回取りに行く。そういう使い方をしていると思う。

でも、「そのくらい」を覚えるのが苦にならない人にとっては、オーバーヘッドが大きすぎてまどろっこしいのかもしれない。タブ同士の関係はこぼれることなくメインメモリの上に載りきっているから、わざわざ画面の中にまで同じ情報を残しておく必要性が無い。そういうことなんじゃあないだろうか、と思う。

極端なことを言えば、ツリー型タブというのは脳味噌の性能が極めて低い障碍者がフツーの人に後れを取らないように、フツーの人に合わせて作られた社会の中で日常生活を送るために不足分を補うために使う、義肢とか車椅子とかそういう物に相当するのかもしれない。電動車椅子に乗れば確かに誰でもラクに移動できるけど、自分の足で立って歩ける人は、車椅子を降りて歩いた方が自由にもっといろんな所に行ける。また、電動車椅子に慣れきってしまうと、脚がひなびて自力で立てなくなってしまうかもしれない。使わなくても生きていけるなら、使わない方が健康でいられるのかもしれない。ツリー型タブという物については。

ツリー型タブがあると遅くなるとかツリー型タブが重いとかその辺の話について - Sep 17, 2012

ツリー型タブが重いという声をたまに見かけるんだけど、そういうのを見かける度に「ほんとかなあ?」とついつい思ってしまう。

もちろん、素のFirefoxに対して余計な処理を加えるわけだから、そりゃあ、元の状態より重くはなると思う。でも、「重い」って言ってる人の言ってる「重い」は、なんというか、そういうレベルのことを言ってるわけではないって気がする。もっと鈍重な、全体的に動作がヤバイくらい緩慢になる、そういうのを指してる気がしてる。

で、そういう現象が起こるとして、本当にそれがツリー型タブのせいなのかどうか。ツリー型タブを使っている時と使っていない時とで、同じ数のタブを開いていて、同じ時間だけブラウジングした状態で、ツリー型タブがある場合だけ顕著に性能が劣化するのかどうか。というのが、この件で自分が一番気になっているポイントなのです。

何故僕はこうまで頑なに「ツリー型タブが原因で遅くなっているのだ」と素直に認めようとしないのかというと、基本的にツリー型タブはブラウザのコンテンツ領域や履歴にはタッチしないように設計しているつもりなので、「コンテンツ領域に起因するメモリリークがある」とか「履歴を消去したら軽くなった」とかの報告があるという事自体が、どうにも不可解なのです。

実際、about:memoryではタブ毎にメモリの使用状況を確認できますが、「about:memoryを見ると、メモリが解放されていないことが分かる」と言われた再現条件をこちらで試行しても問題が再現せず、その後報告者の環境でもツリー型タブだけをインストールした状況では問題が再現しなくて、どうやら他のアドオンがこの問題の原因になっているのではないかという話になったこともありました。

ツリー型タブがあるとタブを開く数が増える&1つ1つのタブの生存期間が長くなる傾向があると思うので、他のアドオンやFirefox自身が潜在的に抱えてはいるものの普段の利用では無視できる程度だったという問題があったとしたら、それらがより顕在化しやすい状態になるとは思います。例えばあるアドオンを入れていると全体の動作が0.1秒遅くなるとして、5つくらいタブを開いている状況ではそれが気にならなかった。でも、ツリー型タブを入れてタブを50とか開きっぱなしにしていると、些細な重さでもそのまま×50されるから、シャレにならない重さになる。こういう事は十分にあり得ます。でもツリー型タブ単体でそういう事が起こるとはどうしても自分には考えにくいのです。

「コンテンツ領域への参照を残しているせいでメモリリークが発生する」というのは自分もアドオン開発初期にはよくやらかしていたミスなので、かなり後の方になって開発したツリー型タブの頃にはそこらへんのことは想定に入れられるようになっていて、多少速度を犠牲にしてでも安全になるように、だいぶ気をつけて設計したという認識があります。「そこまでやらんでもええんちゃうん」って所まで偏執的にやってることすらあります。Firefox本体のコードでもaddEventListenerした物をremoveEventListenerせずに放りっぱなし(多分ガーベジコレクション任せ?)になってるのとか見ると、怖いなーって思ってしまうくらいです。

その一方で、僕は「これこれこのアドオンと衝突してるんだけど」という報告を受ける度にそのアドオンのソースを見るという事がこれまで結構あったのですが、ソース見てげんなりすることが割とありました。これメモリリークするやろ、とか。で、そういう問題を解決しようと思ったら、根本的なアーキテクチャの変更が必要だったりして。そういうとこまで見だすときりが無いし、大体それは僕の領分でもないので、ツリー型タブとの衝突の原因になってるとこだけ見てあとは見て見ぬふりしてるんですけども。

そういう惨状を見てるから、僕の心情としてはついつい、まず最初に他の原因の方を疑ってしまいます。それらの可能性がなくてちゃんと明らかに「ツリー型タブが原因だ」と断言できる、という所にまで絞り込まれてないと(はい。ほんとにツリー型タブが原因で問題が起こってる可能性も、もちろんあるとは思ってます。そこまで完全否定はできないです。)、調べようという気になかなかなれないです。これって、思い上がりすぎですかね?

あと、それとは全然別の話として、ツリー構造が何らかの理由で壊れてしまうせいで、無限ループが発生してフリーズしてしまう……という事は時々起こるみたいなので、そういうのはもう完全にツリー型タブの責任なので今後も粛々と直していきたいとは思ってます。

いずれの場合にしても、メンテナンスに十分に時間を割けない現状では「ツリー型タブのせいで問題が起こってる、かもしれない」という段階では動けなくて、「間違いなくツリー型タブのせいで問題が起こってて、この手順で100%再現できる」という所まで絞り込んでもらってることが、こっちで対処できるためには絶対に欠かせない条件という感じです。できれば「この部分が問題になっている」という所まで明らかにしてもらえてると嬉しいし、もっと言えば修正パッチをpull requestでもらえるのがベストなんですが。

それから、何と言おうとツリー型タブを入れてFirefoxの動作がおかしくなり、ツリー型タブを削除したことでこれらが解決されたことは事実なのです。 という風な話はもう何度あったか覚えてられないくらいにあって、GitHubのIssue Trackerを探してもゴロゴロでてくるんだけど、詳しく聞いてみると大概は他のアドオンとの衝突です(全部がそうだというわけではないですが、感覚的には、そうである場合の方が多かったという印象です)。こういうのも、じゃあどのアドオンと衝突してるのかという所まで明らかにさえしてもらえれば、何か手の打ちようが出てきくる可能性がありますので、より快適な生活を望む場合には、衝突の解消に協力してもらえたらなーって思います。

セッションの復元とツリー構造の維持 - Aug 07, 2012

ツリー型タブを3ヶ月ぶりに更新した。

地味な修正が多い中で特に地味な修正だけど、セッション復元でツリー構造が壊れるという問題について一定の改善を見られたのではないかと思ってる。実際に効果があったのかどうかは、今後同様のバグ報告が減るかどうかで見るしかない。

何故ツリーが壊れるか、という事の前にそもそもツリーが壊れるってどういう事やねん、という話なんだけど、ツリー型タブを使っててごく希に、「リンクから新しい子タブを開いても子タブにならない」とか「親にあたるタブには折り畳みのためのつまみが表示されているのに、子になったはずのタブはインデントされておらず、つまみをクリックしても子になったはずのタブが折り畳まれない」とかそういう怪しい挙動になる事があった。

何故そういう事が起こるのか。これはタブの管理方法に理由がある。

ツリー型タブでは、個々のタブに一意なIDをあらかじめふっておいて、tab.setAttribute(TreeStyleTabService.KPARENT, parentTab.getAttribute(TreeStyleTabService.kID)) とか tab.setAttribute(TreeStyleTabService.kCHILDREN, childTabs.map(function(aChild) { return aChild.getAttribute(TreeStyleTabService.kID); }).join('|')) とかいう感じにIDの文字列をキーにして互いの関係を保持している。親のタブや子のタブを取得する時は、TreeStyleTabService.getParentTab(tab) とした時にその中で tab.getAttribute(TreeStyleTabService.KPARENT) して取得したIDを使って(DOM3 XPathなどで)実際の要素を改めて取得するという風になってる。何故こうしたかというと、

  • タブの要素ノードに直接 tab.parentTab = parentTab のようにしてしまうと、うっかり参照を消し忘れたらいつまで経ってもメモリが解放されないんじゃないか、という心配があった。(意図しない事態の発生を防ぐため)
  • 実際には setAttribute() するのと同じタイミングで SessionStore.setTabValue(tab, TreeStyleTabService.kPARENT, parentTabId) のようにしてセッションに情報を複製しており、クラッシュ時のツリーの復元などに役立てている。(ツリー構造の永続的な保存のため)

といった理由があってのことだった。

これはそれなりに堅牢なやり方なはずだと思ってたんだけど、実際にできあがってからJavaScriptデバッガでプロファイリングしてみたら、IDの文字列からタブの要素ノードを探すという処理が呼ばれる回数が突出して多くて、それが結構無駄な待ち時間を増やす元になっているらしかった。それで、もう少し高速に動作するような仕組みを後から加える事にした。setAttribute(TreeStyleTabService.kID, id) するのと同じタイミングで、gBrowser.treeStyleTab.tabsHash[id] = tab; としてハッシュテーブルを作っておき、それ以後はそちらだけを参照するという、ごくシンプルな物だ。

上記のおかしな挙動は、このハッシュテーブルが原因だっていた。このハッシュテーブルの仕組みが上手く働くためには、「ハッシュテーブルに入っていないタブはない(すべてのタブがハッシュテーブルで管理されている)」という前提が必要になる。もし万が一何らかの理由でハッシュテーブルの管理から漏れてしまうと、そのタブはAPI経由では永久に見つけられないことになってしまう。そのタブを親として子タブを登録する、という風な事はできるのに、その子タブに保存された情報から親のタブを探そうとしても、ハッシュテーブルに無いタブだから見つけられない。こういう不整合の発生は考慮に入っていなかったので、親子関係に基づくインデント幅の調整などの処理が中途半端な所で止まってしまって、上記のような色々と不可思議な現象が発生していた。

色々調べた結果、大量のタブが一気にセッション復元された時に「現在のタブ」になっていたタブが、このハッシュテーブルの管理から漏れてしまうことがあるようだという事が分かった。なので、そのような場合もちゃんとハッシュテーブルに入るようにした。これによって、手元で再現性100%だったケースについては問題が起こらないようになった。これまでに報告があった全ての問題がこれと同一の原因かどうかは不明だけれども、今までよりは問題が起こりにくくなったという事は言えると思う。

あと似たような問題で、タブの復元のタイミングによっては「ツリー構造だけの高速な復元」ができなくなるという現象も起こっていた。ツリー型タブが完全に初期化されるよりも前にFirefoxのセッション復元の仕組みによってタブが復元されていた、という想定外の状況が発生していて、それで色々な前提が崩れてしまっていた。理屈から言ってなんでそうなるのかがてんで分からなくて、どうにかしてタブが復元されるよりも前のタイミングに割り込みたかったんだけど、どうも無理っぽかったので、既にタブが復元されてしまっていた場合でもちゃんとそれらのタブを「後から初期化」するようにした。

そういう地味な修正ばかりが入っている版です。

tabFx2Compatible.xul、tabFx2Compatible.css、tabFx2Compatible.xmlを使わないようにした - Feb 09, 2012

ツリー型タブ情報化タブの今日付でのリリース分から、tabFx2Compatibleという自作のライブラリ(?)を使わないようにした。当初はマルチプルタブハンドラでも使ってたけど、ツリー型タブ・情報化タブに先駆けて一足先に使わないようにしていた。今回残りの2つでも利用をやめたことで、TBEやり直し組のアドオンからはこのライブラリが全く姿を消したことになる。作ったのが2008年の2月からだから、丸4年くらいは使ってたのか。

このライブラリは元々、Firefox 3での仕様変更に追従するために仕方なく作った物だった。

Firefox 2ではタブの中に任意の要素(サムネイル描画用のcanvasだったりカウンタ表示用のlabelだったり)をXBLのコンテナ要素を使って動的に追加できていた。しかしFirefox 3になる時、具体的には2007年の12月頃に、高速化のためにそういう冗長性を排除する変更が行われてしまって、JavaScriptで動的にタブの内容を追加するということが原理上不可能になってしまった。

正確には、方法はあった。Firefox本体がタブに適用していたXBLによるバインディングを独自のバインディングで置き換えて、それを使ってタブの内容を変更するというやり方だ。しかし、XBLのバインディングは同時に1個だけしか適用できないという制限がある。複数のアドオンが同じ事をやろうとしたら、結局どれか1つのアドオンしかまともに動かないという事になってしまう。他の人の作る物との互換性という話に限らず、リッチでファットなAll-in-One型のアドオンであったTBEを捨てて各機能ごとに個別のアドオンに開発し直す道を選んでいた僕にとっては、自分の手がける物同士の互換性を維持するためにも、これは致命的な問題だった。

前述のライブラリは、特定のアドオンのために特化したバインディングを適用するのではなく、Firefox 2時代の「ある程度好きなようにタブの内容を増やせる余地がある」、冗長性を持った汎用的なバインディング定義を、Firefox 3向けに復活させるという物だった。

風向きが変わったのは2010年9月の事だった。Firefox 3.7のモックアップで示された視覚的なデザインを実現するために、タブのバインディングに再び冗長性が付与された。メジャーバージョンとしては、この変更はFirefox 4から反映されている。僕はこの仕様変更をうけてtabFx2Compatibleを改修し、Firefox 4以降のタブのバインディングの構造と、Firefox 2以前のタブのバインディングの構造の、両方を併せ持つ状態に変更した。

という経緯を見ると分かるかもだけど、このtabFx2Compatibleというライブラリは本質的に、Firefox 3.0から3.6までの間のバージョンに対応するためには欠かすことができなかった。ツリー型タブ等のアドオンの対応バージョン範囲の下限がこの範囲に重なっている間は、このライブラリは絶対に使う必要があったので、いくらTMPやVimperator等とバインディングの衝突の危険性があったとしても、構成ファイル群から外すことができなかった。

今回、Firefox 10のESR(延長サポート版)がリリースされたことにより正式にFirefox 3.6の死期が確定したので、サポート終了を待たずにFirefox 3.6のサポートを打ち切って、それと同時にtabFx2Compatibleも廃止することにした。レガシーなFirefox 2由来のDOMツリー構造を前提にして書かれたスクリプトやスタイルシート等を、全面的にFirefox 4以降の既定のDOMツリー構造に合わせて変更する作業は、それなりの手間を要したけれども、これでカビの生えた過去から決別できるのなら安い物だと思った。

選択肢の1つとして、tabFx2Compatibleを今後も使い続ける(Firefoxのタブのバインディングの構造が変わったら、その度にtabFx2Compatibleを更新していく)というやり方もあった。今この瞬間にかける労力を最小化する事を選ぶのなら、そういう選択もあり得たと思う。でも、tabFx2Compatibleは元々Firefox 3から3.6までの暗黒期を乗り越えるためだけに用意された物だったのだから、用済みになったのなら、思い切って捨てた方が今後のためになると思った。

こういう切り替えをできる時にやっておかないと、またTBEみたいな事になってしまう恐れがある。TBEでは、タブの移動等といった基本的な機能すらFirefox本体に備わっていなかった頃から開発していたため、TBE自身で独自の「タブを移動する」などのAPIを実装していた。そういう古いオレオレAPIから、Firefox 1.5くらいから新しくFirefox本体に備わったTabMoveとかのDOMイベントベースでのやり方にスイッチする事の手間を惜しんで、「とりあえず今動いてるから」と独自のオレオレAPIを維持することに固執してしまったがために、TBEはFirefox 1.5とともに時代に取り残され死んでしまった。そんな愚はもう繰り返してはいけない。

それにつけても、あの辺(Firefoxのタブまわり)の開発をやってる人達の意向に振り回されっぱなしだった4年間だったなー……と思うと、なんか感慨深い。

ツリー型タブでタブのツリー構造を高速に復元できるようにしたりPDFでも「自動でタブバーを隠す」できるようにしたりした - Dec 14, 2011

Tree Style Tab 0.13.2011121401で、長年の課題だった点に対する改善を入れてみた。現実逃避です。

まず1点、ツリーの復元にすごく時間がかかるという問題。

今まではSSTabRestoringイベントのタイミングでツリー構造を復元してたんだけど、これだとタブがちょっとずつしか復元されないせいでツリーもちょっとずつしか復元されない→ツリー全体が復元されるまで場合によってはものすごく時間がかかる、という問題があった。でもSSTabRestoringイベントが発行される前の時点ではnsISessionStoreのsetTabValue()で保存した情報を取り出せないからどうしようもなくて、ずっと放置してた。

これが最近のバージョンのFirefoxではSSTabRestoringより前でもセッションに保存された情報を取り出せるようになってたので、じゃあってんで、真っ先にツリー構造だけ復元するようにしたという次第。

前のエントリではnsIObserverでメッセージを拾うとか何とか書いたけど、結局その方法はやめた。最終的にどうやったのか、という事を簡単に書いておく。

おさらいとして、Firefoxが備えているセッション復元機能は以下のような流れで処理を行っている。

  1. SSWindowStateBusyイベントが発行される。
  2. 必要な数のタブを開く。タブを使い回す時は、今読み込んでいるタブの内容を破棄する。
  3. セッションの情報に基づいてタブを初期化する(表示・非表示の切り替え、タイトルの設定など)。この時SSTabRestoringイベントが発行される。
  4. セッションの情報に基づいて履歴を復元する。この時SSTabRestoredイベントが発行される。

問題は、1と2の間に割り込んで、2で使い回される事になるタブのツリー構造を一旦リセットしないといけないけど、そのためのAPIが無い(どのタブが使い回される事になるのか、を知る方法が全く無い)っていう事。

  • __SS_tabStillLoadingとか__SS_restoreStateとかを見たら分かるんじゃないか?と思ったけど、これを使っても、「Bar Tab相当の機能」によって待機状態のままになってるタブと、これからセッションが復元されようとしている状態のタブとを見分けられない。
  • プロパティアクセスやDOMノードへの属性値の設定を監視するのはコストが大きそうだし、他のアドオンやFirefoxの機能を壊してしまいそう。
  • タブを使い回す直前にnsISHistoryのPurgeHistory()というメソッドが呼ばれていて、nsISHistoryにはnsISHistoryListenerインターフェースを備えたリスナを登録できるようだったので、これを使えばいけるか!?と思ったけど、nsISHistoryには1個しかリスナを登録できなくてアドオンでリスナを追加するとFirefox自身が追加したリスナが失われてしまって、全体的に動作がぶっ壊れてしまった。
  • nsSessionStoreはJavaScriptコードモジュールではなくXPCOMコンポーネントなので、プロトタイプやシングルトンとして動作してるインスタンスに対してフックを仕掛ける事もできない。

とかなんとかああでもないこうでもないといじり回していて、もう最後の手段という事で、<browser/>stop()を置き換えてフラグを立てたり下ろしたりするようにした。nsSessionStoreは1と2の間で、使い回す予定のタブをプライベートメソッドのrestoreHistoryPrecursor()に渡してそれらの内容を初期化するという事をやってて、その中で必ずstop()を呼んでいたので、stop()が呼ばれた時にJavaScriptのスタックを辿って、restoreHistoryPrecursor()から呼ばれていたら「このタブはこれから使い回されようとしている」というフラグを立てるようにしたrestoreHistoryPrecursor()は他の場所からは絶対に呼ばれないから、これが一番確実だと思う。

そうすると、

  1. SSWindowStateBusyイベントを拾って、「これからツリーが復元されようとしている」ことを検知して、「ツリーが復元されようとしている」フラグを立てる。
  2. restoreHistoryPrecursor()のタイミングで、「これからセッション情報が復元されようとしているタブ」にフラグが立つ。
  3. 復元されるタブの1個目に対してSSTabRestoringイベントが発行される。この時、「ツリーが復元されようとしている」フラグが立っていたら、2でフラグが立てられたタブだけを収集してきてツリー構造を復元する。
  4. 2つ目以降のタブのSSTabRestoringイベントが発行される。以下略。

という具合にして、個々のタブのセッションの復元完了を待たずに一気にツリー構造を復元できるようになるわけです。

この改善は、Firefoxのセッション復元APIを使ってるSession Managerとの併用でもちゃんと活かされる。Tab Mix Plusみたいに独自のオレオレセッション復元機能を持っててそれを使ってる環境は……知らない。多分動かないだろうけど対応する気力無い(そもそも、僕自身Session Managerは使っててもTMPは使ってない)。

次、2点目。タブで開いてる内容がPDFだったりFlashだったりすると「タブバーを自動で隠す」が機能しないという問題。

ポインタの位置に応じて自動的にタブバーを表示したり隠したりという事をやるためのベタなやりかたは、Firefoxのウィンドウ(XULドキュメント)でキャプチャリングフェーズでmousemoveイベントを監視するという方法だ。ポインタがタブバーの近くに来たらタブバーを表示して、コンテンツ領域の上に載ったら(タブバーから離れたら)タブバーを隠す、という感じ。AutoHideというアドオンはそうしてたし、ツリー型タブもそうしてる。

ところが、何度か中野さんが解説されてた気がするけど、Firefoxはプラグイン(FlashとかPDFとか)の描画領域の上で発生したクリックやら何やらのイベントは全部プラグインが握りつぶすようになってる。そのため、普通にmousemoveイベントを監視してても、プラグインの領域にポインタが載ってる間は何もイベントが発生しない。ポインタがタブバーから離れた事はmouseout等で検知できても、コンテンツ領域に載ったという事を検知する事ができない。

(ちなみに、ツリー型タブではタブバーの「近く」にポインタがあるときはタブバーを隠さないで、ちょっと離れたら初めてタブバーを隠す、という風にしている。そうしないと、タブバーの幅を変えるためにスプリッタをドラッグしようとしても、ちょっとポインタがぶれただけですぐタブバーが隠れてしまってストレスが溜まってしょうがない。なので、mousemoveでポインタの座標を監視するという方法に頼らざるを得ないのです。)

同じ理由で、マウスジェスチャ系のアドオンもプラグインがあるとジェスチャがまともに動かないという事が結構あるみたい。そこをどうにかしようとすると、CとかC++とかでコンポーネントを作ってもっとプラットフォーム寄りの所でポインタの位置を拾うしかなくて、「プラグインの上でもジェスチャできる」が売りのMonkey Gesturesは実際そのようにしてたと思う。

ネイティブなコンポーネントを書くというのは嫌っていうかそんな手間かけたくなかったので代わりにどうやったかっていうと、すごく単純な話で、background: rgba(0,0,0,0.01) !important; -moz-appearance: none !important;にした<panel/>をコンテンツ領域の上に重ねて開くようにした。たったそれだけ。そうするとXULの要素の上だから普通にmousemoveイベントが発生して、ポインタの正確な位置を取れるようになった。

透明なポップアップを表示する時には、セオリーというか注意点がいくつかある。

  • background: transparent !important;で完全な透明にしてはだめ。完全に透明な<panel/>は何のイベントも拾ってくれない。なので、rgba(0,0,0,0.01)(ものすごく薄い半透明の黒)のように、何かとにかく色を付けておく必要がある。
  • Linuxでは半透明のpanelは表示できない(Geckoが対応してなくて、ピクセルごとに完全透明か完全不透明かのどっちかしかできない)。なので、background: transparent !important;で完全透明にしつつborderでちょっとだけ線を表示して、<panel/>の中に不透明な部分を作ってやる。
    • ただ、それだとcompiz等ではpanelに影ができてしまってかっこわるい。不透明な部分が矩形になってると影ができて、矩形以外の形だと影ができないっぽい(ロケーションバーのfaviconの所に出るドアハンガーUIに影が表示されないのもこれが理由)ので、<panel/>の左右にだけ枠線を付けるという風にして、領域が矩形にならないようにする。

透明な<panel/>を使う方法にはいくつか弊害があって、今まではそれを気にして採用を避けてた。が、今回のアップデートではそれぞれ「できる所までは頑張ろう、どうしても無理な所はもう知らん」というスタンスで割り切ることにした。

  • ユーザがコンテンツ領域の中をクリックしたつもりで<panel/>がクリックされてしまうと、「クリックしたのに何も起こらない」という状況が発生してしまう。
    • →タブの内容がプラグインで表示されてるかどうかを見て、必要な時だけ透明な<panel/>を使うようにした。タブバーが隠れて<panel/>が閉じられた後でなら普通にクリックできるので、タブバーが隠れる前にコンテンツ領域をクリックするようなせっかちな人の事はもう諦める事にした。
  • 透明な<panel/>以外の部分(ツールバーボタンなど)をクリックすると、環境によっては、透明な<panel/>が閉じられるだけで終わってしまう。
    • popupBoxObject.setConsumeRollupEvent(Ci.nsIPopupBoxObject.ROLLUP_NO_CONSUME)ある程度は抑制できるっぽい(Ubuntu 10.04LTSのcompizだと、Firefoxのウィンドウの中に対してはclick-throughが通用するようになったが、他のアプリケーションのウィンドウをいきなりクリックした時は相変わらずだった)。どうしても駄目な時については、もう、諦める。

なんで方針を変えた(安全に完璧に提供できない弊害の多い方法は使いたくない→弊害があっても使う)かというと、「同じ要望がもう数えるのも嫌になるくらいしょっちゅう来ててウンザリしたから」っていうのが最大の理由だ。

いや、つまり「PDFとか表示してるとタブバーが隠れてくれない」問題は、割と誰でもぶち当たってしまう問題なわけですよ。それに対して、実際に調べたわけではないけど、多分上に挙げたような「弊害」が実際に致命的な問題になるような人は多分そんなに多くないだろうと思うわけですよ。どっちの人を大事にしたらいいかっていう事で天秤にかけて、前者を救う事にした。という話なのです。

そういう意味では、TMPの独自のセッション復元機能を使ってる人を見捨てるってどうなんっていう言い方もあるだろうけど、あんなもん(TMP独自のセッション復元機能)ぶっちゃけ捨てていいですよ。Firefox本体のセッション復元機能に勝ってる部分、ありますか? ないですよね? いやよく調べないまま書いてるんだけどさ。

Compatibility problem with Tab Mix Plus - Feb 07, 2011

I got a mail from Tab Mix Plus developers team. So I updated compatibility codes in Tree Style Tab for the latest dev-build of TMP. After that I got another mail again, and he said that the latest TST doesn't work with the last public release of TMP. This is the reply:

Hello, onemen.

First, I really think TMP helps very huge people from poor tabbed browsing features of Firefox itself. It is a great thing. So, I hope my addons including TST work with TMP correctly.

However, I'm afraid I can't support both versions of TMP (the latest dev-build and the last public version) anymore, because I believe that they are too different to support simple hacks. I already removed many codes based on the latest dev-build of TMP by this commit, so I can't believe TST works with the last public release only with minor changes only about symbols (function names). And, if I restore codes for old TMP, then both old and latest TMP will override them again and re-introduce many unexpected problems. That is too terrible.

To be honest, it was very painful to read dynamic-eval codes in TMP and TST itself because they are many tree-times eval-ed codes (defined by TMP => overridden by TST => overridden by TMP again). So I don't want to do such a painful work again for the last public release...

Yes, I apologize that I'm also using many eval() to hack TMP. So I believe that both addons TMP and TST should remove all eval-based hacks for each other and make themselves plaggable via their public APIs. TST already defines some public APIs. I agree that they are too less APIs to make compatible TMP with TST now. I'll add new APIs to do it if they are really required for high compatibility. I'll make efforts to keep stable those APIs in future versions. I don't know what APIs are required for TMP, so, I hope to listen your idea.

On the other hand, I hope that TMP provides some public (and stable) APIs for addon developers, like:

  • An API to add extra properties for TabmixSessionData
  • Custom DOM events for TMP specific features

If there is any public document already, could you tell me the URI?

regards,

I can't believe that I keep the current method (eval-based dirty hacks) to make them compatible.

XBLとアドオンとDOMツリーの切り貼りとツールバーカスタマイズの危険な関係 - Jan 23, 2011

ツリー型タブPersonal Ttitlebarと併用できるようにするためにずいぶん骨を折った。

Firefox 4のタブバーの仕様

Firefox 4ではタブバーとtabbrowser要素がDOMツリー的に切り離されて、tabs要素がカスタマイズ可能なtoolbarの中に置かれるようになった。この話は以前に勉強会で発表した(この資料の日付を見て「うわーもうあれから1年近く経とうとしてるのか、Firefox 4の開発どんだけ遅れとんねん」とか「そんな昔のことを『ちょっと前に発表しましたが』とか書こうと思ってしまった自分ってどないやねん」とか思ったのですがそれはどうでもいいです)んだけど、その時こんな話をした。

  • 今まではorientとordinalやdirectionで簡単にタブバーの位置を右や左や下には動かせたが、それができなくなった。
  • Tab Mix Plusは、タブバーのtabs要素をDOMツリーから一旦切り離してtabbrowser要素の下にappendChildで再挿入することで、タブバーの位置を変えるようにしたようだ。
  • ツリー型タブでは、DOMツリーは触らずに、toolbarの表示位置をposition:fixedで動的に位置を合わせることで擬似的にタブバーの位置を変えたように見せかける方法を採ることにした。
    • この話をしたら会場から失笑の渦が巻き起こった。「無茶しよるなー」的な。

その時も特には説明しなかったんだけど、何故僕はそうまでしてDOMツリーの変更を避けたがるのか。今回Perosnal Titlebarとの衝突を解決するためにここで散々悩まされたので、改めてここに記しておこうと思う。

Firefoxのツールバーと絡むアドオンを作る時に注意する事

Firefoxのツールバーの各機能は、ユーザが任意にドラッグ&ドロップで並べ替えたりアイテムを追加・削除したりできるようになっている。この時内部的には当然だけどDOMツリーが動的に切り貼りされている。

この時、ツールバーから削除されたボタンに対してDOMのイベントを監視し続けるのは変だし、そのボタンへの参照をアドオンの中の変数でずっと持ったままだと、いわゆるメモリリークが起こることもある。なので、僕はツールバーにボタンを追加するアドオンについては、ツールバーのカスタマイズに入る前に一旦「ボタンの終了処理」を行って、ツールバーのカスタマイズが終わった時点でもう一度「ボタンの初期化処理」を走らせるようにしていることが多い。

ちなみに、こういう事をするためにDOMイベントが使えればいいんだけど、Firefox 3.6までのバージョンではそんな配慮は全然無い不親切な設計なので、カスタマイズを開始する関数の BrowserCustomizeToolbar() とかカスタマイズ終了時に呼ばれる BrowserToolboxCustomizeDone() とかツールボックスの customizeDone() メソッドだとかを上書きして処理を挟み込んでやらないといけなかった。Minefieldではいつの頃からか beforecustomization とか aftercustomization とか customizationchange とかのイベントが発行されるようになったので、こういうダーティなことはやらなくても済むようになった。もっと早くからこうしてくれていればよかったのに……いやそれは今はどうでもいい。

自分で追加したツールバーのボタンについてそういう事が必要なのと同様に、例えば標準のWeb検索バーの挙動を変更するセカンドサーチのような「既存のツールバーボタンの挙動を変える物」も、同じような事をしないといけない。特に、既存のボタンにXBLで追加されたメソッドを後から上書きしているようなケースでは。セカンドサーチの例で言うと、こういう感じだ。

  • セカンドサーチはWeb検索バーにXBLで追加されたメソッド handleSearchCommand() を上書きする。
    • このメソッドが上書きされていることを前提として、他の機能も成り立っている。
  • Web検索バーがDOMツリーから一旦取り除かれ、再度挿入されると、その時点でXBLの定義が再度適用される。そのためセカンドサーチが上書きしたはずの handleSearchCommand() メソッドも元に戻ってしまう
    • Web検索バーがツールバーからパレットに移動されなくてもそうなる。何故なら、ツールバーのカスタマイズ時にはすべてのカスタマイズ可能な要素が一旦DOMツリーから取り除かれた後、toolbarpaletteitem という要素にappendChild()されて、元の要素があった位置に挿入される、という処理が行われるから。
    • ツールバーのカスタマイズを完了する時はこの逆の操作が行われる。つまり、すべての toolbarpaletteitem 要素が削除されて、代わりに、その中にあった要素が toolbarpaletteitem があった位置に再挿入される。

Firefox 4でタブバーがツールバーに移動された時も、これと同じ事が起こるんじゃないかとちょっと思ってたけど、実際はそうならなかった。Firefoxの他の部分が「タブがある事」を前提に設計されているせいで、タブバーをツールバーカスタマイズでツールバー上から完全に取り除いたら、他の機能が色々と破綻してしまうから……という事なんだと思われる。なので今のMinefieldでは、タブバーのtabs要素はメニューバーと同様に「ツールバーの中にあるけど動かせない要素」という扱いになっている。

そういう事情で、Firefoxもタブバーまわりのコードは「DOMツリーが動的に編集されても動作するような堅牢に設計」にするための注意は払われていないし、他のタブ周りのアドオンもそういう設計にはなっていない事が多い。というか、そういうアドオンがあまりに多いからFirefox本体の側でもタブバーをカスタマイズで移動できないようにせざるを得なかったって事なんじゃないかと思うんだけど。

Personal Titlebarを使った時に起こる事

Personal Titlebarというアドオンは、Firefox 4の(Windowsでの)タイトルバーに任意のツールバー用のボタンを配置できるようにするという物だ。それだけでなく、タブバーすらカスタマイズで移動できるようにしてしまう。それが問題だった。

前述した通り、Firefoxのツールバーはカスタマイズのモードに入っただけでもDOMツリーが切り貼りされる。Personal Titlebarがあるとタブバーのtabs要素もそういう処理の対象になる。ツールバーのカスタマイズに入るだけでもタブバーの各プロパティが初期化されてしまい、ツリー型タブが上書きしたメソッドも元に戻ってしまって、表示がグチャグチャにぶっ壊れる。これが衝突の真相だった。

これを回避するためには、単純に考えれば「ツールバーのカスタマイズに入る前にすべてを元に戻し、カスタマイズが終わったら再度初期化する」ということをやればいいということになる。でも話はそう簡単にはいかなかった。

beforecustomization のタイミングでできる事には限りがある

beforecustomizationイベントは、普通の同期型のイベントとして発行されていて、このイベントを捕捉したすべてのイベントリスナの中で処理が終わった後、ツールバーカスタマイズのための本来の処理が始まるようになっている。「このアドオンの終了処理は無事に終わりました。ツールバーのカスタマイズを初めても大丈夫ですよ。」という事を、アドオンの側が明示的に通知する手段はない。単にイベントリスナの処理が終わったらその時点で「終了処理が終わった、もうカスタマイズを初めても大丈夫だ」と判断されるような単純な設計だ。

一方で、ツリー型タブはタブバーの表示を縦にしたり横にしたりとダイナミックな切り替えをする時に、タイマーを多用している。何故かというと、XULの属性値やCSSのプロパティを変更した後、そのイベントループ中では結果が適用されないままなので、現在のイベントループを一旦終了させた上で、setTimeout()で続きの処理を次のイベントループ内で行わないといけない、という場面が多いからだ。

なので、beforecustomization イベントを捕捉した時点でタブバーの「終了処理」を行おうとしても、現在のイベントループの中でできる事までしか終了処理を終わらせられない。中途半端な状態でツールバーのカスタマイズに突入してしまう事になるし、しかも、次のイベントループに回すための続きの処理がツールバーのカスタマイズに突入した後で走ってしまうから、もうあっちもこっちも色々前提が崩れててシッチャカメッチャカな事になる。

必要なのは「今のイベントループの中で終了処理を完結させる」「次のイベントループを待つ必要がある終了処理を行う」この矛盾した2つをどうにかして両立させる事だ。

DOMイベントが発行されるまで今のイベントループを強引に停止させる

結論から言うと、XPCOMのスレッドの機能を使えばできる。

doAndWaitDOMEvent : function TSTUtils_doAndWaitDOMEvent() {
  var type, target, delay, task;
  Array.slice(arguments).forEach(function(aArg) {
    switch(typeof aArg) {
      case 'string': type = aArg; break;
      case 'number': delay = aArg; break;
      case 'function': task = aArg; break;
      default: target = aArg; break;
    }
  });

  if (!target || !type) {
    if (task) task();
    return;
  }

  var done = false;
  var listener = function(aEvent) {
      setTimeout(function() {
        done = true;
      }, delay || 0);
      target.removeEventListener(type, listener, false);
    };

  if (task)
    setTimeout(function() {
      try {
        task();
      }
      catch(e) {
        dump(e+'\n');
        target.removeEventListener(type, listener, false);
        done = true;
      }
    }, 0);

  target.addEventListener(type, listener, false);

  var thread = Components
          .classes['@mozilla.org/thread-manager;1']
          .getService()
          .mainThread;
  while (!done)
  {
    thread.processNextEvent(true);
  }
},

こんなユーティリティメソッドを作った。DOMイベントターゲット、そのターゲットに到達するはずのDOMイベント名、別のイベントループで実行されなくてはならない処理を含む関数、を引数として受け取り、渡された関数を実行して、今のスレッドの処理を止める。指定されたDOMイベントが発火したら、今のスレッドを止めるための無限ループから抜ける。これで、beforecustomization イベントのタイミングで複雑で長い終了処理を実行できるようになった。

まとめ

Personal Titlebarというアドオン1つとの衝突を解消する事だけ考えるなら、タブバーだけは移動できないようにPersonal Titlebarのコードに対してさらに変更を加えてしまうという手もあった。ただ、それでは「既にPersonal Titlebarでタブバーの位置が変更されていた所にツリー型タブがインストールされた」という場合には意味がないし、そもそもFirefox本体の側がタブバーをカスタマイズで位置変更できるようにする可能性もある(Add-on SDKベースで作るアドオンなら互換性が担保されるから、ということでSDKベースでない既存のアドオンをバッサリ切り捨てる事になるような変更もFirefox本体に入れやすくなるから)。なので頑張ってみた。

ただ、こういう事が起こる原因になるから、自分が作るアドオンの中では極力DOMツリーはいじらないようにしたいとは、今も変わらず思ってる。高速化のために最上位のDOMツリーをゴッソリ入れ換えるのもNGだし、ましてや、WebページのbodyのinnnerHTMLを文字列として一括置換するなんてのは論外だ(そういう事をしても既存のイベントリスナに影響を与えないといった風に仕様でちゃんと定められていて実装もそうなっているのなら問題ない。仕様や実装がそう変わったんだったら高速化のためにそういう工夫を取り入れるのはむしろ奨励すべき事だとは思う)。

Firefox本体の側で行われた変更なら他のアドオンも追従するだろうけど、僕個人でこうして細々と開発しているアドオンのためにまで、わざわざ他のアドオン作者の人が頑張って対処してくれるとは思えない。僕だって、他の知らないアドオンのために事前に対処はできない。僕にできる事は、「自分が作る物については、他のアドオンに与える影響を小さくするようなるべく配慮する事」、ただそれしかない。

ツリー型タブとマルチプルタブハンドラのイベント周りのAPIをちょっと変えた - Jan 11, 2011

Tree Style TabMultiple Tab Handlerを更新した。

今回のアップデートでも例によってMinefield対応のための修正をちょっとずつ入れてるんだけど、その中で1つ、なかなか気付いてなくてハマってた所があった。それはカスタムイベントを使ってた部分。

DOM2 Eventsではこんな風にして任意のDOMイベントを発行できる。

var event = document.createEvent('Events');
event.initEvent('MyCustomEvent', true, false);
event.status = 'current status';
event.tab = tab;
gBrowser.dispatchEvent(event);

受け取る側はこれをaddEventListener()で登録したリスナで拾うようにすれば、各々のモジュールの結合度合いを弱められる。なので僕は自分のアドオンでも積極的にこれを使ってる。

が、これがMinefieldでは使えなくなってた。

多分Compartment(JavaScriptのメモリ空間をスクリプトのオリジンだったかウィンドウだったかごとに分ける機能)が入ったからだと思うんだけど、Chrome URLのスクリプトで上記の例のように追加した任意のプロパティを、JavaScriptコードモジュール側のイベントリスナで参照できなくなってた。上記の例だと、捕捉したイベントのevent.tabundefinedになってしまってて、こういうやり方で情報を引き渡してた部分がエラーになってしまってた。wrappedJSObjectundefinedなので、生のオブジェクトを辿る事もできなかった。

MDCにある任意のカスタムイベントを実装する方法の詳しい説明によると、XPIDLでインターフェースを定義してC++で実装を書いてという事をやれば、今までと完全に同じAPIで任意のイベントを発行できるようなんだけど、それはちょっと重たすぎてやる気になれない。

なので次善の策として、汎用のデータを受け渡すためのイベント型があればそれを使おうと思って検索したら、Firefox 3以降ではDataContainerEventとかMessageEventとかの型のイベントが利用可能になってたという事を知った(今更)。

渡すデータがJSON文字列化できる物なら、WebSocketで定義されてるMessageEventがいいっぽい。

var event = document.createEvent('MessageEvent');
event.initMessageEvent('MyCustomEvent', true, false,
  JSON.stringify({ status : 'current status',
                   tab : tab.getAttribute('id') }),
  '', '', null);
gBrowser.dispatchEvent(event);

受け取った側はJSON.parse(event.data)でデータを復元できる。

DOM要素とかも渡したいなら、nsIVariant型でデータを受け渡せるDataContainerEventを使うしか無さげ。

var event = document.createEvent('DataContainerEvent');
event.initEvent('MyCustomEvent', true, false);
event.setData('status', 'current status');
event.setData('tab', tab);
gBrowser.dispatchEvent(event);

受け取った側はevent.getData('tab')のようにしてデータを取得できる。

ということで、プロパティアクセスにしてた所は全部DataContainerEventのやり方を使うように直した。ただ、後方互換性のためにプロパティアクセスでも情報はセットしてあって、同じCompartmentのスクリプトからなら多分今まで通りのやり方でも情報を受け取れると思う。

あと、DataContainerEventの存在を知る前に、MDCのドキュメントに書いてあった「イベント名がnsDOMで始まってない物は任意の情報は受け渡せないよ」という部分を読んでイベント名を「nsDOMTreeStyleTab...」という感じに変えていて、実際これでちゃんと動くようになった部分もあったんだけど、結局DataContainerEventにするようにしたからこれは結果的には余計だったかもしれない……

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

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき