Firefoxのタブ切り替えをいじる

#はじめに
「オープンソースのソフトウェアをいじってみる」という課題が大学で出され、無謀にも**Mozilla Firefox**に挑戦してみました。この投稿もその一環です。

この課題は2人1組で取り組んだためこの投稿も2人で書いています。そのため以下の記述は2人の環境差による一貫していない部分があると思いますがご容赦ください。

##環境・バージョン

- Ubuntu 14.04 LTS 64bit
- mozilla-release ver.???
    - [~~リンク~~](http://ftp.mozilla.org/pub/firefox/releases/latest/source/) <- 切れてます
    - 2015/10/26時点での最新版のはずですが、内部にversionを示す部分が見当たらず、リンクも切れており不明です（圧縮ファイルは消してしまいました :fearful: ）

##既存機能と目標
タイトルにもあるようにタブの切り替えについていじりたいと思います。_Firefox_は元々強力なタブ機能を備えており、タブ間の移動も右に**Ctrl + Tab**、左に**Ctrl + Shift + Tab**で切り替えできます。またタブグループというタブをまとめる機能も存在し、**Ctrl + Shift + E**でグループのビューと編集が行える画面に移行します。**Ctrl + Shift + @**や**Ctrl + Shit + ^**でグループ移動もできます。
もはや機能として十分な気もしますが、これを改造して

- **Ctrl + カーソルキー**で
- グループ内移動もグループ移動も
- プレビューを見ながら

タブ切り替えできるようにしてみたいと思います。

#実装までの道のり
##ビルド

まず、[「Firefox の簡単なビルド方法」](https://developer.mozilla.org/ja/docs/Simple_Firefox_build)を参考に*mozilla-release/.mozconfig*を

```:.mozconfig
. $topsrcdir/browser/config/mozconfig
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../../firefox_build
ac_add_options --enable-debug
```

このように作成し、*mozilla-release*内で

```
make -f client.mk
```

でビルドしようとしましたが、ここでいきなり問題が発生しました。

```
sudo apt-get build-dep firefox
```

で依存パッケージをインストールできず、

```
sudo apt-get upadte
```

も途中でエラーを起こして止まってしまいます。

→これはjp.ubuntuのサーバーがおかしいようで、他のサーバーに変えると無事依存パッケージのインストールが行えました。

上記の参考URLにしたがって*.mozconfig*を作成しビルドすると、gstreamer周りのパッケージが無く依存関係が満たせていないと言われました。
あれこれ試して、

```
apt-get install phonon-backend-gstreamer1.0
apt-get install gstremer1.0*
apt-get install gstreamer0.10
```

などでパッケージをインストールしてみましたが、相変わらず依存関係がgstreamer周りのパッケージがないと言われました。
しかし、*.mozconfig*に`ac_add_options --disable-gstreamer`を追加すると`make`が通るため、問題はやはりgstreamerパッケージがないことによる模様...
最終的に、以下のようにしてgstreamerパッケージをインストールすることに成功しました。

```
apt-get install libgstreamer0.10-dev libgstreamer-plugins-base0.10-dev
```

この`make`が通った後、*firefox_build/dist/firefox*から起動を確認できました。

##ソースを眺める
非常に大きなプロジェクトの中でいじるべき場所を見つけるべく、それらしい文字列で

```
grep -rn 'hogehoge'
```

を繰り返し、文字列の存在場所を見ていきます。
本筋と関係ありませんが

```
grep -rch ''
```

で各ファイルの行数が表示されます。この出力された数字を全部足してみると2000万行を超えました。このプロジェクトがいかに大きいか分かります。

[事前情報](http://great-hemi.hatenablog.com/entry/2015/11/01/213531)により*browser.js*ファイルをいじるとよいのではないかという希望があったため、探してみると同名のファイルがたくさんヒットしました。しかもみんな中身が違います。
色々といじってみた結果、*mozila_release/browser/base/content/browser.js*に新しくタブを開く関数らしきものを見つけたため、中身をコメントアウトしてビルドして実行してみたところ、新しくタブを開けなくなりました。しばらくこのファイルをいじってみます。

`grep`をしていくうちに*mozilla-release/browser/base/content/browser-sets.inc*でショートカットキーと呼び出す関数を対応づけていることを発見しました。そして呼び出される関数の多くは*browser.js*内に定義されています。この二つをいじることで実際に挙動も変化し、逆にショートカットでよばれる関数を追うことで、既存の機能を担当している関数を調べることができます。

##タブに関するクラス・関数
ようやくオリジナル関数の作成に取り掛かります。
タブに関するjsファイルがたくさんあるのでそれぞれがどういう依存関係なのか、何が記述されているのか理解するのに時間がかかりました。

`groupitem`や`groupitems`、`panorama`(グループアイテムビューの旧名称だそうです)といったワードを`grep`し、検索結果からそれらしいファイルをピックアップしていきます。
その結果、
*browser/components/tabview/groupitems.js*
に`GroupItem`クラスと`GroupItems`クラスの定義があり、また
*browser/base/content/browser-tabview.js*
にタブ及びタブグループ周りの関数が定義されていました。
この2つのファイルを詳しく見ていきます。

*browser-sets.inc*ファイルの中に**Ctrl + Shift + E**(グループアイテムビュー)で呼び出される関数が記述されていたため、順に追いかけていきます。
まず*browser-tabview.js*内の`TabView_toggle`が呼び出され、そこから`TabView_show`、さらに`self._window.UI.showTabView`を呼び出しています。
`showTabView`関数は*browser/components/tabview/ui.js*の中に定義されていましたが、ここで`TabItems.startHeartbeart`や`TabItems.checkHeartbeat`といった同期・非同期処理を管理するような関数が呼ばれており、追いきれなくなりました。

次の手がかりとして**Ctrl + Shift + @**や**Ctrl + Shit + ^**といったタブグループ遷移ショートカットをたどろうとしたのですが、これらのショートカットは*browser-sets.inc*の中では定義されておらず、`grep`してもそれらしいものが引っかからないためここでしばらく挫折していました。

仕方がないで勘に頼って*browser/base/content/browser-tabview.js*をを頭から眺めているとタブグループのショートカットキー**Ctrl + Shift + @**や**Ctrl + Shit + ^**で呼び出される関数が定義されている箇所を奇跡的に発見しました。

```js:browser-tabview.js
  // Adds new key commands to the browser, for invoking the Tab Candy UI
  // and for switching between groups of tabs when outside of the Tab Candy UI.
  _setBrowserKeyHandlers: function TabView__setBrowserKeyHandlers() {
    if (this._browserKeyHandlerInitialized)
      return;

    this._browserKeyHandlerInitialized = true;

    let self = this;
    window.addEventListener("keypress", function(event) {
      if (self.isVisible() || !self._tabBrowserHasHiddenTabs())
        return;

      let charCode = event.charCode;
      // Control (+ Shift) + `
      if (event.ctrlKey && !event.metaKey && !event.altKey &&
          (charCode == 96 || charCode == 126)) {
        event.stopPropagation();
        event.preventDefault();

        self._initFrame(function() {
          let groupItems = self._window.GroupItems;
          let tabItem = groupItems.getNextGroupItemTab(event.shiftKey);
          if (!tabItem)
            return;

          if (gBrowser.selectedTab.pinned)
            groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true});
          else
            gBrowser.selectedTab = tabItem.tab;
        });
      }
    }, true);
  },
