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 79/239: « 75 76 77 78 79 80 81 82 83 »

エイリアン展:他の星にはどんな生き物たちが住んでいるんだろう? - May 12, 2008

お台場の日本科学未来館でやってるエイリアン展に、こないだの土曜日に行ってきた。一人で。

入っていきなり、リプリーと対峙したあのクイーンエイリアンがいてびびった。Zone1はいわゆる空想上のエイリアンの紹介で、宇宙人好きには多分よく知られてる「墜落したUFOから発見された宇宙人の死体」もあった。死体っていうか、台の上でひくひく動いててキモいんですけどー!! エイリアン、と聞くとこんなのをイメージする人が多そうだし実際僕もそうなんだけど、まさにそんな感じのものが色々置いてあった。

Zone2とZone3はもっと別次元の話。宇宙船に乗って飛んできたりするような高度な知的生命体としてのエイリアンではなく、E.T.(Extraterrestrial life)という言葉の意味そのままの、「地球じゃない環境で生きている生命体とはどんな物なんだろうか」ということを紹介している。Zone2では地球上の生き物についての展示がたくさんあって、地球上ですら生命の形は千差万別なんだということが色々な例を挙げて紹介されていた。

Zone3では二つの仮想の星を想定して、その星の自然環境からどんな生態系が成り立つのかを科学的に想像し、その結果を紹介していた。このZone3の内容は元々イギリスのテレビ番組のために作られたものらしく、売店でもDVDが売られてたのでつい買ってしまった。

E.T.の住む星 BOX
E.T.の住む星 BOX
posted with amazlet at 08.05.12
アートポート (2006-09-29)
売り上げランキング: 25316

赤色矮星のごく近くを回るために自転が止まってしまっている惑星「オーレリア」と、連星の太陽のまわりを回る巨大ガス惑星のさらにそのまたまわりを回る衛星「ブルームーン」それぞれについて、大気や気候を考えて、その環境に適合した生命とはどんなものか、その生命が組み込まれている生態系とはどんなものか、ということを考えた上での想像上の生態系が再現されている。僕が特に面白いと思ったのはブルームーンの方で、大気が濃い=空気の密度が高い=空を飛ぶのが容易ということから、空を飛ぶ生命が多いだろうと考えたという話。SFものとかで特に大きな意味もなく地球とよく似た植物が生い茂っていたり、あるいは巨大な生物が特になんの説明もなく飛行していたり、というのを目にするけども、こうやって根底の部分(大気の組成)から考えると面白いものだなあ、と。

帰ってからDVDを見たけど、映像の中では語られていないようなもっと詳しい情報が展示にはあって(例えば、スカイホエールの翼はハニカム構造であるとか、ブルームーンの生命は三相性の物が多いとか)、じっくりゆっくり見てくればよかったなあと思った。

Zone4はSETIの話。地球外知的生命体との交信の試みについての展示と、来場者同士でのメッセージ交換を星々の間での交信に見立てたインタラクティブアートみたいな物があった。SETIといえば、SETI@homeはネットに繋ぎ始めた頃からずっとやってるけど、僕が生きてる間に成果が上がることはあるんだろうか……

当初考えていたより科学的な内容がずっと多くて(まあ会場が会場だから当たり前か)、しかし目で見て分かりやすいようなかみ砕いた展示内容で、子供から大人まで楽しめそうないいイベントだなと思った。子供連れやカップルも多く来てた。あと中国人の人も多かったようだ……このためにわざわざ飛行機で来たんだろうか?

行ったのが昼過ぎでエイリアン展を見終わった頃にはもう閉館時間だったので、常設展示の方は見れなかったんだけど、次に来る時はそっちも見てみたい。

ヲタファッション革命 - May 08, 2008

ヲタファッション革命第一弾

おお。いい結果になったみたいでよかった。オサレ小物まで行くと僕にはちょっと付いていけんけど……

これのBeforeとAfterの違いが分からん(どっちもダセえ)と思う人というのは、きっとオシャレさんなんだろう。そういう人達と、脱オタしたいと思ってる僕達みたいな人とでは、そもそも目指してる物が違うんだから、まあ、そういう人達から辛辣な意見が出てきたって、それは無視してイイと今では思える。

彼らは「いかに凡人から抜きん出るか」とか「いかに自分が楽しめるか」を目指しているんだと思うけど、そうじゃなくて僕らが求めてるのは、「凡人から二歩も三歩も出遅れて悪目立ちしている状態から、いかにして凡人レベルまで引き上げて溶け込むか」「いかにして最下層から脱して、劣等感を感じずに済むようになれるか」っていうこと。

