Home > Latest topics

Latest topics 近況報告

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

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

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

Page 11/241: « 7 8 9 10 11 12 13 14 15 »

ツリー型タブでセッション復元時にツリーが壊れる問題について調査中 - Sep 29, 2009

表題の件について、どーも実際に表示されてる内容とセッション情報とが食い違ってるケースがあるようだ。

Firefoxのタブとかのセッション情報はJSONっぽい文字列で保存されてて、最初はJSON整形で読みやすい形にして調べようと思ってたけど、めんどすぎたので、以下のようなスクリプトでツリー構造の所だけ可視化してみた。

var sv = gBrowser.treeStyleTab;

var session = sv.SessionStore.getWindowState(window);
eval('session = '+session);

var result = [];

session.windows[0].tabs.forEach(function(aInfo) {
   var entry = aInfo.entries[aInfo.entries.length-1];
   var item = {
         label    : entry.title+' / '+entry.url,
         id       : aInfo.extData[sv.kID],
         children : (aInfo.extData[sv.kCHILDREN] || '').split('|'),
         parent   : (aInfo.extData[sv.kPARENT] || ''),
         items    : []
      };
   var bullet = '*';
   var tab = sv.getTabById(item.id);
   if (tab.getAttribute(sv.kPARENT) != item.parent) {
      item.label += '\n<WRONG PARENT>';
      bullet = '?';
   }
   if (tab.getAttribute(sv.kCHILDREN) != item.children.join('|')) {
      item.label += '\n<WRONG CHILDREN>';
      bullet = '?';
   }
   item.label = item.label.replace(/^/gm, '  ').replace(/^./, bullet);
   var current, index;
   if (result.some(function(aItem) {
         if (!aItem) return false;
         if (aItem.items.some(arguments.callee)) return true;
         current = aItem;
         index = aItem.children.indexOf(item.id);
         return index > -1;
      })) {
      if (current.items.length <= index) {
         while (current.items.length < index) current.items.push(null);
         current.items.push(item);
      }
      else {
         current.items[index] = item;
      }
   }
   else if (result.some(function(aItem) {
         if (!aItem) return false;
         if (aItem.items.some(arguments.callee)) return true;
         current = aItem;
         return aItem.id == item.id;
      })) {
      current.items.push(item);
   }
   else {
      result.push(item);
   }
});

var string = result.map(function(aItem) {
      var children = aItem.items.map(arguments.callee).join('\n');
      return children ?
            aItem.label+'\n'+children.replace(/^/gm, '  ') :
            aItem.label ;
   }).join('\n')+'\n';

alert(string);

で、調べてみたら、やっぱりツリー構造がおかしい。ツリー表示はタブの属性値の方に基づいて行われてて、その属性値をnsISessionStoreのsetTabValue()deleteTabValue()でセッションの方にミラーしてるんだけど、ミラーされてるはずの値が期待通りにミラーされてないようだ。

追記。

……nsSessionStore.jsを読んでたら原因が分かった。

  • setTabValue()では内部で最後にsaveStateDelayed()を呼んでいるため、変更がファイル(プロファイルフォルダ内のsessionstore.js)にすぐ書き出される。
  • deleteTabValue()ではsaveStateDelayed()が呼ばれてないために、他の処理の中でsaveStateDelayed()が呼ばれるまでは変更がファイルに書き出されない。
  • ファイルが書き出されないうちにFirefoxを終了したり再起動したりすると、メモリ上のセッション情報は破棄されて、ファイルに書き出されたセッション情報が次回起動時に読み込まれる。
  • よって、deleteTabValue()だけで情報をミラーしたつもりでいると、ゴミ情報が残ったままになってしまうことが多々ある。そのゴミ情報が邪魔をして、期待通りにツリー構造が復元されなくなってしまっている。
  • 調べてみたらBug 510965 - deleteWindowValue and deleteTabValue API functions need to call saveStateDelayedというバグも立っていた。

deleteTabValue()する前にsetTabValue()で空の値をセットして強制的にセッション情報を書き出させるようにしてみたところ、上記のスクリプトで調査してもセッション情報との間でのツリー構造の不整合は検出されなくなった。

結論:deleteTabValue()マジ使えねえ……

追記。

それでも全然駄目だった。Firefoxがセッション情報をファイルに書き出す時、読み込み中であるというフラグが立っている(Firefox 3.5以前ではtab.linkedBrowser.parentNode.__SS_data._tabStillLoading、Firefox 3.6以降ではtab.linkedBrowser.__SS_data._tabStillLoadingtrueである)タブについてはキャッシュされた情報を書き出すようになってるのに、このフラグがタブの内容の読み込み完了後も立ちっぱなしになってるせいで、常にキャッシュされた古い情報が書き出されてしまい、setTabValue()で設定された新しい値が無視されてしまう。