```

###タブグループ遷移関数 `interTabGroupMove`

ここから`getNextGroupItemTab`関数をラッピングする形でタブグループ間の遷移を行うオリジナル関数を作成します。`TabView__setBrowserKeyHandlers`関数を参考にしています。

```js:browser-tabview.js

  interTabGroupMove: function TabView_interTabGroupMove(count, flag)
  {

    for (var i = 0; i < count; i++) {
      let self = this;
      self._initFrame(function() {
        let groupItems = self._window.GroupItems;
        let tabItem = groupItems.getNextGroupItemTab(flag);
        if (!tabItem)
          return;

      if (gBrowser.selectedTab.pinned)
        groupItems.updateActiveGroupItemAndTabBar(tabItem, {dontSetActiveTabInGroup: true});
      else
        gBrowser.selectedTab = tabItem.tab;
      });
    }

  },
```

`TabView_interTabGroupMove(count, flag)`関数は、`flag`でタブグループを遷移する方向を、`count`で遷移する数を引数としています。`GropuItems`をID指定でgetするような関数を見つけられなかったためforループを用いる強引なタブグループ間遷移となってしまいましたが、これで所望の機能の一部分を実現できました。

###左右タブ遷移関数 `tabMove`

次に左右のタブ間遷移を行うオリジナル関数を作成します。これは、**Ctrl+Tab**で呼び出される関数がわかっていたので難しいことはありませんでした。

```js:browser-tabview.js

  tabMove: function TabView_tabMove(count, flag)
  {
      let self = this;
      self._initFrame(function() {
        if(!flag){
          gBrowser.tabContainer.advanceSelectedTab(1, true);
        } else {
          gBrowser.tabContainer.advanceSelectedTab(-1, true);
        }
      });  
  },