敢えて露悪的な言い方をすれば、仮に物凄いオシャレさんな人達から見て「プッ、ダセえ」と思われるような服装であったとしても、それでもまだ「見下せる先がある状態」になれてはいる。それだけでもう僕らにとっての「脱オタ」は十分に意義あることだと思う。『下の下』でも『下の中』でも『下の上』でもない、『中』のレベルになれた、その間にある谷を越えられた、っていうことが大事。

そもそも他人の目線をいつも気にして劣等感を抱いて「脱オタしたい」なんて言っちゃってるような自意識過剰な弱虫たる僕らのような人種は、その程度のことで劣等感を解消できてしまえる、満足できてしまえるのですよ。オシャレさんな人達が「自分が楽しむために」「自分が満足するために」ファッションにコストをかけるのと同じで、僕らもまた、「自分の心を満足させるために」そこにコストを投じているわけで、本質的に別に間違ったことをしているとは思えない(まあ、動機のベクトルは不健全に歪んでると思いますけどね)。

それに「誰にも見下されたくない」とまで思ってしまうと、どこまで行ってもきりがないじゃないですか。「とりあえず世間の平均から見下されずに済むレベル(平均の平均までいかなくても、『中の下』までいければ『中』には引っかかれている)まで上れたら、それでいい」と思えるレベルは、コストパフォーマンス的に最も妥当な落とし所だと思う。

……とまで書くときっと言いすぎで、リンク先の記事で「革命」した人はここまで黒い感情を抱いてはいないと思うし、大多数の脱オタ希望者ももっと健全な物に近い感覚ではあるんだろうけど、まあ、そーいうタチの悪い人もここにいるんですよってことで。

結婚 - May 08, 2008

仕事がきっかけで知り合った方が、今度結婚されるそうな。

まともな人間関係を築けるようになった?と思う高校くらいからの人間関係で結婚した人達を思い出しながらカウントしてみたら、10組を超えてた。交友関係の狭いひきこもり性の僕としては、結構驚くような数字なんだけど。

僕より年上で結婚した人も僕より若くて結婚した人も色々いて、というか同い年・同学年の友達というのが少ないので、いわゆるひとつの「みんな結婚していっちゃって……」的な感覚はイマイチ薄い。こう、なんというか、僕がこうやってぼんやり生きてる間に時間というのは流れていく物なんだなあとか、みんな色々考えて生きてんだなあとか、そういうことを、やっぱりボンヤリした頭でポカーンと口開けて眺めてる、そんな感じ。

羨ましいかどうかで言えば、多分、羨ましいとは思う。しかし今も昔も、どこか遠い世界の出来事の話にしか感じられないなあ、という感覚が強い気がする。なんだろうなあ。新婚とか同棲とかそういうのを取り扱った漫画、恋愛物だとかラブコメだとか、そういうのを結構な量消費してきて、そういうのを消費したからこそ、そういう物を消費物として楽しんできてしまったからこそ、自分自身のことについては明るい未来のイメージが思い浮かばないというか。言葉にするのが難しいのだけれども、こう、前述したような甘々な物を好んで消費するという僕のメンタリティ故に、それが却って宜しくない結果を招きそうだなあ、みたいな。

親にゲーム買って貰えないから攻略本だけ買ってスミズミまで読んでそのゲームを味わい尽くした気分になって、それで満足できてしまってそのうちゲーム本編の方に関心が無くなってしまってほんとにやらないうちから飽きてしまったりとか、あるいはその逆に、期待に無駄に胸膨らませてしまって、実際プレイしてみたら期待したほど面白くなくてガッカリするとか。そんなんするくらいだったら最初から誰かに貸してもらってゲーム本編をプレイするなり、プレイできるようになるまで情報断って我慢するなりっていうのが、健全なあり方っていうものなんじゃないか? なんでそういう健全な生き方を僕はできない・しようとしないんだ? というかなんというか。

考えがまとまらない。

まあそんな風に僕はうじうじ自問自答してしまわずにはおれんのですけど、そんな事は関係なくて、というか皆さん僕よりずっと健全に生きれてると思うから、みんなは幸せになって下さいなと。妙に後ろ向きなエールを送る次第です。

Gmail宛にメールを片っ端から転送すると元のアドレスのドメインが受信拒否されるようになることがある、という問題 - May 07, 2008

