Home > Latest topics

Latest topics 近況報告

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

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

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

宣伝2。Firefox Hacks Rebooted発売中。本書の1/3を使って、再起動不要なアドオンの作り方のテクニックや非同期処理の効率のいい書き方などを解説しています。既刊のFirefox 3 Hacks拡張機能開発チュートリアルと併せてどうぞ。

Firefox Hacks Rebooted ―Mozillaテクノロジ徹底活用テクニック
浅井 智也 池田 譲治 小山田 昌史 五味渕 大賀 下田 洋志 寺田 真 松澤 太郎
オライリージャパン

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

タブの追加・削除のアニメーション - Aug 08, 2010

Firefoxで、タブを閉じる時にいきなりタブが消えるんじゃなくちょっとずつ消えるようなアニメーションが実装されたようだ。ツリー型タブにアニメーション効果を加えた時にも書いたけど、状態が不連続に変化するよりも連続的に変化した方が分かりやすいことは間違いないので、この改良は歓迎すべき事だと思う。

使い勝手的な面とか見た目的な面とかでは多分他の人が言及していると思うので、僕は実装とかAPIとかの面で感心した点に言及してみる。

このパッチが入るちょっと前に「新しいタブを開く時にアニメーション効果を適用する」という処理も入ったんだけど、この2つには違う所が1点ある。それは、既定の状態がアニメーション有りか無しかという点だ。

  • gBrowser.addTab(uri)で、アニメーション有りでタブが開かれる。
  • gBrowser.addTab(uri, { skipAnimation : true })で、アニメーション無しでタブが開かれる。
  • gBrowser.removeTab(tab)で、アニメーション無しでタブが閉じられる。
  • gBrowser.removeTab(tab, { animate : true })で、アニメーション有りでタブが閉じられる。

今回のremoveTab()の既定の挙動とフラグ指定の仕様は大いに評価したい。何故かというと、これは互換性を最大限保つために必要なことだからだ。

タブを閉じる時にアニメーション効果を適用させるということは、タブに「閉じるという操作は行われたが、まだ実際にはDOMツリー状に存在している」という状態が加わったということだ。そのため、gBrowser.mTabContainer.childNodesで取得したタブの一覧の中に、本来であれば処理対象にしては行けないタブが含まれてしまうようになる。

これは大いに混乱を招く。アドオンの多くはタブを閉じる際のアニメーション効果のことなど知らないから、タブが閉じられたらすぐにDOMツリーからも消えることを期待している。そういう前提で書かれたコードが、片っ端から動かなくなってしまう恐れがある。

今回のパッチでは、この混乱を最小限にするための配慮が行われている。このような状態が発生する場面を「タブのクローズボックスがクリックされた時」と「Ctrl-Wなどのキーボードショートカットで現在のタブが閉じられようとした時」だけに限定することで、それ以外の場合は明示的に指示されない限りはアニメーションしないようにしてある。アドオンが要であるFirefoxとしては、APIの変更でアドオンが作りにくくなる・人気のアドオンが動作しなくなることのデメリットの方が、美しい設計のAPIにすることのメリットを上回ると考えられるから、この判断は合理的と言っていい。

今後の課題は、互換性を重視したことでAPIが分かりにくくなってしまった点をどうするか、ということではないかと個人的には思っている。特に、addTab()removeTab()という対になったAPIにおいて、似たような事をやるのにそれぞれやり方が違うというのは、あまり誉められる事ではないから。

ただ、今はとにかく、Mozillaが互換性を重視する決定をしてくれたことをありがたく思っている。

XUL/Migemo 0.13.xで中身の方をだいぶ変えて(環境によっては)高速化しました - Jul 09, 2010

半分眠ったまんまで作業してたのでしょーもないregressionを仕込む→修正して公開→またregressionということを繰り返した結果、既に0.13.3になってしまったわけですが、バージョン0.13.0で結構大きくいじりました。機能的には全然変わってないですが。

  • 開発メモにちょっと書いてるけど、このバージョンからFirefox 2とThunderbird 2をサポートしなくなりました。
  • Gecko 1.8どころかGecko 1.9.0も切り捨てたという事で、Firefox 3.5より後で入った便利機能を心置きなく使えるようになったり、Firefox 2→Firefox 3の間で大きく変わったAPIの両方に対応するためのめんどくさい場合分けをゴソッと削除できたりしたので、だいぶスッキリした気がします。
    • ウィンドウごとに分ける必要がないコードでXPCOMコンポーネント化してなかった物は積極的にJavaScriptコードモジュール化しました。
    • Thunderbird専用のコードは、XPCOMコンポーネントにしてはあったものの、そんなに汎用性が無かったので、これも全部JavaScriptコードモジュール化しました。
  • 検索でヒットした箇所が多い時に、Safari風の強調表示等の機能を有効にしているとフリーズしてしまう問題について、ヒット箇所の強調表示を段階的に非同期で行うようにしてみました(0.13.4)。
  • Gecko 1.9.2以降ではnsIDOMWindowUtilsのnodesFromRect()を使うようにしたので、見えているスクロール位置から検索を始める処理が相当速くなりました。
  • Minefieldに最近入った機能のおかげで、DOM Rangeから検索用のテキストを取得する部分が爆速になりました。nodesFromRect()の効果と合わせて、Minefield 4.0b2preではページ内検索のストレスがほとんど無くなったと思います。