```

`tabMove(count, flag)`関数はタブをいくつ遷移するのか(`count`)とどちら方向に遷移するのか(`flag`)を引数にとり`TabView_interTabGroupMove(count, flag)`と同じ使い方が出来る様に実装しようと試みました。しかし、上のソースを見たらわかるように、引数`count`は使われていません。これは、`TabView_interTabGroupMove(count, flag)`と同様にforループを回したところタブ移動が1タブ分しか行われなかったためです。
原因は特定できていませんが、今回の目的においては選んでいるタブが左右に１つ遷移することができたら十分であるためこのままとしました。

##タブの遷移とその画面
###ポップアップ画面を作る
*browser.js*を眺めた結果として、中に書き加えた変更はブラウジング中常時適応されそうです。そこで**Ctrl + カーソルキー**でタブ遷移用の画面を呼び出すために以下を追加します。

```js:browser.js
var inputKeyCode;

document.onkeydown = function (e) {
    if(!e.ctrlKey){
        return;
    }

    const code = e.keyCode;
    if(code <= 40 && code >= 37){
        inputKeyCode = code;
        const wid = window.screen.width / 1.8;
        const hei = window.screen.height / 1.5;
        showModalDialog("original.html", window, "dialogWidth:720px;dialogHeight:480px;maximize:no;minimize:no;status:no");
    }
}
```

これでキーが押されると画面中央にポップアップウィンドウが表示され、そこに*original.html*が読み込まれます。またその時にどの方向のカーソルキーで立ち上げたのかを保持しておきます。`showModalDialog`を用いることでポップアップ中の親ウィンドウへの操作を制限しています。

###original.htmlを作成
子ウィンドウに読み込んでもらうhtmlファイルを作成しようとして問題がひとつ発生しました。*browser.js*と同じディレクトリにファイルを置いたのですが、ビルドしてもオブジェクトディレクトリに反映されません。ということはビルドされる対象ファイルはどこかで指定されているはずと思い、探してみると*mozilla-release/browser/base/jar.mn*がそれらしいです。

```:jar.mn
content/browser/original.html                 (content/original.html)
```

この一行を追加したら、無事オブジェクトディレクトリにも*original.html*が反映されるようになりました。今後も新規ファイルを追加する場合はここをいじれば大丈夫そうです。

###親ウィンドウのデータと制御
ここからは*original.html*に処理を記述していきます。この改造が初javascriptであったので今思うと色々書き直したいところではあります(今だとセミコロン付けない派です)が、当時の流れに沿って行きたいと思います。*original.html*のスクリプトの部分のみ書いていきます。

```js:original.html
var pw = window.opener; // parent window for using objects in browser.js
var tabView = pw.TabView;
var groupItems = null;

tabMove(pw.inputKeyCode);

var timer1 = setInterval( function(){
	if(tabView.getContentWindow()){
		groupItems = tabView.getContentWindow().GroupItems;
		document.getElementById("text2").textContent = groupItems;
		groupItems.flushAppTabUpdates();
		clearInterval(timer1);
		setTabData();
	}
}, 50);


document.onkeyup = function (e) {
	if(!e.ctrlKey){
		window.close();
	}
}

