せっかくだから探検に行く

目標地点があると探検し甲斐がありますよね。

ソースコードを手に入れる

https://developer.mozilla.org/ja/Developer_Guide/Source_Code/Mercurial

# 数百メガバイトの履歴が .hg フォルダにダウンロードされるため、しばらく時間がかかります。

時間かかりすぎて諦めた。よく見たらアーカイブもあるらしい。
https://developer.mozilla.org/ja/Developer_Guide/Source_Code/Downloading_Source_Archives
今使ってるのは3.5.9なので…
ftp://ftp.mozilla.org/pub/mozilla.org/firefox/releases/3.5.9/source/

名前 サイズ 最終更新日時
firefox-3.5.9.source.tar.bz2 46350 KB 2010/03/15 7:46:00

ダウンロードする。大きいなあ。

ビルドする

説明があるのでこれを見ながら進める。
https://developer.mozilla.org/ja/Developer_Guide/Build_Instructions
Windows版のビルドに必要なものを説明した文書があるので読みながら道具をそろえる。
https://developer.mozilla.org/ja/Developer_Guide/Build_Instructions/Windows_Build_Prerequisites
親切だなあ。他はあるのでMozillaBuildだけダウンロードしてインストールする。
これで必要なものがそろったみたい。

ソースアーカイブを展開。ファイル数が4万近い。

標準オプションの状態で configure および make を実行しても、動作するビルドを作り上げることはできません。 .mozconfig ファイルを使って、相応のリリースビルドを入手してください。

まだ準備がいるらしい。
https://developer.mozilla.org/ja/Configuring_Build_Options
ここを見ながら .mozconfig を書いてソースディレクトリに入れる。

.mozconfig に次の行を追加することでオブジェクトディレクトリが有効になります。
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../obj-@CONFIG_GUESS@

Firefox以外ビルドする予定ないけど @TOPSRCDIR@/../mozilla-ff-obj-@CONFIG_GUESS@ にしておく。

ブラウザ (Firefox)
.mozconfig ファイルで Firefox の デフォルトの mozconfig ファイル を読み込んでください。
. $topsrcdir/browser/config/mozconfig

ここに書いてある通りに .mozconfig に書く。

ac_add_options --disable-optimize
最適化を無効にします。デバッガでの解析を簡易化します。

デバッガ使うかもしれないので --disable-optimize 入れる。

ac_add_options --enable-debug
デバッグマクロや他のデバッグ専用コードを有効にします。ビルドは著しく遅くなりますが、パッチを書く際に大変有用です。

ビルドの時間が長くなるのは嫌なので --enable-debug 入れない。

ac_add_options --disable-tests
デフォルトでは多くの補助的なテストアプリケーションがビルドされます。これらはデバッグmozilla のソースにパッチを当てるのに役立ちます。これらのテストアプリケーションを無効にすることでかなりビルド時間を短縮し、ディスクスペースを減らすことができます。

テストは無効にしておこう。あとはデフォルトのままにしておく。
こんな .mozconfig ファイルができた。

# My first mozilla config
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../mozilla-ff-obj-@CONFIG_GUESS@
. $topsrcdir/browser/config/mozconfig
ac_add_options --disable-optimize
ac_add_options --disable-tests
ac_add_options --disable-mochitest

準備完了。
2008はVC9らしいので C:\mozilla-build\start-msvc9.bat を起動。けっこう時間かかる。
ソースは D:\Projects\mozilla\mozilla-1.9.1 に入れたのでそこに移動してからビルド。

$ cd /d/Projects/mozilla/mozilla-1.9.1/
$ make -f client.mk build

はい、失敗。

nspr4.dllのビルドがうまくいってない。
どうやらバグらしい。
https://bugzilla.mozilla.org/show_bug.cgi?id=338224
--disable-optimize を指定していて --enable-debug を指定していないという場合にビルドできない。
.mozconfig を修正。

# My first mozilla config
mk_add_options MOZ_OBJDIR=@TOPSRCDIR@/../mozilla-ff-obj-@CONFIG_GUESS@
. $topsrcdir/browser/config/mozconfig