Minefieldで検索が速くなったのは、Bug 39098 – Elements with visibility:hidden, visibility:collapse, or display:none get copied to the clipboardがfixされたおかげです。

  • これまでXUL/Migemoのページ内検索が非常に重かったのは、Rangeを一旦文字列にして正規表現でマッチングして、その結果を使ってもう一度ページ内検索するという仕組みに理由があります。
  • この時、RangeをそのままtoString()で文字列にすると、CSSで非表示になっている要素のテキストまで一緒に取得されてしまうのですが、その後rangefindで検索する時は非表示のテキストは検索対象にならないため、場合によってはヒット箇所が飛ばされてしまったり検索が止まってしまったりという問題が起こります
  • そこで現在のXUL/Migemoでは、
    DOMWindow.QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsIWebNavigation)
       .QueryInterface(Ci.nsIInterfaceRequestor)
       .getInterface(Ci.nsISelectionDisplay)
       .QueryInterface(Ci.nsISelectionController)
       .checkVisibility(textNode, 0, textNode.nodeValue.length)
    というやり方で個々のテキストノードの可視・不可視の状態を判別して不可視のテキストを除外した結果を取得して、正規表現のマッチングに使うようにしていました。
  • しかしこの方法は、テキストノードの数が増えると判断の回数も同じだけ増えてしまうため、MXRのページのように内容が小さなテキストノードに分断されているページだととんでもなく処理に時間がかかってしまいます。
  • 5月末にBug 39098で入った変更によって、テキストリンクの高速化の時にも使ったnsIDocumentEncoderに、「非表示状態のテキストを結果の文字列に出力しない」というオプションが加わりました。なのでMinefield上では、この機能を使ってRangeの中の可視状態のテキストだけを一気に取得するようにしました。

Minefield 4.0b2preにXUL/Migemo 0.13.xを入れてMXRのページでページ内検索してみると、効果の程がよく分かると思います。僕は、今すぐにでもFirefox 3.6を窓から投げ捨ててしまいたくなりました。

あとはJägerMonkeyが入ってくれれば……

ところで、他のアドオンからXUL/MigemoのAPIを呼び出す時はちょっと注意が必要になってます。

  • ずっと前に「ユーザ辞書の内容がWebページのスクリプトから取れてしまうのはセキュリティ的に宜しくない」という指摘を受けたんですが、どうやってこの問題を回避すればいいものか悩んで、結局、グローバルなJavaScriptのプロパティのmigemoオブジェクトから機能を呼び出す時には常にシステム辞書の内容だけを返すように仕様を変えました。
  • ユーザ辞書に追加された単語も含めた正規表現を取得したい場合、Components.utils.import('resource://xulmigemo-modules/service.jsm')でユーティリティを読み込んでXMigemoCoreの各メソッドを呼ぶか、Cc['@piro.sakura.ne.jp/xmigemo/factory;1'].getService(Ci.xmIXMigemoFactory).getService('ja')てな感じでXPCOMコンポーネントを直接呼び出すかする必要があります。
  • XMigemoCoreでもxmIXMigemoCoreでもどっちにしても、これらが持つ機能で正規表現を生成する時は戻り値は常に文字列(正規表現のソース文字列)になるので、その都度new RegExp()してやる必要があります。
  • 辞書が絡まない機能はmigemoのメソッドを使って問題ありません。XUL/MigemoのUI周りでもそうしてます。

nsIDOMWindowUtilsのnodesFromRect - Jul 07, 2010

nsIDOMWindowUtilsのnodesFromRect、解説がなくてさっぱり使い方が分からなかったので使ってみて調べた結果を記しておく。

インターフェースの定義はこんなん。

nsIDOMNodeList nodesFromRect(in float aX,
                             in float aY,
                             in float aTopSize, 
                             in float aRightSize,
                             in float aBottomSize,
                             in float aLeftSize,
                             in boolean aIgnoreRootScrollFrame,
                             in boolean aFlushLayout);

8個もの引数を取るんですよ……

  • Gecko 1.9.2(Firefox 3.6)から使える。
  • 矩形で示された範囲から、その範囲に含まれるDOMノードを収集して返す。
  • Fennec(Firefox Mobile)用に実装された(Bug 489127 – nodesFromRect required for better usability on mobile devices)。
    • Fennecはタッチスクリーン型のスマートフォンで使われる前提のプロダクト。
    • 指で触った所にあるリンクを拾いたい。
    • でも、小さい画面だと正確にリンクをタッチするのはどう考えてもムリ。
    • そういう時は、タッチスクリーンで検出した座標の周囲のある程度の範囲も含めて取得して、リンクが2個以上あったらそれぞれを選択肢として表示するなどの対策を取れたらいいよね。

そういう経緯で実装されたので、第1から第6までの引数は経緯から考えたら納得のいく指定の仕方と言えよう。

  • 第1引数・第2引数は検索の中心座標を示す。「現在のスクロール位置」は0, 0になる(scrollX, scrollYを指定すると「ビューポートの左上からscrollX, scrollY」という意味になってしまうので注意!)。
  • 第3、第4、第5、第6引数は、検索する範囲を中心座標から上・右・下・左のそれぞれの方向に何ピクセル広げるかを示す。この並び順はCSSでお馴染みの物ですね。
  • 第7引数は、親フレームのスクロール状態を考慮するかどうかを示す真偽値。らしい。falseを渡した場合、ビューポートの外の矩形を範囲として指定すると空のノードリストが返るみたい。
  • 第8引数は、未確定状態のレイアウト情報を確定するかどうかを示す真偽値。らしい。

戻り値のノードリストは、どういう訳か文書中の登場順の逆順で返ってくるようだ。

XUL/MigemoのMinefield対応に向けてのメモ - Jul 07, 2010

