たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。 以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
ツリー型タブのWebExtensions移行の後半作業と並行して進めてたマルチプルタブハンドラのWE移行ですが、こちらも一区切りとしてバージョン2.0をリリースしました。こちらはTSTほどには語る内容が無いので手短に。
マルチプルタブハンドラ(以下、MTH)は元々、「そこにあるFirefoxのタブを雑にドラッグして選択したらメニューが出てきて『で、この選択したタブをどうしたい?』と次のアクションを訊ねてくる」という、操作対象を起点とした操作、本来の意味での「オブジェクト指向」らしい操作というコンセプトに基づくアドオンでした。そのコンセプトを具現化するために、レガシー版ではタブの上でのクリックやドラッグどいった操作をハンドルし、時にFirefox本体の挙動をキャンセルして、「タブをドラッグして選択」という挙動を実現していました。
ところが、WebExtensionsではFirefoxのタブの上での生のイベントをハンドルすることが許されていません。そういうAPIが欲しいという要望は挙がっていましたが、WebExtensions APIのコンセプトに反するということでWONTFIXになっています。よって今後もそういう事ができるようにはならないでしょう。
ただ、WE版TSTは専用のサイドバーを提供していて、その中で発生するイベントを扱う事に関してであれば自分の裁量で好きなようにAPIを提供できます。 なので、WE版MTHは当初は「TSTに完全に依存したアドオン」として開発を始め、TSTにAPIを実装してはMTHでそれを使ってみるという形で両者の開発を並行して進めていたのでした(TSTのAPI整備とその動作検証のための体のいい実験台になってもらったとも言えます)。 その過程で、ツールバーボタンから開いたパネル内であればドラッグ操作でのタブの選択もできなくはないと思い立ったため、TST完全依存のアドオンではなく一応単体でもそれなりに機能するアドオンとしてリリースする方針に切り替えて不足部分を整備し、この度ようやくリリースに至った次第です。
そういう経緯なので、本来意図していたMTHのユーザー体験は、WE版においてはTSTとの併用時においてのみ実現されています。単体のWE版MTHはあくまで仮の姿で、TSTと併用した時にのみ真価が発揮されるとは、なんとも中二病くさい仕様ですね。
ところで、Firefoxにはずいぶん前から(具体的には8年前から)タブを複数選択する機能の実装提案がなされています。 この機能がいつまで経っても実装される様子がないので、渋々MTHを今までメンテナンスしてきた、という部分も実を言うとあるのですが、なんとここに来てそれなりの優先度で解決(実装)に向けて動き始めてきたようです。 もっと早くやってくれていれば、MTHはWE版を開発せずそのまま開発終了にできたかもしれなかったのに……
ともあれ、将来のどこかの時点でMTHがお役御免となるのなら、それに越したことはありません。 その日が来るまでの間は、できる限りメンテナンスを続けていこうと思っています。
以上、MTHのWE版リリースの裏事情でした。
Here is the English version of this article. このエントリはQiitaとのクロスポストです。
2017年の8月下旬に思い立って、ツリー型タブのWebExtensions版を作り始め、去る9月26日にバージョン2.0としてリリースしました。
重い腰を上げて取り組む気になれたのは、必須と目していたAPIが一通り実装されてきて、Firefox 57でようやく技術的に作れる目処が立ってきたからでした。 関係者の皆さんの尽力に改めて感謝の意を表明します。
やっている事自体はそう難しい話ではなく、技術的に目新しいトピックは無いのですが、主に歴史的資料としてレガシーなアドオンの移行の一事例の記録を残しておこうと思います。
I started to develop WebExtensions-based version of the Tree Style Tab at late August 2017, and released as the version 2.0 at 26th November.
The largest reason why I did it is: many numbers of new WebExteisons APIs I required are landed to Firefox 57. Thank you developers for their great effort.
There is no technical novelty topics, but I wrote this as a historical document: a migration story of a very legacy addon.
Firefox用アドオンは現在、Mozillaによる署名が必須となり、未署名のアドオン=Mozillaによる内容のチェックが行われていないアドオンはインストールできない、インストールしても有効化できなくなっています。
Firefox 47までのバージョンおよび法人向けの長期サポート版(ESR版)ではxpinstall.signatures.required
という隠し設定を使って未署名のアドオンを強制インストールできるようになっていましたが、この設定はFirefox 48で削除されました。
そのため、リリース前に自作アドオンを自分で使って試したい(ドッグフーディングしたい)人は、正式リリースされたFirefoxでアドオンを試す方法がなくなってしまっています。
この問題に対する救済措置として、上記隠し設定が利用可能な「ノーブランド版Firefox」がアドオン作者向けとして公開されています。 このエントリでは、ノーブランド版Firefoxの使い方と注意点をまとめておきます。
ノーブランド版と通常リリース版の違いは以下の点です。
xpinstall.signatures.required
をfalse
に設定すると、アドオンの署名チェックを無効化できる。Mozilla Wikiの、アドオンの署名に関する情報のページ内の「Unbranded Builds」という項目にインストーラのダウンロード用リンクがあります。 2016年8月3日現在ではリリース版Firefox 48.0相当の物へのリンクがありませんが、親ディレクトリから辿っていくとインストーラを入手できます。
このインストーラを実行すると、通常のFirefoxとは異なり、構成ファイル一式は「C:\Program Files (x86)\Mozilla Developer Preview」のようなフォルダにインストールされるようになっています。 インストールしたはずの物が見つからない!という時はインストール先を確認してみて下さい。 カスタムインストールすれば今までと同じ位置(「C:\Program Files (x86)\Mozilla Firefox」など)にインストールする事もできます。
xpinstall.signatures.required
を再設定してアドオンを有効化する。
今まで通常版のFirefoxでxpinstall.signatures.required
をfalse
にして使っていた人が、Firefox 48に自動アップデートされてアドオンが無効化されたという場合、「このアドオンは署名チェックに引っかかって無効化された」という情報がプロファイル内に保存されます。そのため、その後でノーブランド版を入れて起動しても、アドオンは無効化されたままとなります。
xpinstall.signatures.required
を1回true
に設定して、もう1回false
に設定し直すと、この情報がリセットされてアドオンが再び有効になります。自分はこれに気付いていなくてハマりました。browser.tabs.remote.autostart
=true
(これは設定ダイアログにあるチェックボックスに対応する項目)browser.tabs.remote.force-enable
=true
(アクセシビリティ機能によるe10s無効化を抑止)extensions.e10sBlocksEnabling
=false
(アドオンがあることによるe10s無効化を抑止extensions.e10sBlockedByAddons
=false
(同上)Recently I wrote a blog entry for developers of addons for Firefox, who are planning to migrate his/her XUL-based addon to WebExtensions. This is the full version which includes some side topics not related to WebExtensions. I hope this helps you to migrate your ancient addon to WebExtensions.
ツリー型タブの最近の改善でやったことをまた地味に解説するよ。誰得な記事。
ツリー型タブには、「ブックマークからタブを複数開いた時に1つのツリーになってた方がいいけど、でもツリーにするには親になるタブが必要で、だからといって同列のブックマークの中で1つだけが親になるのってなんか違和感あるよね」という場面のための「グループ化専用のダミーのタブ」を開く機能がある。というか、about:treestyletab-group
というURIを読み込んだタブはそういう用途のタブとして扱うようになる。そのときタブの内容が空っぽだと味気ないので、そのタブ配下のタブのツリーをタブの内容として表示するようになっている。
それとはまた別の話で、ツリーになっているタブの上でしばらく待つとそのタブ1つだけじゃなくて配下タブの名前も一緒にツリー表示して、さらにもう少し待つとそれらがリンクに切り替わってクリックしたらそのタブに切り替えられる、という機能もある。
見た目で分かるかもだけど、これらのツリーを生成する部分は共通のpseudoTreeBuilder(疑似ツリー)というモジュールになってる。
この疑似ツリーは、ちょっと前までのバージョンでは、タブの数が多くなるとそのままツリーが下に伸びていって、ウィンドウやツールチップの高さに収まらなくなった分はスクロールして見るという仕様になってた。 元々、おまけの機能だからそれほど作り込む必要性も感じてなかったんだけど、未処理のissueがたくさん溜まってたのを断捨離してたらその中に「このツリーをマルチカラム表示して欲しい」という要望があった事に気がついた。
このエントリを書くにあたって改めて調べ直してたら、別のもっと古いissueでは否定的なことを自分で言ってたみたいなんだけど、実際自分で使ってて「長いタイトルが折り返されるという事もなく巨大なツールチップが画面を覆い尽くして、そのくせちびちびスクロールしないと全項目は確認できない」という状況に意外とイライラしていたため、この機会に「じゃあやってみっか」と手を着けてみた。
で、最終的にはどうにかそれらしくマルチカラム表示できるようなった。
基本的にはCSSのマルチカラム機能を使ってるんだけど、ツールチップの方で僕の意図したとおりの結果を得るためにはちょっと工夫が必要だった。
「ツールチップの方で」と限定したのは、後で分かったんだけど、タブの中に表示する方はそこまで苦労しなくても勝手にいい感じにGeckoがレンダリングしてくれるから。 なので先にそっちの方から書いていきます。
先に要件を整理しよう。
まずコンテナとして、コンテンツ領域いっぱいに広がるボックスを置いておく。
コンテナには、内容が溢れたらスクロールできるようにoverflow:auto
も指定しておく。
#tree {
overflow: auto;
box-flex: 1;
-moz-box-flex: 1;
}
で、この中に置くツリーをマルチカラムにしたいわけなんだけど。
CSSのマルチカラム表示を有効にするだけなら、マルチカラムにしたい内容を含む上位の要素に列の数か幅を指定してやればいい。
2列なら、こう。この場合、列の幅はコンテナの半分になる(自動的に決定される)。
#tree > .treestyletab-pseudo-tree-root {
-moz-column-count: 2;
/* -moz-columns: 2 auto; と同等 */
}
列幅固定なら、こう。この場合、コンテナの中に1列しか収まらない時はマルチカラムにならないという、いわゆるレスポンシブデザイン的な挙動になる。
#tree > .treestyletab-pseudo-tree-root {
-moz-column-width: 20em;
/* -moz-columns: auto 20em; と同等 */
}
列数固定だとウィンドウの幅が広いときに余白ばかり広がってしまってあまり嬉しくないので、ツリー型タブではとりあえず主観で20em
と指定してみた。
自分の使ってる環境では、これでだいたい2~3列になる。
マルチカラム表示したいツリーを構成しているXUL要素の中で、内容が複数列に泣き別れてもいい物(=「1つのタブ」に対応する「アイコンとラベル文字列のペア」以外のすべてのボックス)は明示的にdisplay:block
にしないといけない。
display:-moz-box
のままだと、要素の作るボックスは途中でぶった切られる事が無いので、マルチカラム表示にはならない。
/* required to apply CSS multi columns */
#tree > .treestyletab-pseudo-tree-root,
#tree > .treestyletab-pseudo-tree-root vbox {
display: block;
}
当初はこれに気付いてなくて、わざわざXHTMLとXULを混ぜて実装してた。
でも、後になってからdisplay
の値を変えればXUL要素でも問題ない事が分かったので、今は全部XULに戻してある。
しかし、これだけだと項目の数が多い時に「縦に長いツリーがマルチカラムで表示される」「画面に収まらなかった部分は下にスクロールしないと見えない」ということになる。
こういうのは一覧性が悪くて死ねる。 だって、左の列を上から順に下まで見た後、次の項目を見るにはまた一番上までスクロールし直さなきゃいけないんですよ? あり得ないでしょ……
つまり、各列がコンテンツ領域の高さより高くなられると困る。 項目の数が多いときは、「縦に長ーいリストを2列で表示する」んじゃなくて、「コンテンツ領域の高さに収まる短いリストを3列、4列と列を増やして表示させる」方がいい。 ということ。
じゃあってんでさっきの要素に最大の高さを100%
と指定してみても、これは効果がなかった。相変わらず、親のボックスの大きさを超えてスクロール可能なまま。
#tree > .treestyletab-pseudo-tree-root {
-moz-columns: auto 20em;
height: 100%;
}
なので、resize
イベントでコンテナの高さを調べて、その都度ピクセル値で適切な高さを指定するようにしてみたら、ようやく縦のスクロールバーが消えて横方向にスクロールできるようになった。
...
this.window.addEventListener('resize', this, false);
...
handleEvent : function GT_handleEvent(aEvent)
{
switch (aEvent.type)
{
...
case 'resize':
return this.onResize();
...
}
},
...
onResize : function GT_onResize()
{
...
var container = this.document.getElementById('tree');
var tree = container.firstChild;
PseudoTreeBuilder.columnizeTree(tree); // ここでcontainerの高さをtreeのheightにピクセル値で指定し直す。
},
CSSのマルチカラムでは「列の数」の指定は絶対ではなくて、コンテナの幅と高さから溢れた分の内容は自動的に列の数を増やして表示するようになってる。
コンテナがoverflow:auto
になっていれば、溢れた分は横スクロールすれば見える。
全体の高さを指定していても、項目の数が「全部1列で表示するには多いが、2列を埋めるには足りない」程度の時には、領域の下の方に妙に余白が出てしまう。
これは-moz-column-fill:balance
という初期値のせい。この指定の時には、各列の内容の高さがなるべく揃うようにレイアウトされるため、結果として下の方に余白が産まれることになる。
まぁそれはそんなに悪くはないんだけど、今までコンテンツ領域の高さいっぱいまで並んでた物が急にその半分とかくらいの高さになるのは気持ち悪い。 違和感が無いのは、やはり、領域の高さに収まらなくなった分からちょっとずつ次のカラムに流し込まれていくという感じだろう。
そのための指定が-moz-column-fill:auto
で、これを指定しておくと、1列目は下まで項目が埋まって、入りきらなかった分だけが2列目に溢れるようになる。
実は、この-moz-column-fill:auto
を使うのにも、ボックス全体の高さを明示的に制限しておく必要がある。
というのも、-moz-column-fill:auto
の時は「高さが指定の高さに収まらなかったときに、その分を次のカラムに流す」という事になるので、高さが未指定だと「内容が溢れることもないからいつまで経ってもマルチカラムにはならず、縦にどんどんリストが伸びていく」という結果になってしまう。
……というわけなので、やっぱり全体の高さはスクリプトで動的にピクセル値で指定してやらないといけないのでした。
こちらも要件を整理する。
ここで曲者なのが3つ目の要件。 コンテンツ領域の中であれば「与えられた領域の中を最大限使って、残りは余白扱いにすればいい」ということになるんだけど、ツールチップだとそうもいかない。 というか、必要最小限のサイズで出てくれないと困る。 「内容を表示するのに必要な最小の大きさをどうやって決めるのか?」 これがツールチップの場合の難しい所だ。
先に結論を言ってしまうと、「とりあえず許されてる最大の大きさで試しにレンダリングしてみて、その後、実際に必要な大きさで改めて表示し直す」というのが答えになる。
とはいうものの、ツールチップを1回表示した後そこから小さく縮めるというのは見た目が宜しくない(一瞬だけとはいえ目に見える状態で大きなツールチップが出るというのはイラつきますよね)。
じゃあどうすれば?という事になるんだけど、逆転の発想というかなんというか、ツリー型タブでは「そもそも最初からベストのサイズで表示する事は諦める」という解決策をとっている。 どういう事かというと、いきなり違うサイズのツールチップを出すとギャップで見当識を失うので、まずは元の普通のツールチップと同じ大きさ・位置で1回表示して、そこからアニメーションでツールチップを必要な大きさまで拡大する、という事をしてる。
ツリー型タブの高機能なツールチップはだいたい元のツールチップよりは大きくなるので、この「1回小さめのサイズで表示した」状態で必要なサイズを確定させて、後からゆっくり拡大すればいい。
元より小さなツールチップで何故「最大の大きさで試しにレンダリングしてみる」ができるのかというと、XULのarrowscrollbox要素を使ってるから。 これは元々は、ツールチップが画面いっぱいに広がっていてもまだ内容が収まりきらない時のための対策として使っていた。 arrowscrollboxは中にどんなに大きな物を置いてもそれ自体やその親(ツールチップ)の大きさが広がらないので、安心して「めいっぱい大きく表示して、最小限必要なサイズをその結果から割り出す」ということができる。
「幅も高さもいい感じにする」というのは難しいので、ツリー型タブの場合はまず「画面の全体を覆い尽くさない程度」ということで画面の高さの70%(例えば画面が縦1024ピクセルなら、その7割の717ピクセル)を最大の高さとして、その中でマルチカラム表示の指定を反映させている。
arrowscrollbox > .treestyletab-pseudo-tree-root {
-moz-columns: auto 20em;
-moz-column-fill: auto;
height: 717px; /* ←実際にはその都度計算する */
max-height: 717px; /* ←実際にはその都度計算する */
}
こうすると、高さについては指定の高さまでに収まって、幅は必要な分だけ自動的に列が増えていくという形で、ツリー全体を収めるのに必要な領域を確定する事ができる。
高さは既に分かっているので、残るは幅なんだけど、これはツリー自体のboxObject.width
やclientWidth
からは分からない。
というのも、この時点でのマルチカラム表示されたツリーは「列の幅は指定されているが列の数は指定されていないので、要素自身の幅を超えて列が表示されている(可能性がある)」という状態で、要素自体の幅を調べても意味がないのです。
overflow:auto
になってる要素の幅を計測しても中身の幅は分からない、というのと同じね。
こういう時の「本来の内容の幅」は、RangeのgetBoundingClientRect()
で調べられる。
これは、DOMのRangeが含んでいる範囲の全要素が収まる矩形の位置や大きさを計測してくれるという便利なAPI。
これを使ってツリーの要素の「内容を」選択してそのサイズを測れば、ツリー全体を収めるのに本当に必要な最小のサイズが分かる。
var range = tree.ownerDocument.createRange();
range.selectNodeContents(tree);
var rect = range.getBoundingClientRect();
range.detach();
あとは、ツールチップ全体を広げるだけ。 「ツリー全体を収めるのに必要な幅」と「画面の幅さの80%(例えば画面が横1280ピクセルなら、その8割の1024ピクセル)」のどちらか小さい方をツールチップの新しい幅にしてやる。 ツリーはツールチップの中のarrowscrollbox要素の中に置かれているので、ツールチップがツリーよりも小さければ、はみ出た部分はスクロールして見る事ができる。
以上、マルチカラムなポップアップをいい感じに表示するためのノウハウの話でした。
実は、ここに書いた内容はmasterのHEADでの話で、解説を書くにあたって調べ直した結果の洗練された内容になってる。 今リリースしてるバージョンでは、そこまで洗練されてない状態の(でもだいたい同じような結果を得られている)コードになってるので、どこが無駄だったのかを洗い出して「こいつ技術力低いなあ!」と笑いものにしてくれていいです。
それにしても、昔なつかしNetscape Communicator 4では、フォルダの中にブックマークが大量にあるときはこんな風に段組表示してくれて一覧性が高くてものすごく便利だったんだけど、Geckoエンジンベースで作り直されたNetscape 6以降や、その流れの先にあるFirefoxには、その機能はついぞ引き継がれなかった。 業を煮やして複数のポップアップを並べることで擬似的に再現できないか?という実験をしてみたこともあったんだけど、不安定でダメだった。
今だったら、このやり方で行けるんじゃないか?という気がする。 というか、SUMOの質問に寄せられてる回答でStylish用のスタイルシートが公開されてて、2つの例のうち1つはCSSマルチカラムでやるようになってたので、間違いなくできる。
ただ、例に挙がってるStylish用のスタイルシートはカラム数が固定されていて、1列でいい時にまで常に2列3列になってしまうという問題がある。 ネスケ4の頃のそれのような使い勝手を実現するには、ここで解説したような細かい調整をやらないといけないんだと思う。 僕自身はやる元気がないので、誰か代わりにやってくれないかなあ……と、実現する見込みの低そうな事をネットの片隅でこっそり呟いてみる次第です。
ツリー型タブではタブバーに表示するスクロールバーについて、普通のスクロールバーよりも細く表示するようにしてるんだけど、その実装方法に少し改善があったので誰得だけど解説しておく。
ちょっと前までのバージョンでは、単純にCSSのmax-width
で以下のように実現していた。
tabs.tabbrowser-tabs
.tabbrowser-arrowscrollbox
> scrollbox
> scrollbar[orient="vertical"],
tabs.tabbrowser-tabs
.tabbrowser-arrowscrollbox
> scrollbox
> scrollbar[orient="vertical"] * {
max-width: 10px;
min-width: 10px; /* この指定がないと逆に最小サイズが大きくなってしまう */
}
tabs.tabbrowser-tabs
.tabbrowser-arrowscrollbox
> scrollbox
> scrollbar[orient="vertical"] {
font-size: 10px;
}
この方法の難点は、一部のプラットフォームで……というかWindowsでスクロールバーの端のボタンの表示がずれるということ。 スクリーンショットを見ると、「▼」がちょっと右にずれてるのが分かる。 何故こうなるかというと、Windowsのスクロールバーの端のボタンのアイコンは最小サイズが大きめに定義されているようで、それよりも小さなサイズを指定してもWindows側の最小サイズが優先されてしまうせい。
この問題は、実用上は問題がないので長らく放置してたんだけど、最近ツリー型タブのissue trackerで閉じられてないままの古いissueを断捨離してる時にもっとこうしたらいいよという提案があったことに今頃気がついて、その方針を最近のバージョンで採用してみた。
これはどうやってるのかというと、スクロールバーの幅を小さくする代わりに、「スクロールバーの左右にマイナスのマージンを設定して他の要素の下に潜り込ませる」ことで、擬似的に細く見せている。
何ピクセル潜り込ませるかは環境によって変わってくるので、document.getComputedStyle()
なども使ってその都度計算するという面倒なことをしている。
これで、「▼」がずれるということがなくなった。
しかし、今度は副作用としてタブバーの背景色を変えている時に表示が変になるという問題が起こってしまった。 例えばClassic Theme Restorerをインストールして且つ「Mixed」スキンを選択した状態だと、水色の背景の一部がグレーになってしまってた。 これは、先の「マイナスのマージンを設定して他の要素の下に潜り込ませる」という手法において、スクロールバーが潜り込む先となる(スクロールバーの上に載って一部を覆い隠す)ボックスの背景色の指定が必要となってしまって、決め打ちで指定した色がユーザのカスタマイズ後の色と違うせいで見えてしまっているということ。 タブバーの背景色を変える方法はuserChrome.cssやらテーマやらClassic Theme Restorerやら色々とあるので、すべての場合にマッチする万能の対策は事実上無い。
ということで頭抱えてしまって、なんとかスクロールバーの潜り込んだ部分(はみ出た部分)を綺麗に消す方法はないかと、思いついた方法を色々試してみた。
max-width
でちゃんと細くできてたんだし)と思って試してみたら、ボタン部分だけがスクロールバーからはみ出すという結果になってしまった。
overflow-x:hidden
だな!と思って、スクロールバー全体にこれを指定してはみ出したボタンだけをどうにかしようと思ったけど、実際やってみたらスクロールバー全体の動作がぶっ壊れてしまうので駄目だった。
万策尽きてDOMインスペクタの画面とにらめっこしてた所、clip
という名前が見えた。そういえばCSSにはそういう機能もあるんだった。試したこと無かったから、じゃあそれで行けるんじゃないか?
ということで実際試してみたんだけど、これもスクロールバーのボタンには効いてくれなかった。
でも、MDNのプロパティ解説を見るとdeprecatedと書いてあって、誘導先を見たらclip-pathを使えと書いてある。
clip-path
というとFirefoxのタブの見た目などを丸くするのに実際使われてるプロパティだったはずなので、こっちなら大丈夫だろうと思って再挑戦したら、ようやくうまくいった。
Classic Theme Restorerとの併用時にも何ら問題がないことが見て取れる。
実装は以下のようになってる。
tabs.tabbrowser-tabs
.tabbrowser-arrowscrollbox
> scrollbox
> scrollbar[orient="vertical"] {
font-size: 10px;
max-width: 10px;
min-width: 10px;
clip-path: url(#treestyletab-box-clip-path);
}
tabs.tabbrowser-tabs
.tabbrowser-arrowscrollbox
> scrollbox
> scrollbar[orient="vertical"] * {
font-size: 10px;
max-width: 100%;
min-width: 10px;
}
tabs.tabbrowser-tabs
.tabbrowser-arrowscrollbox
> scrollbox
> scrollbar[orient="vertical"] scrollbarbutton {
font-size: 10px;
margin-left: -3px;
margin-right: -2px;
}
参照してるSVGのクリッピングパスは以下の通り。
<svg:svg height="0">
<svg:clipPath id="treestyletab-box-clip-path"
clipPathUnits="objectBoundingBox">
<svg:path d="m0,0 V1 H1 V-1 H-1 z"/>
</svg:clipPath>
</svg:svg>
このクリッピングパスはどういう形かというと、仮想的なキャンバス全体を覆い尽くす矩形になってる。m
から始まる相対指定で(0,0)
から(1,1)
までを覆う矩形を作っている(左上から始めて、下、右、上、左と反時計回りに進んでパスを閉じてる。最初は逆方向の時計回りに回ってしまってうまくいかなかった)ので、クリッピングパスの適用対象になるボックスがどんなサイズであってもそれと同じ大きさになる。
スクロールバーに対してこのクリッピングパスを反映すると、この矩形からはみ出る部分、つまりネガティブマージンではみ出してるボタンが切り取られて表示されるようになる、というわけ。
今回の実装自体のノウハウをそのまま流用できる場面はそうそうなさそうだけど、なんかの機会にまた使うかもしれないので、解説してみた。
要点をまとめると、「要素のスクロールの可否といった動作に影響を与えないで、見た目の表示サイズだけを小さくしたい時は、clip-path
を使うとよい」といったところでしょうか。
以上、ちっこい三角ひとつのためだけに四苦八苦させられた話なのでした。
De-coupling Reviews from Signing Unlisted Add-ons | Mozilla Add-ons Blogの私的な翻訳です。
長いので3行で説明 – 今週中(2015年12月4日)までに、私達は非公開のアドオンに対する署名を完全に自動化し、人力レビューを発生させないようにすることを考えています。
ここ数日、アドオンの署名手続きの最初のステップについての議論がありました。「バリデータ(検証器)」として知られる一連のコードによる、アドオン登録時の機械的レビューの改善についてです。バリデータは登録されるアドオンについて様々な理由から人力のレビューを喚起し、署名手続きを中断させます。これはアドオンのリリースを遅らせることに繋がります。なぜなら、署名を要求するという要件がFirefox 43以降のバージョンでは強制されるからです。
過去には、バリデータの有用性について議論が持ち上がったこともありました。悪意ある開発者であればバリデータによる検証を回避するコードを書くことができるからです。私達は、バリデータのできることには限界があることを承知しています。現実には、あくまで既知の危険なコードに付いてしか検出できず、それでは対応できない未知の危険なコードはたくさんあります。しかし、バリデータはレビューの過程の1要素に過ぎず、私達は開発者がアドオンをより提供しやすく、それを使う人達がより安全になるようにしたいと願っています。私達はバリデータについて、完全なマルウェア検出機構として動作することを意図しておらず、むしろ、Firefoxユーザにより適切な形でアドオンを届けられるように開発者を手助けすることを意図しています。
このことを考慮して、私達は非公開のアドオンに対する関門としてのバリデーションを取り除こうとしています。私達は開発者が非公開のアドオンを提供しやすいようにしたいと考えており、従ってレビューは署名手続きとは独立して行っていくことになります。今週末(2015年12月4日)、私達は非公開のアドオンに対する署名を完全に自動化し、人力レビューを発生させないようにすることを考えています。この日付は不確定な物で、これを可能にするために必要な技術的・手続き的・ポリシー的な変更を我々がどれだけ早くできるかに依ります。今月の初めに導入されたアドオンの署名APIは、署名手続きの完全な自動化が可能となり、この件についての解決策の一部となるでしょう。
私達は、アドオン開発者の方々には引き続き、MDNで概説されているFirefoxアドオンのポリシーへの同意を求め、署名のための登録に先立ってそれらのポリシーに自身のアドオンが違反していないかを確認することをお願いしていきます。開発者の方々はアドオンのレビュアー向けガイド(アドオンがレビューを通過しなかったりブロックリストに入れられてしまったりする主な理由を概説しています)の内容も把握するようにして下さい。
私は先週を通じて得られたそれらの情報と洞察をもたらしてくれたすべての人に感謝しています。私達はアドオン開発者やユーザの方々にとってFirefox上での体験を可能な限り痛みを伴わない物にしていきたいと思っています。また、「生き辛い」やり方であるように思われる場合があったとしても、そのような生き辛さは、私達の目標には絶対に含まれません。どうか、私や他のチームメンバーに直接、気軽に意見を言い続けて下さい。
私はそれらを可能にするための次のステップのより具体的な概要を投稿するつもりで、その進捗はbug 1229197で見ることができます。前もって、あなたの我慢強さに感謝します。
kev(訳注:Kev Needham、Mozilla Corporationの雇用スタッフ。)
ということなのですが、これはあくまで非公開(unlisted)のアドオンに限定した話のようで、公開(listed)のアドオンについてはこの限りではない模様です。実際、先日新バージョンを公開しようとした公開のアドオンでは人力レビューを経なければ署名は得られませんでした。
このエントリ中では触れられていませんが、1229197 – Allow unlisted add-ons to be signed without passing the validatorでは「非公開アドオンなのであればサイドローディングの可否に依らず署名手続きにレビューが不要なようにする」という話になっているようで、実際に行われた変更でも確かにそのようになっています。
なお、このエントリでは、「アドオンが人力レビューを通過できないのでいつまで経っても署名を得ることができず、テストも配布もできない」という状況が発生していた事について、「人力レビューの通過を署名の必須要件に含めなくした」という事が述べられていますが、その後の人力レビュー自体については言及されていません。 なので、Mozillaのポリシー的に許容され得ない危険なアドオン(例えば、リンク先のローカルファイルのショートカットを自動で開くような物)については、仮にバリデータ(Linterに名前が変わったようですが)での機械的チェックを通過して署名を得られたとしても、その後のエディターによる判断で「これはマルウェア的だからやっぱりブロックリストに入れよう」とされてしまう可能性は依然としてある、と思っておいた方が良さそう……というのが今のところの自分の認識です。
ちなみに、第一報からだいぶ時間が過ぎた今頃になって何で訳してるの? と思われそうですが、それまでは「マルウェアなどの危ないアドオンを排除したいから署名義務化します、危険なアドオンはレビューで弾くから安全です」と説明されていたのが、ここに来ての「機械的チェックで問題無ければ原則署名します、危険なアドオンでもレビュー無しで署名します」への方針転換だったので、そりゃちゃぶ台返しすぎないか?!と驚いて話を鵜呑みにできずにいたからです。
しかし実際に読んでみた限りでは、確かに非公開のアドオンについては署名を完全に自動で得られるようになったものの、その後の人力レビューで弾かれる可能性は否定されていませんでした。「先に全部閉め出しておいて安全な物だけ通す」から「先に全部通しておいて危険な物だけ潰す」への方針転換だったという事になるので、タイミングはずれはするものの危険な物はやはり許容されず、一般ユーザとしては安心して使えて、開発者は依然としてレビューを通過できないことの恐怖に怯えなくてはならないという、大枠の所は変わらないようでした。やれやれ。
具体的にアドオンを作る・使う側の人間はどう対処すれば良いのか、というのを自分の理解でまとめてみる。判断のソースは原文のコメント欄での質問と回答で示されている情報です。
現在AMOでのアドオンの公開に際してはPreliminary ReviewとFull Reviewの2段階のレビューがあり、Preliminary Reviewを通過できればサイト上に掲載され、その上でさらにFull Reviewを通過できれば検索結果にヒットしたり一覧に表示されたりするようになる、という感じなんだけど、Preliminary Reviewではセキュリティ上の重大な脅威が無いならとりあえずは通過できる事が多い。しかし、Firefoxのセキュリティ機構をバイパスするためのアドオン、例えばThunderbirdでメールに添付されたWindowsショートカットを直接実行できるようにするアドオンのような物は、どれだけ多くのユーザが切望していても、例え顧客企業で必要とされていても、レビューを通過できない。今回の件の記事のコメント欄でも、署名チェック機構をバイパスするようなアドオンは審査を通過できない(=XPIに署名して貰えない=インストールは許可されない)という事が明言されている。
「公表できない物」っていうのは、例えばクリティカルな情報を含んでいるとか、組織内利用専用とか、そういうこと。なぜ公表できるかどうかが焦点になるのかというと、AMOのサイト上で一般向けに公開されないとしても、インターネット上のサービスにパスワードもかけずにファイルをアップロードし、どこの誰かも分からないボランティアのスタッフに自由にソースコードを見られる、という事を許容できるかどうかという話になるから。
企業などの組織内で使うためのアドオンについては「第3の選択肢」を用意するという事になっているようだけれども、具体的な詳細が公表されていないので、現状では最悪のケースも想定しておいた方がいいんじゃないかって気がする。例えば「特別なパートナーシップ契約を結んで、非公開でレビューを受けられるようにする」みたいな話だったとして、Mozillaにとって特別なパートナーシップ契約を結ぶだけのメリットを感じられない規模の組織は、詰んでしまうことになるので。
正道はやはり、クリティカルな情報を含まなくていい形で公開できるアドオンとして開発しておき、クリティカルな情報はMCDなり何なりで後から反映できるようにしておく、という事だとは思うんですけどね。
率直な感想としては、「ウチの製品をユーザがどう使えるかはウチが決めますよ、使い手のあなたたちに使い方を決める権利はありませんよ」「ウチの製品の上で使いたいならそれなりの質の物じゃないと許しませんよ」って言ってるような印象で、tivoizationと似た感じのニオイを感じられてまったくウンザリする話だ、って感じではあります(現状でも、Firefox OS向けのアプリは既にそうだったと記憶してる)。ある程度普及して、「Mozillaという組織の名前を冠した信頼と実績のFirefoxというブランド」が一定の価値を持ち、自分がやろうとしている事のリスクもよくわからないままに致命的な操作をしてしまいかねない層のユーザの数が無視できないレベルに達しており、そしてそれを狙った悪質な攻撃が増加している、という前提に基づくと、やむを得ない判断だと理解はできるんですが。だから怨むべきは、Mozillaではなく、面白半分だったり悪意だったりで人に迷惑かけてる黒アドオン作者の方。
まあ、Firefoxの名前とブランドロゴを外したビルドを使う分には関知しないという抜け道は残してくれているようなので、そこがMozillaの良心だと思ってます。
Firefoxのマルチプロセス設計への移行がいよいよ目前に迫っている。
Firefoxにおけるマルチプロセス化のための仕組みそのものはElectrolysis、略してe10sと呼ばれており、Firefox 4の頃から既に入っていて、Firefox Hacks Rebootedでも詳しく解説してたんだけど、ブラウザのUIを動かすプロセスとコンテンツ領域のプロセスを分けるというのはFirefoxではかなりの大ごとだった。一時は「こんなん無理!」っつって計画が凍結されてたほどだったと記憶してる。
e10s移行が困難だった理由は、多分、Firefoxというアプリケーションそのものの設計にあったのだと思う。FirefoxというWebブラウザは、「GeckoというHTML・XMLレンダリングエンジンの上で動作するJavaScript製のローカルWebアプリケーション」といった感じの設計になっている。なのでコンテンツ領域内の操作のためのコードとUI部分の操作のためのコードが非常に似通っていて、シームレスに連携しやすかった、否、「連携しやすすぎた」と言える。あまりにシームレスに連携できたせいで、ブラウザのUIに関わるコードとコンテンツ領域内の操作に関わるコードが渾然一体の密結合になってしまい、それが「UI部分とコンテンツ領域内との間はテキストメッセージだけを非同期に通信しあう」というe10sベースの設計への移行を阻んでいた、という事なのではないか……と僕は思っている。
どういう理由があったのかは知らないけど、最近のバージョンのFirefoxではe10sベースの設計への本格的な以降のための準備が着々と進められている。上記のような密結合だった部分が「UI領域専用」「コンテンツ領域専用」「両方で共通」といった感じに分離されてきているし、同期処理前提だった部分が非同期処理前提に改められている。 セッション保存の仕組み(ページ内のテキストボックスへの入力内容を保存する所とか)、リンクのクリック操作のハンドリング、その他多数の部分が大幅に書き直されている。アドオンから見た時のAPI的な互換性は可能な限り残すようにしてあるようで、その努力の様子が非常に興味深い。
で、アドオン側での対応をどう進めるかなんだけど、実際にいくつかのアドオンをe10s対応に改修してみて、アドオンの性質によって典型的なパターンがあるようだという事が分かった。
browser
のcontentWindow
やcontentDocument
に一切触れず、コンテンツ領域内からBubblingしてくるイベントも捕捉しないタイプのアドオンは、何も考えなくてもそのままe10sで動く。var linkURIs = Array.map(gBrowser.contentDocument.links, function(aLink) { return aLink.href; });
と書けたけれども、こういう事ができなくなる。
UI領域とコンテンツ領域の境界をまたぐ時に非同期処理を挟まないといけないので、処理を「UI領域からコンテンツ領域へ、リンクを収集する指示のメッセージを送る」「コンテンツ領域で指示のメッセージを受け取って、リンクを収集し、UI領域へ結果を報告するメッセージを送る」「UI領域で報告のメッセージを受け取って、次の処理を行う」という3つの段階に分けなくてはならない。
マルチプルタブハンドラの場合、「選択したタブの情報をクリップボードにコピーする」という機能のために、タブ(で開いているページ)の情報からクリップボードにコピーするための文字列を得る処理をコンテンツ領域側に移動した上で、UI領域からコンテンツ領域へ・コンテンツ領域からUI領域への橋渡しを行うためのコードを追加し、前後の処理をPromiseで繋ぐようにした。上記の5パターンの最初の方の物ほど実装が容易で、後の方の物ほど実装が面倒になる(その上、メッセージの往復を待たないといけないのでオーバーヘッドも大きくなる)。 特に深い意味もなく後の方のパターンで実装していた機能は、どうにかして前の方のパターンで実装できないか検討した方がいい(Firefox本体の設計変更も、おそらくそういう風に進められたのではないかと思う)。 実際に、情報化タブではWebページのサムネイル画像を取得する処理について、元々はUI領域で「サムネイルが必要だ」となったタイミングでその都度同期処理していたのだけれども、まずサムネイル取得の処理はコンテンツ領域に移し、処理が走るタイミングについて、コンテンツ領域側で発生したイベントをトリガーとしてサムネイル画像をUI領域側にpushで送りつけるように改めた。 これは上のリストで言うと、5番目のパターンから4番目のパターンに設計変更した、ということになる。
それでもどうしても5番目のパターンで実装しないといけないケースというのはあって、前後の処理とどうやって繋ぐか(同期処理だった物をどう非同期化するか)というのが問題になる。 自分の場合は、こういう時はPromiseを使うのがいいと思ってる(以前ならJSDeferredを使ってたんだけど、Mozilla Add-onsのレビューが通らないため、最近になってPromise.jsmのES6 Promise互換APIを使うようになった)。 ツリー型タブでも、コンテンツ領域内にプラグイン(Flashなど)で描画されている領域があるかどうかを調べるための処理で、このパターンが残っている。 Promiseを使うコードはUI領域側のブリッジにまとめてあり、こんな感じになってる。
sendAsyncCommand : function CB_sendAsyncCommand(aCommandType, aCommandParams)
{
var manager = this.mTab.linkedBrowser.messageManager;
manager.sendAsyncMessage(this.MESSAGE_TYPE, {
command : aCommandType,
params : aCommandParams || {}
});
},
checkPluginAreaExistence : function CB_checkPluginAreaExistence()
{
return new Promise((function(aResolve, aReject) {
var id = Date.now() + '-' + Math.floor(Math.random() * 65000);
this.sendAsyncCommand(this.COMMAND_REQUEST_PLUGIN_AREA_EXISTENCE, {
id : id
});
return this.checkPluginAreaExistenceResolvers[id] = aResolve;
}).bind(this));
},
handleMessage : function CB_handleMessage(aMessage)
{
// dump(JSON.stringify(aMessage.json)+'\n');
switch (aMessage.json.command)
{
...
case this.COMMAND_REPORT_PLUGIN_AREA_EXISTENCE:
var id = aMessage.json.id;
if (id in this.checkPluginAreaExistenceResolvers) {
let resolver = this.checkPluginAreaExistenceResolvers[id];
delete this.checkPluginAreaExistenceResolvers[id];
resolver(aMessage.json.existence);
}
return;
}
},
checkPluginAreaExistence
メソッドは、その実行を示すユニークなidを伴ってコンテンツ領域に指示のメッセージを送ると同時に、新たに生成したPromiseのリゾルバ関数を、idと対応付けて保持する。メソッドの戻り値はPromiseとする。という風にする事で、checkPluginAreaExistence
メソッドをthenableなメソッドとして他の処理の中に無理なく組み込める(次のコールバックには、コンテンツ領域側での処理で得られた結果が渡る)ようになってる。
UI領域→コンテンツ領域→UI領域 と境界を2回以上またぐ処理を実装する時は、多少の差異はあれどだいたいこんな感じになるんじゃないだろうか。
必要かどうかで言うと、このパターンでPromiseは必須ではなくて、Promiseのリゾルバ関数を保持・実行する代わりに、単にcheckPluginAreaExistence
メソッドでコールバック関数を受け取って、それを保持・実行するようにしてもいいんだけど。
非同期の処理同士を何度も書き連ねる時のコールバック地獄は見たくない(今はそういう連携をする必要がないとしても、今後いつ必要になるとも限らない)ので、僕は自分で書く非同期処理は、インターフェースとしては基本的にPromiseを使う方向で統一するように考えてる。