結論:nsISessionStore/nsSessionStore.jsは腐ってる……

まとめると、以下のようなメソッドを使うようにしてやれば色々と幸せになれそうです。

setTabValue : function(aTab, aKey, aValue) {
   if (!aValue) return this.deleteTabValue(aTab, aKey);

   try {
      this.checkCachedSessionDataExpiration(aTab);
      this.SessionStore.setTabValue(aTab, aKey, aValue);
   }
   catch(e) {
   }

   return aValue;
},

deleteTabValue : function(aTab, aKey) {
   try {
      this.checkCachedSessionDataExpiration(aTab);
      this.SessionStore.setTabValue(aTab, aKey, '');
      this.SessionStore.deleteTabValue(aTab, aKey);
   }
   catch(e) {
   }
},

checkCachedSessionDataExpiration : function(aTab) {
   var data = aTab.linkedBrowser.__SS_data || // Firefox 3.6-
              aTab.linkedBrowser.parentNode.__SS_data; // -Firefox 3.5
   if (data &&
       data._tabStillLoading &&
       aTab.getAttribute('busy') != 'true' &&
       aTab.linkedBrowser.__SS_restoreState != 1)
      data._tabStillLoading = false;
},

2010年1月29日追記。Firefox 3.6以降とFirefox 3.5以前でフラグが保存される場所が少し違っていたので、その旨を修正した。

2010年9月27日追記。Firefox 4 の最適セッションリストア原文)の影響によって、まだ実際にはセッションが復元されていないタブなのに、ビジー状態でなくなっているというケースがあり得るようになった。そのため、aTab.getAttribute('busy')だけでビジー状態を判別すると、これからセッションを復元して欲しい・読み込み中のタブであるにも関わらず_tabStillLoadingをfalseにしてしまい、セッションが復元されなくなってしまうという問題が起こっていた。なので、タブの属性値と併せてaTab.linkedBrowser.__SS_restoringも確認するようにサンプルコードを修正した。

2010年12月6日追記。aTab.linkedBrowser.__SS_restoringが廃止されてaTab.linkedBrowser.__SS_restoreStateというプロパティが使われるようになっていたので、それにあわせてサンプルコードを修正した。

addTab, loadOneTabの引数がFirefox 3.6で変わる? - Sep 16, 2009

trunk gBrowserのloadOneTabとaddTabの引数が変わった - alice0775のファイル置き場 - Yahoo!ジオシティーズを見て初めて知ったけど、TrunkでgBrowser.addTab()gBrowser.loadOneTab()の仕様が変わったようだ。

すでに追加されている「現在のタブの隣に新しいタブを開く」機能は、リファラが渡されていれば現在のタブの隣に、そうでなければタブバーの右端にタブを開くという挙動になっている。これに対し、リファラを渡さなくても現在のタブの隣に新しいタブを開けるようにしたい、という要望が出た(当然と言えば当然だ)。

それを実現するには、普通に考えると、gBrowser.addTab()gBrowser.loadOneTab()の引数でそういう挙動を指定できるようにしてやらないといけない。しかしどっちのメソッドもすでに多数の引数を受け付けるようになってて(現状でもすでに6個ある!)、これ以上引数を増やすのってどうなん? と。関数の引数が多いのは悪い設計の典型例だ。こういう場面ではハッシュなりなんなりを使うのが定石ですわな。そこで件のバグが立ったと。

最初に提出されたパッチは、引数リストにさらにaRelatedToCurrentを加えつつ、各引数に対応する値をプロパティに持つオブジェクトを2番目の引数として渡した時はそっちを使うようにするという風になってる。これだけ見ると「また引数増やすのかよ、しかも新方式(ハッシュによる指定)もサポートするのかよ。マンドクセ。」と思うところだけど、2番目に提出されたパッチでは引数の数の方は変更が無くて、aRelatedToCurrentに相当する引数を指定したい時はハッシュを使わなければならないようになってる。実際にチェックインされた内容は後者のパッチの通りだ。

この事から、今後は新方式のAPI(新しいタブの挙動はgBrowser.addTab()gBrowser.loadOneTab()の第2引数でハッシュで指定する)が標準となり、旧方式のAPI(gBrowser.addTab()gBrowser.loadOneTab()に沢山の引数を渡す)はあくまで後方互換性のためにのみ残されている、という風に考えることができる。

参考までに、新旧それぞれの書き方を示しておこう。

// new API
var newTab = gBrowser.addTab('http://www.example.com/', {
               referrerURI          : referrer, // nsIURI
               charset              : 'Shift_JIS',
               postData             : null,
               inBackground         : true,
               allowThirdPartyFixup : false,
               relatedToCurrent     : false
             });