# https://bugzilla.mozilla.org/show_bug.cgi?id=338224
ac_add_options --disable-optimize
ac_add_options --enable-debug

ac_add_options --disable-tests
ac_add_options --disable-mochitest

今度は成功。makeを動かしてから終了するまで一時間半ぐらいかかる。

プロファイルを作る

基本的には普段使っているFirefoxと同じものなのでそのまま動かしてもいいんですが、念のためにプロファイルを分けることにする。

"C:\Program Files\Mozilla Firefox\firefox.exe" -ProfileManager

これでプロファイルマネージャが起動できるので新しいプロファイルを作成し、起動時に毎回選択するように設定しておく。
普段使ってるFirefoxを起動したときはこう。

今回ビルドしたのを起動したときはこんな感じ。

もちろん文字化けもする。

探しに行こう

途方もない量のソースコードから目的の場所を見つけるため、適当な言葉でgrepする。
手がかりは…

  • 文字化けがおきているのはエラーコンソール
  • JavaScriptの例外は化けるけど、他のエラーは化けない
  • 上位バイトが0になっているということは途中でwchar_tからcharにキャストしてるんじゃないだろうか

"error console"という文字列を求めてxpconnectを歩き回ったりlayoutに迷い込んだり、どこをどう進んだかわからなくなった頃、js/src/jsexn.cppの下の方で遭遇。