document.onkeydown = function (e) {
	tabMove(e.keyCode);
}

function tabMove(code){
    ~~~
}
```

`window.opener`でそのウィンドウを開いた親ウィンドウのオブジェクトを取得します。これを通じて*browser.js*の変数や関数にアクセスすることでメインウィンドウを操作します。
`TabView`オブジェクトは*browser-tabview.js*に定義されていて、タブ周りの情報のほとんどを持ってる感じのやつです。しかしこの中のウィンドウ情報ははじめから格納されているわけではないっぽいので、`setInterval`で取得できるまで待ちます。`GroupItems`はそのウィンドウの全グループのタブの情報が入ってます。

とりあえず`tabMove`というキーに応じてタブを移動する関数（これは*browser-tabview.js*で作成した`TabView`オブジェクトの関数とは異なります）があるとして、それをまずウィンドウをポップアップさせた時の入力で実行します。これは簡単な移動のためにも複数回の入力が必要では煩わしいからです。
また**Ctrl**を離した時にポップアップを消すことで邪魔にならないようにします。

###タブ遷移
`tabMove`関数の中身を書いていきます。`TabView`オブジェクト内に、同グループ内の左右タブ移動に`tabMove`、前後のグループ移動に`interTabGroupMove`を用意したので、これをそれぞれのキーに割り当てます。
`moving`関数については次の項目で解説します。これはタブ遷移時の簡単なアニメーション用です。

```js:original.html

function tabMove(code){
	switch(code){
		case 37: // left
		tabView.tabMove(1, true);
		moving(0, 14);
		break;

		case 38: // up
		tabView.interTabGroupMove(1, false);
		moving(10, 0);
		break;

		case 39: // right
		tabView.tabMove(1, false);
		moving(0, -14);
		break;

		case 40: // down
		tabView.interTabGroupMove(1, true);
		moving(-10, 0);
		break;

		case 84: // "t"
			pw.openUILinkIn(pw.BROWSER_NEW_TAB_URL, "tab");
			setTabData();
		break;

		case 87: // "w"
		if(pw.gBrowser.selectedTab !== pw.gBrowser.tabContainer.advanceTakeTabUdon(1, true)){
			pw.BrowserCloseTabOrWindow();
			setTabData();
		} else {
			if (groupItems) {
				deleteItem = groupItems.getActiveGroupItem();
				tabView.interTabGroupMove(1, false);
				deleteItem.destroy(true);
				setTabData();
			}
		}
		break;

		case 82: // "r"
		if (groupItems) {
			deleteItem = groupItems.getActiveGroupItem();
			tabView.interTabGroupMove(1, false);
			deleteItem.destroy(true);
			setTabData();
		}
		break;

		case 77: //"m"
		var uri = window.prompt("URLを入力", "http://www.google.co.jp/");
		//document.getElementById("text3").textContent = uri;
		if(uri){
			pw.openUILinkIn(uri, "tab")
			setTabData();
		}
		break;

		case 78: //"n"
		if (groupItems) {
			groupItems.newGroup().newTab("about:blank", true);
			setTabData();

		}
		break;

		case 83: //"s"
		var words = window.prompt("google検索", "What do you want to know?");
		if(words){
			pw.openUILinkIn("http://www.google.co.jp/#q="+words, "tab")
			setTabData();
		}
		break;

		default:
		pw.console.log("code:" + code);
		break;
	}
}

function moving(down, right){
    ~~~
}
```

このポップアップウィンドウが表示されている間は親ウィンドウのショートカットは無効になっているので、空いたキーにも様々な機能を割り当てています。詳細は割愛しますが、これでポップアップのまま新規タブや新規グループを開いたり、閉じたりできます。新規タブは空タブ、URL直接打ち込み、Google検索の3通りで開けます。

###プレビュー位置の移動
`moving`関数の中身を見ていきます。スクリプトの外部も併記します。
かなり汚くて書き直したいところですが、とりあえずプレビュー画像表示位置を動かしていることだけ分かれば大丈夫です。この中で使われている`setTabData`関数が実際に画像を読み込んでいるところです。その解説である次の項でラストです。

```html:original.html
<div><img id="center_tab" style="position:absolute;"></div>
<div><img id="up_tab" style="position:absolute;"></div>
<div><img id="dawn_tab" style="position:absolute;"></div>
<div><img id="right_tab" style="position:absolute;"></div>
<div><img id="left_tab" style="position:absolute;"></div>
<div><img id="up_tab2" style="position:absolute;"></div>
<div><img id="down_tab2" style="position:absolute;"></div>