作業メモ。

  • gFindBarの遅延初期化には対応した。window.watch('gFindBarInitialized', function() { ... })で、遅延初期化のタイミングで初期化を行える。
  • XPCOMコンポーネントの登録方法の仕様変更にも対応した。これから先、できれば仕様は変わらないでいて欲しいけど……
    • classDescriptionにホワイトスペース文字が入ってると、カテゴリへの登録がうまくいかないかも。nsUpdateTimerManagerに対応するマニフェストファイルなんかを見てみた感じでは、実装のクラス名をそのままclassDescriptionにするといいのだろうか?
    • nsSidebar.jsに対応するマニフェストファイルを見て分かったけど、今までカテゴリマネージャに登録する必要があったケースは全部マニフェストファイルでやるという事のようだ。マニフェストファイルの方に書いておかないと、ちゃんと認識されなかった。
  • これまで、Firefox 2からFirefox 3.6の間でメソッド名やプロパティ名が変わった物はFirefox 2に合わせるようにしてたけど、今後はMinefield 4.0b2pre基準にする事にする。今の所はほとんどFirefox 3.6と同じだけど。
  • Firefox 2からFirefox 3.6までの間それぞれのためのコード、特にFirefox 2用とそれ以降用とでコードを書き分けてた部分が多かったので、思い切ってFirefox 2用のコードは全廃する事にした。
  • rangefindを使う時に注意がいる
  • 他アドオンとの連携にも注意がいる

しないといけないなーと思ってる課題。

  • Minefieldに既に入ってる、スマートロケーションバーの候補として出てきたplaceが既にタブで開かれている場合にそのタブに切り替える機能への対応。
  • Thunderbird 3。とりあえずフォルダペイン内での絞り込みはできるようにしとかないと……とは思ってる。

スマートロケーションバー関係を調べて分かった事。

  • Minefieldは、タブで閲覧中のページをtabbrowserが持ってるプログレスリスナで常時監視してて、タブで閲覧中のplaceの一覧をmoz_openpages_tempという名前のテーブルに保存している。
    • このテーブルはplace_idopen_countという2つのカラムを持ち、ページの遷移に応じて内容が随時更新される。
  • 検索でヒットしたplaceについて、このmoz_openpages_tempopen_countが0より大きい物は、オートコンプリートの候補として返される時にURIの前にmoz-action:switchtab,という文字列が付与され、lichlistitemのtype属性用の値にはactionという値が設定される。
  • スマートロケーションバーのオートコンプリートの実装はいつの間にかJavaScriptになっていた。Cより読み慣れてるから助かる。
  • この機能が有効になるのは、オートコンプリート用のtextboxのautocompletesearchparam属性の値にenable-actionsという文字列が含まれている時だけのようだ。
    • 機能が無効になっている時は、単に上記の「URIの前にmoz-action:switchtab,という文字列が付与され~」という処理がスキップされる。
    • この事から分かる通り、moz_openpages_tempopen_countはオートコンプリートの候補の並び順には影響しない。あくまでfrequencyベースで検索していて、ヒットした候補の中にたまたま現在タブで開かれているplaceが含まれていた場合にだけ、この機能が発動するという仕様のようだ。
  • ロケーションバーへの入力時に「タブで開いてる奴だけ表示」という風に制限する時の既定のキーワードは「%」。browser.urlbar.default.behaviorで指定する時のフラグは128(1 << 7)。

Minefield 4.0b2preではどうも同期的な処理がことごとく失敗するようになっている気がする - Jul 06, 2010

XUL/MigemoのMinefield 4.0b2pre対応のために色々検証していて、1つとても困った問題にぶち当たった。rangefindを使ってWebページ中の要素を装飾する時に、前から後ろに向かって処理を行うと検索が止まってしまう。

Components.utils.import('resource://gre/modules/debug.js'); 
const Cc = Components.classes;
const Ci = Components.interfaces;

function decorate() {
  var span = d.createElement('span');
  span.setAttribute('style','font-size:150%;');
  foundRange.surroundContents(span);
}

/* コンテンツ領域にテスト用の内容をロードする */
var d = Cc['@mozilla.org/appshell/window-mediator;1']
          .getService(Ci.nsIWindowMediator)
          .getMostRecentWindow('navigator:browser')
          .content.document;
d.documentElement.innerHTML = 'Firefox, Firefox, Firefox.';
d.documentElement.clientTop; /* ←伏線 */

/* rangefindを初期化する */
var find = Cc['@mozilla.org/embedcomp/rangefind;1'].createInstance(Ci.nsIFind);
find.findBackwards = false; /* 前から後ろに向かって検索 */
find.caseSensitive = false;

var findRange= d.createRange(); /* 検索する範囲 */
findRange.selectNodeContents(d.documentElement);
var startPoint = findRange.cloneRange(); /* 検索の始点 */
startPoint.collapse(true);
var endPoint = findRange.cloneRange(); /* 検索の終点 */
endPoint.collapse(false);

/* 検索を実行 */
var term = 'Firefox';
var foundRange = find.Find(term, findRange, startPoint, endPoint);
NS_ASSERT(foundRange !== null, '1回目で失敗');

decorate(); /* DOMツリーを編集して装飾する */

/* 検索の範囲を変える(編集した箇所より後を検索の範囲にする)*/
findRange.setStart(foundRange.endContainer, foundRange.endOffset);
startPoint.setEnd(foundRange.endContainer, foundRange.endOffset);
startPoint.collapse(false);

/* もう一度検索を実行 */
foundRange = find.Find(term, findRange, startPoint, endPoint);
NS_ASSERT(foundRange !== null, '2回目で失敗');