// old API
var newTab = gBrowser.addTab(
               'http://www.example.com/',
               referrer, // nsIURI
               'Shift_JIS',
               null, // postData
               true, // inBackgorund
               false // allowThirdPartyFixup
             );

本当だったらもっと早く、Firefox 3.0になる前の時点でこういう事は済ませておくべきだったんだろうと思う(そのための「メジャーバージョン」でしょ?)。でもまあ、いつかはやらなきゃいけないことだ。新しい引数が追加されるというタイミングは、移行のいいきっかけではある。

ツリー型タブとTab Mix Plusの衝突について調べていてGeckoのバグを見つけた - Aug 13, 2009

ツリー型タブが入ってるとスターアイコンからブックマークの内容を編集できない、という報告を見て、そんなバカなこっちじゃちゃんと動いてるのに!と思って薄々そうなんじゃないかなあと思いながらよく報告を見てみたら、Tab Mix Plusと組み合わせた状態であると書いてあり、やっぱり……と思いながら両方を入れてみたら確かに問題が再現した。まあここまではよくある話。

エラーが起こっている箇所はエラーコンソールのメッセージから容易に特定できたんだけど、でもどう見てもそこでエラーになるはずがないという箇所でエラーになっていた。具体的には1つ前のエントリに書いたcreateContextualFragment()の所。色々条件を変えて調べてみたら、どんな簡単なソースを渡した場合でもcreateContextualFragment()の返り値が常にnullになっているようだった(Firefox 3.0.13でのテスト結果)。で、さらに条件を変えながら色々試して分かったのは、そもそもツリー型タブと組み合わせなくても、Tab Mix Plusが入ってるだけでcreateContextualFragment()が全然使い物にならなくなる(Firefox 3.0.xや3.5では常にnullが返り、Trunkでは常に例外が発生する)ということだった。

さすがにこれは何かおかしいと思って、エラーコンソールに表示されるエラーをよく見ると、XMLのパースエラーで「属性が二重に定義されている」とメッセージが出ている。それでピンと来てTab Mix Plusのソースを見てみたら、怪しい記述を見つけた。オーバーレイ用のXULドキュメントで、idがmain-windowであるwindow要素のオーバーレイ内容にXML名前空間宣言が含まれている、というものだ。もちろんこれはXML的に全く問題がないはずの記述なのだけれども、まさかと思いながらそこを書き換えてみたら、Tab Mix PlusがあってもcreateContextualFragment()が失敗しなくなった。それで、ここが原因だと確信が持てたということで、Bugzillaにバグとして報告してみた。

Bug 510157 – nsIDOMNSRange.createContextualFragment() fails when there is applied XUL-overlay including XML namespace declarations

条件がややこしい上に、一体どこが一番悪いのか分からなかったんだけど、一番表面上のトリガーになってるように見えてるのがDOM Traversal-Rangeだったので、そこのバグとして報告してある。

この問題を回避しようと思ったら、Tab Mix PlusのオーバーレイでXML名前空間宣言を書くのは本当のルート要素だけという風に書き換えるのが一番手っ取り早いんだけど……できれば本体(Gecko)の方を直してほしい所ではある。

ツリー型タブの修正 - Aug 11, 2009

先週1週間は夏休み取って家に缶詰でずっともえじら組のマンガ描いてたんだけど、その間大量にバグ報告が来てたのをずっと見て見ぬふりしてたのを今週になってやっと修正した。

ブックマークフォルダの内容をタブで開けなくなるという問題はFirefox 3.0.xでのみ発生する問題で、原因はJavaScriptコードモジュールのPlacesUtilsにFirefox 3.5から追加された機能をそうとは知らずに使ってしまっていたせいだった。

あとブックマーク周りの変更が結構ボロボロだったのをだいぶ直した。特にスターアイコンのことは自分であんまり使わないからすっかり忘れてて、直すのに難儀した。Firefox自身がeditBookmarkOverlay.xulを動的に読み込んでいて、そのeditBookmarkOverlay.xulに対してツリー型タブがオーバーレイを適用しているために問題が……とか、とてもバッドノウハウくさい。結局、XULオーバーレイでどうこうするのは諦めてJavaScriptで動的にDOM要素を生成して挿入することにした。

var range = document.createRange();
range.selectNodeContents(container);
range.collapse(false);
range.insertNode(range.createContextualFragment(<![CDATA[
   <row align="center" id="treestyletab-parent-row">
      <label id="treestyletab-parent-label"
         control="treestyletab-parent-menulist"/>
      <menulist id="treestyletab-parent-menulist"
         flex="1"
         oncommand="TreeStyleTabBookmarksServiceEditable.onParentChange();">
         <menupopup id="treestyletab-parent-popup">
            <menuseparator id="treestyletab-parent-blank-item-separator"/>
            <menuitem id="treestyletab-parent-blank-item"
               value=""/>
         </menupopup>
      </menulist>
   </row>
]]>.toString().replace(/^\s*|\s*$/g, '').replace(/>\s+</g, '><')));
range.detach();