<script type="text/javascript">
var tabC = document.getElementById("center_tab");
var tabU = document.getElementById("up_tab");
var tabD = document.getElementById("dawn_tab");
var tabR = document.getElementById("right_tab");
var tabL = document.getElementById("left_tab");
var tabU2 = document.getElementById("up_tab2");
var tabD2 = document.getElementById("down_tab2");

var tabU_base = null;
var tabD_base = null;

var subwid = innerWidth / 3.3;
var subhei = innerHeight/ 3.5;
var diff = subwid / 25;

tabC.style.width = subwid;
tabC.style.height = subhei;
tabC.style.left = (innerWidth - subwid) / 2;
tabC.style.top = innerHeight / 2.8;
var CLeft = tabC.style.left;
var CTop = tabC.style.top;

tabU.style.width = subwid;
tabU.style.height = subhei;
tabU.style.zIndex = 2;
tabU.style.left = (innerWidth - subwid) / 2;
tabU.style.top = innerHeight / 18;
var ULeft = tabU.style.left;
var UTop = tabU.style.top;

tabU2.style.width = subwid;
tabU2.style.height = subhei;
tabU2.style.zIndex = 1;
tabU2.style.left = (innerWidth - subwid) / 2 + diff;
tabU2.style.top = innerHeight / 18 - diff;
var U2Left = tabU2.style.left;
var U2Top = tabU2.style.top;

tabD.style.width = subwid;
tabD.style.height = subhei;
tabD.style.zIndex = 2;
tabD.style.left = (innerWidth - subwid) / 2;
tabD.style.top = innerHeight / 1.5;
var DLeft = tabD.style.left;
var DTop = tabD.style.top;

tabD2.style.width = subwid;
tabD2.style.height = subhei;
tabD2.style.zIndex = 1;
tabD2.style.left = (innerWidth - subwid) / 2 + diff;
tabD2.style.top = innerHeight / 1.5 -diff;
var D2Left = tabD2.style.left;
var D2Top = tabD2.style.top;	

tabR.style.width = subwid;
tabR.style.height = subhei;
tabR.style.left = (innerWidth * 1.5 - subwid) / 2 + subwid/4;
tabR.style.top = innerHeight / 2.8;
var RLeft = tabR.style.left;
var RTop = tabR.style.top;

tabL.style.width = subwid;
tabL.style.height = subhei;
tabL.style.left = (innerWidth * 0.5 - subwid) / 2 - subwid/4;
tabL.style.top = innerHeight / 2.8;
var LLeft = tabL.style.left;
var LTop = tabL.style.top;

function moving(down, right){
	if(!(down || right)){
		return;
	}

	var moveTab = function(tab, Left, Top){
		var L = parseInt(Left);
		var T = parseInt(Top);
		var timer = 1;
		var id = setInterval(function(){
			if(timer === 20){
				tab.style.left = L + "px";
				tab.style.top = T + "px";
				setTabData();
				clearInterval(id);
			}else {
				tab.style.left = (L + timer * right) + "px";
				tab.style.top = (T + timer * down) + "px";
				timer += 1;
			}
		}, 15);
	};

	if( ( pw.gBrowser.selectedTab != pw.gBrowser.tabContainer.advanceTakeTabUdon(1, true) ) || ( down != 0 ) ){
		moveTab(tabC, CLeft, CTop);
		moveTab(tabR, RLeft, RTop);
		moveTab(tabL, LLeft, LTop);
	}
	if(down != 0){
		moveTab(tabU, ULeft, UTop);
		moveTab(tabD, DLeft, DTop);
		moveTab(tabU2, U2Left, U2Top);
		moveTab(tabD2, D2Left, D2Top);
	}

}