JSBool
js_ReportUncaughtException(JSContext *cx)
{
    jsval exn;
    JSObject *exnObject;

jsというディレクトリはSpiderMonkeyと同じなのでここはJavaScriptのコード。「キャッチされなかった例外を報告」という関数名はエラーコンソールに例外を出力する処理のように思える。
ここからたどっていくと…

js_ReportUncaughtException
 └→ js_GetStringBytes
    └→ js_DeflateString

jsstr.cppの中のjs_DeflateString

char *
js_DeflateString(JSContext *cx, const jschar *chars, size_t nchars)
{
...
        for (i = 0; i < nbytes; i++)
            bytes[i] = (char) chars[i];
...
    return bytes;
}

キャストしているところを発見。実にあやしい。
jscharって何だろう。JavaScriptのcharってことかな。定義をたどっていくと…

// js/src/jspubtd.h
typedef uint16    jschar;

// js/src/jsotypes.h
typedef JSUint16 uint16;

// js/src/jstypes.h
typedef uint16_t JSUint16;

// VCにはuint16_tが無いみたいなので
// js/src/jsstdint.h
typedef unsigned __int16 uint16_t;

というわけで符号なし16ビット整数。
jsapi.hのコメントには"uint16/jschar ECMA uint16, Unicode char"という記述があるのでJavaScriptで文字列を作るときに使う16ビット値で間違いない。もし最初に書いた「やめてー!」がここを通ったら上位8ビットが0になって下位8ビットだけが残る形の文字化けをするはず。

ソースコードを書き換えてみる

js_DeflateStringが今回の文字化けに関係しているかどうか確認するためにちょっといじる。

char *
js_DeflateString(JSContext *cx, const jschar *chars, size_t nchars)
{
...
        for (i = 0; i < nbytes; i++) {
            if (chars[i] > 0xFF) {
                bytes[i] = 'E';
            } else {
                bytes[i] = (char) chars[i];
            }
        }
...
    return bytes;
}

これで文字化けのとき全部Eになるはず。

確定。

文字化け解消を目指して進む

文字化け解消にたどり着くための道筋が見えてきた。

  • js_DeflateStringのすぐ上に、これと対になりそうなjs_InflateStringという関数がある
  • charとjscharの文字列を相互に変換する関数のようだ
  • どちらもjs_CStringsAreUTF8という変数の真偽で動作が変わるようになっている
  • js_CStringsAreUTF8を真にすればcharの側がUTF-8じなって文字化けが解消できるんじゃないか

js_CStringsAreUTF8について調べる。
js/src/jsprvtd.h

#ifdef JS_C_STRINGS_ARE_UTF8
# define js_CStringsAreUTF8 JS_TRUE
#else
extern JSBool js_CStringsAreUTF8;
#endif

js/src/jsapi.cpp

#ifndef JS_C_STRINGS_ARE_UTF8
JSBool js_CStringsAreUTF8 = JS_FALSE;
#endif

JS_PUBLIC_API(JSBool)
JS_CStringsAreUTF8()
{
    return js_CStringsAreUTF8;
}

JS_PUBLIC_API(void)
JS_SetCStringsAreUTF8()
{
    JS_ASSERT(!js_NewRuntimeWasCalled);

#ifndef JS_C_STRINGS_ARE_UTF8
    js_CStringsAreUTF8 = JS_TRUE;
#endif
}

よくわからないけど、どうやらJS_C_STRINGS_ARE_UTF8を使って切り替えるみたいだ。JS_C_STRINGS_ARE_UTF8を#defineするconfigureオプションがあればいいんだけど。

ない。JS_C_STRINGS_ARE_UTF8を定義するとどうなるのかよく調べないといけない。
js_DeflateStringで化けた文字列がどこに行くのかデバッガでトレースして調べる。

js_ReportUncaughtException
 └→js_ReportErrorAgain
    └→JSContext::errorReporter
      errorReporterは関数ポインタ。NS_ScriptErrorReporterが入ってた。
       └→NS_ScriptErrorReporter
          └→nsAutoString::AssignWithConversion
             └→…(略)…→CopyASCIItoUTF16

js_InflateStringには行かない!
途中で他に気になる部分があったので調べる。js_CStringsAreUTF8は諦める。

void
NS_ScriptErrorReporter(JSContext *cx,
                       const char *message,
                       JSErrorReport *report)
{
...
      const PRUnichar *m = reinterpret_cast<const PRUnichar*>
                                             (report->ucmessage);
      if (m) {
        msg.Assign(m);
      }

      if (msg.IsEmpty() && message) {
        msg.AssignWithConversion(message);
      }

messageに化けた文字列が入ってる。report->ucmessageに何か入ってればそっちを使うんじゃないかな。ucってのがUnicodeっぽいので文字化けしないかも。
js/src/jsapi.h

struct JSErrorReport {
...
    const jschar    *ucmessage;     /* the (default) error message */

エラーメッセージ?これか?
js_ReportUncaughtExceptionの方を見ると、report->ucmessageは0になってる。ucmessageを設定してみる。

JSBool
js_ReportUncaughtException(JSContext *cx)
{
...
    if (!reportp &&
        exnObject &&
        OBJ_GET_CLASS(cx, exnObject) == &js_ErrorClass) {
        const char *filename;
        uint32 lineno;
        const jschar *chars; // 追加

        ok = JS_GetProperty(cx, exnObject, js_message_str, &roots[2]);
        if (!ok)
            goto out;
        if (JSVAL_IS_STRING(roots[2])) {
            bytes = js_GetStringBytes(cx, JSVAL_TO_STRING(roots[2]));
            chars = js_GetStringChars(cx, JSVAL_TO_STRING(roots[2])); // 追加
...
        reportp = &report;
        memset(&report, 0, sizeof report);
        report.filename = filename;
        report.lineno = (uintN) lineno;
        report.ucmessage = chars; // 追加
    }
...
        js_ReportErrorAgain(cx, bytes, reportp);
...

bytesは化けた文字列。charsは正しい文字列のはず。

文字化け直った。


目標地点に到着、終了。

最新版では既に直ってるみたい

Mercurialリポジトリは大きすぎるのでWeb上でソースを見てたんですが、関係しそうな変更を発見。
http://hg.mozilla.org/mozilla-central/rev/34eb552d42f5
Bug 557346
https://bugzilla.mozilla.org/show_bug.cgi?id=557346
3.6.4と3.5.10で修正されているみたいですね。
例外のメッセージは遠慮なく日本語で書くことにする。