@gmail.com宛メールの遅延について - www.ecc.u-tokyo.ac.jp等で警告されているけれども、Gmail宛にメールを転送すると自分が本来使っている元のメールアドレスのドメイン全体が受信拒否転送元となるメールサーバからの接続が拒否(あるいはそれに近い制裁措置を)されるようになってしまうそうだ。

何が起こっているのかというと、こういうことらしい。例として、自分の本アドレスが「example@example.jp」、Gmailのアドレスが「example@gmail.com」だとして説明してみる。

  1. メールアドレス「example@example.jp」宛のメールが届くメールサーバや手元のメールソフトで、受信したメールを自動的にexample@gmail.com宛に転送するように設定する。
  2. spamも含めてexample@example.jp宛に届いたメールすべてが、example@example.jpから自分が利用しているメールサーバからexample@gmail.com宛に転送されるようになる。
  3. Gmailから見るとexample@example.jpからそのメールサーバから大量のspamが送信されてきているように見えるので、Gmailはexample.jpドメインからのメールそのメールサーバが踏み台としてspamの送信に悪用されていると判断して、そのサーバからの通信に対して一律で制裁措置を取るようになる。
  4. 自分もexample.jpドメインのアドレス同じメールサーバを使っている他のユーザも、他の人のGmailのアドレス宛に普通に送ったメールが制裁措置のせいで届かなくなる。

という感じ? 色々誤解してたみたいなので直してみたけど間違ってるかも。とにかく通信がうまくいかなくなるらしいということではあるそうだ。

こうなることを防ぐには、「example@example.jpのメールサーバで転送設定をする時に、spamを除外した後のメールだけをGmail宛に転送するようにする」というのも一つの手だけれども、一番確実なのは、そもそも「転送」という手段を使わないで、Gmailの側でMail Fetcherを使ってexample@example.jp宛のメールを吸い上げるようにするということ(※ただし、GmailのMail Fetcherの場合、その本アカウントでPOPを利用できる必要がある。リンク先を開くと英語のFAQが表示されることがあるようだけど、右上の言語切り替えで「日本語」を選ぶと日本語の説明が読める)。

Mail Fetcher機能を使うと、example@example.jp宛に送られたメールをGmailが普通のメールソフトのように勝手に見に行くようになるので、前述したようなspam大量配信と見なされることもない、はず。その上でGmailのアカウントでPOPアクセスやIMAPの設定をしておいて、手元のメールソフトからGmailを見に行くようにしておけば、GmailがMail Fetcher機能でexample@example.jpから吸い上げたメールを、手元のメールソフトで見られるようになる。もちろんMail Fetcherが吸い上げた時点でspamメールはGmailのフィルタによってふるい落とされているので、Gmailをspamフィルタ代わりに使うのならこうした方がいいと思う。

ここではGmailを例に挙げたけど、同じような制裁措置をする無料メールサービスがいくつか出てきているそうなので、他のサービスでも何でもとにかくメールの転送を使ってる人は、自分が使ってるやり方について見直しといた方がいいかもね。

ちなみに、これは「すでにGmail(などのサービス)から制裁措置を喰らってしまっているドメインのメールアドレスからとりあえず自分だけでもGmail宛にメールを送れるようになる方法」ではなくて、あくまで「自分が使っているメールアドレスと同じドメインのメールアドレスを使っている他の人に、今後迷惑をかけないための方法」なので、そこのところを勘違いしないように。

数日ぶりに外出した - May 07, 2008

連休中一歩も家から出ないで不毛なことに精を出していたのだけれども、ほんとに一歩も家から出ないままだと人としてどうかと思ったので、夕方から外出してみた。

本当はエイリアン展に行こうと思ってたんだけど、朝になって火曜日休館だって事に気がついたから……

  • クリーニングに出してから1ヶ月近くなんだかんだで放置してしまっていた(店の前を通る時には終業後だったり、休みの日はやっぱり一歩も外出しなかったりで)礼服を受け取ってきた。
  • 自転車のブレーキが本格的に全然きかなくなってたことに恐怖を覚えたので、工具を引っ張り出してきてブレーキのワイヤーの締め付けを少し調整してみた。
  • 図書館(引っ越してきてから一度も行ったことがない)に行ってみたら連休中は休館だった。
  • 近所にあるけど一度も行ったことがなかった回転寿司屋に行ってみた。味の方はまあまあかな……(と、偉そうなことを言ってはみるものの大して味の分からん人なので、これで満足できるんだから、寿司食いたくなった時はここでいいやと思った)
  • 本屋に行ってSBRとイキガミとマーガレットを買ってきた。
  • シークレットシークレットのPVを見ててむらむらと欲しくなったのでpinoを買って帰ったが、コンビニにあったのは26個入りの物でPerfumeが食べていたトレイの上に載ってるやつじゃなくてちょっと残念だった。ピックは外れた。

