IT戦記

プログラミング、起業などについて書いているプログラマーのブログです😚

Mozilla 台湾の audio 要素のデモが超カッコいい

Firefox 3.5 から

audio 要素という音楽を再生するための要素が導入されますが、 Mozilla 台湾のサイトに載っている audio 要素のデモがすごくかっこよかったので紹介します。

百聞は一見にしかず

Firefox 3.5 (現状 Beta 4)で見てみてください

http://moztw.org/demo/audioplayer/

Firefox 拡張を jQuery で書く! Jetpack を使ってみた。

はじめに

JavaScript が書ければ、誰でも簡単に Firefox の拡張が書けてしまう。しかも、もれなく jQuery が付いて来る!
というものを Mozilla Labs がリリースしたみたいですね。
https://jetpack.mozillalabs.com/

というわけで

少し触ってみました

Jetpack Feature の書き方

Jetpack で書く Firefox 拡張を「Jetpack Feature」といいます。
これは、以下の 2 つのものを用意すれば誰でも簡単に公開することが出来ます。

  • JavaScript ファイル
  • 公開用 HTML ファイル
JavaScript ファイル

JavaScript ファイルには、 Jetpack Feature のアプリケーションコードを書きます。

(function() {
    Jetpack.statusBar.append({
        onReady: function(doc) {
            for (var i = 0; i < 10; i++)
                Jetpack.notifications.show('Hello, world!'); // Hello, world を 10 回表示
        }   
    }); 
})();

詳細は、公式ページの Tutorial や Reference を見たほうが早いです。

公開用 HTML ファイル

以下のような link 要素を含んだ HTML ファイルを書きます。

<!DOCTYPE html>
<html>
    <head>
        <link rel="jetpack" href="hello.js" />
        <title>Hello, Jetpack!</title>
    </head>
    <body>
        <h1>Hello, Jetpack!</h1>
    </body>
</html>

こうすることで、この hello.js で書かれた JavaScriptJetpack Feature として公開することができます。
具体的には、 Jetpack をインストールした Firefox でこの HTML を見ると「この Jetpack Feature をインストールしますか?」的なメッセージが表示されます。

更新チェッカーを書いてみた

Hello, world だけじゃあまりにもあまりなので、

(function() {
    Jetpack.statusBar.append({
        onReady: function(doc) {

            // データの永続化は Jetpack.sessionStorage で
            if (!Jetpack.sessionStorage.feedUrl) {

                // グローバルに prompt がなかったのでコンテンツのを使う
                Jetpack.sessionStorage.feedUrl = Jetpack.tabs.focused.contentWindow.prompt('input feed url');
            }   

            var url = Jetpack.sessionStorage.feedUrl;

            // setInterval はある
            setInterval(function() {
                // jQuery の get 関数を使う
                $.get(url, function(content) {
                    // MD5 を取る
                    var md5 = CybozuLabs.MD5.calc(content);
                    // 更新をチェック
                    if (Jetpack.sessionStorage.feedMd5 != md5) {
                        // 更新をお知らせ
                        Jetpack.notifications.show('Contents updated! ' + Jetpack.sessionStorage.feedMd5 + ' to ' + md5);
                        // MD5 を取る
                        Jetpack.sessionStorage.feedMd5 = md5;
                    }   
                })  
            }, 10000);
        }   
    }); 
})();

/* 以下に CybozuLabs.MD5 ライブラリのコードをはり付ける */

「プライバシー情報の消去」をする人は、 Firefox 3.1 で「Pathtraq の定番ランキング」をライブブックマークしておくと便利

はじめに

もうすぐ、 Firefox 3.1 がリリースされますね!というわけで、 Firefox 3.1 から(たぶん)のちょっと便利な機能を紹介したいと思います。

Firefox の「プライバシー情報の消去機能」

何かと、恥ずかしいページを見てしまったときなどに便利な機能ですね。
ちなみにWindows の場合 Ctrl+Shift+Del、 Mac の場合 Command+Shift+Del で出来ます。知ってました?

この機能便利なのですが、、

ロケーションバーの補完に頼ってる人にはちょっと辛い面もあります><
補完機能が使い物にならなくなってしまうのです。
たとえば、普段は以下のように補完が効くので、 b → tab → enter で目的のページにたどり着けます。

しかし、プライバシー情報の消去を行った後だと、以下のように、何も候補が出なくなってしまうのです。

この問題が Firefox 3.1 から少し改善されます

Firefox 3.1 では、「プライバシー情報の消去」(Firefox 3.1 では「最近の履歴を消去」)したときに、ライブブックマークしているフィードが配信している URL が補完候補に追加されるようになったのです。

なので、

よくいくサイトを配信しているフィードを、ライブブックマークしておくと、便利です!

よくいくサイトを配信しているフィードといえば

Pathtraq の定番ランキングですね!
マウント アンマウント | パソコン豆知識

ライブブックマークの仕方

ライブブックマークのやり方を解説しておきます。
フィードを配信しているページに行って、「ブックマーク」メニューを開くと、以下のように、「このページを購読」というメニューが選択できます。

このメニューを選択して、以下のようなページが表示されるので、「ライブブックマーク」を選択し、

「購読」を押し、以下のダイアログでさらに「購読」を押します。

ライブブックマークしておくと

プライベート情報消去後も、以下のように補完することが可能です。

やりましたね!

まとめ

これは便利ですね!
(Firefox 3.1)++
Pathtraq++

Firefox メモ

alert の位置を知る

  • dom/src/base/nsGlobalWindow.cpp 4035 行目
  • nsGlobalWindow::Alert

JavaScript を書く

alert(0);
var img = document.createElement('img');
img.onload = function() { alert(1) };
alert(2);
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif';
alert(3);
document.body.appendChild(img);
alert(4);

Firefox で開く

0 -> 2 -> 3 -> 1 -> 4

gdb 起動(Firefox のビルドが必要)

$ gdb MinefieldDebug.app/Contents/MacOS/firefox-bin
(gdb) b nsGlobalWindow::Alert
(gdb) r

alert(1) のところだけ見る

var img = document.createElement('img');
img.onload = function() { alert(1) };
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif';
document.body.appendChild(img);

バックトレース

#0  nsGlobalWindow::Alert (this=0x1c97f420, aString=@0x18cc38d0) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4035
:
:
#16 0x1314fcf1 in nsImageLoadingContent::Event::Run (this=0x17f93040) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:833
:
:

nsImageLoadingContent::Event::Run のコードを見てみる

(gdb) b nsImageLoadingContent::Event::Run
(gdb) c
Breakpoint 2, nsImageLoadingContent::Event::Run (this=0x17e55150) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:823
823	  if (mMessage.EqualsLiteral("load")) {
(gdb) l
818	NS_IMETHODIMP
819	nsImageLoadingContent::Event::Run()
820	{
821	  PRUint32 eventMsg;
822	
823	  if (mMessage.EqualsLiteral("load")) {
824	    eventMsg = NS_LOAD;
825	  } else {
826	    eventMsg = NS_LOAD_ERROR;
827	  }
(gdb) l
828	
829	  nsCOMPtr<nsIContent> ourContent = do_QueryInterface(mContent);
830	
831	  nsEvent event(PR_TRUE, eventMsg);
832	  event.flags |= NS_EVENT_FLAG_CANT_BUBBLE;
833	  nsEventDispatcher::Dispatch(ourContent, mPresContext, &event);
834	
835	  return NS_OK;
836	}
837	
(gdb) 

うーん。送信してるところが見たい

(gdb) reverse-search "load"
823	  if (mMessage.EqualsLiteral("load")) {
(gdb) reverse-search "load"
249	    FireEvent(NS_LITERAL_STRING("load"));

あった!

249 行目で止めてみる

(gdb) b nsImageLoadingContent.cpp:249
(gdb) c
Breakpoint 4, nsImageLoadingContent::OnStopDecode (this=0x1c6dd39c, aRequest=0x1c6dbf80, aStatus=5505024, aStatusArg=0x0) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:249
249	    FireEvent(NS_LITERAL_STRING("load"));
(gdb) 

バックトレース

Breakpoint 4, nsImageLoadingContent::OnStopDecode (this=0x1c6dd39c, aRequest=0x1c6dbf80, aStatus=5505024, aStatusArg=0x0) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:249
249	    FireEvent(NS_LITERAL_STRING("load"));
(gdb) bt
#0  nsImageLoadingContent::OnStopDecode (this=0x1c6dd39c, aRequest=0x1c6dbf80, aStatus=5505024, aStatusArg=0x0) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:249
:
:
#10 0x13227d41 in nsHTMLImageElement::SetSrc (this=0x1c6dd380, aValue=@0xbfff99a4) at /Users/amachang/mozilla/content/html/content/src/nsHTMLImageElement.cpp:219
#11 0x11223f98 in nsIDOMHTMLImageElement_SetSrc (cx=0xb67000, obj=0x1cc65340, id=441507292, vp=0xbfffa5f8) at dom_quickstubs.cpp:6846
:
:
#64 0x000026e3 in main (argc=1, argv=0xbffff8e8) at /Users/amachang/mozilla/browser/app/nsBrowserApp.cpp:156

ながいなー

よーく見ると

nsHTMLImageElement::SetSrc から load イベントが発火されていることが分かる
でも

alert(0);
var img = document.createElement('img');
img.onload = function() { alert(1) };
alert(2);
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif';  // ここで発火されるのに
alert(3); // ここが alert(1) より先に起こるのは何故?
document.body.appendChild(img);
alert(4);

alert(3) で止めてバックトレースを見る

Breakpoint 1, nsGlobalWindow::Alert (this=0x1c97f420, aString=@0x1e75d860) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4035
4035	  FORWARD_TO_OUTER(Alert, (aString), NS_ERROR_NOT_INITIALIZED);
(gdb) p aString
$7 = (const nsAString_internal &) @0x1e75d860: {
  mData = 0x1e66a898, 
  mLength = 1, 
  mFlags = 5
}
(gdb) p aString->mData   
$8 = (PRUnichar *) 0x1e66a898
(gdb) p (char*)aString->mData 
$9 = 0x1e66a898 "3"
(gdb) bt
#0  nsGlobalWindow::Alert (this=0x1c97f420, aString=@0x1e75d860) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4035
#1  0x0049e1c1 in NS_InvokeByIndex_P (that=0x1c97f420, methodIndex=65, paramCount=1, params=0xbfff84e4) at /Users/amachang/mozilla/xpcom/reflect/xptcall/src/md/unix/xptcinvoke_unixish_x86.cpp:179
:
:

うーん。

別スレッドなのかな?

全スレッドバックトレース

(gdb) thread apply all bt

でも、 alert(1) も alert(2) も main から繋がってたから。たぶん同じスレッドだと思うんだけどなー

あそっか

イベントは発火されてもすぐには実行されなくて、しかるべきタイミングでイベントのキューを見に行くのか。

そのタイミングってどこよ

もっかい nsImageLoadingContent::Event::Run で止めてみる
Event::Run のバックとレースを見てみると alert(3) の nsGlobalWindow::Alert から呼ばれていることが分かった

#0  nsImageLoadingContent::Event::Run (this=0x1d67fe50) at /Users/amachang/mozilla/content/base/src/nsImageLoadingContent.cpp:823
#1  0x004833c0 in nsThread::ProcessNextEvent (this=0x719030, mayWait=1, result=0xbfffadfc) at /Users/amachang/mozilla/xpcom/threads/nsThread.cpp:510
#2  0x0040d198 in NS_ProcessNextEvent_P (thread=0x719030, mayWait=1) at nsThreadUtils.cpp:227
#3  0x1298c2da in nsXULWindow::ShowModal (this=0x16991bc0) at /Users/amachang/mozilla/xpfe/appshell/src/nsXULWindow.cpp:396
:
:
#10 0x133b6904 in nsGlobalWindow::Alert (this=0x1c53aa10, aString=@0x1d69f080) at /Users/amachang/mozilla/dom/src/base/nsGlobalWindow.cpp:4064

でも、なんで XULWindow::showModal が NS_ProcessNextEvent_P を呼び出すかが分からない

NS_ProcessNextEvent_P (マクロ名 NS_ProcessNextEvent)を呼び出してるところを grep してみるとたくさんあることに気がつく。
これらのタイミングでイベントは処理されるのか。

  • alert されたとき
  • window.open したとき
  • HTML のパースが終わったとき

などなど
つまり、

var img = document.createElement('img');
img.onload = function() { alert(1) };
img.src = 'http://www.hatena.ne.jp/images/top/h1.gif'; // この時点で画像のロードが終わっていて
alert(3); // この alert に誘発されて onload イベントハンドラが起動した
document.body.appendChild(img);

ってことか

じゃあ、 Firefox の画像って非同期にロードされてる訳じゃないのかな?

試しに、以下のような CGI を作ってみる

#! /usr/bin/perl

print "Content-Type: image/gif\n\n";

# 10 秒停止
sleep(10);

open IMG, "hogehogehogehoge.gif";
binmode IMG;
binmode STDIO;
while (my $bin = <IMG>) {
    print STDOUT $bin;
}
close IMG;

で、以下のような JavaScript からこの image.cgi を起動

var start = new Date;
var img = document.createElement('img');
img.onload = function() { alert(1) };
img.src = 'image.cgi';
document.body.appendChild(img);
alert(4);

お。ちゃんと非同期になってるなー

とりあえず

今日はこの辺で、結局なんで Firefox の img.src の設定が遅いかは分からなかった

Firefox の nightly で LDR が動かない

原因

Firefox nightly がまだバグってて

alert(window.Function == Function); // false;
alert(window.Function == function(){}.constructor); // false;
alert(Function == function(){}.constructor); // true;

てな感じになる

対処

ページの先頭で

window.Function = Function;

とやれば動く