function setTabData(){
    ~~~
}

</script>
```

###プレビュー画像の読み込み
最後の項ですが、ここが一番の山場でした。枠を５つ用意したことからわかるように、現在・同グループ左右・前後グループのタブのプレビュー画像を読み込みます。これがカーソルキーで移動する先のタブに対応します。

タブデータがあればそこから`tabPreviews.get`を用いてプレビュー画像を取得できるので、タブデータの取得方法を何とかして探します。
左右タブは既存の関数で取ってこれそうなのが見当たらないので`gBrowser.tabContainer.advanceTakeTabUdon`を*mozilla-release/toolkit/content/widgets/tabbox.xml*の適当な位置に追加して、これで取得します。このファイルは*browser-tabview.js*で使用されている`gBrowser.tabContainer.advanceSelectedTab`を追っている時に見つけました。書き方は前後の記述を参考にします。

```xml:tabbox.xml
      <method name="_takeNewTabUdon">
        <parameter name="aNewTab"/>
        <parameter name="aFallbackDir"/>
        <parameter name="aWrap"/>
        <body>
        <![CDATA[
          var requestedTab = aNewTab;
          while (aNewTab.hidden || aNewTab.disabled || !this._canAdvanceToTab(aNewTab)) {
            aNewTab = aFallbackDir == -1 ? aNewTab.previousSibling : aNewTab.nextSibling;
            if (!aNewTab && aWrap)
              aNewTab = aFallbackDir == -1 ? this.childNodes[this.childNodes.length - 1] :
                                             this.childNodes[0];
            if (!aNewTab || aNewTab == requestedTab)
              return;
          }

          var isTabFocused = false;
          try {
            isTabFocused =
              (document.commandDispatcher.focusedElement == this.selectedItem);
              return aNewTab;
          } catch (e) {}
          this.selectedItem = aNewTab;
          if (isTabFocused) {
            <!-- aNewTab.focus(); -->
            return aNewTab;
          }
          else if (this.getAttribute("setfocus") != "false") {
            let selectedPanel = this.tabbox.selectedPanel;
            document.commandDispatcher.advanceFocusIntoSubtree(selectedPanel);

            // Make sure that the focus doesn't move outside the tabbox
            if (this.tabbox) {
              try {
                let el = document.commandDispatcher.focusedElement;
                while (el && el != this.tabbox.tabpanels) {
                  if (el == this.tabbox || el == selectedPanel)
                    return;
                  el = el.parentNode;
                }
                <!-- aNewTab.focus(); -->
                return aNewTab;
              } catch(e) {
                return aNewTab;
              }
            }
            return aNewTab;
          }
          return aNewTab;
        ]]>
        </body>
      </method>

      <method name="advanceTakeTabUdon">
        <parameter name="aDir"/>
        <parameter name="aWrap"/>
        <body>
        <![CDATA[
          var startTab = this.selectedItem;
          var next = startTab[aDir == -1 ? "previousSibling" : "nextSibling"];
          if (!next && aWrap) {
            next = aDir == -1 ? this.childNodes[this.childNodes.length - 1] :
                                this.childNodes[0];
          }
          if (next && next != startTab) {
            return this._takeNewTabUdon(next, aDir, aWrap);
          }
        ]]>
        </body>
      </method>