不毛な連休の、不毛な最終日でした。

Safari風アニメーションの実現方法と、クリック時にスクリーンを表示しない設定について - May 06, 2008

検索がヒットした箇所をハイライト表示していて「次候補」「前候補」を辿る時、フォーカスした要素をアニメーションで強調表示させる機能について、今までは簡易的な実装としてspan要素をposition:relativeにしてtopプロパティをいじることで「ぴょこん」とジャンプするような効果を付けてたんだけど、0.8.4でパクリ元のSafariと同じようなアニメーション効果(フォーカスされた箇所が一瞬拡大される)にするようにした。まああくまで擬似的な物なんですが。

検索にヒットした箇所にアニメーション効果を表示するやり方としては、canvasを使う方法など色々考えられましたが、思いつく限り最も単純なやり方で、要素をコピーして絶対配置するという方法で実装しました。これはText Shadowで折り返されたテキストに影を付ける方法を考えた時に思いついた手法の応用で、こんな風にしてます。

前のテキスト
<span style="position: relative;">
  ハイライトされたテキスト
  <span style="position: absolute;
               top: -0.2em;
               bottom: -0.2em;
               left: -0.2em;
               right: -0.2em;
               font-size: 1.02em;">
    ハイライトされたテキスト<!-- 複製されたノード -->
  </span>
</span>
後のテキスト

position: relativeなインライン要素の中にposition: absoluteな要素を置くと絶対配置の基準がそのインライン要素になる、というCSSのポジショニングの特性を利用して、同じ位置に配置しています。また、top/bottom/left/rightの各プロパティにマイナスの値を設定することで、四辺が親のボックスより大きくなるズームっぽい効果が得られます。フォントサイズも一応いじってますが、あんまり分かりませんね。

手抜きなので、折り返された語句は正しく表示できません。あと、Safariみたいにアニメーションが終わった後もその箇所を特別に強調する、という効果は付けてません(そのうちやるつもり)。

もう一つ、0.8.5での改良点。0.8.2からSafari風強調表示を有効にした状態で半透明のスクリーンの下に隠れているリンクなどをクリックした時にクリックイベントを再送するようにしましたが、この半透明のスクリーンはクリックすると消えてしまうため、ミドルクリックなどで新しいタブでリンクを開いた時にも強調表示が解除されてしまうという欠点がありました。そこで、強調表示を解除しない例外的な操作の設定(正確には「この操作だった場合は一度消した強調表示を自動的に再表示する」という機能なんですが)をできるようにしてみました。

デフォルトでは、ミドルクリック、Ctrl-左クリック(リンクを新しいタブで開く)、Alt-クリック(リンク先を保存)、Shift-クリック(リンクを新しいウィンドウで開く)あたりの操作に対して、強調表示の状態を維持するように設定してあります。他のアドオンを使ってすべてのリンクを常に新しいタブで開いているようにしているから、そういうケースでも強調表示を解除しないようにしたい、という場合には設定をabout:configあたりで編集する必要があります。

この動作を決めている設定はxulmigemo.highlight.hideScreen.restoreButtonsという文字列型の設定です。値は「1,0+1,0+2,0+4,0+8,0+6,0+12」という風なカンマ区切りのリストになっていて、一つ一つが「この場合には強調表示を維持する」という場合の指定になっています。例えば「1」は「ミドルクリック」、「0+2」は「Ctrl-左クリック」を意味しています。プラス記号の左側はボタン番号(0=左クリック、1=ミドルクリック、2=右クリック)で、プラス記号およびその右側の数字はモディファイアキーの指定です(このパートは省略可能)。

モディファイアキーはnsIDOMNSEventの定数プロパティで定義されているフラグで指定します。Altキーは1、Ctrlキーは2、Shiftキーは4、Metaキー(MacのCommandキー)は8で、複数のキーを同時押しした場合を指定するにはそれぞれの数値を足した数を指定します。例えば「0+6」と書いた場合、プラス記号の右側の6は2と4の合計なので、「Ctrl-Shift-左クリック」の意味になります。このフラグ指定の意味がよく分からないという人はビット演算の話を見て下さい。

ハイライト表示のためにテキストフィールド内に挿入されたspan要素が邪魔になる件 - May 04, 2008

前のエントリの続き。

