Home > Latest topics

Latest topics 近況報告

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

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

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

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

宣伝2。日経Linux 2011年9月号から、Linuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。そちらもよろしく。

Page 1/5: 1 2 3 4 5 »

CSS3のborder-imageを先行実装した-moz-border-imageの仕様変更とその対策 - Jan 14, 2012

ツリー型タブの組み込みのテーマで主にMac OS X向けに用意してある「Metal」の表示が、Nightly 12.0a1で盛大にぶっ壊れてた。原因は、CSS3のborder-imageの先行実装である-moz-border-imageの仕様が変わったせいだった。

当初の実装では、-moz-border-imageはこんな風に書くようになってた。

-moz-border-image: url("tab.png") 10 5 10 10 / 10px 5px 10px 10px stretch stretch;

(Metalのタブの画像の分割の様子) 「Metal」の場合、右の辺だけ5ピクセル幅でそれ以外の辺は10ピクセル幅という事にしていた。しかし、単にこう書くと、「タブの内容」の周囲に「10ピクセル幅の枠線」が付くことになるので、タブの高さが上下合わせて20ピクセル広がってしまう。なので、「タブの内容」の方に

margin: -10px -5px -10px -10px;
padding: 10px 5px 10px 10px;

という感じでネガティブマージンとそれを相殺するパディングを指定して、枠線とタブの内容を重ねることでタブの大きさをそれほど大きく変えないようにしていた。

それが、Nightlyではこんな事になってしまってた。 (タブの高さが異常に小さくなってしまっている。) 新しいborder-imageの仕様に合わせて実装が変わったということなのか、「タブの内容の周囲に10ピクセル幅の枠線が付」いても、その分ボックスの大きさが広がるという事が無くなったようだ。にもかかわらずネガティブマージンを適用していたがために、今度は逆にタブの高さが上下合計で20ピクセルも小さくなってしまって、このスクリーンショットのように極細なタブになってしまっていた……という事だった。

そこで、とりあえずネガティブマージンの指定を外してみたところ、タブの高さが変になる現象は改善された。が、今度はborder-imageに指定した画像の真ん中が抜けてしまう(今までは画像の中央部分が拡大されて背景画像代わりになっていた)という、また別の現象が起こっていた。 (タブバーの背景色が、タブの中央部分だけ透けて見えている。)

最初は「バグか?」と思ったんだけど、Firefox自身の既定のスタイルシートでどう使われてるのかソースコードを調べてみたら、理由が分かった。以下の例のように、「fill」というキーワードを明示的に書かないと真ん中の部分は埋められないようになったということのようだった。

-moz-border-image: url("tab.png") 10 5 10 10 fill / 10px 5px 10px 10px stretch stretch;

この変更はBug 497995で行われたもので、提案されてる最新の仕様が変わったので書き方も変えましょう、という話なんだけど、アドオンで古いバージョンのFirefoxにも対応させてる場合はそうもいかないんだよね。この仕様変更はFirefox 12から反映されることになるようなんだけど、もうすぐ出るというESR(主に企業向けの長期サポート版)はFirefox 10ベースで、そっちは古い仕様に基づいた実装のまま出回っちゃうわけです。Firefox 3.6の後を引き継ぐ形のFirefox 10はやっぱり一応サポートしといた方がいいと思うわけで、でもFirefox 12のためにCSSの記述を変えたらFirefox 10では枠線用の画像が全く表示されない(fillなんて未知のキーワードは文法違反!ということで-moz-border-imageの指定自体が無視される)し、かといってFirefox 10用の記述にしておくとFirefox 12でタブの真ん中が透明になってしまうし……あちらを立てればこちらが立たずの典型だ。

ネガティブマージンを使ってる部分はいかにもハック的だから、別のファイルに分けてchrome.manifestで条件付きのディレクティブで読み込ませるのもいいと思うんだけど、fillキーワードの方はそれはちょっと気が進まなかった。たった4文字のためにファイル分けるなんて、メンテナンスコストの増え方と得られる効果が割に合わない気がした。

それで少し悩んだんだけど、おそらくこの変更によって-moz-border-imageの扱いが「いろんな機能を持ってる単一のプロパティ」から「複数のプロパティの値を一括して指定するためのプロパティ」に変わった(DOM Inspectorで見てみたら見慣れない「-moz-border-image-なんちゃら」系のプロパティがいくつも表示されてた)ということが、解決の糸口になった。fillキーワードはそれらの個別プロパティの中の「-moz-border-image-slice」というプロパティに指定する物らしかったので、以下のように並べれば、1行目でFirefox 10とFirefox 12の両方に対して基本的な指定を適用して、2行目でFirefox 12用に正しい値を個別に上書きする(Firefox 10にとっては-moz-border-image-sliceは未知のプロパティなので、古い環境では2行目は無視される)ということだ。これなら、メンテナンスコストをそれほどかけずにFirefox 10にもFirefox 12にも対応できる。