decorate(); /* DOMツリーを編集して装飾する */

エラーコンソールにこれをコピペして実行してみると、2回目の方で必ず失敗してしまう事が分かる。本当は「Firefox」という文字列が2箇所装飾されて欲しいのに、最初の1箇所だけで処理が止まってしまう。

これ、上のサンプルの中で伏線と書いている部分が鍵なんだけど、どうもこういうことらしい。

  • DOMツリーを編集すると、編集した箇所から先の範囲が「不確定」な状態になる。
  • 「不確定」な範囲に対しては、rangefindは一切の検索を行えない。
  • DOM要素のプロパティにアクセスするなどしてレイアウト情報を参照すると、状態が強制的に「確定」される。
  • または、setTimeout()等で少し遅らせて処理を行えば、その時には状態が「確定」されている。
  • 状態が「確定」されると、その範囲をまた検索できるようになる(最初の方にある d.documentElement.clientTop は、実はそのための物)。

1回目の検索結果のRangeの箇所でDOMツリーを切った貼ったしているので、その箇所より後の部分はどう頑張ってもそのままの流れでは検索できないようになってしまっている、ということのようだ。

なので、

  • 2回目以降の検索を実行する前に span.clientTop あたりにアクセスしてやれば(これ以外にもclientLeftでもoffsetWidthでもレイアウト系のプロパティなら何でもいいっぽい)、whileforのループを回し続ける事ができる。
  • 毎回setTimeout()で状態の「確定」を待ってやるというやり方でもよい。

という風な回避策があると言える。でも、後者は言わずもがな、前者も毎回レイアウト情報を参照するからクソ重くなりそうで、できればどっちの方法もとりたくない所だ。

将来的にどうなるのかは知らんけど、とりあえず今のところは、後方検索(Rangeの後ろの方から前の方に向かって検索する)ならこの問題に引っかからずに済むみたい。編集した箇所から先の部分が「不確定」になっても、後方検索だと「編集した箇所から先=もう検索が終わった範囲」なので。(→と思ってたけどやっぱり動作が怪しいので安全めな方に倒すということで毎回clientTopにアクセスする方法を使う事にした。なんか負けた気分。)

この「rangefindでループを回してDOMツリーを切った貼ったして装飾する」というやり方はFirefox 2以前のページ内検索における「すべて強調表示」の実装方法だったんだけど、今のFirefoxではDOMツリーはいじらずに強調箇所を選択範囲として処理するようになってて、この問題は問題にならないようだ。今でもFirefox本体でこういうことをやってるところがあれば「これってregressionなんじゃないの」とbugzillaに報告できるところだと思うんだけど……

Firefoxのバージョン間の差異を吸収するライブラリをこそ、僕は欲しているというのに。 - Jul 03, 2010

Firefox 3.6以前とMinefieldとでは、アドオンマネージャのAPIがまるっきり変わってしまった。

Firefox 3.6以前のアドオンマネージャ(Cc['@mozilla.org/extensions/manager;1'].getService(Ci.nsIExtensionManager))は同期的なAPIで、頑張ってラップすればvar enabled = isEnabled('treestyletab@piro.sakura.ne.jp');みたいな感じで「その場で結果を取得する」ことができた。でも今のMinefield(アドオンマネージャがタブで開かれるようになった奴)では、インストール済みのアドオンの情報を取得しようと思ったら必ずコールバック関数を使った非同期なAPIでやらなきゃいけないようになってしまった。

// このコードはMinefield(Firefox 4)以降でないと動かない
Components.utils.import('resource://gre/modules/AddonManager.jsm');
AddonManager.getAddonByID(
  'treestyletab@piro.sakura.ne.jp',
  function(aAddon) {
    if (aAddon && aAddon.isActive) {
      // ツリー型タブがインストール済みで、
      // 且つ有効化されている時の処理
    }
    else {
      // ツリー型タブが利用できない時の処理
    }
})

Firefox 3.6以前と今のMinefield(つまり将来のFirefox 4)の両方に対応しようと思うと、この差異をどうやって吸収するかがネックになる。そこで、メインスレッドの処理を一時停止して処理の完了を待つ裏技を使って、同期的なやり方で他のアドオンの有効・無効の状態を調べたり設定ダイアログを開いたりするためのライブラリをMinefieldでもそのまま使えるようにしてみた。

// このライブラリを使うと、Firefox 3.6でもMinefieldでも
// 違いを意識しないでコードを書けるようになる。
var extensions = window['piro.sakura.ne.jp'].extensions;
if (extensions.isAvailable('treestyletab@piro.sakura.ne.jp')) {
  // ツリー型タブが利用できる時の処理
}
else {
  // ツリー型タブが利用できない時の処理
}

そしたら、Minefieldで起動時にセッションが復元されないという現象に遭遇してしまった。条件を絞り込んでいくと、どうもアドオンマネージャのタブが開かれた状態のセッションが復元される時に問題が起こっていて、さらに辿っていくと、上記の裏技で新アドオンマネージャの処理を止めていると、アドオンマネージャのタブの読み込みが阻害されてしまってセッション復元が半端な所で止まってしまうという事のようだった。

仕方がないので、Firefox 3.6以前のやり方に合わせるのではなく今のMinefieldのやり方に合わせる方向でestensions.jsのAPIを拡張して、今後はそっちの使い方を推奨する事にした。