Safari風ハイライトに限らず元々、Firefoxの検索での「すべて強調表示」では、背景色と文字色を指定したspan要素を検索がヒットした箇所に動的に埋め込むという形で、ハイライト表示を実現している。これはinput要素やtextarea要素の場合でも全く同じ。実はFirefoxではテキスト入力欄もすべて、内部的には編集可能なHTMLとして実装されていて、それ故にspan要素の埋め込みも可能になっている。

ただ、この時span要素が埋め込まれる先のDOMツリーはchildNodesとかのプロパティでは辿れない場所にあって、アクセスするにはこんな風にする必要がある。

var editable = content.document.getElementsByTagName('textarea')[0];
var nodesInEditable = editable
     .QueryInterface(Components.interfaces.nsIDOMNSEditableElement)
     .editor
     .rootElement
     .childNodes;

textareaでこれをやってみると、改行が内部的にはbr要素で表現されているとかそういうのも見て取れる。nsIFindで検索する時はテキストフィールド内のこうした「隠しDOMツリー」も普通に検索対象になるようで、span要素を埋め込む時も特に変わったことはしなくていいようだ。

しかし、ここで一つ問題がある。こうしてテキストフィールド内に普通のspan要素を埋め込んでしまうと、その要素は、選択も内部の文字の編集もできない、ワープロでいえば埋め込まれた画像みたいな状態になってしまう。普段は特に意識せずに済むけど、常に強調表示を有効にするようにしていると当然テキストフィールド内でハイライト表示が行われることになる場合も多くなり、この問題が目につくようになってくる。というか僕自身がテスト用ドキュメントでテストしていて、いいかげんウザくなってきたのでなんとかしたかった。

理想的には、テキストフィールドにフォーカスされた時に自動的に強調表示を解除するという風な挙動にできるとよかったんだけど、試してみるとどうもなかなか大変そうだということが分かった。挿入されるspan要素にonclickなどの属性でイベントハンドラを設定してみたところ、getAttributeやdispatchEventなどのメソッド、あるいはparentNodeなどのプロパティを参照しようとするとパーミッションエラーが表示されてしまった。これではnode.parentNode.removeChild()という風なことができないし、独自イベントを発行してChrome領域のスクリプトに後の処理を任せるということもできない。

これについては幸いにも、createRangeなどの機能は使うことができるようだったので、deleteContentsやextractContentsを使って自分自身を削除させるようにはできた。強調表示された箇所を選択して「選択範囲のソース」を表示してみれば、こんな風になっていることが分かると思う。

<h1>B.B.S. <span class="sub"><span onmousedown="
  try {
    var xpathResult = this.ownerDocument.evaluate(
        'ancestor::*[contains(&quot; INPUT input TEXTAREA textarea &quot;, concat(&quot; &quot;, local-name(), &quot; &quot;))]',
        this,
        null,
        XPathResult.FIRST_ORDERED_NODE_TYPE,
        null
      );
    if (!xpathResult.singleNodeValue) return;
  }
  catch(e) {
    // permission denied, then this is in the input area!
  }
  var range = document.createRange();
  range.selectNodeContents(this);
  var contents = range.extractContents(true);
  range.selectNode(this);
  range.deleteContents();
  range.insertNode(contents);
  range.detach();
" id="__firefox-findbar-search-id" style="...">掲示板</span></span></h1>

無駄とは分かっていても一応、正攻法の判別処理も入れてある。テキストフィールド以外の部分に挿入されたspanにも全部このイベントハンドラが設定されてしまうのは、挿入先に応じて挿入する内容を変えるのがめんどかったから。まあとりあえず、これがあっても正常に動かなくなるわけではないし、別にいいかなと。

これだとキーボード操作に対して反応させることができない(そもそもカーソルをspanの中に移動できない)し、本当は特にクリック等の操作をしなくても、テキストフィールドにフォーカスが当たった時点で強調表示を解除するという風な挙動を実現したかったわけで、そこら辺まだまだ改善の余地はある。

XUL/MigemoでのSafari風ハイライト処理で、要素の下にあるリンクにクリックイベントを送る - May 04, 2008

一つ前のエントリの続き。

「すべて強調表示」の時に強調箇所以外を暗くするというSafari風ハイライト表示は、元はSafariHighlightを取り込ませてもらったものなんだけれども、基本的には、「画面の最全面に半透明の黒いボックスを表示して全体を覆う」「その上にz-indexを調整して強調箇所を浮かび上がらせる」という二つの操作が鍵になっている。で、このうち前者の方の操作のせいで、「暗くなった所にあるリンクをクリックしても、ハイライトが解除されるだけで、リンク先には飛べない」という問題が起こっていた。

