Mar 28, 2007

タブキラー復活

Firefoxの主要機能を開発するために投じられた多くの人々の多大な努力をぶち壊しにする悪夢の拡張機能、タブキラーがFirefox 2のために帰ってきましたよ。

Firefox 2正式対応は伊達じゃない。今回のアップデートではなななななんと! Firefox 2の新機能の「最近閉じたタブをもう一度開き直す」機能をいじくり倒して、「最近閉じたウィンドウをもう一度開き直す」という機能に作り替えてみました。ああ激しく無駄な努力。

ソース見ればだいたい分かると思うけど、一応、実装の仕方を以下に説明しておく。

「閉じたタブの復元」機能を「閉じたウィンドウの復元」機能に作り替える上での鍵になるのは、nsISessionStoreのgetWindowStateメソッド(指定したウィンドウのタブの状態をシリアライズして文字列として取得する)とsetWindowStateメソッド(シリアライズされた文字列を元にウィンドウの状態を復元する)という機能だ。

「最近閉じたタブ」メニューには、そのウィンドウ内で閉じたタブの一覧が表示される。nsISessionStoreの実装を見てみたところ、閉じたタブの情報の扱いは以下のようになっていることが分かった。

  • 内部的には、「そのウィンドウ内で閉じたタブの一覧」を、ウィンドウごとに保持している。
  • ウィンドウを閉じた場合、それが最後のウィンドウでない限りは、ウィンドウの情報をすべて破棄してしまう。
  • もちろん、そのウィンドウ内で閉じられたタブの情報も、ウィンドウの情報と一緒に消えてしまう。

ウィンドウごとに履歴が別れてしまっているので、このままでは、あるウィンドウで閉じたタブを他のウィンドウで開き直すということはできない。ではどうすればいいかというということで考えたのが、以下のやり方だ。ここに、2つのウィンドウA, Bがあり、Aは単一のタブXを、Bは単一のタブYを保持していると仮定しよう(タブキラーがインストールされていても、内部的には最低一つのタブを持っていることになる)。

  1. Aを閉じる。
  2. Aを閉じる直前の処理に割り込んで、getWindowStateメソッドでウィンドウの状態を丸ごと取得する。
  3. Bに対してsetWindowStateメソッドを使用して、ABの内容を統合する。BYと、Xの複製であるX'の2つのタブが開かれた状態になる。
  4. BにおいてX'を閉じる。

これで、ウィンドウAのタブXを閉じた履歴をウィンドウBに残すことができる。

「タブを開き直す」処理を「ウィンドウを開き直す」に置き換える時も、これと同じようなことをする。

  1. Bの中で、タブを開き直す処理を実行する。
  2. Bの中で、タブX'が復元される。
  3. タブを開き直した直後の処理に割り込んで、新しいウィンドウCを開く。このウィンドウは空のタブZを保持している。
  4. Bの内容をgetWindowStateで取得する。
  5. Cに対してsetWindowStateメソッドを使用して、BCの内容を統合する。CZと、X'の複製であるX''Yの複製であるY'の3つのタブが開かれた状態になる。
  6. BにおいてX'を閉じる。
  7. CにおいてY'Zを閉じる。

これで、タブXだけを含んだウィンドウを開き直すことができる。

ただ、このままだと6と7のステップでそれぞれのウィンドウに、Y'を閉じたという履歴情報が「最近閉じたタブ」の一覧に残ってしまう。これはちょっとまずい。これらのステップでタブを閉じる時には、nsISessionStoreに履歴が残らないようにしないといけない。

nsISessionStoreを見てみると、どんなタブに対しても「最近閉じたタブ」の履歴を残すというわけではなく、履歴を残さない例外があることが分かった。その条件とは、「セッションヒストリの履歴の数が1つしかなくて、現在のページがabout:blankである」場合。要するに、新しく開かれたばかりの空っぽのタブは、閉じても履歴に残らないということである。

つまり、履歴を残さずにタブを閉じるためには、タブを「開いたばかりの空のタブ」と同じ状態に戻してから閉じればいいわけだ。それには、タブに対応しているbrowserのセッションヒストリにアクセスしてすべての履歴を削除したあと、そのタブで表示しているページをabout:blankに置き換えればよい。


if (tab.linkedBrowser.sessionHistory)
    tab.linkedBrowser.sessionHistory.PurgeHistory(
      tab.linkedBrowser.sessionHistory.count
    );
tab.linkedBrowser.contentWindow.location.replace('about:blank');

タブキラーの場合は各ウィンドウで一つだけタブを残すという前提があるので、上記のような処理は割と簡単に書けた。ウィンドウごとに複数のタブを開ける通常の状態でも、「元々そのウィンドウで開かれていたタブ」と「他のウィンドウで開かれていて、今のウィンドウの中にインポートされたタブ」とをちゃんと区別してやれば、タブをウィンドウからウィンドウへ移動するといったことも可能と思われる。というか、TBEをFirefox 2の機能を使って実装し直す場合はそういうことになりそうな感じだ。

つまり今回の試みは、ジョーク拡張機能を作り込むだけじゃあなくて、TBEのFirefox 2対応に必要な技術の勉強でもあったわけです。……と、強引なこじつけをしてみるテスト。

エントリを編集します。

wikieditish message: Ready to edit this entry.











拡張機能