こんな感じにしておけば、XULの「タグを書くだけでUIを作れる」という利点をそれほど殺さなくても済む……と思う。E4XのXMLオブジェクトを生成した物を既存のDOMツリーに直接組み込むことができれば話は早いんだけど、そういうことは無理っぽいので、createContextualFragment()にしてる。ここではE4XのCDATAマーク区間をヒアドキュメント代わりに使ってるんだけど、文字列置換でタグの間の空白文字を消してるのと、toString()をわざわざ書いていることに注意が必要。前者を忘れると要素ノードの間にいちいちテキストノードが生成されてややこしいことになるし、後者をを忘れるとStringクラスの物ではなくXMLオブジェクト自身の方のreplace()メソッドが呼ばれてしまって文字列置換にならないので。

修正ついでに、ブックマーク項目の「親のタブ」を設定する機能について、もうちょっと自由に使えるように手を入れてみた。ツリー構造を書き換えるのと同時に、ツリーとして表示される時の順番に合わせてブックマークを自動的に並べ替えるようにした。

ツリー型タブでブックマークにツリー構造を保存できるようにしてみた - Jul 31, 2009

ツリー型タブ 0.8.2009073101/02で、「このツリーをブックマーク」や「すべてタブをブックマーク」した時に、ツリーの構造を含めてブックマークを保存するようにしてみた。だいぶ前から要望を受けてて、「確かにそうするべきだよなあ」とは思ってたんだけど、どうやって実現すればいいかで悩んでた。でもFirefox 2のサポートを切ったことによって、API経由でPlacesデータベースに色んな情報を簡単に保存できるようになったので、思い切って実装してみた。

他のアドオンからもこの機能を使えるように、APIを用意してある。複数のタブからブックマークを作成する場合、以下のように、PlacesUIUtils.showMinimalAddMultiBookmarkUI()でブックマークの追加を行う前後にツリー型タブのAPIを呼んでやると、タブのツリー構造がブックマークに保存される。

var tabs = Array.slice(gBrowser.mTabContainer.childNodes);

var isTSTBookmarksTreeStructureAvailable = (
        'TreeStyleTabBookmarksService' in window &&
        'beginAddBookmarksFromTabs' in TreeStyleTabBookmarksService &&
        'endAddBookmarksFromTabs' in TreeStyleTabBookmarksService
    );
if (isTSTBookmarksTreeStructureAvailable)
    TreeStyleTabBookmarksService.beginAddBookmarksFromTabs(tabs);
try {
    PlacesUIUtils.showMinimalAddMultiBookmarkUI(tabs.map(function(aTab) { return aTab.linkedBrowser.currentURI; }));
}
catch(e) {
}
if (isTSTBookmarksTreeStructureAvailable)
    TreeStyleTabBookmarksService.endAddBookmarksFromTabs();

このAPIはマルチプルタブハンドラでもさっそく使ってる。

やってることはどういう事かというと……

  1. まずTreeStyleTabBookmarksService.beginAddBookmarksFromTabs()の方では、ブックマークされる予定のタブのツリー構造をシリアライズして内部に保持した上で、ブックマークの監視を開始する。
  2. 次に、PlacesUIUtils.showMinimalAddMultiBookmarkUI()で複数のブックマーク項目が新たに作成される。この時、TreeStyleTabBookmarksServiceはブックマークの追加を監視していて、新しく作られたブックマークのIDを内部に保持する。
  3. 最後に、TreeStyleTabBookmarksService.endAddBookmarksFromTabs()の中で、追加されたブックマークと元になったタブとを対応させ、ツリー構造の情報(親のタブにあたるブックマーク項目はどれか、という情報)を、ブックマークのアノテーションとして保存する。この時、タブの数と作られたブックマークの数とが一致しない場合(ブックマークの追加がキャンセルされたとか、未知の機能によってタブと関係ないブックマークが同時に作成されたとか)は想定外のエラーということで、何もせず終了する。

とりあえず一番簡単なやり方で実装してみたので、保存した後のブックマークの順番や親子関係をいじくり回すとちょっと変なことになる。一応、そんなに大きな問題は起こらないで見た目上は何となく自然な形に収まるように、と工夫はしてみたんだけど……どうだろう。

保存された「どのタブが親か?」という情報は、ブックマークのプロパティから編集できるようにしてある。親を付け替えられるようにしてみたけど、横着してるのでちょっと制限が厳しい。そのうち、親を付け替えたらそれに応じてブックマーク項目自体の親フォルダ内での位置も自動的に入れ替えるようにでもしてみようかなー。

