Home > Latest topics

Latest topics > ウィンドウ間でのタブの移動、というか、フレームの内容の入れ換え

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

ウィンドウ間でのタブの移動、というか、フレームの内容の入れ換え - Oct 19, 2008

Firefox 3.1からは、ウィンドウをまたいでタブを移動できるようになる。アドオンの作者はこれに合わせて修正を行わないといけない場合がある。

また、この機能を実現するために新しく用意された「表示されている2つのフレームの内容を破壊せずに入れ換える」APIは、他の目的でも利用することができる。

背景

Firefox 3はタブを新しいウィンドウのタブバーにドラッグ&ドロップするとタブを「移動」できる。けど、これは実は擬似的にやっているだけで、実際には「移動元のタブを移動先のウィンドウに複製」して「移動元のタブを閉じる」という風なことが行われているに過ぎない。だから、例えばYouTubeなどで動画を再生中のタブを別のウィンドウに「移動」したらページ自体が読み込み直されて動画が最初まで巻き戻されることになるし、livedoor ReaderのようなJavaScriptアプリケーションなら最初の画面まで戻されてしまう。

Firefox 3.1ではこの点が改良されて、上記の「タブの複製」を使った擬似的なものではなく本当に、タブを別のウィンドウに「移動」できるようになる。Minefield 3.1b2preあたりで実際に試すこともできる。

ウィンドウをまたいだタブの移動の実現方法と、アドオンと組み合わせた時の問題

上記の変更によって、一部のアドオンが導入された環境では、ウィンドウをまたいでタブを移動しようとすると、移動できなかったり、移動前後のタブの挙動がおかしくなったりする。アドオンがページの読み込みを監視するためにbrowser要素のwebProgressプロパティを使ってプログレスリスナを登録している場合に、この問題が起こる。

タブの「移動」で何が起こっているのかを調べると、この理由が見えてくる。

Firefoxでは、ブラウズ領域はbrowser要素というインラインフレームの一種で表現されている。単純に考えるとbrowser要素を現在のウィンドウのDocumentから切り離して別のウィンドウのDocumentにインポートし、appendChild()なりinsertBefore()なりで挿入すればいいのでは? ということになるが、これは実際にはやっても上手くいかない。親DocumentからremoveChild()等で切り離した段階で表示中のページは破棄されてしまうので、再挿入しても期待通りの結果は得られない。これが、Firefox 3以前でウィンドウをまたいだタブの移動が不可能で、セッション管理機能を使った擬似的な再現に留まらざるを得なかった理由だ。

ちなみに、同じtabbrowser要素の中でのタブの並べ替えでは、そもそもbrowser要素側には全く変更は起こらない。対応するtab要素とbrowser要素は動的に生成された一意なid文字列で紐付けられており、tab要素の方だけを並べ替えておけば表示上はタブの並べ替え完了ということになるからである。

Firefox 3.1では、tabbrowser要素swapBrowsersAndCloseOther()メソッドbrowser要素swapDocShells()メソッド、そしてnsIFrameLoaderswapFrameLoaders()メソッドが追加された。これらは「すでに画面内に表示されている2つのフレームの内容を破壊しないで入れ換える」というもので、これがFirefox 3.1でのウィンドウをまたいだタブの移動を実現する鍵となる。

Firefox 3.1でウィンドウ間でタブを移動する時は、実際には「ドロップ先のウィンドウに新しい空のタブを開く」「ドラッグ元のタブに対応しているbrowser要素と、新しいタブに対応しているbrowser要素の、DocShellを入れ換える」「ドラッグ元のタブを閉じる」という3つの処理が行われている。プログレスリスナが登録されたままDocShellを入れ換えると不整合が起こってしまうので、Firefox 3.1自身がこの操作を行う時は、DocShellを入れ換える前に双方のbrowserからプログレスリスナを削除して、入れ替えが終わった後に再登録する、ということをswapBrowsersAndCloseOther()メソッドでやっている。しかし既存のFirefoxに合わせて作ったアドオンは当然のことながらこんな事情は知らないので、「新しいタブを開く」と「DocShellを入れ換える」の間(具体的にはTabOpenイベントのタイミングなど)にアドオン用のプログレスリスナを登録していると、不具合が発生してしまう。これが、特定のアドオンがあるとタブの移動に失敗することの原因である。

ちなみに、DocShellに登録されるプログレスリスナとは異なり、browser要素自身に登録されるDOMのイベントリスナはこの処理の影響を受けない。DOMノードとしてのbrowser要素自身には全く変化は起こらないので、登録済みのイベントリスナを再登録するといった必要はない。(もちろん、そのbrowser要素の中に表示されたページ内のDOMノードに対してイベントリスナを登録している場合は、やはり注意が必要な場合がある。)

そこで今回、プログレスリスナを使っているアドオンのほとんどに対して、以下のようなコードを加えることにした。