まあ、問題というか実装上そうならざるを得ないという感じで、本家のSafariもこういう仕様だったと思うからまあいいじゃんと思わなくもないんだけど、要望はあるようなので対応しなきゃなーと思ってた。で、この度晴れて対応してみた。highlight.jsの最後の方に付け加えたresendClickEventメソッドがそれ。

resendClickEvent : function(aEvent) 
{
  var utils = aEvent.view
    .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
    .getInterface(Components.interfaces.nsIDOMWindowUtils);
  if ('sendMouseEvent' in utils) { // Firefox 3
    var flags = 0;
    const nsIDOMNSEvent = Components.interfaces.nsIDOMNSEvent;
    if (aEvent.altKey) flags |= nsIDOMNSEvent.ALT_MASK;
    if (aEvent.ctrlKey) flags |= nsIDOMNSEvent.CONTROL_MASK;
    if (aEvent.shiftKey) flags |= nsIDOMNSEvent.SHIFT_MASK;
    if (aEvent.metaKey) flags |= nsIDOMNSEvent.META_MASK;
    window.setTimeout(function(aX, aY, aButton) {
      if (ZoomManager.useFullZoom) { // Firefox 3のフルズームへの対応
        aX = aX * ZoomManager.zoom;
        aY = aY * ZoomManager.zoom;
      }
      utils.sendMouseEvent('mousedown', aX, aY, aButton, 1, flags);
      utils.sendMouseEvent('mouseup', aX, aY, aButton, 1, flags);
    }, 0, aEvent.clientX, aEvent.clientY, aEvent.button);
  }
  else { // Firefox 2, emulation
    var args = [
        'click',
        aEvent.bubbles,
        aEvent.cancelable,
        aEvent.view,
        1,
        aEvent.screenX,
        aEvent.screenY,
        aEvent.clientX,
        aEvent.clientY,
        aEvent.ctrlKey,
        aEvent.altKey,
        aEvent.shiftKey,
        aEvent.metaKey,
        aEvent.button
      ];
    window.setTimeout(function(aSelf, aFrame, aX, aY) {
      var node = aSelf.getClickableElementFromPoint(aFrame, aX, aY);
      if (!node) return;
      var event = aFrame.document.createEvent('MouseEvents');
      args.push(node);
      event.initMouseEvent.apply(event, args);
      node.dispatchEvent(event);
      if ('focus' in node) node.focus();
    }, 0, this, aEvent.view, aEvent.screenX, aEvent.screenY);
  }
},

getClickableElementFromPoint : function(aWindow, aScreenX, aScreenY) 
{
  var accNode;
  try {
    var accService = Components.classes['@mozilla.org/accessibilityService;1']
              .getService(Components.interfaces.nsIAccessibilityService);
    var acc = accService.getAccessibleFor(aWindow.document);
    var box = aWindow.document.getBoxObjectFor(aWindow.document.documentElement);
    accNode = acc.getChildAtPoint(aScreenX, aScreenY);
    accNode = accNode.QueryInterface(Components.interfaces.nsIAccessNode).DOMNode;
  }
  catch(e) {
  }

  var filter = function(aNode) {
    switch (aNode.localName.toUpperCase()) {
      case 'A':
        if (aNode.href)
          return NodeFilter.FILTER_ACCEPT;
        break;
      case 'INPUT':
      case 'TEXTAREA':
      case 'BUTTON':
      case 'SELECT':
        return NodeFilter.FILTER_ACCEPT;
        break;
    }
    return NodeFilter.FILTER_SKIP;
  };

  if (accNode &&
    accNode.nodeType == Node.ELEMENT_NODE &&
    filter(accNode) == NodeFilter.FILTER_ACCEPT)
    return accNode;

  var doc = aWindow.document;
  var startNode = accNode || doc;
  var walker = aWindow.document.createTreeWalker(startNode, NodeFilter.SHOW_ELEMENT, filter, false);
  for (var node = walker.firstChild(); node != null; node = walker.nextNode())
  {
    var box = doc.getBoxObjectFor(node);
    var l = box.screenX;
    var t = box.screenY;
    var r = l + box.width;
    var b = t + box.height;
    if (l <= aScreenX && aScreenX <= r && t <= aScreenY && aScreenY <= b)
      return node;
  }
  return null;
},