// 新しい書き方。これも、Firefox 3.6でもMinefieldでも
// 違いを意識しないでコードを書いて大丈夫。
var extensions = window['piro.sakura.ne.jp'].extensions;
extensions.isAvailable('treestyletab@piro.sakura.ne.jp', {
  ok : function() { /* ツリー型タブが利用できる時の処理 */ },
  ng : function() { /* ツリー型タブが利用できない時の処理 */ }
});

Firefox 3.6以前でこのAPIを呼んだ場合は、非同期にならずにその場でコールバック関数が呼ばれるという実装上の違いがあるけれども、基本的に非同期で実行される前提でコードを書いておきさえすれば、Firefox 3.6以前でもMinefieldでもそのまま動くようになってる。コールバック関数を渡さなければ今まで通りの同期的なAPIとして動作するので、コールバック関数はどーしても使いたくない!という場合は、非推奨ではあるけど今まで通りの使い方もできる。

FUELとかJavaScriptコードモジュールとか、Firefox本体の方で色々ユーティリティっぽい物が用意されつつあるけど、僕の立場(複数のバージョンのFirefoxをサポートしたいという前提がある)では、それらはまるっきり役に立たない。新しいバージョンのFirefoxで標準のコードモジュールが増えた所で、現行のリリース版のFirefoxにも対応させるなら、結局それは使えないのだから。しかも、Firefoxのバージョンが上がったらAPIが使えなくなっちゃいましたなんて事もザラにある(今回の話も、nsIExtensionManagerがゴッソリなくなってしまったせいで起こった問題だ)。

そういう「簡単に書けますよ」っていうだけのAPIは、もう、ぶっちゃけどうでもいい。そんな物より、Firefoxの複数のバージョン間での差異を吸収するライブラリこそが僕には必要なんだ。新しいやり方に合わせて書いておけば古いバージョンのFirefoxでもそのまま使える、というのでも、古いやり方のままで新しいバージョンのFirefoxでも動く、というのでも、どっちでもいいんだけど、とにかく1つの記述でどっちのバージョンでも動くようにしておきたい。そうじゃないと、新しいFirefox用と古いFirefox用とで目的が重複するコードがどんどん増えていって、片方は直したけどもう片方は直し忘れてたみたいな穴がどんどん増えていって、すぐ破綻してしまう。

FUELはFirefoxのバージョン間の違いを意識しないで使えるようなAPIの提供を目指してたはずだと思ってたけど、今となってはその計画も頓挫してすっかりうち捨てられてしまったような印象がある。実際、今回の件についてもFUELのAPIは互換性を失う形であっさり変更されてしまってて、もうFirefox 3.6の物と同じ使い方はできないし、Firefox 3.6の物の使い方もMinefieldではできない。JetpackはRebootで明後日の方向に飛んで行ってしまって、少なくとも「Firefox 4から先」の事しか眼中になくてFirefox 3.x系はガン無視っぽい。結局、Mozilla本家はアテにならない。アドオン作者が自分達でやる以外にない。

そういう理由で作ったライブラリ類をリポジトリの中にまとめて置いてあるので、似たような事を考えてる人は覗いてみるといいかもしれない。JavaScriptコードモジュールとして使える物はmodestのJavaScriptコードモジュールの紹介ページに簡単な紹介を書いておいたけど、どれもソースの頭の方に用例を付けてあるので、まあ見てもらえばだいたい分かるんじゃないかな。

一応、簡単な説明。

  • jstimer.jsm:JavaScriptコードモジュール等のDOMWindowを参照しづらい場面でsetTimeout()とかsetInterval()とか書けるようにするライブラリ。
  • namespace.jsm:JavaScriptコードモジュール同士で名前空間を共有できるようにするライブラリ。同じJavaScriptコードモジュールの同じ内容のファイルを複数のアドオンでそれぞれ別々に持たせて、それぞれのメモリ空間も別々に確保される、というのがものすごくあほらしく思えたので作ってみた。
  • animationManager.js:JavaScriptでアニメーションさせる時に、複数アドオンで1つのタイマーを使い回して効率よくアニメーションさせるためのライブラリ。jstimer.jsmと併用すればJavaScriptコードモジュールとしても使える。
  • arrowScrollBoxScrollHelper.js:arrowscrollboxの中に1つ大きなボックスが入っていてその中に小さなボックスがたくさんある、という場面でarrowscrollboxのスクロール処理がぶっ壊れる問題を回避するライブラリ。
  • autoScroll.js:mousemoveまたはdragoverイベントに基づいてタブバーの自動スクロールを行うライブラリ。
  • bookmarkMultipleTabs.xul / bookmarkMultipleTabs_bookmarkPropertiesOverlay.xul:複数のタブをまとめて1つのブックマークフォルダにブックマークするためのライブラリ。Firefox本体の機能だと「全部のタブを保存」しかできないので。
  • boxObject.js:HTMLDocumentのgetBoxObjectFor()を使ってたコードを、手直しせずにそのままFirefox 3.5とかで動くようにするためのライブラリ。
  • extensibleToolbarButton.css / extensibleToolbarButton.xml / extensibleToolbarButton.xul:Firefox本体のツールバーボタンに後からappendChild()とかで内容を追加できるようにするためのライブラリ。
  • extensions.js:このエントリでメインの話題にしてる、「他のアドオンがインストールされているかどうかを調べる」「他のアドオンの設定ダイアログを開く」といった事を簡単に行えるようにするライブラリ。
  • operationHistory.js:何かの操作をアンドゥ・リドゥできるようにしたいときのための汎用的な履歴管理ライブラリ。詳しくは解説のエントリを参照してください。
  • prefs.js:FUEL/STEELが無いような古いバージョンも対象にする場合向けの、簡単に設定を読み書きできるようにするライブラリ。
  • stopRendering.js:ウィンドウ内の再描画を一旦止めて、その間に色々GUIをいじくる処理をして、最後にまとめて表示に反映させる、という事をやるためのライブラリ。画面がチラついてなんか気持ち悪い、という不快感を和らげるのが目的。Firefox 3.6以前とMinefieldとでは実装が中身の自動的に切り替わるけど、APIとしてはFirefoxのバージョンの違いを意識せずに使えるようになってる。
  • stringBundle.js:stringbundle要素をXULの方に埋め込まなくても、同じAPIでpropertiesファイルの中の文字列を読めるようにするAPI。
  • tabFx2Compatible.css / tabFx2Compatible.xml / tabFx2Compatible.xul:Firefox 3以降でtabbrowserのtabの中にappendChild()とかで要素を追加できるようにするためのライブラリ。Firefox 2以前のDOMツリー構造を再現して、中にいくつかのボックスを増やす。
  • UninstallationListener.js:アドオンのアンインストールが行われたタイミングで、変更した設定を自動的に元に戻すとかの後片付け的な処理をやるためのライブラリ。Minefieldでは多分動かなくなってる気がするので、後で直しときます。