-moz-border-image: url("tab.png") 10 5 10 10 / 10px 5px 10px 10px stretch stretch;
-moz-border-image-slice: 10 5 10 10 fill;

FirefoxのmozRequestAnimationFrame()の仕様が変わってた - Dec 02, 2011

2011年11月30日付けのNightly 11.0a1でツリー型タブが動かないっていう報告を受けたので調べてみたら、なんかJavaScriptベースでアニメーション効果を実装する時に使えるFirefox 4から(だったっけ?)の新しい機能に基づいたアニメーション管理のための仕組みが期待した通りに動かなくなってて、あれこれ試してるうちにどうもmozRequestAnimationFrame()の使い方のうちDOMイベントベースでやる方のが動かなくなっててコールバック関数を使う方法でならうごくっぽいという事が分かったので、ツリー型タブ0.12.2011120101からはそのように変更した。

えむけいさんが教えて下さった所によると、これはBug 704171 – Remove the no-argument form of requestAnimationFrameでの変更による物で、要するに「DOMイベントベースの方法は正式な仕様になりそうにないし誰も使ってないし、もう廃止してよくね?」ってことで、Geckoでしか使えなかったDOMイベントベースの方法は廃止されて、WebKit等でも利用できるのと同じ形式のAPIに統一されたんだそうだ。まさにその当日にMDNのドキュメントを調べてて、その廃止された方の使い方が解説されていて、MDNにも書いてある使い方なのにおっかしーなーと首をひねってたんだけど、単にドキュメントの更新が間に合っていなかっただけだった。

現実的には妥当な決定だと思うけど、なんか釈然としない。例外のメッセージで「仕様が変わった、古い使い方はもう使えない」とかそういう情報を出してくれれば、もうちょっとすんなり原因に気がつけただろうに……

JSDeferredを小さくする - Feb 27, 2011

JSDeferredは非同期処理の制御に特化しててサイズも小さくて素敵な軽量ライブラリだ!と僕は思ってるんだけど、世の中を見回してみると「軽量ライブラリ」って言われてる物は3KB未満とかそういうのが結構あるようだし、jQueryも1.5.1のminified版は28KBって書いてあるし、そうなるとjsdeferred.jsのコメント付き版が0.3.4で19KBというのは「軽量」と呼ぶにはひょっとしてちょっとでかいのかな……という気がしてきた。

なので、JS MinifierとかPackerとかそういう風なやつでどれくらい小さくなるのか実験してみた。

/packer/はそのままだと構文エラーで動かなくなってしまった。}Deferred. って部分が7箇所あってこれがエラーになってるので、全部 };Deferred. に置換(Base62圧縮後だと }4.};4. に置換)したら一応エラーは出なくなった。/packer/の構文解析が貧弱なせいっぽいので、JSDeferredの側で問題になる所にあらかじめセミコロンを入れておけば、この問題は無くなるっぽい気がする。……と書いたからか、ちょよんごさんが対応してくれた。ありがとうございます!

あと、closure-compiler を通すと 2668bytes でした (jsdeferred のレポジトリで rake すると URL が出るようになってます)というアドバイスも頂いたので早速Closure Compilerにかけてみた所確かに小さくなったのですが、シンボル類まで全部失われちゃってこれ単体だと他のスクリプトと組み合わせられないのが残念ですね。(Closure Compilerは他のスクリプトも全部合わせて一緒にコンパイルして使うのが前提ってことなんだろう)

Excelの「AA」とかのカラム名とカラム番号の数値を相互変換する - Feb 15, 2011

96桁目のセルに所定の値が入ったCSVを作る、みたいな事をやらなきゃいけなくなったんだけど、GnumericにしろExcelにしろカラム名が数字じゃなくてA, B, C, ... Z, AA, AB, ... ZZ, AAA, ...というヘンテコ表記になってるからどこのセルに値を入れればいいかわかんないよウワァァァァン!!!! となったのでJavaScriptで解決した。


var input = prompt('input number or column name');
if (!input) return;

var symbols = 'abcdefghijklmnopqrstuvwxyz';
var result;
if (/^[0-9]+$/.test(input)) {
   input = parseInt(input);
   result = [];
   while (input > 0) {
     result.unshift(symbols.charAt((input - 1) % symbols.length));
     input = Math.floor((input - 1) / symbols.length);
   }
   result = result.join('').toUpperCase();
}
else {
  result = 0;
  input = input.toLowerCase().split('').reverse();
  for (var i = 0, maxi = input.length; i < maxi; i++) {
    result += (symbols.indexOf(input[i]) + 1) * Math.pow(symbols.length, i);
  }
}
alert(result);

カラム名→カラム番号の計算方法は分かったけど、頭が悪い僕にはカラム番号→カラム名の計算式が分からなかったので、検索して出てきたエクセルの1000列目は ALL - つまみ食うのアルゴリズムを丸パクリした。阿呆ですんません……

なんでそこで詰まったかっていうと、単純な26進数の変換ではうまくいかなかったからなんですよね。A==0、B==1という感じで26進数として扱うと、26==Zで27==BAになるんだけど、実際は繰り上がった後はBAじゃなくてAAにならなきゃいけない。そこの所を考慮した計算を多分簡単なループで実現できるとは思ったんだけど、頭が働かなくて無理だった。

JsDoc ToolkitでJavaScriptコードモジュールのDoc Commentを出力する - Aug 20, 2010

Doc Commentの必要性

最近、JsDoc Toolkitの導入を考えてる。

  • ライブラリとして抜き出したコードを公開しておきたいけど、ライブラリ自体の使い方を書いたページを準備するのが面倒だし、多分誰も見てくれなさそう。
  • ソースコードの中にコメントとして使い方を埋め込んでおくと、見るべきファイルが1つで済むから良さそうだけど、どんな書式で書くと分かってもらいやすいかが問題だ。

ツリー型タブのAPI紹介等ではXPIDLっぽい書き方にしてみてるけど、オレオレ表記なので分かってもらえない可能性があるという心配はずっとしている。

ところで、Firefox自体のソースを見ていると以下のような書き方をしているのをよく見かける。

(略)

/**
 * Given a starting docshell and a URI to look up, find the docshell the URI
 * is loaded in.
 * @param   aDocument
 *          A document to find instead of using just a URI - this is more specific.
 * @param   aDocShell
 *          The doc shell to start at
 * @param   aSoughtURI
 *          The URI that we're looking for
 * @returns The doc shell that the sought URI is loaded in. Can be in
 *          subframes.
 */
function findChildShell(aDocument, aDocShell, aSoughtURI) {
(略)

これはJavaで標準的に使われているJavadocという「ソースコードの中に埋め込まれたコメントを自動的に収集してHTML形式でドキュメントを生成する」仕組みに基づいたもので、同じ形式でコメントを埋め込めるよう、Javadocの仕様に準拠した実装が言語ごとに存在しているようだ。JavaScriptならJsDoc Toolkit、C言語ならGTK-Docが一般的らしい。あとJavaScriptに関してはGoogle Closure ToolsのClosure Compilerも対応しているらしい

事実上の標準としてみんなが見慣れた形式なのであるならば、これに合わせて書くのがいいだろう。と思ったので、とりあえずライブラリとして切り出して公開しているコードにJavadoc形式で使い方の解説を埋め込んでみることにした。

JsDoc Toolkitの使い方

  1. Javaをインストールする。
  2. JsDoc Toolkitをダウンロードして展開しておく。
  3. JsDoc-Toolkitを使うで配布されているバッチファイル「jsdoc.bat」を、JsDoc Toolkitのjsrun.jarとかと同じ位置に置く。
  4. JavaScriptのファイルの中にDoc Commentを書く。書き方はJsDoc Toolkitによる開発効率向上を目指して - @IT等を見ると例がある。
  5. jsdoc.batの起動オプションにJavaScriptのファイルを渡して起動する。
  6. jsdoc.batと同じ位置にjsdocという名前のフォルダができて、その中に生成されたHTMLファイルがあるので、index.htmlをブラウザで開いて眺めてニヨニヨする。

JavaScriptコードモジュールとJsDoc Toolkit

  • JavaScriptコードモジュールではファイルの拡張子として「.jsm」を使うことが多いみたいなんだけど、.jsmなファイルにDoc Commentを埋め込んでJsDoc Toolkitに渡してみてもドキュメントを出力してくれなかった。
  • UxU用のテストケースまで走査するため、フォルダごと渡してまとめてドキュメントを生成させてみると、ファイル一覧の中にテストケースのファイルまで出てきてしまう。
  • @exampleに例を書く時に、例の中に<とか>とかのHTML的にまずい文字が含まれていると、出力されるHTMLがぶっ壊れてしまう。

JsDoc Toolkit自体のソースを見てみた所(JsDoc Toolkitはそれ自体がJavaScriptで書かれている。Java上で動作するJavaScript実行環境のRhinoの上で動作している。)、ファイルの拡張子でフィルタリングを行っているらしいということが分かった。あと、テンプレートのファイルの方を編集すれば、例に埋め込んだコードのせいでHTMLがぶっ壊れてしまう問題は回避できるようだった。

そういうわけで当面の所はこんな変更を加えて使ってみることにした。以下はjsdoc_toolkit-2.3.2.zipに対する差分です。

diff -ur jsdoc-toolkit-orig/app/lib/JSDOC/JsDoc.js jsdoc-toolkit/app/lib/JSDOC/JsDoc.js
--- jsdoc-toolkit-orig/app/lib/JSDOC/JsDoc.js   2009-01-24 18:42:04.000000000 +0900
+++ jsdoc-toolkit/app/lib/JSDOC/JsDoc.js    2010-08-20 10:17:17.602464000 +0900
@@ -69,7 +69,8 @@
 JSDOC.JsDoc._getSrcFiles = function() {
    JSDOC.JsDoc.srcFiles = [];

-   var ext = ["js"];
+   var ext = ["js", "jsm"];
+   var ignorePattern = /\.test\.js$/i;
    if (JSDOC.opt.x) {
        ext = JSDOC.opt.x.split(",").map(function($) {return $.toLowerCase()});
    }
@@ -89,7 +90,7 @@
                        }
                    }

-                   return (ext.indexOf(thisExt) > -1); // we're only interested in files with certain extensions
+                   return (ext.indexOf(thisExt) > -1) && !ignorePattern.test($); // we're only interested in files with certain extensions
                }
            )
        );
diff -ur jsdoc-toolkit-orig/app/run.js jsdoc-toolkit/app/run.js
--- jsdoc-toolkit-orig/app/run.js   2009-01-08 06:32:58.000000000 +0900
+++ jsdoc-toolkit/app/run.js    2010-08-16 17:28:28.673092400 +0900
@@ -337,7 +337,7 @@
        if (!path) return;

        for (var lib = IO.ls(SYS.pwd+path), i = 0; i < lib.length; i++) 
-           if (/\.js$/i.test(lib[i])) load(lib[i]);
+           if (/\.jsm?$/i.test(lib[i])) load(lib[i]);
    }
 }

Only in jsdoc-toolkit: jsdoc.bat
diff -ur jsdoc-toolkit-orig/templates/jsdoc/class.tmpl jsdoc-toolkit/templates/jsdoc/class.tmpl
--- jsdoc-toolkit-orig/templates/jsdoc/class.tmpl   2009-09-03 06:37:31.000000000 +0900
+++ jsdoc-toolkit/templates/jsdoc/class.tmpl    2010-08-18 15:10:02.542253900 +0900
@@ -300,7 +300,10 @@

                <if test="data.example.length">
                <for each="example" in="data.example">
-               <pre class="code">{+example+}</pre>
+               <pre class="code">{+String(example)
+                                     .replace(/&/g, '&amp;')
+                                     .replace(/</g, '&lt;')
+                                     .replace(/>/g, '&gt;')+}</pre>
                </for>
                </if>

@@ -399,7 +402,10 @@

                    <if test="member.example.length">
                    <for each="example" in="member.example">
-                   <pre class="code">{+example+}</pre>
+                   <pre class="code">{+String(example)
+                                         .replace(/&/g, '&amp;')
+                                         .replace(/</g, '&lt;')
+                                         .replace(/>/g, '&gt;')+}</pre>
                    </for>
                    </if>

@@ -466,7 +472,10 @@

                    <if test="member.example.length">
                    <for each="example" in="member.example">
-                   <pre class="code">{+example+}</pre>
+                   <pre class="code">{+String(example)
+                                         .replace(/&/g, '&amp;')
+                                         .replace(/</g, '&lt;')
+                                         .replace(/>/g, '&gt;')+}</pre>
                    </for>
                    </if>

@@ -565,7 +574,10 @@

                    <if test="member.example.length">
                    <for each="example" in="member.example">
-                   <pre class="code">{+example+}</pre>
+                   <pre class="code">{+String(example)
+                                         .replace(/&/g, '&amp;')
+                                         .replace(/</g, '&lt;')
+                                         .replace(/>/g, '&gt;')+}</pre>
                    </for>
                    </if>

モックが必要な場面、モックが有効な場面 - Aug 11, 2010

モック(Mock)とスタブ(Stub)の違いがよく分かってなかったんだけど、何が違うのか、そしてモックはどう使う物なのかということを、すとうさんに教えてもらって今更理解した。あとで会社のブログに書くつもりだけど、メモとして要点だけまとめておく。

  • テストは基本的に、粒度の細かいブラックボックステストにした方がいい。
    • 内部に持ってる隠しプロパティの値が正しいかどうか?という風な実装べったりのテストは、実装の変更に非常に弱い。
    • なので、関数の返り値だけ見て検証できるような設計が、自動テストしやすい設計という意味で「良い設計」と言える。
  • しかしブラックボックステストには限界がある。
    • 色々な副作用を伴う機能だったり、非同期で処理するような機能だったり、1回の実行で複数の状態を遷移する機能だったり、という風に、単純に関数の実行→返り値を検証 とするだけではテストできない機能もある。
      • ユニットテストのレベルでそういう機能があるのは設計が良くない証拠なので、こういう物は関数を細かい単位に解体して、単純に関数の実行→返り値を検証 というブラックボックステストを行えるような設計に直すべき。
      • 処理待ちしてやりさえすればいいような場合、処理待ちのための機能を持ったテスティングフレームワークを使うと、単純な非同期処理なら簡単にブラックボックステスト化できる。
    • 色々な副作用を伴う機能や、状態遷移があるような機能をブラックボックステストできるようにしようと思うと、「内部で状態の遷移のログを取っておいて、最後にそのログの内容が期待通りになっているかどうかを検証する」という風な形にならざるを得ない。
      • ということをやろうと思うと、本番用のコードの中に「ログ取り用の処理」のような「実際に使う場面では無駄」なコードが増えていってしまう。
      • テストしやすくするためにテスト対象の実装に手を入れるのはよくあることだし、そうやって手を入れた結果として関数が小さな単位に分割されていったり関数名と入出力の対応が分かりやすくなっていったりするのなら、それによって動作が安定するようになったりコードのメンテナンス性が高くなったりするから、いいことだ。でも「テストのためだけの実装」が増えていって、動作が不安定になったりコードのメンテナンス性が落ちたりするのでは本末転倒だ。
  • ブラックボックステストにし続けるためのコストが、本来の実装に悪影響を及ぼすようなレベルになってしまったら、そろそろホワイトボックステストに移行していい頃合いだ。
    • 検証対象の機能の粒度が大きくなってくると、これはもう避けられない事と考えた方がいい。

自分は今まで、とりあえずユニットテストに注力していて、ある意味脅迫観念的な勢いで、ブラックボックス度合いを高くする事を心がけてた。今まではそれでだいたい問題なかった。でも最近になって、ブラックボックステストにしようとすると無理があるというケースにぶち当たるようになった。1つの機能の中でコロコロと遷移する内部状態を、どうにかして検証したいというようなケースが出てきた。

それですとうさんに相談したら、そういう時はモックを使えばいいと言われた。でも、話を聞く限りだとモックというのはテスト対象の実装の中の処理の流れを追う物のようなので、それじゃブラックボックステストにならないじゃないかと思った。それをそのまま言ったら、確かにテストはできるだけブラックボックステストになってた方がいいけど、機能テストやインテグレーションテストのような粒度の大きな単位のテストでは、処理の中で起こる様々な出来事や副作用を色々モニタリングして、すべての処理が期待通りに動いているかどうかを検証しないといけないから、必然的にホワイトボックステストにならざるを得ないと言われた。

それを聞いて、目の覚めるような思いをした。そうか、ブラックボックステストとホワイトボックステストの使い分けはそこが基準になるのか、と。今まで自分がホワイトボックステストを書かずに済んでいたのは、状態の遷移を伴うような機能を作る必要がなかったからだったんだ、テストをどうも書きにくいなあと思っていた機能は本当はホワイトボックステストにしたほうがいい物だったんだ、と。

そんなわけでUxUにモックの機能を実装した

「JavaScript Mock」で検索するとJSMockjqmockが上位に出てきたので、最初はそれらを参考にするように(メジャーな実装があるんだったらそれをそのまま取り入れるなりAPIを合わせるようにするのが望ましい)と言われたんだけど、ドットで繋げるメソッドチェインの記法がガンガン出てきて頭パンクした。

もう少し下の方までスクロールするとMockObject.jsというのが出てきて、こっちはファイル全体で3KBに満たない小さなライブラリなので、まずはここから始めることにした。何せコードが短いから、読むのもそんなに苦にはならない。モックの概念を言葉で説明されてもさっぱりだったけど、一通りの処理の流れを見たら、「モックというのは一体何をやらなきゃいけないのか」「どういう振る舞いが期待されているのか」ということがよく分かった。

MockObject.jsと同等の機能を一通り実装した後でもう一度JSMockの方を見たら、なるほどこれはこういう意味だったのかというのがやっと分かった。サンプルコードを見ても、モックという物の意味をそもそもよく知らない時点では、どこからどこまでがJSMockの部分なのかさっぱり分からなかったんだよね。ということで、MockObject.jsに加えてJSMock互換のAPIも付け加えてみた。jqunitの方は……もう別言語だからシラネ。

あと有名なのはJsMockito? これも頑張ったらできるかなあ、というかMITライセンスだしそのままぶち込んだ方が早いか……

JavaScriptのスタックトレース - Jul 26, 2010

先日のブラウザー勉強会吾郷さんにお会いした際に、JavaScriptには言語の仕様として「例外がどこから投げられたのか」を知る為の仕組みが無いので独自のフレームワークを作る時に困ったという話を伺った。オレ標準JavaScript勉強会で何話せばいいか困ってた所だったので、それをネタに発表させてもらう事にした

