宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
Firefoxでは、タブがセッション情報も伴って復元された時に、SSTabRestoring
とSSTabRestored
という2つのイベントが発行される。イベントのoriginalTarget
はいずれも復元されたタブの要素で、SSTabRestoring
はセッション復元処理が走ったけれどもタブの読み込みは完了していない段階、SSTabRestored
はタブの読み込みが完了した段階で発行される。
これらのイベントが発行される場面は、3つある。
この3つの場合を、特に1とそれ以外とを判別したい、その方法を考えてみたよ、というのがこのエントリの主題です。
例えばツリー型タブでは、タブの親子関係が変更された時のインデントの変更やツリーの折りたたみ時にアニメーション効果を適用している。しかしながら、Firefoxのセッション復元時に複数のタブのツリー構造を一気に変更する場合には、いちいちアニメーションしてたら重くてしょうがない。なので、この時だけはアニメーション効果を適用しないようにしたいと僕は思った。
しかしながら、前述のSSTabRestoring
とSSTabRestored
からは、そのイベントが発行されたのが上記の3つの場面のうちいずれなのかが分からない。どの場面でイベントが発行されたのかを判別するには、他の情報も参照する必要がある。
実は1の場合については、セッションが復元される時にObserverServiceに登録したオブザーバに対してsessionstore-windows-restored
というメッセージが通知される。なので、これを監視すればいいんじゃないか?と、最初のうちは考えてた。
でも、話はそう単純には済まなかった。実際にイベントを監視してみると、セッション復元時には以下のような順番でイベントが発行されていることが分かった。
SSTabRestoring
イベントが発行される。sessionstore-windows-restored
が通知される。SSTabRestoring
イベントが順番に発行される。SSTabRestored
イベントが読み込みの終わった物から発行される。3と4はこの通りにならないこともあって、あるタブのSSTabRestoring
が発行されてすぐに読み込みが完了したタブについては、次のタブのSSTabRestoring
が発行される前にSSTabRestored
が発行される場合もある。
一番致命的なのは、複数タブのセッション復元が始まったことは通知されるのに、全部のタブのセッション復元が終わったことは通知されないという点だ。
sessionstore-windows-restored
が通知されたらその後に発行されたSSTabRestoring
は全部ウィンドウ単位のセッション復元の一部なんだな、と判断してしまうと、例えば10個のタブを開いたウィンドウのセッションを復元した後、1つタブを開いて、閉じて、開き直した時、そのタブまで「ウィンドウ単位のセッション復元の一部」と誤爆してしまう。SSTabRestored
が発行されるまでの間にSSTabRestoring
が発行されたタブ」がウィンドウ単位のセッション復元なんだな、と判断してしまうと、後続のタブのSSTabRestoring
よりも前にSSTabRestored
が発行された時に、セッション復元が終わってないのに終わったと見なされてしまう。という具合で、「ウィンドウ単位でのセッション復元の終わり」がいつなのかが分からないと、誤爆しまくりで全然役に立たない。
また、重ねて困ったことに、sessionstore-windows-restored
の通知はsubjectがnull
なので、どのウィンドウでセッション復元が開始されたのかすらオブザーバ側からは分からない。別のウィンドウでセッション復元がはじまった時に来た通知を見て「これからこのウィンドウで開き直されるタブは全部、ウィンドウ単位のセッション復元の一部なんだな」と判断してしまってはいけない。
dump()
をコードの中に埋め込んで調べたところ、nsSessionStore.js内の各処理とイベントは、以下のような順番で起こっているらしいということが分かった。
SSTabRestoring
が発火sessionstore-windows-restored
が通知されるSSTabRestoring
が発火×タブの個数分SSTabRestored
が発火×タブの個数分SSTabRestored
が発火したら、すべてのタブの復元が完了このうちnsSessionStore::restoreHistoryPrecursor()やnsSessionStore::restoreHistory()やnsSessionStore::restoreDocument_proxy()は、タブを1つ復元するだけの時にも使われてる。
よくコードをよく読むと、nsSessionStore::restoreHistoryPrecursor()の中で「復元するタブの数だけ新しくタブを開く」「それらのタブのtab.linkedBrowser.parentNode.__SS_data._tabStillLoading
をtrue
にセットする」という処理が行われて、その後でsessionstore-windows-restored
がオブザーバに通知されていた。(このtab.linkedBrowser.parentNode.__SS_data._tabStillLoading
は、タブの復元が完了した段階でundefined
になる。)
ということは、sessionstore-windows-restored
が通知された時にウィンドウ内の全部のタブを調べて、tab.linkedBrowser.parentNode.__SS_data._tabStillLoading
がtrue
であるタブが2つ以上ある(復元待ちのタブが複数ある)なら、そのウィンドウでこれからウィンドウ単位のセッション復元が行われようとしている、と考えてよいわけだ。
で、SSTabRestored
が発行される度に復元待ちのタブの数を確認して、復元待ちのタブの数が0になったら、ウィンドウ単位でのセッション復元が終わったと判断できる。
厳密には、「ウィンドウ単位でのセッションの復元中に別途タブを1個だけ復元した」という場合にもそのタブが「ウィンドウ単位のセッション復元の一環で復元された」と見なされてしまうので、この方法にも穴はある。たくさんのタブのツリー構造を一気に変更した時にアニメーションでクソ重くなるのを避けたい、という僕の目的ではこれで必要十分なので、これ以上は追求してない。
以上をコンパクトにまとめると、こんな感じになる。
var ObserverService = Cc['@mozilla.org/observer-service;1']
.getService(Ci.nsIObserverService);
var observer = {
restoringWindow : false,
getRestoringTabsCount : function() {
return Array.slice(gBrowser.mTabContainer.childNodes)
.filter(function(aTab) {
var owner = aTab.linkedBrowser;
var data = owner.parentNode.__SS_data;
return data && data._tabStillLoading;
}).length;
},
observe : function(aSubject, aTopic, aData) {
if (aTopic == 'sessionstore-windows-restored')
this.restoringWindow = this.getRestoringTabsCount() > 1;
},
handleEvent : function(aEvent) {
switch (aEvent.type) {
case 'load':
window.removeEventListener('load', this, false);
window.addEventListener('unload', this, false);
gBrowser.addEventListener('SSTabRestoring', this, false);
gBrowser.addEventListener('SSTabRestored', this, false);
return;
case 'unload':
ObserverService.removebserver(this, 'sessionstore-windows-restored');
window.removeEventListener('unload', this, false);
gBrowser.removeEventListener('SSTabRestoring', this, false);
gBrowser.removeEventListener('SSTabRestored', this, false);
return;
case 'SSTabRestoring':
this.onTabRestoring(aEvent);
return;
case 'SSTabRestored':
this.onTabRestored(aEvent);
return;
}
},
onTabRestoring : function(aEvent) {
var tab = aEvent.originalTarget;
if (this.restoringWindow) {
// ウィンドウ単位でのセッション復元時の処理
}
else {
// タブが個別に開き直された時の処理
}
},
onTabRestored : function(aEvent) {
if (this.restoringWindow)
this.restoringWindow = this.getRestoringTabsCount() > 0;
}
};
window.addEventListener('load', observer, false);
ObserverService.addObserver(observer, 'sessionstore-windows-restored', false);
セッションストアAPIを使ってタブにいろんな情報を紐付けて、その情報に基づいてあれこれしたい人には、役に立つんじゃないでしょうか。(でもそんな変なことやってる人はほとんどいないんだろうなー)
の末尾に2020年11月30日時点の日本の首相のファミリーネーム(ローマ字で回答)を繋げて下さい。例えば「noda」なら、「2009-10-27_sstabrestoring.trackbacknoda」です。これは機械的なトラックバックスパムを防止するための措置です。
writeback message: Ready to post a comment.