初期化処理を書く時に注意がいるようになったみたい - Jul 03, 2010

ツリー型タブが入ってるとMinefield 4.0b2preがぶっ壊れる現象に遭遇した。

何が原因なのかちょっとずつ絞り込んでいったところ、DOMContentLoadedイベントが発火するよりも前の時点で<tabbrowser id="content"/>に触っていたのが原因ぽいという事が分かった。ツリー型タブの初期化処理の中でtabbrowser要素の属性値を取ろうとしてdocument.getElementById('content').getAttribute('...')としただけで、XBLのconstructorとかそのへんがぶっ壊れて、tabbrowser要素の初期化処理が全く行われない状態になってしまってたようだ。

ツリー型タブのインストール後にツールバーのカスタマイズ内容が失われるという話も、これが原因だったんだろうか? 状況としてはよく似てるんだけど。

この問題の発生を避けるには、初期化処理を必ずDOMContentLoadedイベントかloadイベントのタイミング以降で行うようにするのが多分一番簡単だと思う。

window.addEventListener('load', function() {
  window.removeEventListener('load', arguments.callee, false);
  // 何か初期化処理
}, false);

他にも、複数バージョンに対応したアドオンでバージョン判別のために'MozBorderImage' in element.styleとかやってる場合も危険かもしれない。とにかく、DOM要素(の属性値であるとかDOMオブジェクトのプロパティであるとか)を参照せずにメタ的な情報(XPCOMで取得できるGeckoのバージョン情報とかnsIPrefBranchで取得できる設定値とか)で判断できる場合はなるべくそっちを使った方が安全っぽい。例えばFirefox 3.5以降かどうかを調べるならこんな感じ。

var comperator = Cc['@mozilla.org/xpcom/version-comparator;1']
                   .getService(Ci.nsIVersionComparator);
var XULAppInfo = Cc['@mozilla.org/xre/app-info;1']
                   .getService(Ci.nsIXULAppInfo);
if (comparator.compare(XULAppInfo.version, '3.5') >= 0) {
  // Firefox 3.5.0およびそれ以降
}
else {
  // Firefox 3.0およびそれ以前
}

Firefox 4のTabs on Topを受け入れられないのは頭が硬直化している証拠 - Jun 28, 2010

タイトルは半分は釣り。

Firefox 4のUIのモックアップでそれが目標として示されて以降、「タブがツールバーの上に表示されるようになる」という変更に対しては色んな人が反対の意を示しているようだ。以下もそのひとつ。

反対意見があまりに多いためか、Mozillaもわざわざ動画まで用意して Tabs on Topの正当性を必死で主張している

この件に関して僕は一貫して、タブをツールバーの上に移動するという決定には賛同している。というか、できる事ならもっと前の時点でそうするべきだったと思っている。今の(Firefox 3.6の)UIのデザインを「この方が優れている」という論調で肯定する意見は、ちゃんちゃらおかしいとしか言いようがない。

何故そう言い切れるかというと、今のUIのデザイン(タブがツールバーの下にある)は、言っちゃあ悪いが、実装者の都合に基づくやっつけ仕事の積み重ねの末にある結果でしかないからだ。Firefoxの前身であるPhoenixが出てくるより前、初めてMozilla本体でタブブラウズ機能を実装し始めた頃から見ていたら、それはもうはっきり分かることだ。

そもそもなんでタブがツールバーの下にあったのかと言えば、それまでのタブが無いUI(Netscape Navigator由来)の中にタブを組み込みつつ、他の部分のコードとの互換性を最大限保つために、変更箇所を最小限にとどめる形で実装が行われたからだ。最初のタブブラウズ機能は、それまで「ブラウズ領域」を確保するために用意されていたbrowser要素(iframe要素の高機能版と思って貰えばいい)と入れ換える形でtabbrowser要素という物を置き、タブに関する変更は全部そのtabbrowser要素の中で完結させるという方向性で実装された。それ以後Firefox 3.6に至るまで、根本的な設計はずっと当時の物を引きずってきた。 (Firefox 3.6までの実装におけるボックスモデルの図)

ここで重要なのは、タブがコンテンツ領域の真上に置かれたこと、ツールバーの下に置かれたことに、ヒューマンインターフェースのデザインの観点からの理由は全く無かったという点だ。理由があってそうしたのではなく、単に「そうするのが実装上簡単だったから」でしかないのだ。