if ('swapBrowsersAndCloseOther' in aTabBrowser) {
  eval('aTabBrowser.swapBrowsersAndCloseOther = '+
    aTabBrowser.swapBrowsersAndCloseOther.toSource().replace(
      '{',
      '$& MyAddonService.destroyTab(aOurTab);'
    ).replace(
      'if (aOurTab == this.selectedTab) {this.updateCurrentBrowser(',
      'MyAddonService.initTab(aOurTab); $&'
    )
  );
}

タブを閉じる前にプログレスリスナ等を削除するために用意してあったメソッドと、タブを開いた後にプログレスリスナ等を登録するために用意してあったメソッドとを、swapBrowsersAndCloseOther()メソッドの中で実行するようにする。これで、問題なくウィンドウ間でタブを移動できるようになる。コードの挿入箇所はこの例の位置が無難だろう。

この変更を施したアドオンは以下の通り。

同様のこと(プログレスリスナによるタブの監視)をやっているアドオンを作っている人は注意して欲しい。

APIを利用する

nsIFrameLoaderのswapFrameLoaders()メソッドでフレームの表示内容を入れ換えられる場面は非常に限定されている。なので実際には、browser要素同士の間でDocShellを入れ換えるという場面に特化したラッパであるbrowser要素のswapDocShells()メソッド、tabbrowser要素同士の間でタブとそれに関連付けられたbrowser要素のDocShellを入れ換える場面に特化したラッパであるswapBrowsersAndCloseOther()メソッド、等の方が使い出があるだろう。

tabbrowser要素のswapBrowsersAndCloseOther()メソッドは、引数として2つのタブを取る。1つめはそのtabbrowser要素自身に属しているタブで、もう1つは移動元のタブだ。Firefox自身はウィンドウをまたいでタブを移動する時にしかこのメソッドを使っていないが、実際には、分割ブラウザのように1つのウィンドウ内に複数のtabbrowser要素を置いている場合の各tabbrowser間でのタブの移動にも使える。

ただし、このメソッドはその名の通り第2引数で渡されたタブを必ず閉じるという点と、移動元のタブが最後のタブだった場合は移動元のウィンドウを閉じようとする点に、注意が必要だ。タブが閉じられる時やウィンドウが閉じられる時については一般的な手段(removeTab()DOMWindow.close())が使われるので、TabCloseunloadイベントを監視していれば問題ない。しかし分割ブラウザのように1つのウィンドウに複数のtabbrowser要素があること前提の機能だと、移動元も先も同じウィンドウである場合があるので、ウィンドウを閉じられてしまっては困る。なので分割ブラウザではやむを得ず、移動元のtabbrowserが分割された領域の中の物である場合は分割を解除し、そうでなければウィンドウを閉じる、という風な条件分岐を行うようにswapBrowsersAndCloseOther()メソッドを書き換えている。

それと、browser要素のswapDocShells()メソッドはtabbrowser要素の中にあるbrowser要素同士、または、tabbrowser要素に含まれていないbrowser同士の内容の入れ替えにしか対応していない事にも注意が必要だ。tabbrowser要素の中のbrowser要素はちょっと変わった使い方をされているので、そのままだと内容を入れ換えられない。どうしてもやりたい場合には、両者の差異を解消して同じ処理を行うラッパを自分で書くしかないだろう。

ともかく、このAPIを利用すれば、タブの移動に関わらずFirefox内でサブフレームに表示された内容を任意の場所に移し替えることができる。分割ブラウザではこの機能を使って、分割後の領域を自由に動かせるようになった。また、ツリー型タブマルチプルタブハンドラは、複数のタブをまとめてウィンドウ間で移動する時に限りこのAPIを使うようにしてある。

使える場面はかなり限定的だが、このAPIを使って他にも何か面白いことができないか考えてみてはどうだろうか。

まとめ

  • Firefox 3.1でのウィンドウ間のタブの移動は、実際には、すでに表示されている2つのフレームの間での内容の入れ換え機能によって実現されている。この時にはDocShell自体が入れ替わっている。
  • DocShellの入れ換え前には、全てのプログレスリスナを削除しておかないといけない。(入れ替えが終わった後でのプログレスリスナの再登録も忘れずに。)
  • browser要素のswapDocShells()メソッドかtabbrowser要素のswapBrowsersAndCloseOther()メソッドを使うと、このAPIを簡単に利用できる。

11月20日追記:仕様変更への対応

swapBrowsersAndCloseOther()メソッドの内容が若干変更されたため、上のメソッド改変用コードが動かなくなっていた。ので、Beta 1でもBeta 2でも多分動くように再度修正した。

2009年4月30日追記:仕様変更への対応

swapBrowsersAndCloseOther()メソッドの内容がまた変わっていたので、Shiretoko 3.5b5preベースに修正。Beta1やBeta2では機能しなくなってるけど、気にしない。(そんな前のバージョンをわざわざ使ってる方が悪い)

分類:Mozilla > XUL, , , , , , , , , , , 時刻:00:59 | Comments/Trackbacks (0) | Edit

Comments/Trackbacks

TrackBack ping me at


の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2008-10-19_swapframes.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。

Post a comment

writeback message: Ready to post a comment.

2020年11月30日時点の日本の首相のファミリーネーム(ひらがなで回答)

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のコメント

最近のつぶやき