見ての通り、Firefox 3ではnsIDOMWindowUtilsの新機能を使ってイベントを発行するようにして根本的解決を図り、Firefox 2ではTabScope由来のコードにnsIAccessibleを組み合わせたものを使ってそれっぽいことをしている、という感じです。どっちもタイマーを使って処理を遅らせているのは、画面全体を覆っているスクリーンが消えてからイベントを発行しないと、もう一度スクリーンをクリックしたのと同じ事になってしまうから。

最初、nsIDOMWindowUtilsのsendMouseEventでclickイベントを発行しようとしてうまくいかなかったんだけど、どうやらこれはDOMのイベントをそのまま発行するわけではなく、本当の本当にユーザの操作をエミュレートするという物みたいだ。間を開けずmousedown→mouseupと実行するとクリックしたのと同じ事になる。

強調表示関係ではこのほかに、テキストフィールド内の強調を自動的に解除するようにするための改良もあります。これも長くなったから別エントリで。

XUL/Migemoのリファクタリングとか改善とか - May 04, 2008

pXMigemoFind(pIXMigemoFindの実装)、特にfindメソッドまわりを中心にだいぶ書き直した。といってもアルゴリズム的には変わってなくて、主にメンテナンス性を向上することを目的にした書き換えです。こういうのもリファクタリングと言っていいんでしょうか。

findInDocumentメソッドがかなり長くて中のループのネストも深くなってたので、これをだいぶ細かく分けた。

まず、フレームのツリーを辿る処理がループの最後の方にどかっとあったので、これを取り出してIteratorパターンのヘルパーオブジェクトとして実装することにした(最後の方にあるDocShellIteratorというやつ)。最初Iteratorパターンというのを知った時には「こんなの何の役に立つんだ?」とか「配列みたいに長さを取り出せた方が便利じゃね?」とか思ってたけど、こうして実際に書いてみると、「次に処理対象にする物を探す」部分だけに特化して作り込めるので便利だ、ということがよく分かった。

具体的には、今までは前方検索と後方検索それぞれの場合で「一つ前のフレーム」に処理を移すのか「一つ後のフレーム」に処理を移すのかをいちいち判別してたんだけど、その判別を行う部分まで含めてDocShellIteratorとして分離することで、findInDocumentの側では何も考えずにDocShellIteratorのiterateNextメソッドを呼べば適切な結果が帰ってきてウマーという風にできるようになった。

実際のコードで見ると、findInDocument側は

docShell = this.getDocShellForFrame(doc.defaultView)
  .QueryInterface(Components.interfaces.nsIDocShellTreeNode);

if (aFindFlag & this.FIND_BACK) { // back
  docShell = this.getPrevDocShell(docShell);
  if (!docShell) {
    if (!(aFindFlag & this.FIND_WRAP)) {
      docShell = this.getDocShellForFrame(doc.defaultView.top);
      docShell = this.getLastChildDocShell(docShell.QueryInterface(Components.interfaces.nsIDocShellTreeNode));
      doc = docShell
        .QueryInterface(Components.interfaces.nsIDocShell)
        .QueryInterface(Components.interfaces.nsIWebNavigation)
        .document;
      this.document.commandDispatcher.focusedWindow = docShell
        .QueryInterface(Components.interfaces.nsIDocShell)
        .QueryInterface(Components.interfaces.nsIWebNavigation)
        .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
        .getInterface(Components.interfaces.nsIDOMWindow);
      if (
        !editableInOut ||
        findRange.sRange.startContainer == aDocument.body ||
        findRange.sRange.startContainer == aDocument.documentElement
        )
        aFindFlag |= this.FIND_WRAP;
      continue;
    }
    this.dispatchProgressEvent(found, aFindFlag);
    break doFind;
  }
}
else { // forward
  docShell = this.getNextDocShell(docShell);
  if (!docShell) {
    if (!(aFindFlag & this.FIND_WRAP)) {
      doc = Components.lookupMethod(doc.defaultView.top, 'document').call(doc.defaultView.top);
      this.document.commandDispatcher.focusedWindow = doc.defaultView.top;
      if (
        !editableInOut ||
        findRange.sRange.endContainer == aDocument.body ||
        findRange.sRange.endContainer == aDocument.documentElement
        )
        aFindFlag |= this.FIND_WRAP;
      continue;
    }
    this.dispatchProgressEvent(found, aFindFlag);
    break doFind;
  }
}
doc = docShell
    .QueryInterface(Components.interfaces.nsIDocShell)
    .QueryInterface(Components.interfaces.nsIWebNavigation)
    .document;
if (doc == aDocument) {
  this.dispatchProgressEvent(found, aFindFlag);
  break doFind;
}

こーんなだったのが

aDocShellIterator.iterateNext();

if (aDocShellIterator.wrapped) {
  if (!(aFindFlag & this.FIND_WRAP)) {
    this.document.commandDispatcher.focusedWindow = aDocShellIterator.view;
    if (
      !editableInOut ||
      !rangeSet ||
      aDocShellIterator.isRangeTopLevel(rangeSet.range)
      )
      aFindFlag |= this.FIND_WRAP;
    continue;
  }
  this.dispatchProgressEvent(aFindFlag, resultFlag);
  break;
}

if (aDocShellIterator.isInitial) {
  this.dispatchProgressEvent(aFindFlag, resultFlag);
  break;
}

ここまでスッキリしました。まあその代わり、フレームのツリーを辿る処理(DocShellIteratorの方がちょっと長くなってしまったんだけど、それぞれロジック的には綺麗に分かれているから、今後手を入れる時は「ツリーを辿ること」と「検索すること」のどっちか一方のことだけに集中して作業できるわけで、これこそがイテレータパターンの最も大きな意義だったんですね。デザインパターン万歳。

あと、findInDocumentの中でループの中でループを回していた部分をfindInDocumentInternalメソッドとして分離した。これはロジックを分離する効果はなくて、単にネストを浅くしてコードを見やすくするためだけの変更。のつもりだったんだけど、これをうまくやるために、他の部分も含めてだいぶ手直ししないといけなかった。

何故かというと、単純に内側のループだけをfindInDocumentInternalとして分離したところで、元のfindInDocumentとfindInDocumentInternalとの間でお互いに引き渡さないといけない情報(引数として引き渡す情報と、返り値として戻す情報の両方)が多すぎて、そのまま二つに分けただけだと却って分かりにくくなってしまったから。普通にreturnするだけじゃ情報を渡しきれないから、オブジェクトを引数で渡して、そのオブジェクトのプロパティとして返り値を設定して、変更されたプロパティを呼び出し元の側でまた参照して……という感じになってしまって、せっかくメソッドを分けたのにそこの所でガッツリ繋がってしまってて、これじゃ分けた意味がないよと。

というわけで最低限必要な情報だけに絞り込んでやりとりするように設計を検討したのに加えて、インターフェースの定義も見直して、今まであんまり有効活用してなかったビット演算を多用するようにしたことで、findInDocumentInternalからの返り値は最終的にビット列一つだけにまでまとめることができた。ビット演算いいよビット演算。

ビット演算を有効に利用するためには、どの意味をどのビットに与えるかというのをよく考えておかないといけない。とかなんとか大仰な言い方をしてみたけど、要するに、適当に定数プロパティを名前順に並べて先頭から0, 1, 2, 4, 8……と値を割り振っていくなんていいかげんな事してちゃ駄目だよってことですね。0.8.1まででは


const unsigned short FOUND             = 0;
const unsigned short NOTFOUND          = 1;
const unsigned short WRAPPED           = 2;
const unsigned short NOTLINK           = 4;
const unsigned short FOUND_IN_EDITABLE = 8;

こんなだったけど、これじゃあ実質的にはただの定数値が並んでるだけで単純比較にしか使えない。FOUNDが0だったり「検索はヒットしたけどリンクじゃないからヒット無しとみなす」という意味のNOTLINKなんてのがあったり。一つの値に複数の意味が割り当てられてるんじゃ、重複しないビットを割り当てても意味がないわけです。


const unsigned short NOTFOUND          = 0;
const unsigned short FOUND             = 1;
const unsigned short WRAPPED           = 2;
const unsigned short FOUND_IN_LINK     = 4;
const unsigned short FOUND_IN_EDITABLE = 8;

こーいう風にしておけば、NOTLINKに相当する場合は「FOUND | WRAPED」、そうでない場合には「FOUND | FOUND_IN_LINK」とかそんな風に書けるわけで、これなら「普通にヒットした」「普通にヒットしたけど一度ページの末尾まで検索してもう一度頭からやり直した」「入力欄の中でヒットした」などなどいろんな場合もひっくるめて全部「flag & FOUND」というビット演算いっこで判定できる。

あとSafari風強調表示の改善もあるんだけど、長くなるから別のエントリに分けます。

Page 79/239: « 75 76 77 78 79 80 81 82 83 »

Powered by blosxom 2.0 + starter kit
Home

カテゴリ一覧

過去の記事

1999.2~2005.8

最近のつぶやき

オススメ

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