巻き戻し/早送りボタンが26日以降クラッシュする件 - Jul 27, 2009

巻き戻し/早送りボタンでSITEINFOを使うようにしてるとクラッシュする件。wedataから取得するデータの先頭の方をスキップするようにしたら落ちなくなったので、どうも正規表現が長すぎ(内部的に、すべてのSITEINFOのURLマッチング用の部分を繋げておいて「マッチするルールがあるか無いか」だけを調べるようにしてるんだけど、その正規表現が長すぎ?)なのが原因っぽい。wedataの更新履歴を見ると、この数日の間にもいくつかルールが追加されてるみたいだし。

で、とりあえず250件ごとに区切って正規表現を作るようにしてみたところ、手元の環境では落ちなくなったみたいなので、修正版として速攻で公開してみた。

しかし念のため全部のURLマッチング用の正規表現を繋げた正規表現を使ってテストしてみたところ、これだけではクラッシュしなかった。処理が走るタイミングにも依るんだろうか? これじゃいつまた問題が再発するか分からんよ……

ともあれ、今までの「でかい正規表現にマッチするかどうか判定」→「全部のSITEINFOをループで調べる」というのに比べると、250個単位で「正規表現にマッチするかどうか判定」→「その250個の範囲のSITEINFOをループで調べる」という風に変わったので、場合によっては多少高速になったんじゃないかなーと期待している。

参考までに、テストに使った巨大な正規表現は以下の通り。

続きを表示する ...

設定を簡単に変更できるようにすること、を必ずしも重視しませんよという話 - Jul 24, 2009

掲示板で書いた事を改めてここにもまとめてみる。

  • ツリー型タブの設定ダイアログを「ツール」メニューなどから簡単に呼び出せるようにして欲しい。
  • 閉じているサブツリー内の子タブがフォーカスされた時に、ツリーを展開するのではなく、現在見えている親のタブにフォーカスする、という風なオプションが欲しい。

この2つの要望にはどちらも応えるつもりはない、というよりも、安直に応えてはいけない種類の要望だと思ってる。

あらかじめ言っておくと、過去の「タブブラウザ拡張」ではこのどちらも応えていた。いや、要望に応えたと言うよりは多分、自分から進んでそうしてしまっていた。でも今の自分の考えでは、そうするべきではなかったと考えている。なので、もうしない。

そもそもを言えば、こういう要望が出てきてしまっているということが「失敗」の何よりの証拠だと僕は考えている。「何故、そういう要望が出るのか?」「どうしてそうして欲しいと思うのか?」そこを考えないといけないと思う。

  • 設定ダイアログを簡単に開けるようにしたいのは何故?
    • →設定を頻繁に変えたいから。
    • →設定を頻繁に変えないと使い物にならないから。
    • →だったら、設定を頻繁に変えなくても十分に使いやすいと感じられるように、自動判別の部分や初期設定をよく考えた方が、ユーザはハッピーになれるんじゃないの?
  • 閉じているサブツリー内の子タブにフォーカスが移った時に、そのタブが含まれているツリーを自動展開するのではなく、閉じられていない親タブに代わりにフォーカスして欲しい、と思うのは何故?
    • →本当は閉じられたツリーの中のタブではなく、親のタブの方にフォーカスして欲しかったから。
    • →というか、そのタブ(閉じられたツリーの中のタブ)がフォーカスされるなんて思いもしてなかったから、そうなってビックリした。
    • →使っていて自然と頭の中に思い浮かべるようになった「こう動くはずだ」という予想が裏切られてしまった。
    • →そのように想像させる挙動を他の部分がしているのなら、その想像に合った挙動をするように統一するべきなんじゃないの? 無駄に選択の余地を増やすんじゃなくて、「自然と思った通りに動いてくれる」状態を目指すべきなんじゃないの?

前者の要望は、色んなアドオンがそういう項目を「ツール」メニューにどんどん加えていったらメニューが一杯になってしまう!ということを見過ごしている。絶対に、多分別の人が「ツールメニューの中の項目は使わないので、非表示にするオプションを加えて欲しい」って言ってくるだろう。そしたらまたそれに応えるの?

後者の要望は、タブの一覧のリストやサムネイル一覧などからそのタブを選んだという風な、「本当にそのタブにフォーカスしたかった場合」にはどうすればいいのか?ということを考慮に入れずに安直に対応すると、泥沼に嵌ってしまう。不用意にそのオプションをいじってしまった人が「フォーカスしたいタブにフォーカスしてくれない!」と(自分がそう設定したからだと言う事にも気づかず)「バグ報告」してくるだろうし、それにまた安直に応えて「じゃあ、そういうケースだけは特別に、閉じられたツリーの中のタブにもフォーカスできるようにしよう」なんて後手後手の対応を続けていたら、どこまでいってもきりがない。

