たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
Undo Tab Operationsの核であるoperationHistory.jsは、タブに限らずいろんな操作に対してアンドゥ・リドゥを実装しやすくするための汎用のライブラリとして設計しています(一応)。こいつの使い方を、自分の頭の中の整理も兼ねて少しずつ解説していこうと思います。
まず、読み込みの方法。以下のようにJavaScriptのファイルを読み込ませるだけでOKです。
<script src="lib/operationHistory.js"
type="application/javascript"/>
複数のアドオンでこのライブラリを読み込んでいる場合、最もリビジョンの新しい物が使われます。今後はAPIは変えないor後方互換を維持していくつもりなので、使う方はあんまり気にしないで使えるはずです。
operationHistoryの各機能には window['piro.sakura.ne.jp'].operationHistory
でアクセスできます。以下の説明ではサンプルコードを短くするために、 OH
という変数でこれを参照しているものとします。
var OH = window['piro.sakura.ne.jp']
.operationHistory;
operationHistoryを使って任意の処理をアンドゥ・リドゥ可能にしたい時は、その処理を以下のように実行するようにします。
OH.doOperation(function() {
// 任意の処理
});
doOperation()
の引数に関数を渡すと、それがその場で実行されます。実行時のthis
はOH
自身を指していますが、普通に分かりにくいんで、クロージャを使うなり何なりして好きなように書くといいと思います。
var MyAddon = {
myFeature : function() {
var self = this;
OH.doOperation(function() {
self.myInternalMethod();
});
},
myInternalMethod : function() {
// 何かの処理
}
};
で、これだけだとまだアンドゥ・リドゥはできません。doOperation()
に対して以下の引数をさらに指定してやる必要があります。
"MyAddonOperations"
とかそんな感じで好きに名前を付けて下さい。省略すると、履歴の対象のウィンドウが指定されている場合は "global"
、そうでなければ "window"
になります。"TabbarOperations"
という名前を指定してます。window
です。省略すると、ウィンドウ単位の履歴ではなく、クロスウィンドウな単一の履歴となります。が、動作が怪しいので今の所はウィンドウ単位での使い方だけ推奨しておきます。やり直し可能にしたい処理自体と合わせると、doOperation()
は最大で4つまでの引数を取るという事ですね。引数は全部型が違う(関数、文字列、DOMWindow、オブジェクト)ので、doOperation()
はそれらを受け取った後に、どれがどれなのかを自動的に判別します。なので引数はどの順番で指定しても構いません。
実際のアンドゥ・リドゥ処理は、履歴エントリになるオブジェクトのプロパティとして関数で定義します。
var entry = {
// 内部名
name : "undotab-addTab",
// メニュー等に表示する「やり直す処理の名前」
label : "タブを開く",
// アンドゥ時に実行される内容
onUndo : function(aParams) {
gBrowser.removeTab(this.tab);
},
// リドゥ時に実行される内容
onRedo : function(aParams) {
this.tab = gBrowser.addTab();
},
// 以下、任意のプロパティを好きなようにどうぞ
tab : null
};
OH.doOperation(
function() { // やり直し可能にする処理
entry.tab = gBrowser.addTab();
},
'MyAddonOperations', // 履歴名
window, // 処理対象ウィンドウ
entry // 履歴エントリ
);
onUndo
という名前のプロパティで関数を定義しておくとアンドゥ時にそれが実行されます。onRedo
はリドゥの時に実行されます。どちらもthis
は履歴エントリのオブジェクト自身になりますので、まあ見た通りで分かりやすいんじゃないかと思います。クロージャ使って書いても全然構いません。
name
、label
は、文字列で好きなように名前を付けて下さい。定義しなくても使えますが、デバッグの時にはあると便利ですし、DOMイベントを使った記法(後で解説します)の時には無いと困ります。
上記のようにして履歴に登録した処理は、以下のようにしてアンドゥ・リドゥできます。
// アンドゥ
OH.undo('MyAddonOperation', window);
// リドゥ
OH.redo('MyAddonOperation', window);
undo()
とredo()
の引数には、doOperation()
に対して指定したものと同じ履歴名と処理対象のウィンドウを渡します(こちらも引数の指定順は任意です)。ここではまだ「タブを開く操作」しか書いていませんが、同じ履歴名で「タブを閉じる操作」「タブを移動する操作」などに対してそれぞれアンドゥ・リドゥの処理を書いてやれば、線形にそれらをアンドゥ・リドゥできるようになります。
また、「戻る」「進む」のドロップダウンメニューで項目を指定してそこまで一気に飛ぶのと同じように、goToIndex()
で履歴項目のインデックスを指定してそこまで一気にアンドゥする・リドゥする事もできます。
// 現在の位置を得る
var history = OH.getHistory('MyAddonOperation', window);
var current = history.index;
OH.goToIndex(current-3, 'MyAddonOperation', window);
getHistory()
は、登録済みの履歴項目の全エントリを格納したオブジェクトを取得するメソッドです。それで取得したオブジェクトのindex
プロパティで現在のフォーカス位置を得られるので、上の例ではそこから3つ手前に飛ぶ事になります。
ここまでの説明で既に疑問に思った人もいると思いますが、例えばこんな場合。
function NewTab() {
var entry = {
name : "undotab-addTab",
label : "タブを開く",
onUndo : function(aParams) {
gBrowser.removeTab(this.tab);
},
onRedo : function(aParams) {
this.tab = gBrowser.addTab();
gBrowser.selectedTab = this.tab;
},
tab : null
};
OH.doOperation(
function() {
entry.tab = gBrowser.addTab();
gBrowser.selectedTab = entry.tab;
},
'MyAddonOperations',
window,
entry
);
}
function MoveTab(aTab) {
var entry = {
name : "undotab-moveTab",
label : "タブを移動する",
onUndo : function(aParams) {
gBrowser.moveTabTo(this.tab, this.oldPosition);
},
onRedo : function(aParams) {
gBrowser.moveTabTo(this.tab, this.newPosition);
},
tab : null
};
OH.doOperation(
function() {
entry.tab = aTab;
entry.oldPosition = aTab._tPos;
gBrowser.moveTabTo(aTab, 3);
entry.newPosition = aTab._tPos;
},
'MyAddonOperations',
window,
entry
);
}
NewTab()
を実行→それで開かれたタブに対してMoveTab()
を実行→アンドゥ→アンドゥ→リドゥ→リドゥ という順に操作すると、2つ目の履歴項目のリドゥ時にタブが見つからないせいでエラーになってしまいます。こうならないように、処理対象の要素は固有のIDなどで識別してやらないといけません。
operationHistoryにはそのために、要素に対して一意なIDを自動的に付与してそれを元に要素を検索する仕組みがあります。先の例を安全に書くと、以下のようになります。
function NewTab() {
var entry = {
name : "undotab-addTab",
label : "タブを開く",
onUndo : function(aParams) {
var tab = OH.getElementById(this.tab,
gBrowser.mTabContainer);
gBrowser.removeTab(tab);
},
onRedo : function(aParams) {
var tab = gBrowser.addTab();
OH.setElementId(tab, this.tab)
gBrowser.selectedTab = tab;
},
tab : null
};
OH.doOperation(
function() {
var tab = gBrowser.addTab();
entry.tab = OH.getElementId(tab);
gBrowser.selectedTab = tab;
},
'MyAddonOperations',
window,
entry
);
}
function MoveTab(aTab) {
var entry = {
name : "undotab-moveTab",
label : "タブを移動する",
onUndo : function(aParams) {
var tab = OH.getElementById(this.tab,
gBrowser.mTabContainer);
gBrowser.moveTabTo(tab, this.oldPosition);
},
onRedo : function(aParams) {
var tab = OH.getElementById(this.tab,
gBrowser.mTabContainer);
gBrowser.moveTabTo(tab, this.newPosition);
},
tab : null
};
OH.doOperation(
function() {
entry.tab = OH.getElementId(aTab);
entry.oldPosition = aTab._tPos;
gBrowser.moveTabTo(aTab, 3);
entry.newPosition = aTab._tPos;
},
'MyAddonOperations',
window,
entry
);
}
getElementId()
は、要素に一意なIDが付いていなければ新しいIDを生成て設定した上でそのIDを、既にIDが付いていればその値を、文字列として返します。IDは普通のid属性ではなく別の属性名で保存されるので、通常の動作を破壊することはありません。
getElementById()
は、そのID文字列をキーとして要素を検索するメソッドです。tabbrowserの場合はタブなどの内部の要素は普通のdocument.getElementById()
等では取得できないのですが、getElementById()
はID名の文字列以外に要素ノードを渡すと、その要素の子孫だけを検索するようになります。
ここでは、タブを開く操作のリドゥにおいてsetElementId()
も使用しています。これは、既に生成されたID文字列を新しく復元された要素に付与することで、その要素を元の要素の代わりとして参照できるようにするためです。
とりあえず、まずはこの辺だけ解説しておきます。
Undo Tab Operationsの開発を通じて実装をこねくり回してた汎用のアンドゥ・リドゥ用のライブラリだけど、最低限必要そうな一通りの機能を実装し終えた……と思う。
最初はもっとコンパクトになるかなと思ってたんだけど、なんだかんだで膨らんで35KBちょいになった(2010年1月11日現在)。ライブラリの使い方は……Undo Tab Operationsのソース読んで実際の使われ方を見た方が話が早いかも。
以下、ライブラリの使い方の説明じゃなくてただの苦労話です。
当初は、単純に以下のようにしようと思ってた。
しかし実際やってみるまでもなく、これだと考慮しないといけないケースがあまりに多くなりすぎる。例えばタブを開く操作だけでも、「タブバー上のボタン」「ブックマーク」「リンク」等々色々ある。それら1つ1つに対してアンドゥ・リドゥの処理を定義していくのはさすがに無理がある。また、例えば「タブバーのボタンで新しいタブを開く処理に対応する関数」に対してアンドゥ・リドゥの処理を定義したとして、その関数が他の処理の中から呼ばれないという保証はどこにもないわけで、アンドゥ用の処理がかぶったら、タブが2つも3つも開き直されたり、その逆に2つも3つも閉じられたりしかねない。これは危険すぎる。
という事くらいはすぐに思いついたので、次にこんな風に考えた。
gBrowser.addTab()
やgBrowser.moveTabTo()
などの基本的な関数それぞれに対してアンドゥ・リドゥの処理を定義する。gBrowser.loadOneTab()
(内部でaddTab()
とmoveTabTo()
を呼んでいる)のような関数の存在も考慮して、1つの「やり直し可能な処理の単位」の中で行われたやり直し可能な処理はすべて、トップレベルの履歴項目に子供としてぶら下げる。
gBrowser.swapBrowsersAndCloseOther()
は場合によっては元のウィンドウを閉じてしまうため、ウィンドウを開き直す→ロード完了を待ってからタブを開き直す、という風な事をしないといけない。ウィンドウが開かれるまでの間に下位の項目(タブを開く、タブを閉じる等)のアンドゥ処理が走るとおかしな事になる。なので、下位の項目のアンドゥ処理はキャンセルして、swapBrowsersAndCloseOther()
のアンドゥ処理の中で全部完結させるようにする。例外を設けたのは、「前の履歴項目のアンドゥ処理の完了を待ってから次の履歴項目のアンドゥ処理を始める」という風な、非同期処理を考慮した仕組みを当初備えていなかったせい。なんでその仕組みを先に作らなかったのかというと、作るのがめんどかったからの一言に尽きる。
で、このような仕様で開発を進めて、Undo Tab Operationsについてはとりあえず素のFirefox上でならまともに使えるようになってきたかなあと思ったので、マルチプルタブハンドラとの連携に着手し始めた。そしたら破綻した。
例えばマルチプルタブハンドラは、タブ1つだけのドラッグ&ドロップでの移動を検知して選択されたタブ全部をその近くに移動するようになってるけど、これをアンドゥ可能にしようと思うと、そのアンドゥの処理が始まる前にUndo Tab Operationsによって行われるmoveTabTo()
のアンドゥでタブの並び順が変わってしまうので、最終的なタブの並び順がグチャグチャになってしまう。手っ取り早く解決しようと思うと、Undo Tab Operationsによって行われるタブの移動のアンドゥ処理は全部キャンセルして、マルチプルタブハンドラ側で面倒を見てやった方が、書くのは簡単なわけです。
しかし、そんなことをそれぞれのアドオンがやり始めたら、絶対にどっかで考慮漏れが起こるわけですよ。Undo Tab Operationsとマルチプルタブハンドラだけだったら問題が起こらなくても、そこにツリー型タブや他のアドオンが加わってくると、互いにアンドゥ処理の優先権の取り合いになってしまうのは目に見えてる。
そういう未来が予想できてしまったので、ついに観念して、非同期処理に真面目に対応することにした。で、結局以下のようになった。
addTab()
、removeTab()
など、それぞれの基本的な関数に対してアンドゥ・リドゥの処理を提供する。あとついでに、関数オブジェクトをそのまま履歴項目に使うようにすると履歴項目が増える度にクロージャが増えていってメモリリークの温床になりそうだなあと思ったので、DOMのイベントを監視することでも同じ事ができるようにAPIを整備した。Undo Tab Operations 0.2.2010011001以降ではこっちの方法を使ってそれぞれのアンドゥ・リドゥ処理を実装してある。
処理待ちにはJSDeferredを使ってもよかったんだけど、他のライブラリには依存させたくなかったので、JavaScript 1.7以降のジェネレータ・イテレータと継続関数で実現してみた。イベントオブジェクトのaEvent.wait()
で処理待ち状態になって、aEvent.continue()
で処理の完了を通知するというスタイルにしてある。
クロージャを使っても使わなくても、処理対象のタブを特定するには一意なIDが無いとどうにもならないなあと思ったので、汎用の「要素ノードに自動生成でIDを付与する」とかの機能もライブラリに含めることにした。これを使うことで、閉じられたタブに対応する開き直されたタブを確実に取得できるようになってる。
実際使ってみるとなかなか妙な感じですね。タブをウィンドウ外にドロップしてタブを切り離し→Shift-Ctrl-Zで元に戻す→Shit-Ctrl-Yでまた切り離す なんてことができて、キモくて面白いです。
FUEL廃止はJetpackの台頭と対になっているよ、という話。
FUELとJetpackは、コンセプトが確かに重複してるんですよね。
違うのは、FUELはあくまで既存の「拡張機能」という枠組み(JavaScriptやXULやCSSを使って、XPI形式にして、云々)の中でそれを達成しようとしていたのに対して、Jetpackは「そもそもXULとか要らなくね? JavaScriptだけでよくね?」とちゃぶ台をひっくり返してその目的に特化した物をゼロから作った所だと思う。
「Firefoxに機能を追加するなら拡張機能を使いましょう」というのは、実装の仕方をなるべく1種類にまとめて世界を分かりやすくしたいという、開発者目線の考え方のように思う。そうではなく、「Firefoxに機能を追加できるなら、拡張機能でもGreasemonkeyでもUbiquityでもJetpackでもStylishでも何でもイイじゃん。なんで拡張機能に拘らないといけないわけ?」とユーザー目線の発想で考え直したら、FUELみたいな子供だましではなく、Jetpackのように実行環境を1個丸ごと作りなおす事になりました、と。そういう話の流れなのだと僕には思えた。
既存の拡張機能の作り方に特化して知識を溜め込んできた僕にとっては、はっきり言って恐怖ですらある。自分の存在価値、アイデンティティを揺るがす事態だ。だって、大学在学中からMozillaにのめり込んで、それが縁で就職までしてしまって、今もそれをネタに仕事してるんだもの。
そんな僕にとっては、Jetpackが優遇されてFUELが廃止されるというのは、今まで自分が慣れ親しんでいたやり方が否定されて、開発元からも邪険にされて、お前らは時代遅れなんだよプギャーと指さして笑われて、時代の変化についていけない奴らは死ねばいいじゃないと見捨てられてる、正直そんな気分です。
でも、「今までのやり方が全く通用しない世界に飛び込んでいく」事を恐れて今いる所に留まり続けるのは、それこそ座して死を待つだけだという事も分かってる。
それに、そういう「今までのやり方が全く通用しない世界」を作るのであれば、どうせなら、今までのしがらみから完全に切り離された理想の世界を作って欲しいとも思う。XULやXPConnectといった古いやり方しか知らない僕みたいな人間のために「ほうら今までのやり方も使えるんですよ? だから怖がらないで入ってきて下さいよ」と媚びを売って、古いしがらみまでまた抱え込んでしまうのは、やめて欲しい。
僕はそう考えているので、だから今のJetpackでXULやXPConnectを触れるのは非常にマズイ事態だと思ってる。せっかく理想郷を作ろうとしてるんだったら、そこにつまんないしがらみを持ち込んでくれるなよと。僕みたいなロートルが、新しく出てきたJetpackという世界を、古臭い駄目なやり方で汚していってしまう事が許せないのです。老兵は潔く去らないといけないし、潔く去らせないといけないじゃないか。老兵に取り付く島を与えちゃ駄目じゃないか。そんなことをしたら、僕みたいにだらしのない老兵は甘えてしがみついちゃうじゃないか。
まあそれはさておき、FUELを出してきた時に「これで簡単に作れるようになれますよ」「安定したAPIが提供され続けますよ」なんて夢みたいな事を吹聴していた人達には、「すんません自分はバカでした。見る目がありませんでした。」と謝罪して欲しい所ですよね。それを真に受けて酷い目にあった被害者までいるんだし。うん、僕のことですね。ごめんなさい……
こないだから作ってる汎用のアンドゥ・リドゥ用ライブラリを使って、タブ関係の操作をなんでもアンドゥできるようにするアドオンを作ってみた。名前はそのままUndo Tab Operationsです。
ライブラリ自体が実験段階だし、そのライブラリの使い方も(自分で作っておきながら)まだよく把握しきれてないので、練習がてらという感じです。動作は全体的に非常に怪しいです。あと、これを作る上で「あーこういう機能が必要だよな」と躓く度にライブラリの方に手を入れているので、一体何が目的だったのかもうワケが分かりません。
そう。目的。そもそもは「ツリー型タブでウィンドウをまたいだタブの移動を取り消せるようにしたい」的な所が出発点だったはずなんですよね。なのにどうしてこんなに遠回りをしているのか。富豪的にも程がありますよね。
まあ、「リンクからタブを開く時に今のタブのすぐ右にタブを開きたい、ただし連続してリンクを開く時は開いた順に整列させたい」という目的を達成するためだけに、すべてのタブの親子関係を完全に保持する、なんていうめちゃめちゃ無駄なことに力を注ぐような僕のやる事ですからねぇ。
とりあえずこのアドオンで一通りの基本的なところをカバーして、次に親子関係が絡まない単純な応用という事でマルチプルタブハンドラを、最後にツリー型タブを対応させるという感じで作業を進めていこうかなー。と。思ってますけどどうなるかは分かりません。現段階でものすんごいバグバグなので、ひょっとしたら全部お蔵入りになるかもね。
robocopyを使ったズボラなバックアップで1TBのHDDに3世代のバックアップを作ってたんだけど、CD-Rに焼いて逃がしてたMP3ファイルとかiTunesに突っ込みまくってたらとても3世代は入り切りそうにない感じになってきたんで、2世代だけのバックアップだけにすることにして、一番古いバックアップを消そうとした……んだけど、引っかかりまくってうまく行かない。
コマンドプロンプトを開くときは一応、右クリックして「管理者として実行」で。
半年くらい前に真琴さんの記事を見てTwitterIrcGatewayを導入したけど全然使いこなせてなかったけど他の人が他の人に教えてもらってるのを横で盗み見て設定したらちょっと使いこなせるようになった気がしたよ。
酷く今更です。
ツリー型タブのAPIでこんなのが欲しいというのがあったら言っておくれと書いたところ、Alice0775さんからタブバーのドラッグ操作とかツリーのドラッグ操作とかをアンドゥする機能が欲しい(大意)という要望をいただいた。
そういえばこの前のMozilla勉強会の後の懇親会で、新しいタブが開かれたり新しいウィンドウが開かれたりした時に「戻る」ボタンで元のタブや元のウィンドウに戻れないことについて、あらゆる操作がアンドゥ可能になってないといけないんじゃないの?とかそんな感じの話が出ていたと思う。なので実験的にそういう物を作り始めてみた(自動テスト)。
var current = TreeStyleTabService.currentTabbarPosition;
window['piro.sakura.ne.jp'].operationHistory.doUndoableTask(
// やり直し可能にしたい処理
function() {
TreeStyleTabService.currentTabbarPosition = newPosition;
},
// 履歴の名前(省略可)
'TabbarDNDOperations',
// ウィンドウごとの履歴の場合の対象ウィンドウ(省略可)
window,
// 履歴の項目
{
// 項目名
label : 'タブバーの位置変更',
// アンドゥの時に実行する内容
onUndo : function() {
TreeStyleTabService.currentTabbarPosition = current;
},
// リドゥの時に実行する内容(省略可)
// →省略時は上記の「やり直し可能にしたい処理」が自動的に
// onRedoとして登録される
onRedo : function() {
TreeStyleTabService.currentTabbarPosition = newPosition;
}
}
);
という感じでアンドゥ・リドゥ時の動作を登録して、window['piro.sakura.ne.jp'].operationHistory.undo('TabbarDNDOperations', window)
とかwindow['piro.sakura.ne.jp'].operationHistory.redo('TabbarDNDOperations', window)
とか書くとヨロシク処理してくれる……という風な感じ。「あらゆる操作を一次元で記録して」とタイトルに書いてるけど、自動的に記録するんじゃなくてアドオン作者が手作業で記録する前提で、「履歴の記録」「履歴の呼び出し」の所を管理する手間を軽減するだけのライブラリなんで、そこの所はお間違えなきよう。
関数をそのまま登録するというのが乱暴と言えば乱暴なんだけど、柔軟性を高くしようと思ったらこうするのが手っ取り早いかなーって思いまして。一応ウィンドウごとの履歴とグローバルな履歴の両方を持てるようにしてみてる。イメージ的には、Adobe製品のヒストリ機能のような物を目指してる。
で、枠組みは用意したんだけど、タブバーの位置の移動みたいな単純な機能はいいとして、ツリーの移動みたいなややこしい物をどうやってアンドゥ・リドゥさせるかで暗礁に乗り上げてる。
なんとなく、ツリー型タブからは分離して「タブバー上のあらゆる操作をアンドゥ可能にするアドオン」を新しく作った方がいいような気がしてきた。で、ツリー型タブが入ってる時はそいつのアンドゥ履歴の中に「タブバーの位置変更」の項目が混ざってくる、みたいな連携の仕方。
ツリー型タブに他のアドオン向けのAPIを加えていきたいという話を書いた関係でコードをあちこち見直して書き直していて、カスタムイベントを通知→イベントを捕捉した側でキャンセル→イベントを通知した側でキャンセルを検知して処理を中断 という事をやりたくなって調べた結果をmodestにまとめてみた。
ここに書かずにmodestの方に書いたのは、話のレベル的に今更感があったのと、わりと基礎的な話だからこんな世界の果てのオメガギークの日々の下らない愚痴に混ぜて書くだけにしたら誰にも見てもらえないんじゃないか(それよりももっと多くの人に読んで貰って取り入れてもらって、アドオン同士の連携を取りやすい世の中になってくれると嬉しい)と思ったというのと、というあたりが理由です。
でもなんかほんと今更過ぎるというかものすげえ基本的なことを得意げに解説してしまった気がしていて、失敗だったかなあとちょっとブルーです。
Tree Style Tab 0.8.2009122501でTabberwocky用のコードを入れました。とりあえず、選択範囲のリンクをタブで開く機能と、新しいタブを現在のタブのすぐ隣に開く機能については、ちゃんと動くことを確認してます。他にうまく動かない機能があったら言って下さい。Tab Mix Plusに比べたら全然コードの量も少ないんで、たぶん対応できると思う。
Tab Mix Plusと組み合わせた時の問題もどうにかしようと思ったんだけど、見てみたらTMPの中にTST用のコードが入ってたので、さっくり諦めた。お互いがお互いに手を出すともはや収拾が付かなくなるから。なのでちょうどいい機会だと思って、TMPのフォーラムに「いいかげんこの状況なんとかしようよ」的な提案を書き込んでみた。そのついでに、ソースコード中で「PUBLIC API」と書いておきながら説明を書き忘れてたAPIをドキュメントに追加した。
他のアドオンと連携を取りやすくするためのAPIを加えるのはやぶさかじゃないので、要望があれば是非言ってください。最近の例では、TreeStyleTabService.currentTabbarPosition
やTreeStyleTabService.treeViewEnabled
はメールで「こういう事をしたいんだけどどうすればいいのさ」と問い合わせを受けたので追加したAPIです。