さて、「使いやすい」UIの設計にはいくつかのセオリーがある。

  1. 持って欲しい所は持てそうなように、押して欲しい物は押せそうな形に、見た目を作ること。
  2. 見た目が現在の状態を示すようにすること。
  3. 行う操作と、その結果との関係性を分かりやすくすること。
  4. 他の物に合わせること。

まず第一に、望ましい操作、やって欲しい操作にユーザを誘導するような見た目にするということ。例えば、棒のように突き出た部分があれば人はそれを掴んでみようとするだろう。ぽちっと出っ張った部分があれば人はそれを押してみようとするだろう。見た目は人の行動を誘発する。WindowsでもMac OS Xでもなんでも、ボタンが出っ張った見た目をしているのはそのためだ。

第二に、見た目から状態がすぐに分かるようにするということ。ボタンが「押し込まれた」ような見た目をしていれば、それはもうこれ以上押せないと分かる。オブジェクトの影が他の物の上に落ちていれば、そのオブジェクトが他の物よりも上(手前)にあると分かる。Windows VistaでもMac OS Xでもウィンドウに影が落ちているのはそのためだし、Aero Glassで下のウィンドウが透けて見えるのもそのためだ。

第三に、それを操作したら何が起こるかがはっきり分かるようにするということ。包丁を見たら、取っ手を持って振れば刃も一緒に動く事が分かる。ハサミを見たら、取っ手を握れば刃も動く事が分かる。そういう風に構造が見て取れるものでないなら、何らかの方法で「これを触ったらここがこうなりますよ」って事を分かりやすく示さないと行けない。飛行機のコクピットで着陸脚を操作するためのハンドルがまさに着陸脚そのものの形をしているのは、そのためだ。

最後に、既に似たものがあるのならそれに合わせるということ。今まで使っていたものに似ていればそれだけすぐに使えるし、今まで使っていたものと違っていたらそれだけで操作ミスが増える。自動車のブレーキとアクセルの並び順が、ある車種ではブレーキが左・アクセルが右で、別の車種ではブレーキが右・アクセルが左、なんて事になっていたら、これで事故が起きない方がおかしい。

翻って、今(Firefox 3.6)のタブはどうだろう。

タブ単体の見た目は、悪くない。フォアグラウンドのタブとバックグラウンドのタブはそれなりに見分けやすいし、選択されているタブが他のタブより手前にあるように見えるのも悪くない。押せそうな形をしているし、押せばタブが切り替わる。つまめそうな形をしているし、つまめば並べ替えもできる。

でも、「タブを操作したら何が起こるのか」は致命的に分かりにくい。

  • タブをクリックしたら、タブの上にあるナビゲーションバーの内容と、タブの下にあるコンテンツ領域の内容が変わる。
  • ウィンドウの真ん中あたりにあるタブを閉じる操作をしたら、タブだけでなくウィンドウまで閉じられる。(これなんかは、あまりに酷すぎて僕自身ボロクソに貶しもした。)
  • そもそも、タブが開かれたという事に気づきすらしない人もいる。リンクをクリックしたらボタンっぽい物が1個増えた、でもそれが何なのか分からないし、どうして増えたのかも分からない。

タブをツールバーの上に置くのは、「タブを操作したら何が起こるのか」を視覚的にはっきり示すための一番ストレートで確実な方法なんだ。

とはいえ、今Tabs on Topが非難を浴びているのもまた、上に書いたセオリーの通りではある。「他の物、今までの物に合わせる」ということ。比較の対象は「今のFirefox」に他ならない。

「他の物、今までの物に合わせる」、これは他のすべてを覆しかねないほど重要な事だ。なんだかんだ言って、人は自分の行動を変えようとしないし、今の物と違う物には拒絶反応を示す(上に挙げたそれ以外のセオリーは言わば、「今までの物がどうして使いやすかったのか」を分析する事で、「合わせる」先の既存の物が無い時に新しい物をどう設計するかの指針を示したものと言える)。今までの物に合わせないで別の物を提示するという事には、今までの支持を失うリスクがある。新しい物に絶対の自信がなければ、「今の物と違う」という理由でついて来れなくなる人よりも、新しい物の「分かりやすさ」によって引き付ける事のできる人の数の方が多いと信じていなければ、新しい物は取り入れられない。

要するにMozillaは、今までの支持者を失うリスクよりも、新しいUIで取り込める層の方が多いと見込んだという事だ。あるいは、失った支持者すら、新しいUIの分かりやすさによってもう一度取り込め直せるだろうと見込んでいるのだろう。それだけMozillaは新しいUIに自信を持っているのだろう。

ただ、どうしても受け入れられないという人のためにTabs on Topを無効にするオプションも用意しているあたりが、Mozillaらしいと言えばらしい譲歩ではある。例えばジョブズあたりはこの辺もっと割り切っていて、新しい物を出したら古い物はバッサリ捨てるという風に容赦がない(iMacでのレガシーインターフェース一掃やiPadの有線LAN非対応などはそのいい例だろう)。

まとめよう。

不幸な事にMozillaは、それまであまり知られていなかった「タブブラウズ」という概念を取り入れるにあたって、UI設計のセオリーをまるで無視した実装上の都合からタブブラウズのUIを作ってしまった。

しかし今は違う。メジャーなブラウザはいずれもタブブラウズ機能をサポートし、タブの位置についても、Tabs on Topを採用した例はOperaに続いてGoogle Chromeがある。Firefoxを除けば、タブをツールバーの下に置いているのはIEとSafariだけだ。そしてそのSafariは、タブの連結方向を下ではなく上に向ける事で、少なくともツールバーはタブに従属するものという事を視覚的に示している。採用している実装の数だけを言えば、ツールバーをタブに従属させるデザインの方が多数派になったとすら言える。昔の実装の負の遺産を断ち切るにはそろそろいい頃合いだ。