そういう安直な対応を繰り返した果てにあるのが、かつてのタブブラウザ拡張であり、今のTab Mix Plusであると、僕は考えてる。「多機能で凄い、良い物だ」と人は言うけれども、今の僕にはこれらは「考えることを放棄し続けた結果、肥大化の一途を辿った末路だ」という風に見えてる。既にツリー型タブもそうなりつつあると僕は思ってるので、今後は「設定項目を減らしていく」方向にシフトしたいくらいだ。

アプリケーションを作る立場の人は、要望として口に出された言葉の裏にある本当のニーズを読み取る努力をしておかないと、最終的には自分で自分の首を絞めることになると思う。「その方が格好よさそう」とか「その方が賢そう」とかのあまり意味のない自己満足なこだわりだろ、という風に切り捨てないで、自分自身の身を守るための現実的な対策のひとつとして、実践してほしいなと思う。

そしてその結果、それをエンドユーザとして使う立場に僕がなった時に、またハッピーになれるわけだ。

つまり「情けは人のためならず」とか「自業自得」とかそういう話。

現在のタブを閉じた後のフォーカス移動を制御するオプション(A new option to control focus of tabs when the current tab is closed is required.) - Jul 22, 2009

Q

I wonder if you consider integrating Tab Mix Plus into Tree Style Tabs? Not all of Tab Mix Plus but just the opening/closing/focus behavior (under "events" options of Tab Mix Plus)?

There is a lot of interaction between Tree Style Tabs and Tab Mix Plus. Some things don't work correct because Tab Mix Plus is very slow to update. For example, "duplicate tab" used to work in firefox 3.0, but in 3.5 it doesn't work right and Tab Mix Plus had not fixed it (the error is that the duplicate tab gets stuck in middle of tree).

Also Tab Mix Plus doesn't have enough options to give good control over Tree Style Tabs. So maybe it's better if Tree Style Tabs has own options.

Could you copy Tab Mix Plus code for opening/closing/focus, then add some new options for Tree Style Tab? For example:

  • If close first child tab, focus: up/down
  • If close middle child tab, focus: up/down
  • If close last child tab, focus: up/down
  • If close only child tab, focus: up/down
  • If close sub-parent tab, focus: up/first-child/last-child
  • If close expanded root-parent tab, focus: tab-above/parent-tab-above/first-child-down/last-child-down/

Tab Mix Plusをツリー型タブに統合するつもりはないでしょうか? TMPの全機能でなくとも、単にタブを開く・閉じる・フォーカス関係の挙動(TMPの設定の「イベント」パネルにある機能)だけでもいいんですが。

ツリー型タブとTMPの間には互換性がありますが、TMPの更新が非常に遅いため、いくつかの機能は正常に機能しません。例えば、「タブの複製」はFirefox 3.0では動いていましたが、Firefox 3.5では正常に機能せず、TMPはまだその問題を解決できていません(複製されたタブがツリーの真ん中に現れてしまうという問題があります)。

また、TMPはツリー型タブの挙動を制御するのに十分な数の設定項目を備えていません。ツリー型タブ自体がそのような設定項目を備えることが望ましいのではないでしょうか。

タブを開く・閉じる・フォーカス関係のTMPのコードをコピーして、ツリー型タブに新しい設定項目を加えてもらえないでしょうか? 例えば……

  • 最初の子タブを閉じた時:上にフォーカス/下にフォーカス
  • 真ん中の子タブを閉じた時:上にフォーカス/下にフォーカス
  • 最後の子タブを閉じた時:上にフォーカス/下にフォーカス
  • 最後の子タブを閉じた時:上にフォーカス/下にフォーカス
  • 他のタブの子である親タブを閉じた時:上にフォーカス/最初の子にフォーカス/最後の子にフォーカス
  • ツリーが展開されている最上位の親タブを閉じた時:上にフォーカス/親にフォーカス/最初の子にフォーカス/最後の子にフォーカス
A

Thank you for interesting idea. To be honest, I received some similar suggestions about tab focus of Tree Style Tab. However, I couldn't agree them because I hope to keep Tree Style Tab as simple as possible.

There are three reasons why I implemented only minimal options about tab focus.

  1. "Focus last selected" behavior is available with other existing addons.
  2. "Focus to upper (left) tab" hebavior was made obsolete by an old bugfix of Mozilla.
  3. Options dialog will become too complex.

1) Very special behavior, "focus to last selected tab when the current tab is closed" is available with known tiny addon, "Focus Last Selected Tab". So I'm recommending people to use the addon with TST. (BTW, Focus Last Selected Tab 0.9.x doesn't work with TST correctly, so I'll update TST as soon.)