```

`GroupItems`は*mozilla-release/browser/components/tabview/groupitems.js*で定義されてます。これを眺めていくと`getNextGroupItemTab`とかいうそれっぽい物があります。この返り値から直接のタブデータではありませんが、前後タブグループのそれに近そうなものが取得できます。

またタブの状態を見つつ、`setInterval`関数を用いて、読み込みができていなければ読み込みを繰り返し、プレビューが存在しない特別なタブには適当な画像を当てはめます。新規で用意した画像ファイルは*jar.mn*に忘れず書いておきましょう。

```js:original.html
function setTabData(){
	document.getElementById("text2").textContent = groupItems;

	var nowTab = pw.gBrowser.selectedTab
	document.getElementById("text1").textContent = nowTab.linkedBrowser.currentURI.spec;
	tabToTab(nowTab, tabC);

	if(groupItems){
		tabU_base = groupItems.getNextGroupItemTab(false);
		tabD_base = groupItems.getNextGroupItemTab(true);
		if(tabU_base){
			tabU2.src = "gray.png";
		}else {
			tabU2.src = "empty.png";
		}
		if(tabD_base){
			tabD2.src = "gray.png";
		}else {
			tabD2.src = "empty.png";
		}			
	}else {
		tabU2.src = "empty.png";
		tabD2.src = "empty.png";
	}
	baseToTab(tabU_base, tabU);
	baseToTab(tabD_base, tabD);

	if(nowTab !== pw.gBrowser.tabContainer.advanceTakeTabUdon(1,true)){
		var tempR = pw.gBrowser.tabContainer.advanceTakeTabUdon(1, true);
		tabToTab(tempR, tabR);

		var tempL = pw.gBrowser.tabContainer.advanceTakeTabUdon(-1, true);
		tabToTab(tempL, tabL);
	}else{
		tabR.src = "empty.png";
		tabL.src = "empty.png";
	}

}

function baseToTab(base, tab){
	if(base){
		var uri = base.tab.linkedBrowser.currentURI.spec;
		if(uri.indexOf("about:") === 0){
			if(uri.indexOf("about:newtab") === 0){
				tab.src = "newTab.png";
			}else if(uri.indexOf("about:blank") === 0){
				tab.src = "blank.png";
			}else {
				tab.src = "aboutRobots-icon.png";
			}			
		}else {
			tab.src = pw.tabPreviews.get(base.tab).src;
			var timerId = setInterval(function(){
			if(tab.src === "chrome://browser/content/undefined"){
					tab.src = pw.tabPreviews.get(base.tab).src;
			}else{
					clearInterval(timerId);
				}
			}, 50);	
		}
	}else {
		tab.src = "empty.png";
	}
}

function tabToTab(inTab, outTab){
	if(inTab){
		var uri = inTab.linkedBrowser.currentURI.spec;
		if(uri.indexOf("about:") === 0){
			if(uri.indexOf("about:newtab") === 0){
				outTab.src = "newTab.png";
			}else if(uri.indexOf("about:blank") === 0){
				outTab.src = "blank.png";
			}else {
				outTab.src = "aboutRobots-icon.png";
			}
		}else {
			outTab.src = pw.tabPreviews.get(inTab).src;
			var timerId = setInterval(function(){
			if(outTab.src === "chrome://browser/content/undefined"){
					outTab.src = pw.tabPreviews.get(inTab).src;
			}else{
					clearInterval(timerId);
				}
			}, 50);	
		}
	}
}
```


#最終結果
![Screenshot from 2015-11-26 04:07:39.png](https://qiita-image-store.s3.amazonaws.com/0/101008/05704cdf-fa86-aa34-d968-eb5c24e2cfc1.png "result")

専用ウィンドウとしてこんな感じのポップアップが出るようになりました。画像ではわかりませんが、**Ctrl**を押したまま**カーソルキー**を上下左右にすることで自由自在なタブ遷移ができます。また遷移のたびにプレビューも更新されます。

##改善点

- ポップアップ画面、アニメーションがかっこわるい
    - D3などのライブラリを使ってもう少しスマートに
- ポップアップで親ウィンドウのほとんどが隠れてしまう
    - ウィンドウにこだわることなく、何らかの形でポップアップし、透明度も設定できるように
- プレビュー画像の読み込みが不安定
    - firefoxの既存機能の内には安定してプレビュー画像が取れているものがあるので、プレビュー取得関数周りを変更
- 新規タブグループを作成した際、強制的にタブグループインデックスの末尾に追加される
    - 任意のインデックスに新規タブグループを挿入できるように


##変更を加えたファイル
mozilla-releaseをトップディレクトリとして


- .mozconfig
- browser/
    - base/
        - content/
            - browser.js
            - browser-tabview.js
            - original.html **(new)**
            - その他画像 **(new)**
        - jar.mn
- toolkit/content/widgets/tabbox.xml

[ソースはここにおいてあります。](https://doss-gitlab.eidos.ic.i.u-tokyo.ac.jp/31/BigSoftware)