Kanasan.JS JSDeferred コードリーディング ― 2009年06月30日 23時55分
すっかり記事を書くのが遅れてしまいましたが、Kanasan.JS JSDeferred コードリーディングへ行ってきました (参加者ブログ記事一覧)。JSDeferred は cho45 さん作の、非同期処理を簡単に記述するためのライブラリです。簡単なリファレンスもありますが、実際に動かせるサンプルのほうがどんなものか感覚をつかめると思います。
Deferred オブジェクト
JSDeferred では、ひとつの処理をひとつの Deferred オブジェクトとして表現し、処理の流れは Deferred オブジェクトを順につなげた Deferred チェーンで表します。Deferred オブジェクトは三つのプロパティを持ちます。
callback.ok
プロパティ- (正常) 処理の本体を表す関数。
callback.ng
プロパティ- 例外処理の本体を表す関数。
_next
プロパティ- Deferred チェーン上の次の処理 (継続) を表す Deferred オブジェクト。
さらに、Deferred オブジェクトの実行状態を意識すると、JSDeferred を使って書かれたコードが読みやすくなるのではないかと思います。
- 待機 (idle) 状態
- 処理が実行されない状態。
call
/fail
メソッドが呼び出されると実行中状態へ移る。 - 実行待ち (waiting) 状態
- 処理がそのうち実行される状態。
cancel
メソッドが呼び出されると待機状態へ移る。 - 実行中 (running) 状態
- 正常処理または例外処理が実行されている状態。処理本体の実行が終了したら待機状態へ移る。このとき、処理本体の返り値が Deferred オブジェクトなら、その Deferred オブジェクトの継続 (
_next
プロパティ) を自身の継続と同じにする。そうでなければ、自身の継続たる Deferred オブジェクトを実行する (実行中状態へ移す)。
わかったこと、注意すべきこと
- 単に
new Deferred()
またはDeferred()
とすると、待機状態の Deferred オブジェクトが作られる。 - 処理本体で Deferred オブジェクトが返されたなら、自身の継続を (直接は) 実行しない。
parallel
関数は飛ばして、先にnext
関数、wait
関数を読んだほうがよさそう。next
関数 (Deferred.next
) とnext
メソッド (Deferred.prototype.next
) は別物。next
関数は、実行待ち状態の Deferred オブジェクトを新規作成する。引数に渡された関数がその Deferred オブジェクトの処理本体となる。next
メソッドは、待機状態の Deferred オブジェクトを新規作成し、それを自身の継続とする。引数に渡された関数がその Deferred オブジェクトの処理本体となる。自身の実行状態は変化しない。
next
関数については、とりあえずnext_default
だけを見れば十分。next_faster_way_readystatechange
、next_faster_way_Image
は、特定の環境用に高速化を施した版。詳細は「JSDeferred を高速化する (試し中) - 冬通りに消え行く制服ガールは、夢物語にリアルを求めない。 - subtech」を参考に。
- 他の記事では、「JSDeferredがやっとわかった - by edvakf in hatena」が詳細でわかりやすい。
Deferred チェーンの図示
next(function f() {
...
return next(function g() {
...
});
}).
next(function h() {
...
});
は、最初に次のような Deferred チェーンを作ります。
ここで、Deferred チェーン先頭の Deferred オブジェクトの処理が終了し、f
が Deferred オブジェクトを返すと、Deferred チェーンは次のようになります。
h
(を処理本体とする Deferred オブジェクト) は f
(を処理本体とする Deferred オブジェクト) の継続として実行されるわけではありませんが、g
(を処理本体とする Deferred オブジェクト) の継続として実行されるので、結局 f
、g
、h
の順番で処理が進むことになります。
また、f
で待機状態の Deferred オブジェクトを返せば、処理の流れをいったん f
で止めておき、明示的にその Deferred オブジェクトの call
メソッドを呼び出したときに h
から処理の流れを再開するといったことができます。
Web 開発者の責任 (翻訳) ― 2009年05月06日 19時31分
John Resig 氏による A Web Developer's Responsibility という記事が素晴しかったので、著者の許可を得てここに日本語訳を掲載します。
Web 開発者の最大の負担は、ブラウザのバグと非互換性への対応に膨大な時間を費やすことであるといって間違いないでしょう。それゆえに、それらへの対応に不満をいうのは、Web 開発者全員の常となっていました。ブラウザのバグは迷惑でいらだたしく、仕事を大幅に難しくします。
ブラウザのバグはとてもいらだたしく、通常の開発における最大の負担です。ですから、開発対象のブラウザが、自身のバグを見つけ修正できるようにしてやるのは、すべての Web 開発者にとっての責任です。自分が見つけたバグに対して責任を持ち、「ほかの誰かがこれを見つけるだろう」とは思わないことで、ブラウザの進歩の速度は加速していくでしょう。
ブラウザを支援する解決策は 2 段階からなります。第 1 に、ブラウザのバグを見つけるたびに、個別のブラウザへバグ報告を登録しましょう。第 2 に、自分のサイトを主なブラウザの最新のビルドで積極的にテストしましょう。
Web 開発者の圧倒的多数は、一度もブラウザベンダにバグ報告を登録したことがないばかりか、ブラウザのナイトリー版を使ったことさえありません。これは恥じるべきことです。考えてみれば、ブラウザの何が異常かを評価するのに、ブラウザでの開発に日々を費やす人ほどその資格がある人というのはほとんどいないのです。
ブラウザのバグを登録せず、ナイトリーでのテストもしないプロの開発者を見たとき、私は特に驚きました。ほとんどの開発者の主な仕事のひとつに、クロスブラウザの問題を取り繕うことがあります。よって、バグの数を減らす (そして仕事を劇的に単純化する) ことは最優先事項になります。
私は個人的に全主要ブラウザベンダにバグ報告を登録してきて、良い報告を生み出すいくつかの特徴に気づきました。
良いバグ報告の登録方法
良いバグ報告を生み出す 3 つのポイントは、分類、テストケース、そして単純化です。正しく分類され、単純化されたテストケースが提供されたバグは、どんなものでもブラウザの開発者によるレビューが保証されます。
バグを登録する場所から始めましょう。
バグ報告を登録する
大抵、バグ報告を登録するときは、実際の提出フォームへたどり着く前に、いくつかの深い層を通り抜けなければいけません。最も利用すべきフォームの直接の URL を以下に示します。
バグを登録するときは、登録対象のブラウザの最新のナイトリー (後で説明します) でもテストしておきましょう。これは、ブラウザの開発者が最初に尋ねることのひとつです。もしそのブラウザの現在の開発版でも依然としてバグが存在し、いまだ修正されていないことを示せたのなら、ブラウザの開発者たちはずっと簡単に取り組み始められるでしょう。
注: 多くのバグ報告ページは、報告を提出する前にアカウントの作成を求めてきます。これは迷惑ですが負担は 1 回きりです。
バグを分類する
バグを適切に分類することは重要な第一歩です。しばしば、(レイアウトや DOM といった) 特定のモジュールのオーナーは、新しく来た提出をすべて監視しています。バグを適切な分類に割り当てることは、そのバグを修正するのにもっとも適した人物の眼前へ、直ちにバグを持ち込むことになります。
バグの分類はブラウザ次第です。(Opera や Internet Explorer のような) いくつかのブラウザがバグの登録に簡易的な分類を用意している一方で、他のブラウザ (WebKit/Safari や Mozilla/Firefox) は機能が存在する特定のモジュールを示すために複雑な分類を使います。
Mozilla/Firefox: コンポーネントを選択してください。最も一般的なのは DOM、Layout、JavaScript Engine などです。
WebKit/Safari: コンポーネントを選択してください。最も一般的なのは HTML DOM、Layout and Rendering、JavaScriptCore などです。
Google Chrome: Chrome に対して登録すべきか判断するのには注意が必要です。最初に Safari の最新リリースと最新の WebKit ナイトリーとの両方でバグをテストしてください。もしバグが Chrome のみに存在するのならそこに登録し、そうでなければ WebKit/Safari にバグを登録しましょう。しかし、もうひとつ問題があります。Chrome の JavaScript エンジン (V8) にのみ存在するバグは、(どさくさにまぎれて行方不明にならないよう) Chrome ではなく V8 のバグトラッカーに登録されるべきです。
また、Chrome は明示的な分類手段を提供していません。すべてのバグは開発者によりレビューされ、それから分類されます (このプロセスを制御することはできません)。
複数のプラットフォーム (OS X と Windows、Windows と Linux など) でのテストも手早く行うべきです。バグが複数のプラットフォームに存在することを特定すれば、ブラウザの開発者がバグの原因の発見に必要とする時間を削減するのに劇的に役立ちます。
テストケースを提供する
再現可能なテストケースは、どんな形式であれ何もないよりは優れています。問題が封入された Web ページは一般的に良い取っ掛かりとなります。もし Web ページをバグ報告に直接添付できるのなら、さらに良いでしょう (ブラウザの開発者がそのチケットに取り組むのには、しばらく時間がかかるかもしれません。指定された URL にテストケースがもはや存在しなければ、開発者は単にチケットを閉じてほかの作業へ移るでしょう)。
そうはいっても、悪いテストケースというべきものもあります。最悪なのは「http://example.com の Web サイトを見たら、ブラウザ X では動かなかったので、修正してください」といった類のものです。これは、失敗の正確な理由を見つけだすため、誰かに膨大な時間をとらせることになり、バグをキューのより後ろへと押し出すでしょう。
最良のテストケースは単純さを提供する類のものです。
単純さを提供する
単純なテストケースを提供することは、間違いなくバグ報告を作成する上で一番難しく、かついらだつ部分です。しかしこれは、大概報告に気づき修正してもらう上での重点でもあります。最も資質のある開発者でさえ、十分良いテストケースを作成するのに 30 分もかからないでしょう。
このようなテストケースを作成する手順は単純です。バグがあるページを持ってきて、バグの再現に影響しないものをすべて切り取ってしまうのです。これは、スタイルシート、画像、JavaScript ファイル、JavaScript ライブラリ、そして HTML を含みます。
たとえば、しばらく前に私が Dromaeo テストスイートを走らせていたとき、WebKit がある地点に到達すると常にクラッシュすることに気づきました。私はまず、不必要な HTML、CSS、画像などテストを切り取ることから始めました。最終的に私は単独のテストにたどり着きました。文字列の分割です。それから私は、外部依存が必要なくなるように、可能な限りテストスイートを剥ぎ取っていきました。
最終結果に注目してください:
var str = "", ret, fn = [];
for ( var i = 0; i < 16384; i++ )
str += "a";
for ( var i = 16384; i <= 131072; i *= 2 ) (function(i){
fn.push(function(){
ret = str.split("");
});
str += str;
})();
window.onload = function(){
setInterval(function(){
if ( fn.length )
fn.shift()();
}, 13);
};
残ったものは、依然としてクラッシュを起こすにもかかわらず、これ以上ないほど単純です。この単純化に基づいてこの問題の理由はすばやく特定され、たった 2 週間後には解決されていました。
私のバグに取り組んでいますか?
これは興味深い点です。しかし、Mozilla/Firefox、WebKit/Safari、Chrome では (これらはすべて比較的開かれたプロジェクトなので) 判断しやすい点です。これら各ブラウザでバグの状態を判断する最良の方法を以下に示します。
Mozilla/Firefox: バグは、最初に選択したコンポーネント分類のデフォルトの窓口係に割り当てられるところから始まります。これはまだ何も意味せず、単純に人々の目をチケットにひきつけるだけです。人々はそのチケットに自分自身を CC し始めます (これはその人がそのバグの進行に興味を持つという意味です)。しかしながら、決定的な瞬間は、誰かがそのバグを自分自身に割り当てたときで、事実上その人がそのバグの状態に責任を持ち始めます。ほとんどの貢献者はそのバグにつけられるコメントが自動的にメールされるよう設定しているので、そのバグの状態に関して何かしら疑問があれば、遠慮せずコメントを投稿できます。しかし、そういったことは適度なペースで行ってください (日ごとに、あるいは週ごとにでさえ、更新状況を尋ねることは貢献者をいらだたせるでしょう)。
WebKit/Safari: WebKit は Mozilla/Firefox ととてもよく似た設定を使います。そのバグを管理し完了まで後押ししてくれる誰かをただ受け入れましょう。しかしながら、黄金のチケットはそのバグが「rdar へ行く」時です。Radar は Apple の内部的な (非公開の) バグトラッカーです。バグがそこへ移動したということは、事実上 (そのバグを「所有する」人によってでなくても、別の Apple の従業員によって) そのバグのある時点での完了が保証されるということです。Apple は依然として WebKit の更新の裏での大きな立役者なので、バグが rdar に移されるのは期待すべきことです。とはいっても、rdar は非公開で Apple の従業員向けなので、バグが完全に修正されるか、またそれはいつかを知るという恩恵はもはや得られません。そうなれば後は待つのみです。
Chrome: Chrome は Mozilla/Firefox ととてもよく似たシステムを使います。そのバグが現在割り当てられている人とのコミュニケーションを持続し、彼らが持つだろう質問には何でも答えるようにしましょう。
最終的にバグが解決されたのなら、おめでとうございます! あなたは Web を万人にとってよりよい場所にするのに役立てたのです。
しかし、いつもそうなるとは限りません。
私のバグが却下されたらどうなりますか?
却下されたバグは次の 2 通りに分類されます:
- バグではなかったので却下された。
- ブラウザベンダがそのバグに取り組みたくないので却下された。
1 番目はさらに 2 つの小分類に分けられます。
本当にバグでなかったのなら、おめでとうございます! 標準、あるいはそれ以外のブラウザのあいまいさに関する、今まで詳しくは知らなかったことを学べました。今やあなたはよりよい Web 開発者です! ブログへ向かい、発見したあいまいな新しいバグや API に関して書き、それを世界に向けて説明すべきです。
または、バグなのに所有者が不必要にバグを閉じてしまいました。こうなると、バグを再開するために、この状況について議論する必要があります。
2 番目 (ベンダーがバグに取り組みたくない場合) もまた 2 通りに処理されます。
第 1 に、バグの状況について議論しましょう。これはブラウザの開発者に、この問題を修正するために貴重なリソースをささげるべきという情報を与えるのに役立ちます。
または第 2 に、もし彼らが本当にそのバグの修正を嫌がっているのなら、ブログや Twitter アカウント、それに他の Web 開発者があなたの言うことを聞いている場所で厳しく叱責しましょう。もしあなたの立場に同意してくれる人を誰も見つけられなかったのなら、おそらくあなたがおかしいです。しかし、もしそれが、ブラウザベンダが修正を拒んではいるが正当な問題だったのなら、結束しおおっぴらに苦情を申し立てられるほかの人が簡単に見つかるはずです。まさにこれでいいのです。あなたはこの結果を正しく獲得しました。このバグを明るみに出すのに必要なすべての正当な努力をすることにより、すべての宣言でこれに関して不満を言うのに十分な特権を得るのです。
バグを議論する
ここで、あなたはバグが閉じられた時点にいるものとします。このとき、閉じた人にそれは間違いだ、すなわち、このバグは実際に正真正銘のバグである、と納得させる必要があります。
こうしたときに使える最良の論拠のいくつかを、使うべき順番に沿って以下に示します:
- そのバグが退行であることを示しましょう。それが以前のリリースでは動いており、変更のせいで動かなくなったことを証明しましょう。#2 と併用するととても効果的です。
- 実在の Web サイトが壊れていることを示しましょう。もし実際の利用者が、ポーランドの X 銀行やカナダの Y ショッピングサイトをもはや訪れようとしないことを示せれば、ブラウザはその問題を修正するためにあっさり全力を尽くすでしょう (Opera でない限り。その場合 Opera 側はサイトを強制的に動かすために browser.js を使うかもしれません。しかし、それはまた別の話です)。
- そのバグが修正されないことで破られている Web 標準を示しましょう。もしある特定のバグが修正されないことが原因で、W3C DOM 仕様が正しく実装されていないことを示せたなら、ブラウザベンダはそれを修正しなければいけないと感じるでしょう。もしそう感じないなら、これは格好のブログの題材となるでしょう。
- そのバグを修正しないことで他のブラウザとの互換性が失われることを示しましょう。もし IE と Safari、Opera のすべてがある特定の機能を実装するか、ある特定のバグを修正したなら、Firefox は (それが仕様と矛盾しない限り) 他の実装に従わなければならないでしょう。これは最も議論しづらいことです。しかし、より多くのブラウザが同行していればいるほど、話はより簡単になります。
もしこれらの段階のいずれも証明できなかったなら、何にせよあなたはただ自分の痒いところを掻いているだけであり、そのバグはほうっておくべきです。
例
私が異なるブラウザベンダに登録したバグの代表例をいくつか見せたいと思います。
WebKit/Safari
半径 0 の canvas の arc() が例外を投げる: canvas の arc() メソッドの呼び出しが例外を投げていました。私はとても単純なテストケースを提供し、仕様のうち WebKit が一致していない部分を指し示しました。これは投稿されたのと同じ日に解決されました。
多数の生きたオブジェクトに起因する .split("") でのメモリ不足エラー: ホストされた Web ページを単純簡潔にしたものを提供し、約 3 週間後に修正されました。
Mozilla/Firefox
Array.prototype.sort の大幅な速度低下: 単純簡潔な退行を示しました。正確な問題を突き止めるのにとても役立つ大量の Shark プロファイルデータをバグに含めました。これは 1 ヶ月以内に修正されました。
.children を実装すべき: Mozilla が (他の主要なブラウザすべてに存在する) .children メソッドを実装する必要があるか議論しました。十分な議論から、最終的 (約 6 ヵ月後) にそれを含めることになりました。
Internet Explorer 8
querySelectorAll の NodeList の例外: 単純なテストケースを提供し、「時間不足」のため却下されました。
現時点で私たちはこの問題を修正するつもりはありません。報告を理解してはいますが、残念ながら私たちは、顧客と Web 開発者に対する価値を最大化するために、何の作業をするか選択する必要がある段階にいるのです。
つまり、彼らはそれが問題であるということに同意しながら、修正するつもりはないのです。ですので、私は今それについて不満を訴えています。
HTMLElement.prototype.querySelectorAll が存在しない: 後でわかりましたが、これは querySelectorAll が標準準拠のページにのみ存在する (互換モードには存在しない) ことが原因でした。これはまったく奇妙なことですが、私はその決定の背後にある論拠を理解したと思います。いずれはっきりしますが、一度 IE 8 が動き始めれば、これが多くの人々を困惑させるのではないかと私は思っています。私は今や IE 8 についてより多くのことを知っており、IE 8 に対するより良い開発者です。
Google Chrome
for in ループが定義された順序で発生しない: これは互換性の問題でした (他のすべてのブラウザは特定のやり方で振舞います)。私は単純なテストケースを提供しました。このバグは誤って WontFix として閉じられましたが (これは混乱を招きました)、実際には修正されました。私はここで間違いを犯し、V8 に対して登録すべきこのバグを、実際は Chrome に対して登録してしまいました。V8 側でこの問題を扱うバグをここに示します。
setTimeout(..., 0) の発火が早すぎる: これは実際には Chrome チームによる構造の変更に起因するものでした。Mike Belshe (その変更の作者) が何が起きたのかを説明するために私へ個人的にメールを送ってきました。結果として私はより情報を得て、そのことに関するブログを書きました。
Opera
(私の知る限り、Opera はバグ報告に対する公式の公開された場を提供していません。)
ナイトリービルドのテスト方法
ブラウザのナイトリービルドでのテストの詳細に入る前に、私はおそらく最も一般的な質問に答えるべきでしょう。なぜブラウザのナイトリービルドをテストすることに気を遣うべきなのですか? それには 2 つの理由があります。
第 1 に、バグ報告を登録するときは、提出しようとするバグが既に修正されているかどうかを判別する必要があるでしょう。もしそれがナイトリーで既に修正されていたなら、それを提出するか悩む必要はありません。そのバグは次のリリースで修正されるでしょう。しかしながら、もしそのバグが解決されていなかったら、バグの登録を続けるべきです。
第 2 に、ブラウザがリリースされたときにあなたのコードが壊れていないか確かめるために、そのブラウザの最新のナイトリーであなたのサイトまたはライブラリを定期的にテストすべきです。どれくらいの頻度でテストするかはあなたしだいです。しかし、より頻繁にテストすればするほど、あなたのサイトまたはライブラリが、ある時点で大規模な退行に遭遇する可能性は低くなります。自分たちのサイトを壊す新バージョンのブラウザがマーケットに存在することを見つけて喜ぶ開発者はいないといって間違いないと思います。
ナイトリーに対するバグの登録は、他のバグの登録と同じです。単純化されたテストケースを提供し、退行が起きたことを強調するようにしましょう。もしあなたが十分な頻度でテストしていれば、開発者は行動に取り掛かるに違いないでしょう。
最新のナイトリーを入手する
ブラウザベンダはブラウザの最新版を入手するためのさまざまな方法を提供しています。その上、いくつかのブラウザは他のものよりも頻繁にリリースしています (たとえば、Chrome は 1 日に何回も、Firefox は 1 日に 1 回、Opera は 2 週間かそこらに 1 回更新します)。
Mozilla/Firefox
Mozilla は Firefox のナイトリーリリースを提供しています。これは、Firefox のプロファイルを使って Firefox の既存のコピーと同時にインストールし使えます。一度ナイトリーリリースをダウンロードすれば、毎日自動的に更新されるでしょう。
ダウンロード: Firefox ナイトリーリリース
WebKit/Safari
WebKit ナイトリーを OS X でインストールするのは簡単です。これはプロファイルの詳細なしに完全に並行して存在できます。しかしながら、これらは自動的には更新されません。私は WebKit ナイトリーが (OS X で) 確実に最新であり続けるよう、NightShift を使っています。
Windows でナイトリーをインストールすることはより面倒ですが (いくつかのスクリプトの実行とファイルのコピーを伴います)、動きはします。
ダウンロード: WebKit/Safari ナイトリーリリース
Internet Explorer
Internet Explorer のインストールは「一大事」です。それは以前にインストールされたブラウザのコピーを完全に吹き飛ばしてしまいます。このため、あなたのシステムにインストールされた Internet Explorer の複数のコピーを保持するために、いくつかのトリックを使うべきでしょう。IE 6 (とそれより古いもの) を扱えるインストーラや、IE 7 を考慮に入れた別のものがあります。一度これらのスタンドアローン版をすべてインストールすれば、安心して IE 8 をダウンロードしインストールできます。
IE 8 は各ベータリリースから自動的に更新されていましたが、もうそうしてはいないようです。もし Micirosoft IE Beta Connect プログラムにサインアップすれば、テスト用の最近のビルドを入手できます。重ねて言いますが、これらすべてのビルドは現在の古い版を上書きします。
ダウンロード: IE 8 ベータ、IE 8 ウィークリービルド
Google Chrome
Google は 1 日に複数のビルド (各リビジョンにつきひとつ) を提供しています。これらのビルドはお互いに並行して存在できますが、自動的には更新されません。一人のユーザーがそのために使える自動更新アプリケーションをビルドしました。
ダウンロード: Google Chrome ナイトリービルド
Opera
Opera の複数のバージョンは並行してインストールでき、自動的に更新されます。Opera はナイトリービルドを提供していませんが (2 週間かそこらに 1 回出ます)、比較的最近のブラウザの実例を供給するでしょう。
ダウンロード: Opera デスクトップチームブログ (ビルドはここに投稿されます)
Web 開発の今後に積極的な役割を担うことの重要性は、決して誇張ではありません。他の開発者が先にバグを登録してくれることを期待する、あるいはブラウザベンダがすべての退行の可能性に気づくことを期待するという受身の立場から、積極的に熱心でいることへの移行は、信じられないほど多くの力をあなたにもたらします。Web コミュニティとブラウザの間のコミュニケーションを改善するためにあなたがする最小限の作業は、Web 全体の質を改善するのに大きく役立ちます。
あなたのおかげで修正されたすべてのバグを、名誉の印として身に着けるべきです。あなたは、Web をよりよい場所にするための役割を果たしたのです。
訳文は以上です。
バグ報告は基本的に英語で行いますが、個人的な経験からいうと、テストケースがしっかりしていれば英語が適当でもわかってくれます。英語を書くのは慣れていなくてもコードを書くのは慣れているでしょうから、それを通せばいいのです。
なお、Firefox に関しては、日本語でバグ登録ができる場として Bugzilla-jp が存在し、その案内としてはじめてのバグジラがあります。Opera へのバグ報告や IE へのフィードバックも日本語のものを受け付けてくれるようです。
Kanasan.JS JavaScript 第 5 版読書会 #7 ― 2009年04月28日 00時36分
Kanasan.JS JavaScript 第 5 版読書会 #7 に行ってきました (当日のチャットログ)。範囲は前回に引き続き CSS の操作、そしてイベント周りと、一般に「JavaScript」といったとき話題になりやすい部分です。参加者のブログ記事は「JavaScriptでCSSとイベントを扱う from Kanasan.JS | Blog.37to.net」のほか読書会のページからたどれます。
display: inline-block;
サイ本では解説されていませんが、CSS の display プロパティの値 inline-block に関して話が盛り上がりました。inline-block 及びそれがどのようなレイアウトに使えるかについては以下で解説されています。
ところで、上記ページのサンプルはいずれも Firefox 2 以下で -moz-inline-box を使っています。しかし、-moz-inline-box が指定された要素の子要素は XUL のボックスモデルに従って整形されるため、そのままでは期待した表示になりません。ヨモツネットのサンプルではこの問題への対策として子要素に幅を指定していますが、別のアプローチとして子要素に -moz-box-flex プロパティを指定するという方法もあります。実際にこの方法を用いたページ送りのサンプルを作ってみました。HTML と CSS は次のようになります。
<ul>
<li><a href="http://example.org/">1</a></li>
<li><a href="http://example.org/">10</a></li>
<li><a href="http://example.org/">100</a></li>
<li><a href="http://example.org/">1000000000000</a></li>
</ul>
/* デフォルトのマージン、パディングが影響しないように。 */
ul, li {
margin: 0;
padding: 0;
}
ul {
text-align: center;
}
li {
display: -moz-inline-box; /* Firefox 2 以下用。 */
display: inline-block;
/* 内容の幅が大きいとき自動的に自分の幅も広がるよう、width ではなく
* min-width を用いる。Firefox 2 以下では -moz-inline-box な要素の
* min-width がボーダーボックスの最小幅として解釈されてしまうが、
* ここではボーダー、パディングの幅が 0 なので影響しない。
*/
min-width: 3em;
}
/* IE 6/7 では、デフォルトで inline でない要素は inline-block を
* 指定した後、さらに inline を指定しないと inline-block 表示にならない。
* margin をつけるのは inline-block 間の空白文字が表示されないため。
*/
* + html li {
display: inline;
margin: 0 0.2em;
}
* html li {
display: inline;
width: 3em; /* IE 6 は min-width 非対応だが width で代用可。 */
margin: 0 0.2em;
}
li a {
display: block;
border: 0.1em solid;
/* Firefox 2 以下でマージンボックスの幅が包含ブロック (この場合は
* -moz-inline-box な要素の内容ボックス) の幅いっぱいに広がるように。
*/
-moz-box-flex: 1;
}
/* IE 6 でボーダーボックス全体がリンクとして機能するように。 */
* html li a {
height: 0;
}
この方法の利点として、-moz-inline-box な要素の幅が % 単位で指定されていても内容の幅がそれに追従してくれるといったことが挙げられます。
ちなみに、IE 7 以下での inline-block 表示に display: inline; zoom: 1;
を用いず、いったん inline-block を指定して別の規則で inline を指定していたり、ひとつの規則内に _width: 3em; /margin: 0 0.2em;
とまとめて書くのではなく、* + html ... { margin: ...; } * html ... { width: ...; margin: ...; }
と IE 用の規則に分けて書いていたりするのは、できるだけ CSS の書式に則りたいという個人的な趣味によるものです。
MacIE 5 にも対応しようとすれば IE 6/7 向けの指定を /*\*/ ~ /**/
で囲む必要があるのかもしれませんが、確認できる環境がないのでそこまではしていません。Firefox 2 もすでにサポート期限が切れているので、最低限リンクが機能すればレイアウトが期待通りでなくてもかまわないという姿勢もありでしょう。
この例では -moz-inline-box な要素の子要素がブロック要素だったので子要素に -moz-box-flex を指定しましたが、Takazudo Clipping* のサンプルのように -moz-inline-box な要素の内容がテキストノードだったりインライン要素だったりした場合には、-moz-inline-box な要素に -moz-box-pack: center;
を指定することで内容を中央ぞろえにできます。
このように、inline-block 表示はちょっと無理をすれば大抵のブラウザで可能ですが、別に無理をしなくてもいいときもあります。ページ送りに関していえば、各項目の幅をそろえなくてもいい (「1」の項目と「10」の項目の幅が異なってもいい)、かつ項目内で行の折り返しが発生してもいい (または内容が数字だけなので折り返しが発生しない) というのなら、そもそも inline-block を使う必要がありません。(IE 6 での不具合を避けるために inline-block を指定することはあるかもしれませんが。)
なお、zoom: 1;
という指定はいわゆる CSS ハックの一環として用いられることが多いですが、このときそのまま zoom: 1;
と書いてはいけません。/zoom: 1
、_zoom: 1
、* html ... { zoom: 1; }
など、必ず IE のみに適用されるような書き方をしましょう。これは、WebKit も zoom プロパティをサポートしており、そうしたハックを必要としないブラウザに想定外の影響を与えるのを防ぐためです。もちろん、実際にズーム効果を求めて zoom プロパティを使用する場合はこの限りではありません。
rect 関数の書式
CSS の clip プロパティに rect 関数を指定することで、絶対配置の要素の一部だけ切り抜いて表示できます。rect 関数の引数には切り抜く矩形の各辺の位置を示す値を指定しますが、各引数の区切り文字には注意が必要です。CSS 2 だと、本文中ではスペース区切りなのにサンプルではコンマ区切りになっています。CSS 2.1 だと本文もコンマ区切りに統一され、製作者はコンマ区切りを使うべきであり、ユーザーエージェントはコンマ区切りをサポートしなければならないが、同時にスペース区切りをサポートしても良いということになっています。実際は IE 7 以下がスペース区切りしか受け付けてくれないので、スペース区切りを使うことになるでしょう。
このほかにも clip プロパティと rect 関数の挙動は CSS 2 と CSS 2.1 とで大きく変化しており、「clip CSS 表示と配置 (World Wide Web Guide)」に詳しくまとめられています。
getComputedStyle と currentStyle の違い
ある要素の実際の CSS プロパティの値を調べるのに、IE 以外のブラウザでは getComputedStyle メソッドが、IEでは currentStyle プロパティが使えます。しかし、この両者から得られる値は、前者が使用値 (used value) であるのに対し後者が指定値 (specified value) であるという違いがあります。二つの値に関しては以下の文書が参考になります。
JavaScript から CSS の値を操作するときは、指定値よりも使用値を得られたほうが嬉しい場面が多い気がします。
イベントハンドラ内容属性とスコープ
HTML 要素の属性としてイベントハンドラを記述した場合、そのイベントハンドラを実行する際のスコープにはその要素を表す DOM 要素ノードオブジェクト (これは this
から参照できる値でもあります) が含まれます。this.value
と書く代わりに単に value
と書いても同様に動くということです。サイ本にはこうした動作に関して何も標準が存在しないと書かれていますが、HTML 5 草案ではイベントハンドラを実行する手順が明文化されています。
IE のメモリリーク
IE のメモリリーク問題は、IE 6/7 で部分的には解決されました (IE 6 は更新プログラムを適用した場合)。「部分的」という理由は以下の記事で解説されています。
IE 8 では COM という基盤技術に手を入れることで循環参照によるメモリリーク問題を根本的に解決したようです。
雑感
ブラウザ上で何かしようとするとネックになってくるのが IE の存在です。CSS に関しては IE 8 で大きく前進しましたが、イベントモデルをはじめとする DOM 周りはいまだ独自路線のままです。IE 9 でこれらの点が改善され、Range や XPath といった高度な機能を手軽に扱えるよう、Web 開発者の一人一人が積極的に発言し Microsoft に声を伝えていく必要があると思います。
ちなみに、今回の範囲である DOM Events や DOM Style については、WEB+DB PRESS Vol. 46/47 の連載『JavaScript + ブラウザ探検』でも解説しています。よろしければそちらもご覧ください。
Kanasan.JS Greasemonkey チュートリアル読書会 ― 2009年03月22日 19時28分
ちょうど 1 ヶ月前、2 月 22 日に開催された Kanasan.JS Greasemonkey チュートリアル読書会 (参加者ブログ一覧) に私も参加していました。時間がたちすぎて自分のメモからも情報を読み出せなくなりかけているのですが、何とかここに再現してまとめたいと思います。全体のまとめは「Firebugで作るGreasemonkeyスクリプト~入門と実践(From Kanasan.JS) | Blog.37to.net」に詳しいので、自分の気になった点に関してだけです。
HTML の解析
Greasemonkey スクリプトを書くには対象サイトの HTML を解析する必要があります。多くの人は Firebug を使っているようですが、私が使っているのは DOM Inspector です。これは単に昔から DOM Inspector を使っていたという慣れの問題ですが、一応 Firebug に比べて chrome 文書 (ブラウザ画面などを構成する文書) やアクセシブルノードの解析ができるといった利点もあります。
XPath での文字列置換
XPath には translate
関数がありますが、これは Perl の tr
演算子と同じで、ある 1 文字を別の 1 文字に置き換えることしかできません。ある部分文字列を別の部分文字列に置き換えたいときは、JavaScript と組み合わせたほうが早いです。Firefox 3 では EXSLT に対応しているので XSLT 中の XPath では正規表現による置換が行えますが、DOM 3 XPath を通じての拡張関数の利用はできないようです。
<?xml version="1.0" encoding="utf-8"?>
<?xml-stylesheet type="application/xml" href=""?>
<root xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:regexp="http://exslt.org/regular-expressions"
xsl:version="1.0">
<xsl:value-of select="regexp:replace('Hello, XPath world!', 'XPath', '', 'RegExp')"/>
</root>
document.evaluate
の第 5 引数
document.evaluate
メソッドの返り値である XPathResult
オブジェクトを、別の evaluate
メソッドの呼び出し時に第 5 引数として渡すと、その XPathResult
オブジェクトが再利用されます。しかし、「再利用される」とは具体的にどうなることかという説明は DOM 3 XPath 仕様中になく、そもそも may be reused
(強調は筆者による) なので確実に再利用されるという保証もありません。Firefox (Gecko) と Opera では第 5 引数に渡したオブジェクトが返り値となりますが、Safari (WebKit) では常に新しいオブジェクトを生成して返すようです。ちなみに Firefox では第 5 引数を指定すると意図しない結果になることがあります。
var result = document.evaluate("'foo'", document, null,
XPathResult.STRING_TYPE, null);
document.evaluate("'bar'", document, null,
XPathResult.STRING_TYPE, result);
result.stringValue;
// Firefox 3.6a1pre => "foobar" ("bar" を期待していたのに!)
// Opera 9.64 => "bar"
// Safari 3.1.2 => "foo"
順序付きの型と順序なしの型
XPathResult
には結果の型として順序付きのもの (ORDERED_NODE_ITERATOR_TYPE
、ORDERED_NODE_SNAPSHOT_TYPE
、FIRST_ORDERED_NODE_TYPE
) と順序なしのもの (UNORDERED_NODE_ITERATOR_TYPE
、UNORDERED_NODE_SNAPSHOT_TYPE
、ANY_UNORDERED_NODE_TYPE
) があります。ソースコードを見る限り、WebKit では順序付きかどうかで処理を分けていますが、Gecko では順序なしでも順序付きと同等に扱っているようです。
雑感
初めての Kanasan さん抜きでの Kanasan.JS でしたが、主催の 37to さんをはじめスタッフの方々のおかげでスムーズな進行だったと思います。テーマのほうも初めてだったせいか初参加の方も多く、懇親会ではいろいろと興味深い話が聞けました。スタッフ、参加者の皆さんありがとうございました。「ブログを書くまでが勉強会です」ということで 1 ヶ月も Kanasan.JS を続けてしまい申し訳ありません m(_ _)m
WEB+DB PRESS Vol.49 WAI-ARIA ― 2009年03月02日 17時35分
現在発売中の WEB+DB PRESS Vol. 49 にて Accessible Rich Internet Applications (WAI-ARIA) に関する記事を書かせていただきました。2009 年は Web アクセシビリティの年になるという噂をどこからともなく聞きつけてこのテーマにしたのですが、ちょうど WAI-ARIA 1.0 の最終草案も公開されよいタイミングになったのではと勝手に思っています。
WAI-ARIA とは何なのか、詳しいことは雑誌記事を参照していただくとして、簡単に言えば JavaScript で構築した RIA を機械的に認識するための仕様です。たとえば JavaScript でこったボタンを作ったとしましょう。利用者がどうやって「それがボタンである」ということを認識するかといえば、「周りから浮き出ていてマウスカーソルを乗せると色が変わる」といった感覚によっているわけです。
PC 上でグラフィカルなブラウザ経由でしか Web を使わないのならそれでもいいかもしれませんが、Web の閲覧環境というのは千差万別でしてそれでは困ることも多々あります。そこで、グラフィカルな表現に加えて「これはボタン、すなわち何らかの動作のトリガーとなるものである」という情報を埋め込んでおくことで、個々の閲覧環境に応じた表現をとることも可能となるのです。
さて、雑誌記事中では WAI-ARIA で拡張された tabindex 属性にも触れているのですが、それと関係して「Web2.0ナビ: 送信ボタンの上にリンクがあればtabindex=-1に」という記事がありました。フォーム内で、フォームコントロールのみをキーボードアクセス可能にするために、リンクの tabindex 属性に値 -1 を指定しようという内容です。しかし、これをやってはいけません。そのリンクがキーボードアクセス不能になり、アクセシビリティが低下してしまいます。
そもそも tabindex 属性によるアクセス順の指定には、ページ内での絶対的な順序しか指定できない (特定の要素からの相対的な順序の指定ができない) という欠点があり、乱用すれば直感的な操作を妨げてしまいます。tabindex 属性を使う際には以下 2 点に気をつけ、アクセス順を指定する属性としてではなく、フォーカス可能かどうか・キーボードアクセス可能かどうかを指定する属性として使うべきというのが私の考えです。
- tabindex 属性の値には基本的に 0 (フォーカス可能かつキーボードアクセス可能) か -1 (フォーカス可能だが直接的にキーボードアクセス可能ではない) かのみを用いる。
- HTML 4 で tabindex 属性が定義されている要素 (リンクとフォームコントロール) では基本的に tabindex 属性を用いない。
tabindex="-1"
を使う場面の解説は雑誌記事に譲るとして、執筆にあたってつくづく感じたのは日本語資料の充実具合です。WCAG 2.0 が勧告からわずか 1 か月で日本語に訳され、WAI-ARIA 関連文書の日本語訳もそろっているなど、もう関係各所の皆様には足を向けて寝られません。
そんなこんなで 1 年間続けさせていただいた WEB+DB PRESS での連載も今回が最終回です。正直ライブラリ全盛のこの時期に DOM だの何だの書いていて受け入れられるのだろうかとも思いますが、何らかのヒントにでもなれば幸いです。読者の皆様、編集はじめ関係者の皆様、本当にありがとうございました。
最近のコメント