2) For other cases, I don't agree to add "focus to upper (left) tab" option, because it was the default behavior of old Mozilla and it was "fixed" by usability reason. See bug 123563.

3) As you know, too many options for detailed cases will confuse people including me. So I have to make effort to reduce checkboxes, radiobuttons, and so on. Then, TST already have options to customize behavior of re-structuring tree after the current "parent" tab. I'm using the choice "raise the first child up to the new parent", but people can use others. How to unify options for two features, re-structuring and focusing? I cannot imagine to do it in the small dialog...

Anyway, this is only my policy, not the best answer. If people really want options for the feature, I hope that TST is extended by other developers like Tab Mix Plus born from Tab Mix.

興味深い意見をありがとうございます。正直に言うと、この手の提案は他にもたくさん受けています。しかし、私はツリー型タブを可能な限りシンプルに保ちたいので、これらの提案には同意できません。

タブのフォーカスに関して最小限の設定項目だけしか設けていないのには、以下の3つの理由があります。

  1. 「現在のタブを閉じた時、最後に選択されていたタブにフォーカスする」という挙動は、他の既存のアドオンで実現できる。
  2. 「現在のタブを閉じた時、上(左)のタブにフォーカスする」という挙動は、Mozillaの過去のバグ修正によって廃止された物である。
  3. 設定ダイアログが非常に複雑になってしまう。

1) 「現在のタブを閉じた時に、最後に選択されていたタブにフォーカスする」という非常に特殊な挙動は、既存のよく知られたアドオンFocus Last Selected Tabで実現できます。なので私は、それとツリー型タブを使うことをお勧めしています。(ちなみに、Focus Last Selected Tab 0.9.xはツリー型タブと組み合わせると期待通りには動きません。この点についてはなるべく早くツリー型タブを更新するつもりです。)

2) それ以外の挙動について、私は「現在のタブを閉じた時に、上(左)のタブにフォーカスする」という設定項目を設けることには同意しません。なぜなら、その挙動は古いMozillaの既定の挙動であったものの、利便性の観点から「(問題であるという認識の上で)修正された」からです。詳しくはbug 123563を見て下さい。

3) 言うまでもなく、詳細な場合ごとの過剰な数の設定項目は、私を含めてユーザを混乱させます。なので私は、チェックボックスやラジオボタンなどの数を減らす努力をしないといけません。ツリー型タブはすでに、親タブを閉じた後のツリーの再構築についての設定項目を備えています。私は「最初の子を新しい親にする」という選択肢を使っていますが、他の人は別の選択肢を使っているかもしれません。これら2つの機能(親タブを閉じた時のツリーの再構築と、現在のタブを閉じた時のフォーカスの移動)の設定項目をどのように統合すればいいでしょうか? 私には、それを小さなダイアログの中でやってのける様子が想像できません。

ともかく、これはあくまで私のポリシーであって、最良の答えというわけではありません。もしみんなが本当にその機能のための設定項目を必要としているのなら、Tab MixからTab Mix Plusが生まれたように、他の開発者の人がツリー型タブをさらに拡張してくれることに、私は期待します。

英語でどう書けばいいのか分からなかったんでこういう書き方になったけど、ぶっちゃけ、「カスタマイズのためのチェックボックスやドロップダウンリストは多ければ多いほどいい」という発想には反吐が出る。そういうことは隠し設定で十分だろ、と。about:configでやりゃいいだろ、と。何でもかんでもダイアログの中に詰め込むという発想は、僕がかつて取り憑かれそして我が身を滅ぼすに至った忌むべき考え方だ、と僕は認識している。

nsIZipWriterを使ってFirefoxでZIPアーカイブを作る - Jul 16, 2009

nsIZipWriterを使えばアドオンでZIPアーカイブを作れるということを割と最近になってやっと知ったので、アドオンのXPIへのパッケージングまで全部アドオンでできるやん!と思ったんだけど、使い方がよく分からんかったので、実験と練習もかねてコンテキストメニュー拡張の短縮構文で使える汎用的な処理を書いてみた。

要点だけ抜き出すとこんな感じ。