Tabs on Topに対する非難ははっきり言って、「今までの物と違う」という事それ自体に対する拒否反応でしかない。だから、それを正当化するために「今の配置の方が合理的なんだ」なんておかしな事を言う必要はない。そんな屁理屈を用意しなくても、あなたがTabs on Topを受け入れられない理由は「今までの物と違うから」それだけで十分だ。それは恥ずかしい事じゃあない。「新しい物を作る時はなるべく今ある物に合わせろ」というのが鉄則になるくらいには、人類みな頭が固いんだ。

そして、今までの物を覚えるのに苦労した分だけ「またあんな苦労をして新しく覚え直さなきゃならないのかよ!」と言いたくもなるだろうが、それは杞憂だ。Tabs on Topな新しいUIは、今までの物を覚えるのに要した時間よりもっと短い時間で慣れる事ができる。何故なら、今までタブを使った事がなくてもそのはたらきが見た目で分かる、そういう所を目指したデザインなんだから。

pinTab()、unpinTab()への対応 - Jun 27, 2010

このへんのパッチが投入されて、gBrowser.pinTab()gBrowser.unpinTab()というメソッドが実装された。 (実際に使った所のスクリーンショット) pinTab()にタブを渡すとそのタブが他のタブの左側に寄せられて、unpinTab()に(pinnedな)タブを渡すと元に戻る。ぶっちゃけアレですね、Chromeの似たような機能のパクリですね。

この時、タブはスクロールボタン(左向き三角のボタン)よりもさらに左に表示されるようになるんだけど、これは一体どうやって実現されてるのか。実は、CSSで非常にトリッキーなことをしている。pinTab()に渡されたタブはpinnedという属性の値にvalueが設定されるんだけど、この時、.tabbrowser-tab[pinned="true"]なタブはposition:fixedに設定されて、通常の描画フローから切り離される。その上で、スクロールボックスの左にすべてのpinnedなタブの幅の合計と同じだけのマージンを設けて、pinnedなタブ1つ1つにはネガティブマージンを設定してそれらしい位置に表示する……という感じ。moveTab()の中とかでタブの状態を見て処理を分けていて、pinnedなタブはpinnedじゃないタブの中には移動できないし、その逆も然り。原稿のコードに対する最小限の変更でそれらしい挙動を実現するようにしている。よう思いつくな、こんなの。

大抵の既存のアドオンは影響を受けないはずなんだけど、タブ周りで凝ったことをしてる奴は、下手したら全滅しそうな気がする。というかツリー型タブなんかはお話にならないのが目に見えてる。なのでちょっと頑張ってみた。

  • pinnedなタブのレイアウトの処理。改行とか。
  • pinnedになった時、自動的にツリーから解放するとか。

結果。 (スクリーンショット) 最初は単に、pinnedなタブのレイアウト処理の所のX軸とY軸を入れ換えるだけにしてみたんだけど、それだと縦置きタブバーの場合は無駄な領域がメチャメチャ増えるだけだという事が分かったから、「タブが小さくなる」という所を優先して、24×24固定サイズでアイコンを並べられるだけ並べる(1行に収まらなければ改行する)という風にした。見た目は……あんまり良くないね。すんません。

縦置きしたタブバーとpinnedなタブの相性はすこぶる悪い。結局全部ツリー型タブの方で作り直すのに近い状態になってしまった気がする。でもまあ挙動としてはそれなりに違和感のない状態に落ち着いた。

これからまた実装に仕様変更が入らないことを祈るばかりだ。実装の仕方自体が変わってしまうなら、今回のこの作業はまるっきり無駄になってしまうから。

タブバーの位置を変える方法(How to change the position of the tab bar easily?) - Jun 15, 2010

Q

It is nice if I can switch between tabs on the top and side. I know you can drag it but if the top gets filled up, then its hard to drag it. Then I have to open the prefs to move it. Be nice if it was easier to move the tab bar to different sides quickly.

タブバーの位置を上と左(または右)の間で簡単に切り替えられると便利だと思います。タブバーをドラッグすれば場所を移動できるのは知っていますが、タブバーが上にありタブが沢山開かれていて余白がない場合、タブバーをドラッグするのは難しいです。そういう時は仕方がないので設定ダイアログを使うほかありません。タブバーを異なる位置に簡単に移動できる方法があるといいのですが……

A

I have no plan about (re-)adding the feature to the Tree Style Tab. I think an answer for another question possibly help you: A new option to switch the position of the tab bar by the number of tabs.

By the way, you can start to drag the tab bar without blank spaces in the tab bar. Try to drag something in the tab bar not a tab. (ex. "New Tab" button, "<" button, ">" button, or "List All Tabs" button)

その機能を(再び)ツリー型タブに加える予定はありません。ただ、他の質問に対する回答があなたにとって何らかの助けになるかもしれません。タブバーの縦置き・横置きをタブの数に応じて自動で切り替えたいを参照して下さい。

それはさておき、タブバーのドラッグ&ドロップは、タブバーに余白が無くても行う事ができます。タブバーの中のタブ以外の任意の位置(例えば「新しいタブ」ボタン、スクロールボタン、「タブの一覧」ボタンなど)をドラッグしてみて下さい。

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

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

オススメ

Mozilla Firefox ブラウザ無料ダウンロード