改めてECMAScriptの仕様書を確認してみたけど、確かに3rd Editionと5th Editionでは、例外オブジェクトの機能としてはError.prototype.nameError.prototype.messageしか定義されていなかった(あとはconstructorとかtoString()とかその程度)。Wikipedia(英語の方)のECMAScriptの記事によるとECMAScript 3rd EditionはJavaScript 1.5とJScript 5.5の共通部分を抜き出す形で策定されたようで、実装してない方のIEに合わせてこうなったのか?とも思ったけど、調べてみたらJavaScript 1.5の頃はMozillaもError.prototype.stackはサポートしてなかった。それ以降にMozillaが独自に拡張した箇所ということのようだ。でも今回調べた限りではOperaもChromeもError.prototype.stackをサポートしていた(Operaの場合はこれに加えて、少しフォーマットが違うスタックトレースをError.prototype.stacktraceでも取得できる、ということをedvakfさんに教えていただいた)。メジャーなJavaScript実行環境でこれに対応してないのはIE(IE9PP3を含む)くらいのようだ……と思ったらSafari(Windows版)もサポートしていなかった。

吾郷さんのそれやUxUのようにデバッグを支援するためのフレームワークでは、スタックトレースは欠かせない要素だ。将来のECMAScriptの仕様に取り込まれてくれればいいのになあ、と思う。

勉強会ではせがわようすけさんに突っ込まれたけど、セキュリティのためにはなるべくこういう情報は出さない方がいいものらしい。しかし自分はスタックトレースの何がまずいのかがよく分かっていない。会場では「例えばスタックトレースでスクリプトのURLの中にセッションIDが含まれていたらセッションハイジャックされてしまう危険性がある」という例を教えていただいたけれども、それでもまだピンと来ていなかった。

さらにツッコミを受けたことで「なるほど、クロスドメインの制約を突破されてしまう」という事が問題なのだと分かった。ただ、実際にそれが問題になるケースってほんとにあるのかな? という疑問はまだ残っている。

分かりやすい話として、通常のXMLHttpRequestやiframeでは、別ドメインのドキュメントを読み込む事ができない・読み込んだとしてもその内容にスクリプトからアクセスすることはできない。

var iframe = document.createElement('iframe');
iframe.setAttribute('src', 'http://www.google.co.jp');
document.body.appendChild(iframe);
window.setTimeout(function() {
  try{
    alert(iframe.contentDocument.body);
  }
  catch(e){
    alert(e);
  }
}, 3000);

例えばこういうのは、Error: Permission denied for <http://piro.sakura.ne.jp> to get property HTMLDocument.body from <http://www.google.co.jp>.と言われてエラーになる。しかしスタックトレースにエラー行の詳細な情報が含まれていると、

try {
  document.write('<script type="text/javascript" src="http://www.google.co.jp/" async="false" defer="false"></script>');
}
catch(e) {
  var contentsFragment = e.stack;
}

とか

try {
  var script = document.createElement('script');
  script.setAttribute('type', 'text/javascript');
  script.setAttribute('src', 'http://www.google.co.jp/');
  script.setAttribute('async', 'false');
  script.setAttribute('defer', 'false');
  document.body.appendChild(script);
}
catch(e) {
  var contentsFragment = e.stack;
}

とか

// window.onerrorはECMAScriptの仕様にはない独自拡張
window.onerror = function(aMessage, aSource, aLineNumber) {
  // ここでaMessage, aSourceから情報を取れる可能性がある
}
var script = document.createElement('script');
script.setAttribute('type', 'text/javascript');
script.setAttribute('src', 'http://www.google.co.jp/');
script.setAttribute('async', 'false');
script.setAttribute('defer', 'false');
document.body.appendChild(script);

という風にして、別ドメインのドキュメントの内容を部分的にとはいえ読み取れてしまう可能性がある、というわけだ。特に3番目のwindow.onerrorを使う例はMFSA 2010-47: エラーメッセージのスクリプトファイル名からのクロスサイトデータ漏えいで実際に脆弱性になっていたことが確認されていて、既に修正されている。

なお、実際に現行バージョンのFirefox・Opera・Chromeで試してみたところ、1番目・2番目のtry-catchを使った例ではそもそも例外を例外として(そもそも、エラーが発生したのかどうかすら)捕捉できなかったので、今の所問題にはならない。ただ、今は問題にならなくても、今後登場するJavaScriptの実装ではこういう場合でも情報を取れるようになっている可能性はあるので、将来的に脆弱性の原因になるかもしれないという警告は確かにアリだとは思う。という所までは何とか理解できた。

ブラウザのレベルでは、クロスドメインやクロスオリジンになる時だけは例外を出さないとか詳細な情報を出さないとか、そういう対応の仕方はあるだろうけれども、「ECMAScript」の仕様でそこに言及するのは変だろうなあ。というややこしい事情を勘案すると、もう丸ごと全部仕様からドロップしてしまえという判断になるんかなあ。