// aSources = nsIFileの配列
// aZipFile = 作るZIPアーカイブのnsIFile
// aCompressionLevel = 圧縮率(0で無圧縮、9で最大圧縮、デフォルトは6)
zipFilesAs : function(aSources, aZipFile, aCompressionLevel) 
{
    // Firefox 3以降じゃないと動かないよ!
    if (!('nsIZipWriter' in Components.interfaces))
        throw Components.results.NS_ERROR_NOT_IMPLEMENTED;

    if (!aSources || !aSources.length || !aZipFile) return;

    if (aZipFile.exists() && !this.isZipFile(aZipFile))
        aZipFile.remove(true);

    const PR_RDWR        = 0x04;
    const PR_CREATE_FILE = 0x08;
    const PR_APPEND      = 0x10;
    const PR_TRUNCATE    = 0x20;

    var writer = Components
                    .classes['@mozilla.org/zipwriter;1']
                    .createInstance(Components.interfaces.nsIZipWriter);

    if (aCompressionLevel === void(0))
        aCompressionLevel = writer.COMPRESSION_DEFAULT;

    // 新規ZIPアーカイブと既存のZIPアーカイブではフラグが違う
    var flags = aZipFile.exists() ?
                    PR_RDWR | PR_APPEND :
                    PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE ;
    writer.open(aZipFile, flags);

    aSources.forEach(function(aFile) {
        var entry = this+aFile.leafName;
        // フォルダに対応するエントリの有無を調べる時は、
        // エントリ名の末尾に「/」が必要
        var entryForCheck = aFile.isDirectory() ? entry+'/' : entry ;

        // すでにあるエントリで、フォルダではない物は削除。
        // (フォルダを削除しないのは、内容をマージするため)
        if (!aFile.isDirectory() && writer.hasEntry(entryForCheck))
            writer.removeEntry(entry, false);

        // 新しいエントリを追加
        if (!writer.hasEntry(entryForCheck))
            writer.addEntryFile(entry, aCompressionLevel, aFile, false);

        if (aFile.isDirectory()) {
            var files = aFile.directoryEntries;
            while (files.hasMoreElements())
            {
                arguments.callee.call(
                    entry+'/',
                    files.getNext().QueryInterface(Components.interfaces.nsILocalFile)
                );
            }
        }
    }, '');

    writer.close();
},

// ファイルの内容を実際にZIPアーカイブとして読んでみて、ZIPアーカイブかどうか調べる
isZipFile : function(aFile)
{
    var isZip = false;

    if (!aFile || !aFile.exists()) return isZip;

    var reader = Components
                    .classes['@mozilla.org/libjar/zip-reader;1']
                    .createInstance(Components.interfaces.nsIZipReader);
    try {
        reader.open(aFile);
        try {
            var entries = reader.findEntries('*');
            while (entries.hasMore())
            {
                entries.getNext();
                isZip = true;
                break;
            }
            if (!isZip) {
                entries = reader.findEntries('*/');
                while (entries.hasMore())
                {
                    entries.getNext();
                    isZip = true;
                    break;
                }
            }
        }
        catch(e) {
        }
        reader.close();
    }
    catch(e) {
    }

    return isZip;
},

タブをグループ化するためのダミーのタブ(How to open a custom dummy tab to put tabs in a group?) - Jul 10, 2009

Q

I noticed that you have added support for dummy tabs to group tabs when opening multiple links from the bookmarks menu. This is very close to a feature that I've wanted since I began using this extension a few months ago: to have general dummy tabs available so that I can create and name a dummy tab from the tab bar context menu.

Is it possible to implement this?

ブックマークメニューから複数のページをタブで開く時に、それらをグループ化するための物として、ダミーのタブがサポートされましたよね。これは、私がこのアドオンを使い始める前に求めていた機能にとても近いです。その機能とは、タブバーのコンテキストメニューから任意にこのようなダミーのタブを開き、自由に名前を付ける事ができるというものです。このような機能を実装してくれませんか?

A

On Tree Style Tab 0.7.2009071001, I made dummy tabs customizable. To change the name of an existing dummy tab, switch to the tab and click the label in the content area (or double-click on any position in the content area). It possibly helps you that you create a bookmark for "about:treestyletab-group?(new group)" and put it in the bookmark toolbar.

ツリー型タブ0.7.2009071001において、ダミーのタブをカスタマイズできるようにしました。既にあるダミーのタブの名前を変えるには、そのタブに切り替えて、内容領域のラベル文字列部分をクリックしてください(または、内容領域のどこか適当な所でダブルクリックしてください)。「about:treestyletab-group?(new group)」というURIのブックマークを作成してブックマークツールバーに置いておくと、あなたの助けになるのではないでしょうか。

ツリー絡みの機能ではないので、「ダミーのタブを開くための機能」は付けない予定。なのでブックマーク等で代用してもらう必要がある。

実装の仕方は、「about:treestyletab-group」というabout: URLを使えるようにするためのコードと、表示する内容の実体と、スタイル定義を見ると分かると思う。XHTMLの名前空間のlink要素を埋め込んでfaviconを変えるとかの姑息なテクニックを使ってます。Firefox 3.5でサポートされた-moz-transformは今回初めて使った。

Page 11/241: « 7 8 9 10 11 12 13 14 15 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき