たまに18歳未満の人や心臓の弱い人にはお勧めできない情報が含まれることもあるかもしれない、甘くなくて酸っぱくてしょっぱいチラシの裏。RSSによる簡単な更新情報を利用したりすると、ハッピーになるかも知れませんしそうでないかも知れません。
の動向はもえじら組ブログで。
宣伝。日経LinuxにてLinuxの基礎?を紹介する漫画「シス管系女子」を連載させていただいています。
以下の特設サイトにて、単行本まんがでわかるLinux シス管系女子の試し読みが可能!
伝説の黒歴史ドキッ! 丸ごとウェブ!! ブラウザだらけの討論大会 ~Chatでユーザのポロリもあるよ~をプロデュースした哀さんは最近演劇に凝っておられるようなのですが、その哀さんがプロの劇団で客演するそうで、見に来るようにというサクラ電波を受信しました。
演劇というと、高校時代に漫研の隣が演劇部で、演劇部の友人からかなりカオスな世界であると聞かされていた(いかにワケの分からないことをするかを競うとかなんとか……)ため、苦手意識があったんですよね。でも食わず嫌いみたいなんですよねよく考えたら。
という感じでミュージカルとオペラは見たことあるけど普通の舞台演劇は意外にも未体験だったようです。なので、いい機会かと思って行ってみることにしました。
哀さん曰く、演じるために5回ほどDVD見て毎回泣いたそうなので、(高校の頃に聞いたカオスな情報とは異なって)普通にお話として楽しめるものなのだと推測しています。以下IRCのログより抜粋。
21:30 (i_Phone) ケータイが無いころの長野県高校三年生が 21:30 (i_Phone) 大雪で学校に閉じ込められて 21:31 (i_Phone) 自分の未来も見えなくて 21:31 (Piro) 一人また一人と消えていく「ケッ、俺は殺人鬼なんかと一緒にいられねえぜ! 部屋に帰らせてもらうぜ!」 21:31 (i_Phone) とりあえず学校の出し物のお芝居、頑張ってやろうよ 21:31 (i_Phone) ちげえw
ということで、このエントリを見た人で7日がヒマな人は一緒に見に行ってみませんか。という宣伝なのでした。
ちなみにその翌日の11月8日はFirefox Developers Conferenceが開催される予定です。僕もなんか話すと思うのでよかったら見に来て下さい。
XUL/MigemoがTrunk(3.7a1pre)で動かなくなっていた件について原因を調べてた。
エラーメッセージに見覚えがあるなあと思って検索したら、前に書いたエントリがヒットした。
一個だけ躓いた所として、XPCOMコンポーネントの新しいメソッドをIDL定義に追加して引数にnsISelectionController型のオブジェクトを渡すようにしていた所、XPIDLでのコンパイルは通るんだけど実際に使う時に
NS_ERROR_XPC_CANT_GET_PARAM_IFACE_INFO
というエラーが出てにっちもさっちもいかなくなってしまった。ダメ元で、引数の型をnsISupportsにして受け取り側でQueryInterfaceするようにしてみたところ、ちゃんと動いてくれた。一体何だったんだろうこれは。
ああ、前にも詰まってたところだったか……この時は結局「理由」が分からないままで、とりあえず動くようにはなったからということでそれ以上は調べなかったんだよね。
改めて検索してみたら、似たような問題にぶち当たった人がいたようだった。で、やっと何が問題の原因だったのかが分かった。
新しいXPCOMコンポーネントを定義する時に、インターフェースも新しく定義する場合、XPIDLを使ってインターフェースを定義してやらないといけない。
[scriptable, uuid(4aca3120-ae38-11de-8a39-0800200c9a66)]
interface xmIXMigemoFileAccess : nsISupports
{
AString getAbsolutePath(in AString filePath);
AString getRelativePath(in AString filePath);
AString getExistingPath(in AString absoluteOrRelativePath);
AString readFrom(in nsIFile file, in ACString encoding);
nsIFile writeTo(in nsIFile file, in AString content, in ACString encoding);
};
この時、メソッドの引数や返り値の型として、AStringやlongのようなプリミティブ型(?)だけでなく、nsIFile
のように他のインターフェースを使うこともできる。
この時気をつけないといけないのが、インターフェースには2つの識別子があるということ。1つは、上記の例でいえばinterface xmIXMigemoFileAccess
という部分で定義されているインターフェース名「xmIXMigemoFileAccess」、もう一つは[scriptable, uuid(4aca3120-ae38-11de-8a39-0800200c9a66)]
という部分で定義されているインターフェースID(IID)「4aca3120-ae38-11de-8a39-0800200c9a66」だ。
今回は、XUL/Migemoのコンポーネントの機能のうち、nsIDOMRangeやnsIDOMWindowを値の型として使っていた部分でエラーが起こっていた。
interface xmIXMigemoTextUtils : nsISupports
{
(略)
AString range2Text(in nsIDOMRange range);
(略)
具体的にはこの辺。どうも、Firefox 3.5から3.7a1preまでの間のどこかの時点で、nsIDOMRangeやnsIDOMWindowのIIDが変更されたらしい。XPIDLのコンパイル(.xptファイルの生成)には成功しても、そのあと3.7a1preのFirefoxが.xptを解釈する時に、IIDの方でnsIDOMRangeやnsIDOMWindowのインターフェース定義を探すために、「こんなIIDのインターフェースは定義されてないよ! インターフェースの情報が見つからないよ!」というエラーになっていたようだ。
とぴあさんに色々教えてもらった。元々、XPCOMの元になった(?)COMの世界つまりC++の世界では、インターフェースの識別にはIIDを使うのが原則というかIIDこそが本来の識別子で、nsIDOMRangeとかの名前はそれへの参照に過ぎないということなのだそうな。
インターフェースの内容が変化した時(メソッドの追加等)には、「古いインターフェースの定義が消されて、別のIIDで新たなインターフェースが定義された」というような扱いになるようだ。「IIDが変わった」と前述しているけれども、プログラム的には「IIDが変わっただけで同じインターフェース」なのではなく「全くの別物」という扱いだから、全く互換性は保証されない……というわけ。
なお、互換性を維持したままインターフェースに新しい機能を追加するためには、現在使われているインターフェースの定義はそのまま残して、それを継承した新しいインターフェースを定義する必要があるということになる。「nsIPrefBranch2」とか「nsIGlobalHistory3」などがそれにあたる。
nsIDOMRangeやnsIDOMWindowのIIDが3.7a1preのものと同じになっている新しいSDKを使って.xptファイルを作り直してやれば、3.7a1preでもXUL/Migemoが動くようになるはず。でも、そうすると今度はFirefox 3.5以下で動かなくなる。それは困る。
無難な解決策は、値の型として使うインターフェースを、古いFirefoxから新しいFirefoxまでの間でずっと変わっていないインターフェースにするということ。nsISupports(すべてのXPCOMの基底インターフェース)ならほぼ確実に使える。
interface xmIXMigemoTextUtils : nsISupports
{
(略)
AString range2Text(in nsISupports range);
(略)
このようにインターフェースの定義を変えた上で、実装の方で受け取った値をQueryInterface()
してやる。
range2Text : function(aRange) {
aRange.QueryInterface(Ci.nsIDOMRange);
var doc = aRange.startContainer;
(略)
こうすると、メソッドを呼ぶ時に、DOMのRangeオブジェクトをそのまま引数に渡せる状態を維持できる。
JavaScriptのレイヤからはIIDではなくヒューマン・リーダブルなインターフェース名だけを使うのが一般的なのだけれども、こうしておけばXPCOMのフレームワークが自動的に「新しいIIDのnsIDOMRangeインターフェース」を参照してくれる。実際にインターフェースで定義されている内容には変更が起こっているかもしれないから、確実な動作は保証できなくなるけれども、当該のインターフェースが「既にある機能はなくなったり変更されたりせず、新しい機能が追加されていくだけ」という傾向があるのなら、おおむね問題なく動作し続けてくれると考えられる。
とぴあさんが調べてくれたのだけれども、今回のトラブルの原因になったnsIDOMRangeは定義の頭の方に@status FROZEN
と書かれていて、本来であれば、IIDが変わることもなければメソッドやプロパティなどのインターフェース定義の内容が変わることもない、安心して永続的に使えるインターフェースだったはず……のようだ。
それが、Bug 396392 – Support for getClientRects and getBoundingClientRect in DOM Rangeに提出されたパッチでメソッドが追加されると同時にIIDも変更されてしまった。本当は、これはあってはならない事態らしい。当該バグのコメントでもnsIDOMRangeのIIDは元に戻して、変更はnsIDOMNSRange(Geckoの独自拡張の機能が色々定義されているインターフェース)に対して行うべきと書かれている。おそらく近いうちに、nsIDOMRangeのIIDは元の物に戻されて、XUL/Migemoが動かなくなってしまった問題も解消されるものと思われる。
個人的な感覚としては、インターフェースに変化が無くても実装が変わって挙動も変わりました……なんて事がMozillaではザラにあるので、インターフェースの部分でだけ「ちょっとでも変化があったらIIDは別の物! インターフェースとしても別物!」という風に厳密に区別しても意味なくね? と思う。ぶっちゃけ、「安心して使えるAPI」なんてのはMozillaの世界じゃリップサービスに過ぎないと思ってる。(とぴあさんには、それはプロジェクトのマネジメントがマズイという別のレイヤの問題だよねと言われた。)
あと、現在のFirefox(Gecko 1.9以降)では、自分で新しくインターフェースを定義してXPCOMコンポーネントを作る必要はほとんど無いと言っていい。JavaScriptでコンポーネントを定義してJavaScriptだけから使うのであれば、JavaScriptコードモジュールを使えばよくなった。また、起動直後に処理を行うような場合なんかには相変わらずXPCOMコンポーネントの定義が必要だけど、それは既存のインターフェース(nsISupportsやnsIObserver)だけでも事足りる。XPIDLが必要になるのは、JavaScriptで書かれた機能をC++のレイヤから呼び出したいような時だけだ。普通に開発する分には、こんな事で悩む必要は今や全くない。という事に気がついて、今更になって激しい徒労感を感じている。
表題の件について、どーも実際に表示されてる内容とセッション情報とが食い違ってるケースがあるようだ。
Firefoxのタブとかのセッション情報はJSONっぽい文字列で保存されてて、最初はJSON整形で読みやすい形にして調べようと思ってたけど、めんどすぎたので、以下のようなスクリプトでツリー構造の所だけ可視化してみた。
var sv = gBrowser.treeStyleTab;
var session = sv.SessionStore.getWindowState(window);
eval('session = '+session);
var result = [];
session.windows[0].tabs.forEach(function(aInfo) {
var entry = aInfo.entries[aInfo.entries.length-1];
var item = {
label : entry.title+' / '+entry.url,
id : aInfo.extData[sv.kID],
children : (aInfo.extData[sv.kCHILDREN] || '').split('|'),
parent : (aInfo.extData[sv.kPARENT] || ''),
items : []
};
var bullet = '*';
var tab = sv.getTabById(item.id);
if (tab.getAttribute(sv.kPARENT) != item.parent) {
item.label += '\n<WRONG PARENT>';
bullet = '?';
}
if (tab.getAttribute(sv.kCHILDREN) != item.children.join('|')) {
item.label += '\n<WRONG CHILDREN>';
bullet = '?';
}
item.label = item.label.replace(/^/gm, ' ').replace(/^./, bullet);
var current, index;
if (result.some(function(aItem) {
if (!aItem) return false;
if (aItem.items.some(arguments.callee)) return true;
current = aItem;
index = aItem.children.indexOf(item.id);
return index > -1;
})) {
if (current.items.length <= index) {
while (current.items.length < index) current.items.push(null);
current.items.push(item);
}
else {
current.items[index] = item;
}
}
else if (result.some(function(aItem) {
if (!aItem) return false;
if (aItem.items.some(arguments.callee)) return true;
current = aItem;
return aItem.id == item.id;
})) {
current.items.push(item);
}
else {
result.push(item);
}
});
var string = result.map(function(aItem) {
var children = aItem.items.map(arguments.callee).join('\n');
return children ?
aItem.label+'\n'+children.replace(/^/gm, ' ') :
aItem.label ;
}).join('\n')+'\n';
alert(string);
で、調べてみたら、やっぱりツリー構造がおかしい。ツリー表示はタブの属性値の方に基づいて行われてて、その属性値をnsISessionStoreのsetTabValue()
とdeleteTabValue()
でセッションの方にミラーしてるんだけど、ミラーされてるはずの値が期待通りにミラーされてないようだ。
追記。
……nsSessionStore.jsを読んでたら原因が分かった。
setTabValue()
では内部で最後にsaveStateDelayed()
を呼んでいるため、変更がファイル(プロファイルフォルダ内のsessionstore.js)にすぐ書き出される。deleteTabValue()
ではsaveStateDelayed()
が呼ばれてないために、他の処理の中でsaveStateDelayed()
が呼ばれるまでは変更がファイルに書き出されない。deleteTabValue()
だけで情報をミラーしたつもりでいると、ゴミ情報が残ったままになってしまうことが多々ある。そのゴミ情報が邪魔をして、期待通りにツリー構造が復元されなくなってしまっている。deleteTabValue()
する前にsetTabValue()
で空の値をセットして強制的にセッション情報を書き出させるようにしてみたところ、上記のスクリプトで調査してもセッション情報との間でのツリー構造の不整合は検出されなくなった。
結論:deleteTabValue()
マジ使えねえ……
追記。
それでも全然駄目だった。Firefoxがセッション情報をファイルに書き出す時、読み込み中であるというフラグが立っている(Firefox 3.5以前ではtab.linkedBrowser.parentNode.__SS_data._tabStillLoading
、Firefox 3.6以降ではtab.linkedBrowser.__SS_data._tabStillLoading
がtrue
である)タブについてはキャッシュされた情報を書き出すようになってるのに、このフラグがタブの内容の読み込み完了後も立ちっぱなしになってるせいで、常にキャッシュされた古い情報が書き出されてしまい、setTabValue()
で設定された新しい値が無視されてしまう。
結論:nsISessionStore/nsSessionStore.jsは腐ってる……
まとめると、以下のようなメソッドを使うようにしてやれば色々と幸せになれそうです。
setTabValue : function(aTab, aKey, aValue) {
if (!aValue) return this.deleteTabValue(aTab, aKey);
try {
this.checkCachedSessionDataExpiration(aTab);
this.SessionStore.setTabValue(aTab, aKey, aValue);
}
catch(e) {
}
return aValue;
},
deleteTabValue : function(aTab, aKey) {
try {
this.checkCachedSessionDataExpiration(aTab);
this.SessionStore.setTabValue(aTab, aKey, '');
this.SessionStore.deleteTabValue(aTab, aKey);
}
catch(e) {
}
},
checkCachedSessionDataExpiration : function(aTab) {
var data = aTab.linkedBrowser.__SS_data || // Firefox 3.6-
aTab.linkedBrowser.parentNode.__SS_data; // -Firefox 3.5
if (data &&
data._tabStillLoading &&
aTab.getAttribute('busy') != 'true' &&
aTab.linkedBrowser.__SS_restoreState != 1)
data._tabStillLoading = false;
},
2010年1月29日追記。Firefox 3.6以降とFirefox 3.5以前でフラグが保存される場所が少し違っていたので、その旨を修正した。
2010年9月27日追記。Firefox 4 の最適セッションリストア(原文)の影響によって、まだ実際にはセッションが復元されていないタブなのに、ビジー状態でなくなっているというケースがあり得るようになった。そのため、aTab.getAttribute('busy')
だけでビジー状態を判別すると、これからセッションを復元して欲しい・読み込み中のタブであるにも関わらず_tabStillLoading
をfalseにしてしまい、セッションが復元されなくなってしまうという問題が起こっていた。なので、タブの属性値と併せてaTab.linkedBrowser.__SS_restoring
も確認するようにサンプルコードを修正した。
2010年12月6日追記。aTab.linkedBrowser.__SS_restoring
が廃止されてaTab.linkedBrowser.__SS_restoreState
というプロパティが使われるようになっていたので、それにあわせてサンプルコードを修正した。
新宿マルイワン主催?のお茶会イベントが27日にマルイ本館屋上の庭園で開催されてまして、hknさんが2枚チケットを貰ったけど一緒に行く人のアテがなかったということで、ついて行ってみました。
イベントの中身としてはロリィタなブランドのファッションショーがメインと事前に告知されてたそうなので、そこに僕が混ざり込んでも大丈夫なのか?!と大変不安だったため、事前にパンク系ブランドの服(Sixh. ふわふわもさもさパーカ CMD4-Z808 BK-GY/M)をhknさんに見立ててもらいました。でもあとで分かりましたがフツーの服の人もちらほらいたので、そこまで気張らなくてもよかったようです。まあ買っちゃった以上は今後も着ますけど。先日の1983の飲み会でも着ました。
で、入場。
ロリィタのことはよく分かりませんが、かわいい衣装(読者モデルの人もめちゃめちゃかわいかった)を沢山見れて面白かったです。写真はhknさんのレポートに期待ということで。→hknさんのレポ
trunk gBrowserのloadOneTabとaddTabの引数が変わった - alice0775のファイル置き場 - Yahoo!ジオシティーズを見て初めて知ったけど、TrunkでgBrowser.addTab()
とgBrowser.loadOneTab()
の仕様が変わったようだ。
すでに追加されている「現在のタブの隣に新しいタブを開く」機能は、リファラが渡されていれば現在のタブの隣に、そうでなければタブバーの右端にタブを開くという挙動になっている。これに対し、リファラを渡さなくても現在のタブの隣に新しいタブを開けるようにしたい、という要望が出た(当然と言えば当然だ)。
それを実現するには、普通に考えると、gBrowser.addTab()
とgBrowser.loadOneTab()
の引数でそういう挙動を指定できるようにしてやらないといけない。しかしどっちのメソッドもすでに多数の引数を受け付けるようになってて(現状でもすでに6個ある!)、これ以上引数を増やすのってどうなん? と。関数の引数が多いのは悪い設計の典型例だ。こういう場面ではハッシュなりなんなりを使うのが定石ですわな。そこで件のバグが立ったと。
最初に提出されたパッチは、引数リストにさらにaRelatedToCurrent
を加えつつ、各引数に対応する値をプロパティに持つオブジェクトを2番目の引数として渡した時はそっちを使うようにするという風になってる。これだけ見ると「また引数増やすのかよ、しかも新方式(ハッシュによる指定)もサポートするのかよ。マンドクセ。」と思うところだけど、2番目に提出されたパッチでは引数の数の方は変更が無くて、aRelatedToCurrent
に相当する引数を指定したい時はハッシュを使わなければならないようになってる。実際にチェックインされた内容は後者のパッチの通りだ。
この事から、今後は新方式のAPI(新しいタブの挙動はgBrowser.addTab()
とgBrowser.loadOneTab()
の第2引数でハッシュで指定する)が標準となり、旧方式のAPI(gBrowser.addTab()
とgBrowser.loadOneTab()
に沢山の引数を渡す)はあくまで後方互換性のためにのみ残されている、という風に考えることができる。
参考までに、新旧それぞれの書き方を示しておこう。
// new API
var newTab = gBrowser.addTab('http://www.example.com/', {
referrerURI : referrer, // nsIURI
charset : 'Shift_JIS',
postData : null,
inBackground : true,
allowThirdPartyFixup : false,
relatedToCurrent : false
});
// old API
var newTab = gBrowser.addTab(
'http://www.example.com/',
referrer, // nsIURI
'Shift_JIS',
null, // postData
true, // inBackgorund
false // allowThirdPartyFixup
);
本当だったらもっと早く、Firefox 3.0になる前の時点でこういう事は済ませておくべきだったんだろうと思う(そのための「メジャーバージョン」でしょ?)。でもまあ、いつかはやらなきゃいけないことだ。新しい引数が追加されるというタイミングは、移行のいいきっかけではある。
彼女と東京ドームシティに行ってきた。遊んだレポ。
tokyo-emacsで何故か言及された話のことを考えてて改めて思ったんだけど。もうそろそろ本当に「非モテ」という言葉と自分を関連付けて語らない方がいいな……と思うようになってきた。
「非モテ」という言葉の定義自体が今となっては拡散しまくってて訳が分からんことになってるので、改めて自分の認識を示しておくけれども、僕は「非モテ」とは単なる「モテない」とは違うと思ってる。自分が他人から愛されないことを辛いと感じる。誰でもいいから誰か一人でも自分を必要として欲しいと思う。自分と自分を取り巻く世界との繋がりを欲している。人生の主役になれていなくて、主役になりたい。それを切実に感じ、満たされない思いに苛まれている。ということを僕は「非モテ」という言葉で言い表したいと思ってる。人によっては全然違う認識かもしれない。なのであくまでこれは僕が思う「非モテ」ということで。
益田ラヂヲさんほか何名かの方々から(ちゃんと把握してない。すんません。)再三言われてはいたけれども、なんか「居心地が良くて」決別できてなかった。でも、自分の今の心境であるとか置かれた状況であるとかを改めてよく考えてみて、「スゲー非モテの人(非モテでスゲー人? 非モテのスゲー人?)」と言われるには相応しくないと、今では思ってる。
一番大きかったのは、元彼女と別れた後、「あれ、一人で生きていけそう?」と思えたという事だと思う。付き合うまでや、つきあってからずっと、ひょっとしたらカウンセリングを受けるようになるまでずっと、「異性と付き合える」という事柄が何より大きな価値を自分の中で占めていた。他のどんな事で評価されていても、恋愛できない人間に生きる価値は本質的にはないんだろう、遺伝子を残せない奴に価値はないんだろう、世間はそういう人間を本質的には評価しないんだろう、そう思ってた。
世間的にはやっぱりそうなのかもしれない。やっぱり僕には価値がそれほど無いのかもしれない。でも、以前と違って今はわりと「別にそれでいいよ」「羨んだってしょうがないし」「どうせ手に入らないし」「それは僕の居場所じゃないし」と思うようになった。「自分に自分の居場所があるのならそれでいいじゃん」と思えるようになった。と思う。
書かなきゃ書かなきゃと思ってまだ書けてない加齢会のこともそうだ。人に言われて初めて「そうなのか!」と思ったのだけれども、そこにはちゃんと僕の居場所があったようで。その事に本当に今の今まで注意が向いていなくて。それはとても良くないことだと感じた。無自覚に人の厚意を食い潰して、「そうしてもらって当然」とすら思わない――意識すらしない、そんな風なのはとにかく良くない、と思った。
折しも、最近彼女ができて。これを機に、自分の中でひとつけじめをつけた方がいいんじゃないかという気がしてきたんで。だから「非モテ」という言葉と自分とをしつこく関連付けて語るのは、やめようと思う。少なくとも、自分がその代表(の一人)みたいなツラをしたり、そういう風に受け取られそうな事を言ったりするのはやめようと思ってる。自重しようと思う。
いや、彼女ができたら非モテ卒業、というのは多分違うんだろうと思う。自分の気持ちが楽になってないなら、本人にとっては何も救いになってないわけで。僕自身「あれ、なんか自分と非モテって言葉を関連付けるのに違和感があるな」と思えるようになったのは、前述の通り、元彼女と別れて以降だったわけで。でも、まあ、「別れた」の時に「非モテ」と自分とを切り離す機会を逸してしまったので、今がその次のちょうどいいきっかけのように思ったから。
今度の彼女との関係? うん、今の所良好だと思いますよ。(←デレデレした感じで)
NGワード:ラブプラス
tokyo-emacs #x02 行ってきた - 刺身☆ブーメランのはてなダイアリー
なんでEMACSの勉強会で僕の話題が出るの。全く訳が分かりません!!!
かくいう僕は秀丸ユーザです。Ubuntuではgeditです。
INIファイルから文字列を読み込んで、それを空白(カンマ、コロン、パイプ、など何でもいいけどとにかく区切り文字)で区切ってループ回す、ということをNSISでやろうと思ったらどうやるのがスマートなんだろうか。
とりあえず見よう見まねと試行錯誤でこんな感じに書いたら一応期待通りに動いた。
!include "LogicLib.nsh"
!include "WordFunc.nsh"
!insertmacro WordFind
(中略)
Var STRING
Var PART
Var INDEX
(中略)
StrCpy $STRING "aaa bbb ccc ddd"
StrCpy $INDEX "0"
${While} 1 == 1
IntOp $INDEX $INDEX + 1
${WordFind} $STRING " " "+$INDEX" $PART
${If} $INDEX > 1
${IfThen} $PART == $STRING ${|} ${Break} ${|}
${EndIf}
Call MyFunction
${EndWhile}
${WordFind}
の第1引数が元の文字列、第2引数が区切り文字で、第3引数に「その文字で区切った後の配列の何番目の要素か(添え字は1から始まる)」を指定すると、第4引数の変数に要素の内容に相当する文字列が格納される。0番目あるいは配列の長さ(に相当するもの)よりも大きな数字を第3引数に渡すと、返り値は元の文字列そのままとなるので、これを脱出条件としてループさせてみた。
9月2日追記。配列(のようなもの)の長さが1つの時のことを考慮してなかったので修正した。
Twitterのプロフィール画像の由来を聞かれたので、切り出し元となったW3C子のマンガをFlickrにアップロードしてみた。
3話目はネーム考え中で放り投げちゃったので、続きはありません。誰か続き考えてくれたら描くかもね。
で、なんでプロフィールがこのコマかというと、自分をモデルにした絵でなるべくマヌケな奴を使いたかったからです。
追記。Eric Meyer(CSSのえらいひと)が英語に翻訳してくれと言っているようです。なんという展開……