携帯から送られたメールの中に含まれている絵文字を判別したかった - May 31, 2010

またRailsなんですけど。

携帯端末のメールで絵文字を入力した物を送信して、Railsアプリ(いわゆる勝手サイトにあたるもの)でそれを受信したら何か処理をしたい、っていう場面でどぉぉぉーもうまくいかなくて一日悩んでた。

ActionMailer::Baseを継承した独自のクラス(Mailmanとかそういう名前で定義してる)のMailman.receive()に渡ってきたメールの内容から、絵文字を検出したかった。

class Mailman < ActionMailer::Base
  def receive(mail)
    some_operation(mail.subject)
    some_operation(mail.body)
  end

  def some_operation(string)
    # ここで絵文字を検出したい
  end
end

jpmobileとかMbMailとかが利用できるのかなと思ったんだけど、うまくいかない。DoCoMoの端末から絵文字入りのメールを送っても、絵文字のコードにマッチするはずの正規表現に全然マッチしない。

んで、もっとよく調べてみたら、勝手サイトだからなのか何なのか、DoCoMoのメールサーバからメールが送られる時点で絵文字の情報は完璧に失われるんですね絵文字は全部「〓」に変換されてしまってて、〓の文字コードは(Unicodeだと)0x3013だから、さっきのコード表から作った正規表現にはマッチするわけがない。

まあ、やりたかった事は「絵文字があったらエラーを返す」という事だったので、「〓」が有るか無いかだけ見るというソリューションでだいたい問題なかったんですけれども。(「〓」自体を送信できないという問題は残るけど、こんな文字は普段使わないからその問題は無視する)

1日まるまる無駄にしてしまった……

メールのヘッダに埋め込む用に文字列をBase64エンコードする - May 28, 2010

所用でRuby on Railsのテストケースを書いているのですが、メールを受信してあれこれする処理のテストのためのfixtureを用意するのにいちいちホントにメールを送信してそのソース文字列をコピペするとかそういう事をやってたら面倒すぎたので、Subjectとかのヘッダ部分を簡単に書き換える方法を探した所、Rubyでやるのが早いっぽい事が分かったのですが、忘れそうなのでメモしておきます。

irbを起動して

require "base64"
def encode( str )
  "=?iso-2022-jp?B?" + Base64.encode64( NKF.nkf( '-j --utf8-input', str ) ).chomp + "?="
end

を貼り付ける。後はencode("文字列")でその都度結果を見ればいい。

CSS Transitions(CSSだけで簡単なアニメーション)の使い方・覚え方のまとめ - May 02, 2010

いろんな人がいろんな解説を既に書いてるみたいだけど、ツリー型タブでタブのインデントや折り畳みのアニメーションをCSS Transitionsで書き直してみるにあたり、自分で書いてみるまでよく分からんかったので、理解している内容をまとめてみることにした。

CSS Transitionsのフルスペックの説明でもないし、「こんなことができるぜすげーだろ!」というデモでもない。現実的にどういう場面でどういう風に使う事ができるのか、という事を淡々と述べます。いろんな機能を詰め込んだよく分からんデモスクリプトと組み合わせること前提の変なデモを見たせいで混乱してる僕みたいな人が、CSS Transitionsを理解するための手助けになれれば幸いです。

主な用途

マージンや色などの値を数値で表せるプロパティや、画像を表示する系のプロパティなどについて、スクリプトあるいは:hoverなどの疑似クラスによって動的に値が変化するようにしている場面で、元の状態と新しい状態の間をなめらかに変化させるようにする。

例えば、こんな風に。

ul.menu li {
  transition: margin-left 0.25s ease-out;
}
ul.menu li:not(:hover) {
  margin-left: 0;
}
ul.menu li:hover {
  margin-left: 50px;
}

この例では、分かりやすくするために敢えて指定を分けて書いている。ここでは以下のようなことが起こっている。

  • 何もしていない状態だと、margin-left: 0;が適用されている。
  • :hoverの状態になると、margin-left50pxになるようにアニメーションが始まる。
  • :hoverの状態が解除されると、margin-left0になるようにアニメーションが始まる。

