タブにフォーカスしていない状態でunsafeWindow.console.logをするとスクリプトが止まる

Firebugの仕様に依存したエントリー。既出だったらすいません。
テスト環境:VistaSP1、Firebug 1.3.0、Firefox 3.0.5、Greasemonkey 0.8.20080609.0。

要旨

DOMツリー構築完了前タブにフォーカスしていない状態*1でunsafeWindow.console.logをすると undefinedが返されて*2Greasemonkeyが止まる。console.logなら問題ない。
わかりやすく例えると、unsafeWindow.console.logさせているページをいくつかタブに開く。するとそれらページではunsafeWindow.console.logが書かれているところでスクリプトが停止する。

原因

Firebugの仕様による。FirebugConsole APIを使うために、_firebugConsoleという見えないdiv要素をdocumentに仕込みます→Firebug 1.2 ‘console’ implementation 和訳

_firebugConsoleが存在しない場合に"unsafeWindow.console.logがundefinedになるエラー"が確認された。いろいろ試した結果_firebugConsoleが仕込まれる条件は、『そのページがアクティブであり、consoleオブジェクトがはじめて使われた時。』であった*3。タブにフォーカスしていない状態だと、ページがアクティブではないので_firebugConsoleが仕込まれない。

なぜアクティブじゃないと仕込まれないのか

(所々別のjsファイルに飛ぶのは、勘で開いてみたらたまたま単語が見つかったからそのまま読み進めているだけなので信用しないでください。。)
consoleInjected.jsの_FirebugConsole()中のgetFirebugElementが仕込み部分で、上に辿っていくと、consoleInjector.jsのgetConsoleInjectionScriptでloadFirebugConsoleがwindowの__defineGetter__として実行されている。もうちょっとつついていくとアクティブじゃないといけない理由が見つかるはず。。

consoleInjector.jsでgetConsoleInjectionScriptを実行する部分が二つある。
こっちがページを読み込んだときに仕込むやつ

attachConsoleInjector: function(context, win)
{
  var consoleInjection = this.getConsoleInjectionScript();  // Do it all here.
  Firebug.CommandLine.evaluateInSandbox(consoleInjection, context, null, win);
},

こっちはコンソールに入力したときにそれを解読するやつ(たぶん)

evaluateConsoleScript: function(context)
{
  var scriptSource = this.getConsoleInjectionScript(); // TODO XXXjjb this should be getConsoleInjectionScript
  Firebug.Debugger.evaluate(scriptSource, context);
},


attachConsoleInjectorを追っていくとattachIfNeededに。次にtop.Firebug.Console.injector.attachIfNeededが実行されている部分をさがすとconsole.jsにくる。isNeededGetReadyで実行されてる。Firebug.Console.isNeededGetReadyが実行されている部分を探す。debugger.jsのsupportsGlobal内で、見つかる。そこで実行される条件に

(TabWatcher ? TabWatcher.getContextByWindow(global) : null);

があった。TabWatcherってタブがアクティブかどうかみてるってことかな。。tabWatcher.jsを見てみる。うーん、たぶん。。




GMのuserscriptからunsafeWindow.consoleにアクセスするとき、objectになるunsafeWindow.consoleは、Firebugの中で設定される。
principal実習#1 - ロックスターになりたい

ということなのでFirebug consoleが動いていなければGreasemonkeyからunsafeWindow.consoleにアクセスできないことがわかった。ややこしいのはconsole.logの場合は問題無く表示されるという点。たぶんGreasemonkeyを経由せずFirebugのConsole APIに直接訴えるからOKということ?そんなわけなかった。window.consoleならGMからFirebug.consoleへ直接いけるけど、unsafeWindow.consoleだとGMからFirebugにいって中でプリンシパルとか経由するからか。

問題を整理

  • タブにフォーカスしていない状態でunsafeWindow.console.logをすると存在しないのでエラーがでて止まる。
  • たまにGMスクリプト適用ページを新しいタブで開いて後からみるとGMスクリプトが実行されてない原因はこれだ。

対策

最近のFirebug(この仕様になった1.2以降)を使うなら、unsafeWindow.console.logはすべてconsole.logに直すか以下のような関数にしたほうがいい。

function log(m) { 
    if (unsafeWindow.console) {
        unsafeWindow.console.log(m);
    }else{
        console.log(m); //GM_log(m)でも同じ。
    }
}

それだけ。。これでバックグラウンドにunsafeWindowがあっても大丈夫。

unsafeWindow.consoleが好きな理由

感想

unsafeWindow.consoleだったりconsoleを適当に使い分けてる人は気をつけたほうがいい。AutoPagerizeはこのことを考慮してるのかな。さすがだなぁ。既出だったらすいません2。
ところで今だにunsafeWindowは危険なんだろうか・・。Firefox 3.0.2以降evalの第2引数廃止したから、

⊂(^ω^)⊃ セフセフ?
 ミ⊃⊂彡 

*unsafeWindow.consoleはFirebugの管轄だから関係ないよね。

*1:多くの人がアドオンなどでフォーカスのカスタマイズはしているとおもう。

*2:Firebugのコンソール-オプション-Chromeのエラーを表示を有効

*3:もちろんGreasemonkeyスクリプト実行タイミングのDOMツリー構築後という条件も含む。