何ができる?

  • CSSだけでやれる範囲でいうと、上記のサンプルのように「:hoverになった時にぽわーんとなめらかに出てきて、:hoverが解除されたらぽわーんとなめらかに引っ込む」ようなGUI要素をスクリプト無しで書けるようになる、という所に意義がある。例を挙げると、以下のような物が該当するだろう。
    • :hoverの時だけ出っ張るメニュー項目が、なめらかに出っ張る/引っ込むようになる、とか。
    • :hoverすると色が変わるボタンが、「じわっ……」と色が変わるようになる、とか。
    • :hoverで影が付く時に、「じわっ……」と影が濃くなる、とか。
    • メニューが透明な状態から「じわっ……」と表示されていく、とか。
    • アイコンが90°回転する時に回転がなめらかになる、とか。
  • スクリプトと組み合わせて使う場合、トゥイーンのための実装が要らなくなる(ネイティブ実装に任せてしまえる)というのが多分最大のメリット。
    • ポジショニングやマージンの調整で要素の表示位置を変える時、単に新しい値(=アニメーションの終了時点の値)を設定するだけでトゥイーンしてくれる。
    • CSSで初期値とdurationとイージング関数を指定しておいて、スクリプトで終了値だけを動的に指定する、という使い方。
    • CSSのクラスセレクタであらかじめいくつかの終了値のセットを定義しておいて、スクリプトでclassNameを書き換えて終了値を間接的に指定する、という使い方。
    • タイマーの管理が不要になるので、複数のアニメーションを同時に走らせる場合、アニメーションが全体的に軽くなると思われる。
  • 既に前のアニメーションが行われている間にまた値を変更した場合、現在の時点の値からの変化になる。
    • アニメーションの中断と再開のための面倒なコードは不要。
    • 例えば、margin-left0であったのを、20pxを設定してアニメーションが始まり、20pxに向けて変化していっている最中の10pxの時点でmargin-left0に再設定した場合、前のアニメーションが中断されて、新たに10pxから0に向けて変化するアニメーションが始まる事になる。
    • スクリプトでも動的疑似クラスでも。

「Windows Vista以降のAero Glass有効時のネイティブアプリのボタンみたいな物をWebに簡単に持ち込めるようにする」「GUIの端々にほんのちょっとだけ小粋なアニメーション効果を加えたい時に、スクリプトを書かなくてもよくなる」という話ですね。それ以上でもそれ以下でもないと思っておくと、CSS Transitionsを理解しやすいと思います。

何ができない?

  • テレビアニメのような、長くて複雑なアニメーションは無理。滅茶苦茶頑張ればできるかもしれないけど、CSS Transitionsの仕事ではない。
  • アニメーションと連動したスクリプトの複雑な制御。
    • アニメーション終了時にはDOM Level2 Eventsのイベントリスナで捕捉可能なtransitionendイベント(WebKitではwebKitTransitionEnd)が発行されるので、アニメーションの終了だけは検知できる。
    • それ以外のタイミングではイベントは発行されない(アニメーションが始まったかどうかも分からない)。
  • 無限に繰り返すアニメーション。
    • ループするアニメーションのデモではスクリプトと組み合わせていて、transitionendイベントを使ってその都度新しい「アニメーション終了時点の値」を設定し直してやることで、アニメーションを繰り返している。CSS Transitionsだけではループはできない。
  • どんなプロパティでもアニメーションできるわけではない。仕様書(のWorking Draft)にアニメーションできるプロパティの一覧がある。

書き方

transition:
    アニメーションさせたいプロパティの名前
    アニメーション終了までにかける時間(省略すると0)
    イージングの種類(省略するとease)
    アニメーションが始まるまでの遅延(省略すると0);

これで1セットと考えると分かりやすい。複数のプロパティをアニメーションさせたい時は、

transition: margin-left 1s ease;
transition: margin-top 1s ease;

とは書けないので、カンマ区切りにして

transition: margin-left 1s ease,
            margin-top 1s ease;

と書く。

アニメーション終了までにかける時間はCSS3 Values and Unitsの時間の値で指定する。例えば250ミリ秒で完了させたい時は250ms(ミリ秒単位)または0.25s(秒単位)と書く。アニメーションが始まるまでの遅延も同様。

イージング関数(値の変化の仕方)には、以下のいずれかを指定できる。

linear
等速直線運動的な変化
ease-in
最初はゆっくり、最後にいくにつれて激しく変化
ease-out
最初は激しく、最後にいくにつれてゆっくり変化
ease
ゆっくり始まって、途中で最高速になり、最後はまたゆっくり変化
ease-in-out
easeをもう少しきびきびさせたような感じ
cubic-bezier()
ベジェ曲線でアニメーションの変化の仕方を指定

現状ではまだ仕様が固まっていなくてどのベンダも先行実装の段階なので、ベンダープレフィクス付きの指定を書いてやらないといけない。

-webkit-transition: SafariとGoogle Chrome用の指定;
-moz-transition: Firefox用の指定;
-o-transition: Opera用の指定;
transition: 仕様通りの指定;

CSS TransitionsがRecommendationになったら、ベンダープレフィクス無しの記述だけでいけるようになるはず。

Page 1/5: 1 2 3 4 5 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

内容が似ているかもしれない他の人のblog

「あわせて読みたい」によると、ここを見てる人はこのあたりのサイトも見ると面白いかもね、とのことです。

オススメ

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