株式会社クリアコード > ククログ

ククログ

タグ:

Thunderbirdの設定値の変更を監視するには

はじめに

Thunderbirdのアドオンを作成する際に、設定値の変化を検出したい場合があります。 具体的には、次のようなケースです:

  • ユーザーが関連する設定値を変更した時に、その効果を即座に反映させたい
  • アカウントの登録や変更のタイミングで、特定の処理をフックさせたい

MozillaのXPCOMライブラリには、いわゆる「オブザーバー」の仕組みが備わっています。 この仕組みを利用すれば、上記のような処理を比較的手軽に実装することができます。

以下の記事では、Thunderbirdの設定値の変更を検知して、任意の処理をフックする方法を解説いたします。

具体的な実装方法

nsIPrefBranch インターフェイスに定義されている addObserver() メソッドを利用します。 例として、Thunderbirdの自動更新フラグ app.update.auto を監視するコードのサンプルを以下に示します:

// ブランチオブジェクトを取得する
var Cc = Components.classes;
var Ci = Components.interfaces;
var prefs = Cc['@mozilla.org/preferences-service;1'].getService(Ci.nsIPrefBranch);

// 設定値にオブザーバーを登録する
prefs.addObserver('app.update.auto', function(aSubject, aTopic, aData) {
    // 設定が変更された時の処理
}, false);

このように定義すると、設定値 app.update.auto が変更されるたびに、二番目の引数で与えたオブザーバー関数が呼び出されるようになります(オブザーバー関数の引数については次節で説明します)。なお、最後の引数は「オブザーバーを弱参照で保持するか否か」を制御するブール値です。今回の例では単純にfalse(=通常の参照を持つ)を指定しています *1

コールバック関数の引数について

登録したオブザーバー関数は、次の三つの引数を伴って呼び出されます:

引数名 内容
aSubject 監視対象のブランチオブジェクト
aTopic 文字列 "nsPref:changed"(固定値)
aData 変更された設定名

このうちaSubjectaDataを組み合わせると、コールバック内で変更後の設定値を取得できます。以下に具体的な利用例を示します:

prefs.addObserver('app.update.auto', function(aSubject, aTopic, aData) {
    aSubject.QueryInterface(Ci.nsIPrefBranch);
    var isAutoUpdate = aSubject.getBoolPref(aData);
    if (isAutoUpdate) {
        // Thunderbirdの自動更新がONの場合
    } else {
        // Thunderbirdの自動更新がOFFの場合
    }
}, false);
複数の設定値をまとめて監視する

Thunderbirdの設定値は、一般に木構造をなしています。

実は addObserver() を使うと、末端の葉ノードだけではなく、中間にある内部ノードに対してオブザーバーを登録することもできます。この場合、対象のノードのすべての子孫ノードの変更について、登録したオブザーバー関数が呼び出されます。

例えば、app.update配下の設定値をまとめて監視したい場合は次のように記述します:

prefs.addObserver('app.update', function(aSubject, aTopic, aData) {
    switch (aData) {
      case 'app.update.auto':
        ...
        break;
      case 'app.update.enabled':
        ...
        break
    }
}, false);

この記法は、自分のアドオンの設定値を一括して管理したい場合などに非常に有効です。

まとめ

本記事では、Thunderbirdの設定値の変更を検知して、任意の処理をフックする方法を解説しました。

この仕組みを上手に使うと、設定値にまつわるイベントに対してリアクティブに反応できるようになるので、ユーザーの利便性を高めることができます。アドオンを作成される際は、ぜひお試しください。

*1 どのような場合にこのフラグを利用するかは https://developer.mozilla.org/en-US/docs/Mozilla/Tech/XPCOM/Weak_reference を参照ください。

タグ: Mozilla
2017-11-14

Gecko Embeddedプロジェクト 11月時点のステータス

はじめに

2017年7月6日の記事で紹介した通り、クリアコードは組み込みLinux向けにMozilla Firefoxを移植するプロジェクトGecko EmbeddedWebDINO Japan(旧Mozilla Japan)様と共同で立ち上げ、開発を進めております。Yoctoを使用してFirefoxをビルドしたりハードウェアクセラレーションを有効化する際のノウハウを蓄積して公開することで、同じ問題に悩む開発者の助けになることを目指しています。

その後も継続的に改善を進めており、いくつか進展がありましたので、11月時点のステータスを紹介します。

現在のステータス

ターゲットボードの追加

7月時点では、Firefox ESR52のビルド及び動作はRenesas RZ/G1E Starter Kitのみで確認していましたが、その後iWave RainboW-G20D Q7および Renesas R-Car Gen3でも同様に確認しています。ビルド方法は以下のページにまとめてあります。

Wayland対応のバグ修正

Firefoxは正式にはWaylandをサポートしていませんが、Red HatのMartin Stransky氏がWaylandへの移植作業を行っています。 Stransky氏のパッチはFirefoxの最新バージョンを対象としていますが、Gecko Embeddedプロジェクトではこのパッチを52ESRのツリーに移植した上で、安定化作業を進めています。 本プロジェクトで発見した問題や、作成したパッチはStransky氏に随時フィードバックしています。

最近では以下のような報告を行っています。

WebRTC対応

Wayland版FirefoxではWebRTCでビデオチャットを行おうとするとクラッシュする問題が存在していましたが、この問題を修正し、WebRTCが利用できるようになりました。

WebGL対応

7月時点ではドライバに絡む問題を解決できていなかったためビルド時点でのWebGL有効化手段を提供していませんでしたが、その後問題を解決できたため、パッチを更新しYoctoレシピにビルドオプションを追加しています。

WebGLを有効化するには、ビルド時に以下の設定をYoctoのlocal.confに追加して下さい。

PACKAGECONFIG_append_pn-firefox = " webgl "

また、今回新たに対象ボードとして加えたR-Car Gen3は非常に強力なため、フルHDの解像度でもWebGLが快適に動作することを確認しています。

Canvas 2D Contextのアクセラレーション

WebGLと同様に、Canvas 2D Contextのアクセラレーションについてもドライバに絡む問題を解決できたため、Yoctoレシピにビルドオプションを追加しています。

アクセラレーションを有効化するには、ビルド時に以下の設定をYoctoのlocal.confに追加して下さい。

PACKAGECONFIG_append_pn-firefox = " canvas-gpu "

まとめ

Gecko Embeddedプロジェクトの2017年11月現在のステータスについて紹介しました。 安定化や、さらなるパフォーマンス改善、他のSoCのサポートなど、やるべきことはまだまだ残されていますので、 興味がある方は協力して頂けるとありがたいです。問題を発見した場合はGecko EmbeddedプロジェクトのIssueページに報告して下さい。

タグ: Mozilla
2017-11-09

Firefox ESRの独自ビルドの作成方法(2017年版)

2015年にWindowsでのFirefoxの独自ビルドの作成方法をご紹介しましたが、2017年現在では状況が若干変わっています。この記事では、2017年10月現在においてWindows用にFirefox ESRの独自ビルドを作成する手順をご紹介します。

なお、この記事ではFirefoxの改造の方法は扱っていません。「Firefox」というブランド名を除去した改造版を作成する方法については、過去の記事をご参照ください。

情報のありか

Firefoxのビルドに関する情報は、Build Instructionsから辿ることができます。 Windowsでのビルドなら、Building Firefox for Windowsで必要な情報が手に入ります。

ただ、MDNの情報は様々な場合を考慮して網羅的に書かれているため、各項目に複数の選択肢が示されている場合があり、「とりあえずどこから始めればいいのか?」が若干分かりにくい構成となっています。 そこで、この記事では「Firefox ESRをビルドする」という場面に限定して一通りの流れをご紹介してみます。

ビルド環境の構築

現在新規に導入可能なWindows環境はWindows 10である場合がほとんどです。この記事では、Windows 10 Creators Updateまたはそれ以降のバージョンをビルド環境として想定します。 (検証はWindows 10 Pro 1703 (Creators Update) 15063.632で行っています。)

Firefoxのビルドにあたっては、HDDとメモリに十分な余裕があることが望ましいです。ビルドツール、ライブラリ等のインストール領域用と、リポジトリ自体やビルド中に生成される一時ファイルの分、および仮想メモリのスワップ領域用として、HDDに最低でも15GB程度の空き容量を確保しておくとよいでしょう。また、メインメモリが逼迫しているとビルドに余計に時間がかかります。最低でも4GB程度のメモリがあるとよいでしょう。

Windows上でFirefoxをビルドするために必要なソフトウェアは、すべて無料で入手できます。

  • MozillaBuild
    • 2017年10月現在の最新版は「MozillaBuild 3.0」で、以下の説明はすべてこのバージョンを前提とします。
  • DirectX SDK
    • インストール作業の順番次第では「S1023」という番号を伴ってインストールエラーが表示されることがあります。これは、システムに「Visual C++ 2010 再頒布可能パッケージ」の新しいバージョンが存在する場合に起こることが多いです。一旦「Visual C++ 2010 再頒布可能パッケージ」をアンインストールしてからDirectX SDKをインストールし、完了の後改めて最新の「Visual C++ 2010 再頒布可能パッケージ」をインストールし直して下さい。
    • 古いサポート切れのバージョンのWindows 10においては、「Visual C++ 2010 再配布可能パッケージ」が存在しなくてもエラーコード「S1023」を伴ってインストールに失敗する場合があり、Creators Updateへ更新後は発生しなくなるという結果を得られました。問題との関連性は不明ですが、脆弱性への対応などの観点からも、Windows自体を最新版に更新してから臨むことを強くお勧めします。
  • Windows 10 SDK
  • Visual Studio Community 2015 with Update 3
    • 2017年10月現在のVisual Studioの最新版は「Visual Studio 2017」ですが、公式のビルドはVisual Studio 2015で行われているとのことなので、ここでもVS2015を使うことにします。古いバージョンのダウンロード用ページから、日本語版のインストーラを入手して下さい。(Microsoftアカウントが必要)
    • インストール時の機能の選択で「カスタム」を選択し、「プログラミング言語」→「Visual C++」→「Visual C++ 2015用の共通ツール」にチェックを入れる必要があります。
    • インストールが完了したら、Visual Studioを起動して初期設定を完了させる必要があります。
    • インストールと初期設定を終えたら、累積的なアップデートを適用して更新します。

ソースの入手

Firefoxのソースコードはスナップショットのtarballとしても入手できますが、ここではMercurialのリポジトリをcloneする方法で手順を解説します。

環境構築の際に導入したMozillaBuildはMinGWのコマンドラインコンソールを含んでおり、ビルドの作業はこのコンソールから行います。 C:\mozilla-build\start-shell.bat をダブルクリックしてコンソールを開きましょう。

コンソールを開いたら、リポジトリをcloneするためのディレクトリを用意します。

$ mkdir /c/mozilla-source
$ cd /c/mozilla-source

以下でcloneするリポジトリの中に含まれるファイルは開発用の物のため、Norton Securityなどのウィルス対策ソフトウェアが動作していると、Nortonの誤判定でリポジトリの中のファイルが意図せず削除されてしまうことがあります。ここで作成したディレクトリ(C:\mozilla-source)をウィルス対策ソフトウェアの監視対象にしないように設定しておきましょう。例えばNorton Securityであれば、「設定」→「ウィルス対策」→「スキャンとリスク」→「除外/低危険度」→「スキャンから除外する項目」および「自動保護、SONAR、ダウンロードインテリジェンスの検出から除外する項目」で任意のフォルダをスキャン対象外にすることができます。

次に、Firefoxのリポジトリをcloneします。

Firefoxやその他のMozilla製品のリポジトリはhttps://hg.mozilla.org/で公開されています。 Nightlyの最新版やESR版など、どのバージョンをビルドしたいかによってどのリポジトリをcloneするかが変わります。 ESRを含むリリース版のFirefoxのソースはhttps://hg.mozilla.org/releases/以下にあり、ESRの場合はhttps://hg.mozilla.org/releases/mozilla-esr52のようにバージョン番号を含むパスのリポジトリになっています。

今回はFirefox ESR52.4.1をビルドすることにします。まず https://hg.mozilla.org/releases/mozilla-esr52をcloneします。

$ hg clone https://hg.mozilla.org/releases/mozilla-esr52

リポジトリの規模が数GBと大きいので、cloneにはそれなりの時間がかかります。

Firefox本体のリポジトリをcloneできたら、次は言語リソースのリポジトリをcloneします。 言語リソースのリポジトリは言語ごとに分かれており、ESRを含むリリース版のFirefoxの日本語用言語リソースはhttps://hg.mozilla.org/releases/l10n/mozilla-release/jaです。

$ hg clone https://hg.mozilla.org/releases/l10n/mozilla-release/ja ja

cloneする際に、hgコマンドの3番目の引数としてディレクトリ名を、明示的に言語コード名のjaと指定します。 これは、MozillaBuildでのビルド時には言語リソースが言語コード名に基づいて検索されるためです。

ビルドの準備

ビルドするリビジョンへの切り替え

cloneしたリポジトリは、次のリリースに向けての作業が進行している状態になっています。 Firefox ESR52.4.1のように特定のバージョンをビルドするには、タグに基づいてそのリビジョンに切り替える必要があります。

$ cd /c/mozilla-source/mozilla-esr52
$ hg tags | grep -E -e "FIREFOX_.+_RELEASE" | less

Firefox ESR52.4.1のタグはFIREFOX_52_4_1esr_RELEASEですので、ここにリビジョンを切り替えます。

$ cd /c/mozilla-source/mozilla-esr52
$ hg update FIREFOX_52_4_1esr_RELEASE

言語リソースのリポジトリも対応するリビジョンに切り替える必要がありますが、Firefox 45よりも後のバージョンについては何故か、言語リソースのリポジトリには本体と共通のタグが付けられていません。そこで、Android版FirefoxやThundebrirdのタグ情報を参考にしつつ適切なリビジョンを探すことにします。まず、hg log --graphでコミットグラフを表示します(Webでも同様のコミットグラフを見られます)。

$ cd /c/mozilla-source/ja
$ hg log --graph

Firefox ESRの場合は、ESR版の元になったリリースのタグが属しているコミットグラフの縦線を辿っていき、近いバージョン番号のThunderbirdのタグを探します。すると、THUNDERBIRD_52_4_0_RELEASEというタグの付けられたコミットが見つかります。 これより新しいリビジョンはこの縦線の上には存在しませんので、Firefox ESR52.4.1の日本語リソースはこのリビジョンを使ってビルドしてよいと考えられます。

言語リソースの使用リビジョンを特定できたら、そのリビジョンに切り替えて次の行程に進みましょう。

$ cd /c/mozilla-source/ja
$ hg update THUNDERBIRD_52_4_0_RELEASE
ビルドオプションの指定

ビルド対象のリビジョンをチェックアウトしたら、次は、ビルドオプションを指定します。

ビルドオプションの指定はFirefox本体のリポジトリのトップレベルのディレクトリに.mozconfigという名前のテキストファイルとして保存します。 今回の例ではc/mozilla-source/mozilla-esr52/.mozconfigの位置です。

ビルドオプションは様々な物がありますが、ここでは話を単純化するために、公式のFirefox ESR52.4.1のうちブランディングに関わる部分以外は同一の指定とします。

Firefoxではabout:buildconfigを開くとそのバイナリの元になったビルドオプションの一覧を見ることができます。ただ、実際のmozconfigには、それらに加えて環境変数の指定なども必要です。単にビルドオプションだけを指定した状態だと、Visual Studioのランタイムライブラリがインストーラに含まれないなど、一般ユーザの環境で使用するには不都合があるビルド結果となってしまいます。

ビルド環境が64bit版のWindowsで、Visual Studio Community 2015を使う場合の基本的なビルド設定は、Firefox自体のリポジトリのbuild/win32/mozconfig.vs2015-win64の位置(※今回ビルドしたいのはFirefox ESR52.4.1なので、オンラインで例を見る場合はmozilla-esr52リポジトリの物を参照して下さい)にファイルがあります。

このファイルに書かれている設定内容はMozillaで使用しているビルド環境向けの物なのですが、ここまでの手順通りに必要なソフトウェアを導入した場合、Visual Studioのインストール先パスなどが実際の物と異なっています。 ここまでの手順通りに環境を整えた場合のパスを指定するように改めた上で、Firefox ESR52.4.1のabout:buildconfigに列挙されているオプション群から必要でない物を除外し、ローカライズに必要なオプションを足した物が、以下の例です(行頭の#はコメントアウトです)。

export VSPATH='/C/Program Files (x86)/Microsoft Visual Studio 14.0'

#---------- based on build/win32/mozconfig.vs2015-win64 -------------

if [ -z "${VSPATH}" ]; then
    TOOLTOOL_DIR=${TOOLTOOL_DIR:-$topsrcdir}
    VSPATH="$(cd ${TOOLTOOL_DIR} && pwd)/vs2015u3"
fi

VSWINPATH="$(cd "${VSPATH}" && pwd -W)"

# Windows 10 SDKのインストール先はVisual Studioの配下ではないため、適切なパスを明示的に指定する。
#export WINDOWSSDKDIR="${VSWINPATH}/SDK"
export WINDOWSSDKDIR="/C/Program Files (x86)/Windows Kits/10"
export WIN32_REDIST_DIR="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT"
# 再配布可能なランタイムライブラリはWindwos 10 SDKに含まれるため、こちらも適切なパスを指定する。
#export WIN_UCRT_REDIST_DIR="${VSPATH}/SDK/Redist/ucrt/DLLs/x86"
export WIN_UCRT_REDIST_DIR="${WINDOWSSDKDIR}/Redist/ucrt/DLLs/x86"

# ビルド過程で使われるツール類の一部はVisual Studio配下ではなくWindows 10 SDKに含まれる。
# また、ランタイムライブラリも一部はWindows 10 SDKに含まれている。
# 前述の通り、Windows 10 SDKの正しいインストール先を参照するように改める必要がある。

# 修正前
#export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${VSPATH}/SDK/bin/x86:${VSPATH}/SDK/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}"
#export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${VSPATH}/SDK/Redist/ucrt/DLLs/x86:${VSPATH}/SDK/Redist/ucrt/DLLs/x64:${PATH}"

#export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${VSPATH}/SDK/Include/10.0.14393.0/ucrt:${VSPATH}/SDK/Include/10.0.14393.0/shared:${VSPATH}/SDK/Include/10.0.14393.0/um:${VSPATH}/SDK/Include/10.0.14393.0/winrt:${VSPATH}/DIA SDK/include"
#export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${VSPATH}/SDK/lib/10.0.14393.0/ucrt/x86:${VSPATH}/SDK/lib/10.0.14393.0/um/x86:${VSPATH}/DIA SDK/lib"

# 修正後
export PATH="${VSPATH}/VC/bin/amd64_x86:${VSPATH}/VC/bin/amd64:${VSPATH}/VC/bin:${WINDOWSSDKDIR}/bin/x86:${WINDOWSSDKDIR}/bin/x64:${VSPATH}/DIA SDK/bin:${PATH}"
export PATH="${VSPATH}/VC/redist/x86/Microsoft.VC140.CRT:${VSPATH}/VC/redist/x64/Microsoft.VC140.CRT:${WINDOWSSDKDIR}/Redist/ucrt/DLLs/x86:${WINDOWSSDKDIR}/Redist/ucrt/DLLs/x64:${PATH}"

export INCLUDE="${VSPATH}/VC/include:${VSPATH}/VC/atlmfc/include:${WINDOWSSDKDIR}/Include/10.0.15063.0/ucrt:${WINDOWSSDKDIR}/Include/10.0.15063.0/shared:${WINDOWSSDKDIR}/Include/10.0.15063.0/um:${WINDOWSSDKDIR}/Include/10.0.15063.0/winrt:${VSPATH}/DIA SDK/include"
export LIB="${VSPATH}/VC/lib:${VSPATH}/VC/atlmfc/lib:${WINDOWSSDKDIR}/lib/10.0.15063.0/ucrt/x86:${WINDOWSSDKDIR}/lib/10.0.15063.0/um/x86:${VSPATH}/DIA SDK/lib"

. $topsrcdir/build/mozconfig.vs-common

mk_export_correct_style WINDOWSSDKDIR
mk_export_correct_style INCLUDE
mk_export_correct_style LIB
mk_export_correct_style PATH
mk_export_correct_style WIN32_REDIST_DIR
mk_export_correct_style WIN_UCRT_REDIST_DIR

#--------------------------------------------------------------------

# bug 1356493 の回避のため、Windows 10上ではコマンドの探索先パスを追加する。
export PATH="$PATH:$WINDOWSSDKDIR/bin/10.0.15063.0/x64"
# パスを含む環境変数の内容を変更した後は、ビルドツールの1つである
# 「Pymakeが受け付けるパス形式に変換するために、必ず
# 「mk_export_correct_style 環境変数名」を実行する。
mk_export_correct_style PATH

ac_add_options --enable-crashreporter
ac_add_options --enable-release
ac_add_options --enable-jemalloc
ac_add_options --enable-require-all-d3dc-versions
ac_add_options --enable-warnings-as-errors

# 日本語リソースを使うための指定。
mk_add_options MOZ_CO_LOCALES=ja
ac_add_options --enable-ui-locale=ja
ac_add_options --with-l10n-base=c:/mozilla-source

日本語リソースの参照用に--with-l10n-baseで指定するパスは、言語リソースのリポジトリのパスではなく、その1つ上位のディレクトリのパスです。 MozillaBuildは、ここにMOZ_CO_LOCALESで指定した言語コード名を足したパスの /c/mozilla-source/ja に言語リソースがあることを期待します。

ビルドの実施

準備ができたら、いよいよビルドです。 Firefox本体のリポジトリにcdして、./mach buildを実行すればビルドが始まります。

$ cd /c/mozilla-source/mozilla-esr52
$ ./mach build

ビルドに使用するマシンの性能にもよりますが、現在手に入る一般的な性能のPCであれば1時間以内にはビルドが完了すると思われます。 弊社で検証に使用した環境は以下の性能のホストマシン上の仮想マシンで、仮想環境であることのオーバーヘッドがボトルネックとなった結果、ビルド時間はおよそ130分を要しました。

  • Interl Core i7 3.4GHz
  • 16GB RAMのうち8GBを割り当て

ビルドが完了したら、本当に動作するか確かめてみましょう。 以下のコマンドを実行すると、ビルドされたFirefoxが起動します。 アプリケーション名は「Nightly」になっているはずです。

$ ./mach run

正しく動作することを確認できたら、インストーラを作成しましょう。 これは以下のコマンドで行えます。

$ ./mach build installer

できあがったインストーラは、カレントディレクトリから見てobj-i686-pc-mingw32/dist/install/sea/の位置、フルパスではC:\mozilla-source\mozilla-esr52\obj-i686-pc-mingw32\dist\install\sea\の位置に出力されます。

まとめ

以上、Firefox ESR52.4.1を独自にビルドするための手順を簡単に解説しました。

Firefoxは現在様々な部分の刷新を進めており、現時点での最新の開発版においてはビルドに必要なツールとしてRustやLLVM/Clangのセットアップなども必要になってきています。この記事に記載した手順はあくまでESR52でのものなので、残念ながらそのままでは新しいバージョンのFirefoxには適用できませんのでご注意下さい。これについては、次のESRであるFirefox 59ESRがリリースされた後に改めてフォローアップ記事を公開する予定です。

タグ: Mozilla
2017-10-12

Firefox 57以降での従来アドオン廃止を踏まえたFirefoxの企業利用での対策について

Firefox 57では従来形式のアドオンがすべて無効化され、Firefox 52移行で既に使用可能になっている新方式のアドオンのみが有効になります。 この事がFirefoxの企業・法人利用に与える影響とその対策についてご案内します。

ESR版での影響

Firefoxの製品ラインナップには、約1.5ヶ月ごとに更新されメジャーバージョンが繰り上がっていく一般向けの「通常リリース版」と、1年ほどの間メジャーバージョンが固定されセキュリティアップデートだけが提供され続ける「ESR版」とがあります。

現在の通常リリース版の最新版はFirefox 56.0、ESR版の最新版はFirefox 52.4.0ESRです。 ESR版の次のメジャーアップデートはFirefox 59との同時リリースが予定されており、それまでの間は引き続き52.5.0ESR、52.6.0ESRといったセキュリティアップデートが提供される見込みです。 そのため、ESR版をお使いの場合には今すぐ対策を取る必要はありません。

ただし、Firefox 59ESRでは従来アドオンが無効化されるという事は確定事項なので、いざその時が来ても慌てずに済むように、今のうちから対策を検討しておくのが望ましいです。

アドオンによらないカスタマイズが受ける影響

Firefoxの企業利用においては、管理者が決めた設定を全クライアントに反映するための方法としてMCDが使われる事が多いでしょう。

現時点で判明している限りにおいては、MCDでのカスタマイズはFirefox 57以降のバージョンでも引き続き使用可能と考えられます。

ただし、Firefox 57以降のバージョンでは従来アドオンの無効化に伴い、従来アドオンとの互換性のために残されていたもののメンテナンスの負担となっていた内部的な古い機能・実装の削除が進められています。 MCDでは裏技的に Components.classes[...] といったコードによってFirefoxの内部機能へアクセスすることができていましたが、この方法で呼び出していた内部機能がFirefox 59ESRにおいては削除されている可能性があります。 その場合、代替する方法を調査してMCDの設定ファイルを更新しなくてはなりませんが、場合によっては代替手段がないため設定を諦めなくてはいけないかもしれません。 十分にご注意下さい。

アドオンによるカスタマイズが受ける影響

カスタマイズにアドオンを使用していて、そのアドオンがFirefox 57以降のバージョンに対応していない場合、当然ながらFirefox 59においてもそのアドオンは使用できません。 アドオンをFirefox 57以降の新仕様に対応したバージョンに更新するか、当該アドオンで行っていた変更を代替する他の方法でのカスタマイズに切り替える必要があります。

クリアコードではお客様向けのカスタマイズ用として多数のアドオンを開発・提供しています。 しかしながら、これらの中でFirefox 57以降のバージョンでも使用できるように更新された物はほとんどなく、また更新の可能性も極めて低いと言わざるを得ません。 これは従来のアドオンと新方式のアドオンとの根本的な差異に原因があります。

従来のアドオンはFirefoxの内部に自由に変更を加える事ができ、弊社提供のアドオンの多くは、この性質を使ってFirefoxに「使用を禁止したい機能を無効化・非表示にする」といった変更を加えていました。 (図:従来のアドオンの原理) その一方で、Firefox 57以降で使用可能な新方式のアドオンは、各アドオンを隔離された環境で動作させ、あらかじめ用意されたAPIを経由してのみFirefoxに影響を与えられるという仕組みになっています。 (図:新方式のアドオンの原理) そのため、「行いたい変更を実現するためのAPI」が提供されていない変更は不可能ということになります。

なお、新方式のアドオンで利用できるAPIにはNative Messagingという物もあり、「APIが提供されていない事でもこれを使えば実現できる」という触れ込みとなっています。 ですが、上記の理由でFirefox 57に対応できない弊社製アドオンの多くに対しては、残念ながらこの機能も解決策とはなりません。 Native Messaging Hostはアドオンとローカルアプリケーションとの間で通信ができるという物で、Firefoxの外の世界との連携によってできる事の幅は広がりますが、Firefox内部に対しては依然として何もできないままだからです。

使えなくなるアドオンの代替となるカスタマイズ方法

以上の理由から、アドオンへの依存度が高い場合ほど、Firefox 57以降での変更への対応には注意を要します。 運用上使用を禁止したい機能がある場合には、Firefox上では機能を無効化できないため、別の技術レイヤーで機能を停止・無効化しなくてはなりません。

現在公開中の企業利用向けカスタマイズのまとめでは、目的からカスタマイズの方法を逆引きできる形で資料を整備していますが、中にはカスタマイズにアドオンを必要とする項目も含まれています。 そのため各項目について調査を行い、現時点で代替手法が存在する物についてはその旨を追記し、代替手法が無い物はカスタマイズ項目としては廃止の扱いとするよう内容を更新しました。 (※この資料の内容は随時更新されていますので、必ずその時点での最新版をご参照いただくようご注意下さい。)

ただし、この資料には弊社サポートサービスをご利用になられているお客様の環境で必要となったために調査した範囲のカスタマイズ内容のみが記載されており、それ以外の未知のカスタマイズについては情報がございません。 この資料に記載がないカスタマイズを反映されていて、Firefox 57以降への対応に不安をお持ちの企業担当者様がいらっしゃいましたら、まずはお問い合わせフォームよりご相談下さい。

Firefox 57以降に対応済みのアドオンのご紹介:IE View WE

弊社サポートサービスをご利用のお客様の環境ではアドオン「IE View」の使用頻度が高いものの、このアドオン自体は長らく更新が停止されており、また代替となる他のアドオンでは管理者側で設定を制御できるものが無かったことから、弊社で独自にFirefox 57以降にも対応したクローン版であるIE View WEを開発・公開しています。

従来バージョンのIE Viewとの差異として、URLのリストの形式に互換性が無い点にご注意下さい。 IE View WEでは、URLのリストはFirefoxのアドオンで一般的に使用されるマッチパターンのリストとして解釈されます。 従来バージョンでは http://www.example.com のようにホスト名までのみの指定でも機能しますが、IE View WEではhttp://www.example.com/* のように明示的にワイルドカードを使用してマッチパターンとして記載する必要があります。

Native Messaging Hostのインストール

IE View WEは前述のNative Messaging Hostを使用して外部アプリケーションを起動するという動作を実現しているため、使用にあたっては別途専用のNative Messaging Hostのインストールが必要です。 GitHubのリリース一覧ページからieview-we-host.zipをダウンロードして展開し、install.batを管理者として実行すると実行ファイルがインストールされます。 (一般ユーザーとして実行した場合、そのユーザーでのみ使用可能な状態でインストールされます。ご注意下さい。)

管理者による設定の提供

Firefox 57以降のバージョンで使用可能なManaged Storage機能を通じて、管理者が固定の設定を提供することができます。 GitHubのリリース一覧ページからieview-we-managed-host.zipをダウンロードして展開し、install.batを管理者として実行すると設定ファイルがインストールされます。 (一般ユーザーとして実行した場合、そのユーザーでのみ参照可能な状態でインストールされます。ご注意下さい。) ファイルのインストール前にieview-we@clear-code.com.jsonを編集してdata配下に以下の情報を設定すると、それがそのままIE View WE用の設定となります。

  • ieapp (文字列値, IEの実行ファイルのパス)
  • ieargs (文字列値, IEの起動時に指定する追加の引数)
  • forceielist (文字列値, URLのマッチングパターンの空白文字区切りのリスト)
  • disableForce (真偽値, forceielist`で与えられたリストの無効化)
  • contextMenu (真偽値, コンテキストメニュー項目の有効化)
  • debug (真偽値, デバッグログ出力の有効化)

また、Firefox 56以前のバージョン向けとして、MCDの設定ファイルから設定を読み取る機能もあります。 MCD用設定ファイルの以下の設定項目がある場合、それぞれ対応する設定に反映されます。

  • extensions.ieview.ieapp
  • extensions.ieview.ieargs
  • extensions.ieview.forceielist
  • extensions.ieview.disableForce
  • extensions.ieview.contextMenu
  • extensions.ieview.debug

ただし技術的な制限のため、設定ファイルの配置場所や内容、使用状況によっては設定をインポートできない場合があります。 悪しからずご了承下さい。

まとめ

以上、Firefox 59ESRを見据えたFirefox 57以降のバージョンでのアドオンの仕様変更に伴う企業利用上の注意点とその対策、およびFirefox 57以降でも使用可能なIE ViewのクローンであるIE View WEについてご紹介しました。

クリアコードではFirefoxの企業での利用に際してのカスタマイズのご案内、導入支援、発生したトラブルの原因究明、対策の調査等をサポートサービスとして行っております。 これらの事でお困りの企業担当者様がいらっしゃいましたら、お問い合わせフォームよりご相談下さい。

タグ: Mozilla
2017-10-05

Firefoxで外部アプリケーションを起動するだけのアドオンをGo言語で作る方法と注意点

キーワード:golang, バックグラウンド実行, detach

Firefoxのアドオンは、現在はWebExtensionsというAPI群に基づいて開発するようになっています。 このAPI群には「別のブラウザなどの任意のローカルアプリケーションを直接起動する機能」は含まれておらず、そのようなことをしたい場合にはNative Messagingという仕組みで間接的に実現する必要があります。 これは要するに、「コマンドラインオプションやGUIではなく標準入力から与えられる情報を使って、任意のアプリケーションを起動するランチャー」を開発するということです。 関係を図にすると以下のようになります。

+---------------------+
|       Firefox       |   
|+-------------------+|
||Firefox上のアドオン||
|+-------------------+|
|        ↓↑         |
| <WebExtensions API> |
| (Native Messaging)  |
+--------↓↑---------+
     <標準入出力>
         ↓↑
+---------------------+
|      ランチャー     |
+---------------------+
          ↓
   <システムコール>
          ↓
+---------------------+
|外部アプリケーション |
+---------------------+

このNative Messagingの仕組み自体は、細部を除けばGoogle Chrome用拡張機能での仕組みとほぼ同じ仕様です。 そのためFirefoxとChromeに両対応した実装が既にいくつか存在しており、中には上記のようなランチャーとして振る舞う物もあります。 例えばOpen InというプロジェクトではNode.jsベースで開発されたランチャーアプリケーションの実装を使っています。

この記事では、これと似たような物をGo言語で実装する時の注意点を解説します。

起動したい外部アプリケーションが巻き込みで終了されてしまう問題

Go言語では、外部アプリケーションを起動する方法としてexecパッケージを使うのが一般的です。 このパッケージでは同期実行(外部アプリケーションの終了を待ってから次の処理に進む)のexec.Command().Run()と非同期実行(外部アプリケーションを起動した後、すぐに次の処理に進む)のexec.Command().Start()の2つの機能が提供されています。

例えば、FirefoxのWebページ上のコンテキストメニューに「このページをInternet Explorerで開く」のような項目を追加してそこから別のブラウザを起動するというような場合、exec.Command().Run()でIEを起動するとIEを終了するまでの間ずっとランチャーアプリケーションのプロセスが生き続けることになります。 また、そこから起動されるIEのプロセスはFirefoxから見て孫プロセスという扱いになりますので、うっかりその状態でFirefoxを終了すると、孫プロセスになっているIEまでもがまとめて終了されてしまいます。

ということから、このような場面ではexec.Command().Start()の方を使えばよいと考えられるのですが、実際には期待した通りの結果になりません。 こちらで起動した場合でもIEのプロセスは孫プロセスになってしまい、ランチャーがexec.Command().Start()を実行してIEを起動した後でmain()の最後に到達してプロセスが終了すると(Firefoxがランチャーを終了させると)、やはり孫プロセスのIEまで巻き添えで終了されてしまうのです。

プロセスグループを分けての外部アプリケーションの起動

このような現象が発生するのは、Firefox・ランチャー・孫プロセスとして起動されたIEの全てが同じプロセスグループに属しているからです。 言い換えると、ランチャーが起動する孫プロセスについてプロセスグループを分ければ(プロセスをデタッチすれば)、Firefoxやランチャーが終了した後もIEを動作させ続けられると考えられます。 Go言語の場合、一般的にはこれは以下のようにして実現できます。

...
import (
  "os/exec"
  "syscall"
  "log"
)
...

func Launch(path string, args []string) {
  command := exec.Command(path, args...)

  // Windowsの場合
  command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: syscall.CREATE_NEW_PROCESS_GROUP}
  // Linux, maxOS (POSIX)の場合
  // command.SysProcAttr = &syscall.SysProcAttr{Setpgid: true}

  err := command.Start()
  if err != nil {
    log.Fatal(err)
  }
}

システムコールに渡すパラメータを指定する必要があるため、Windowsとそれ以外の環境とでは書き方が変わってきます。

このようにすることで晴れてプロセスグループが分かれてくれて、ランチャー終了後も外部アプリケーションが生き続けるようになってくれる……と思われたのですが、実際にWindows環境のFirefoxで検証してみたところ、残念ながら期待通りの結果は得られませんでした。

上記のようなコードを使って一般的なコマンドラインアプリケーションとして起動する状態にしたランチャーで試す分には、期待通りの振る舞い(ランチャーの終了後も外部アプリケーションのプロセスが残る)を見せました。 しかし、FirefoxのアドオンからNative Messagingの仕組みを経由して起動されるNative Messaging Hostとしてランチャーを動作させると、依然として外部アプリケーションまで終了されてしまうのでした。

Firefoxに固有の事情

これは、Go言語一般の話や、Google Chromeなどと共通の仕組みとしてのNative Messagingではなく、Firefox固有の事情による現象です。

実はWebExtensionsにおけるNative Messagingの説明に記載がありますが、Windowsにおいてこのようなランチャーから外部のプロセスを起動する際は、CREATE_NEW_PROCESS_GROUPではなくCREATE_BREAKAWAY_FROM_JOBという定数で示されるフラグを指定する必要があります。

Go言語の場合はsyscallモジュールにこの定数の定義が含まれていないため、仕様に基づいて0x01000000という数値を直接記述することになります。

func Launch(path string, args []string) {
  command := exec.Command(path, args...)
  // CREATE_BREAKAWAY_FROM_JOB = 0x01000000
  command.SysProcAttr = &syscall.SysProcAttr{CreationFlags: 0x01000000}
  err := command.Start()
  if err != nil {
    log.Fatal(err)
  }
}

WebExtensionsの元になっているGoogle ChromeのNative Messagingの仕様では特にこのような指定は必要ないため、ChromeとFirefoxの両方に対応したNative Messaging Hostを開発する際には注意が必要です。

このようにフラグを指定して実行すると、無事期待する通りの結果を得ることができました。

(この情報は本記事の初版に対するフィードバックで教えていただきました。ご指摘ありがとうございます。)

有効だった回避策(Windows向け)

結論から述べると、この問題は以下のようなバッチファイルで解決できました。

@ECHO OFF

start %*

これは、自身にコマンドライン引数として渡された内容をそのままコマンド列として非同期に実行するというバッチファイルです。 ランチャーから外部アプリケーションを起動する際に、直接起動せずこのバッチファイル(※正確にはcmd.exe)を介して起動するという使い方をします。

この時の各ソフトウェア同士の関係を図にすると、以下のようになります。

+---------------------+
|       Firefox       |   
|+-------------------+|
||Firefox上のアドオン||
|+-------------------+|
|        ↓↑         |
| <WebExtensions APi> |
| (Native Messaging)  |
+--------↓↑---------+
     <標準入出力>
         ↓↑
+---------------------+
|      ランチャー     |
+---------------------+
          ↓
   <システムコール>
          ↓
+---------------------+
|    バッチファイル   |
+---------------------+
          ↓
   <startコマンド>
          ↓
+---------------------+
|外部アプリケーション |
+---------------------+

単純なのですが、これによってバッチファイルから先のプロセスが別のプロセスグループに分かれるようになり、FirefoxからNative Messaging Hostとしてランチャーを起動した場合でも、Firefoxの終了後も外部アプリケーションのプロセスが残り続けるようになりました。 また、このような動作をさせる場合、ランチャーでバッチファイルを起動する際にはcommand.SysProcAttrの指定はあってもなくても結果は変わりませんでした。

なお、バッチファイルを使うとなるとcmd.exe(コマンドプロンプト)のウィンドウが一瞬表示されるのではないかという懸念もありましたが、実際には特にそのようなこともなく自然に外部アプリケーションが起動しました。 これも、Firefoxがランチャーを起動する際に与えている何らかの指定の影響ではないかと考えられます。

まとめ

以上、Go言語で実装したランチャーを経由してFirefoxから外部アプリケーションを起動する際に、Firefoxが終了した後も外部アプリケーションを起動した状態のままとするためには、バッチファイルを使うとよいCREATE_BREAKAWAY_FROM_JOBフラグの指定が必要であるという注意点をご紹介しました。

環境によってはバッチファイルの実行自体が制限されている場合もあるかもしれません。実際に動作するかどうか、はあらかじめ確認を取っていただくことを強くお勧めします。

FirefoxのWebExtensions APIはGoogle Chromeの拡張機能向けAPIを参考に設計されています。そのためChrome用拡張機能の開発でのノウハウの多くを流用することができ、何か詰まった時は「Chrome用拡張機能ではどうするのが普通なのか?」という観点で調べれば解決策が見つかることが多いです。

しかしながら両者は完全に同一のものではなく、前述のようなFirefoxに固有の注意事項というものもあります。本記事の初版公開時には、オフィシャルのドキュメントにある注意書きを見落としたまま先入観からChrome用拡張機能向けの情報だけを調査していた結果、肝心な情報に全く辿り着けないという結果となっていました。検索も万能ではない(検索語句がずれていると必要な情報に辿り着けない)ために頼りすぎる事・その時得られた結果を過信しすぎる事のリスク、先入観に囚われずにオフィシャルの情報を丁寧に読む事の重要性を改めて実感した次第です。

タグ: Mozilla
2017-09-01

Firefoxの先読み機能の無効化とその確認手順

※この記事の情報はFirefox ESR52を対象としています。これより新しいバージョンではこれらの情報は当てはまらない場合がありますので、ご注意下さい。

Firefoxの先読み機能の種類と無効化の方法

FirefoxにはWebサイトの閲覧時の体感的な快適さを向上するため、実際にリクエストが行われる前から接続やデータのダウンロードを行っておくという「先読み」の機能があります。ただし、無差別に全てのリンクを辿るのではなく、いくつかの条件が揃った時に初めて先読みが発動するようになっています。具体的には以下の要領です。

ユーザーから見れば快適さが増して嬉しい機能ですが、反面、ネットワークのトラフィックやセッション数は増大する事になります。弊社のMozillaサポート事業のお客様からも、契約回線の帯域が限られているなどの理由で、情報システム管理担当者の意向として「先読み機能を無効化したい」というご相談を頂く事があります。また、「既に担当者側でそのような設定を行ったつもりだが、実際にその指定が機能しているかどうかを確認したい」というご相談もあります。

前述の先読みに類する機能を全て無効化する場合、指定は以下のようになります。

  • 全般的な制御
    • network.predictor.enabledfalse(真偽型)
    • network.predictor.enable-hover-on-sslfalse(真偽型)
    • network.predictor.enable-prefetchfalse(真偽型)
  • 次のページを示すリンクの先読み
    • network.prefetch-nextfalse(真偽型)
  • DNSプリフェッチ
    • network.dns.disablePrefetchtrue(真偽型)
    • network.dns.disablePrefetchFromHTTPStrue(真偽型):TLSを使用したページでの挙動の制御。
  • リンクの上にポインタが載った時の投機的接続の抑止
    • network.http.speculative-parallel-limit0(整数型)

では、これらの設定が期待通りに反映されている事をどのように確認すればよいのでしょうか。これがこの記事の本題です。

設定が反映されているかどうかの確認

設定の書き間違いのようなケアレスミスや、それ以外にも何らかの理由から、設定したはずの値が反映されないままになっているという事は度々あります。そのため、この手のカスタマイズはなるべく、実際の挙動から設定の反映状況を確認する事が望ましいです。

データのダウンロードまでも行う場合の確認

HTTP接続からデータのダウンロードまでを行うフルスペックの「先読み」については、先読み対象となるHTTPリクエストが実際に処理されたかどうかを見るのが確実です。例えばこのブログの2016年5月10日の記事同年5月18日の記事へのリンクを含んでいますが、このリンクにはrel="next"という属性が指定され次のページへのリンクである事が示されているため、先読みの対象となります。

このような先読みは、ブラウザコンソールで動作を確認できます。 キーボードショートカットの「Ctrl-Shift-J」を使うかパネルメニューの「開発ツール」(またはメニューバーの「Web開発」)で「ブラウザコンソール」をクリックするとブラウザコンソールのウィンドウが開かれますので、そのウィンドウ上部の「ネットワーク」がハイライトされている状態でこのブログの2016年5月10日の記事を開いてみて下さい。先読みが機能していればGET http://www.clear-code.com/blog/2016/5/18.htmlというメッセージがコンソールに出力され、機能していなければこのメッセージは出力されません。

コンソールに出力される出力が多すぎて見分けが難しいといった場合には、低レベルのログでも動作を確認できます。この方法については後述します。

DNSの名前解決のみ行われる場合の確認

先読み機能には、DNSでの名前解決のみを先行して行う機能(DNSプリフェッチ)もあります。例えば、ページ内に<link rel="dns-prefetch" href="http://dns-prefetch.example.com">のような記述が含まれていると、FirefoxはHTTPでのリクエストではなく、dns-prefetch.example.comの名前解決の問い合わせのみを単独で行います。

この機能が動作しているかどうかを確認するためは、DNSへの問い合わせを行うモジュールが内部的に発行するイベントを監視する必要があります。この確認にもブラウザコンソールを使います。

そのためには、about:configで以下のように設定します。

  • devtools.chrome.enabledtrue(真偽型):スクリプトの実行を可能にする。
  • network.dns.notifyResolutiontrue(真偽型):DNSでの名前解決時に内部的なイベントを発行するようにする。

上記の設定を反映した状態でブラウザコンソールを開くと、コンソール下部に入力欄が出現します。この入力欄に Services.obs.addObserver(function(aSubject, aTopic, aData) { console.log(aTopic+': '+aData); }, 'dns-resolution-request', false); と入力してEnterキーを押すと、この内部的なイベントを捉えてコンソールにメッセージとして出力する事ができます。DNSプリフェッチのための指定が含まれているWebページ(テストケース)を開いた時にdns-resolution-request: dns-prefetch.example.comのようなメッセージがブラウザコンソールに出力されれば、DNSプリフェッチのための指定が処理されたと分かりますし、逆に、そのようなWebページを開いてもこのメッセージがコンソールに出力されなければ、DNSプリフェッチは無効化されていると判断できます。

TCP接続のみ行われる場合の確認

Firefoxの先読み機能の中には、データの読み込みは行わないまでも、ソケット接続のみ確立しておくという機能もあります。リンクの上にポインタが載った時点でまずソケット接続だけ先行して行っておき、クリックされた時にすぐにHTTPのリクエストを送出できるようにするというもので、この機能は投機的接続と呼ばれます。

ソケット接続を確立しただけの段階ではコンソールには何も出力されず、また、DNSプリフェッチのような内部的なイベントも発行されないため、投機的接続が行われているかどうかは、低レベルのログを解析して調べる必要があります。

Firefox 52ではログ収集対象のモジュールやログファイルの出力先は環境変数ではなく設定値で指定します。about:configで以下の設定を作成して下さい。

  • logging.nsHttp5(整数型)
  • logging.NetworkPredictor5(整数型)
  • logging.config.synctrue(真偽型)
  • logging.config.add_timestamptrue(真偽型)
  • logging.config.clear_on_startupfalse(真偽型)
  • logging.config.LOG_FILEC:\Users\Public\predict.log(文字列型)

この状態でFirefoxを再起動すると、logging.config.LOG_FILEで指定したパスの位置にpredict.log-main.123のような名前でログファイルが出力され始めます。(mainは親プロセスのログであることを示します。ファイル名の末尾の数字はプロセスIDです。)投機的接続の機能は通常のWebブラウズの中で暗黙的に行われるため、この状態のまましばらくWebページを2〜3画面分ほど見て回り、十分な量のログを溜めて下さい。

ログが溜まったら、上記のログ出力用の設定をリセットしてFirefoxを再起動し、出力されたログファイルをブラウザで開きます。この時、ログは以下のような内容になっているはずです。

2017-06-02 09:51:57.565000 UTC - [Main Thread]: I/Logger Flushing old log files
2017-06-02 09:51:57.659000 UTC - [Main Thread]: D/nsHttp Creating nsHttpHandler [this=9fe1400].
2017-06-02 09:51:57.659000 UTC - [Main Thread]: D/nsHttp nsHttpHandler::Init
2017-06-02 09:51:57.659000 UTC - [Main Thread]: D/nsHttp nsHttpHandler::PrefsChanged [pref=(null)]
2017-06-02 09:51:57.659000 UTC - [Main Thread]: D/nsHttp nsHttpHandler::PrefsChanged Security Pref Changed (null)
2017-06-02 09:51:57.659000 UTC - [Main Thread]: D/nsHttp nsHttpHandler::MakeNewRequestTokenBucket this=9fe1400 child=0
2017-06-02 09:51:57.659000 UTC - [Main Thread]: D/nsHttp nsHttpAuthCache::Init
2017-06-02 09:51:57.659000 UTC - [Main Thread]: D/nsHttp nsHttpAuthCache::Init
2017-06-02 09:51:57.659000 UTC - [Main Thread]: V/nsHttp Creating nsHttpConnectionMgr @b5a1420
...

続けて、キーボードショートカットの「Ctrl-Shift-K」を使うかパネルメニューの「開発ツール」(またはメニューバーの「Web開発」)で「Webコンソール」をクリックし、ログファイルを読み込んだタブに対してコンソールを表示します。コンソール下部の入力欄に以下のログ解析用のスクリプトをコピー&ペーストし、Enterキーを押して実行します。

var body = document.body.textContent;
var requested = body.match(/nsHttpConnectionMgr::OnMsgSpeculativeConnect/g) || [];
var skipped = body.match(/Transport not created due to existing connection count/g) || [];
!requested.length ? 'not working' : requested.length == skipped.length ? 'all skipped' : 'preconnected';

すると、以下の3つのいずれかの解析結果がコンソールに出力されます。

  • preconnected:投機的接続が行われた。
  • all skipped:投機的接続が無効化されている(もしくは、同時接続数が最大値に達しているため投機的接続が行われなかった)。
  • not working:ネットワーク環境の都合等で投機的接続が行われていない。

3番目の「投機的接続が行われていない」というケースは、そのクライアントが接続しているネットワーク環境によって発生し得ます。 Firefoxは投機的接続を開始する前に接続先ホストがプライベートなネットワークに属しているかどうかを見ており、DNSで名前解決した結果が192.168.1.1010.21.0.100といったプライベートネットワーク上のIPアドレスだった場合には投機的接続の処理を中断するようになっています。 これは、投機的接続は元々、遠隔地にあるホストとのソケット接続の確立に時間がかかるためにフライングで接続しておくという趣旨の機能なので、ネットワーク的に近いホストに対しては行う意義が薄いからです。

他の先読み機能が働いているかどうかもデバッグ用ログから確認する

なお、前項で取得したログを解析すると、データの読み込みを伴う先読みが行われたかどうかも判別できます。今度は以下の解析用スクリプトをWebコンソールで実行します。

var body = document.body.textContent;
var success = body.match(/Predictor::Predict.*\n.*called on parent process.*\n.*not enabled/g) || [];
var all = body.match(/Predictor::Predict.*\n.*called on parent process/g) || [];
(success.length == all.length) ? 'all canceled' ? 'some canceled';

出力結果は以下の2つのうちいずれかです。

  • all canceled:先読み処理が無効化されている。
  • some canceled:先読み処理が有効である。

まとめ

以上、Firefox ESR52において先読みに類する機能の無効化の方法と、その検証方法をご案内しました。

ここで解説している情報の中には、まとまった情報源が無く、お客様のご要望を受けて調査した結果判明したという情報も含まれています。クリアコードではOSSのドキュメント化されていない仕様を調査して明らかにする業務も行っておりますので、導入をお考えのOSSが想定通りの動作をするかどうかに不安がある場合や、既にお使いのOSSが期待通りに動作せずお困りの場合などには、お気軽にお問い合わせ下さい

タグ: Mozilla
2017-06-16

Firefox 52以降でのルート証明書の自動インポート機能でできること、できないこと

Firefox ESR52以降のバージョンでは、隠し設定のsecurity.enterprise_roots.enabledtrueに設定することで、Windowsの証明書ストアに登録されたルート証明書をFirefox側で認識して使えるようになりました。

ただ、この機能の背景や使い方についてユーザー側の期待と実際の挙動との間に若干の齟齬が見られるため、ここで改めて状況を整理してみます。

この機能を有効にすると、以下の事を実現できます。

  • 組織内ネットワークに設置したSSLプロキシやSSLロガーの使用に必要な専用のルート証明書をFirefoxで認識させる。
  • 組織内ネットワークで運用しているサーバーの証明書専用のルート証明書をFirefoxで認識させる。

それに対し、以下のことは依然としてできません

  • Windowsの証明書ストアに元から含まれている証明書、例えば政府認証基盤のルート証明書をFirefoxで認識させる。 (「Firefoxでは日本の官公庁関係のWebサイトの一部が証明書エラーで見られない」という問題を解消する。)
  • 管理者でないユーザーがWindowsの証明書ストアに追加した証明書をFirefoxでも認識させる。

何故こうなっているのかは、機能の背景を知ることで理解できます。

この機能の導入経緯

上記の設定が導入されたBugを見ると、これは「エンタープライズでFirefoxを使いやすくする」という文脈に基づく機能だということが読み取れます。

「エンタープライズ」とは、従業員規模が千人や万人といった単位に達するような大規模な組織での使用ということです。このような規模の組織では組織内専用のルート証明書が必要になる事がままあり、それをFirefoxで使うためには証明書のインポート機能を提供するアドオンを使うか、集中管理の仕組みの実装の裏をかいて強引にインポートさせるかしかありませんでした。ところが、現在Firefoxは古い基盤技術からの脱却を進めているため、これらの裏技的なやり方は早晩使えなくなる見込みが立っています。そこで、裏技ではなくきちんとした正当な機能としてルート証明書をインポートする方法を設ける必要があった、というのがこれらのBugの背景にある事情です。

実際の動作

「Windowsの証明書ストア」は、実際にはそれ専用のデータベースがあるわけではありません。Windowsのレジストリ内には以下のようなレジストリキーの配下に証明書の情報が分散して格納されており、それらをマージして一覧表示した物を「証明書ストア」として見せているということになります。

  • HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates
  • HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates
  • HKEY_LOCAL_MACHINE\Software\Microsoft\EnterpriseCertificates
  • HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates

エンタープライズ運用における「ルート証明書の追加」とは、Active Directoryなどを使って、これらの中で特に以下の位置に証明書を登録する事を指しています。

  • HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates\Root\Certificates
  • HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\Root\Certificates
  • HKEY_LOCAL_MACHINE\Software\Microsoft\EnterpriseCertificates\Root\Certificates

このことから、Firefoxは前述の隠し設定が有効な場合、起動時に上記のレジストリキー配下を走査して、追加された証明書があればそれらをインポートするという設計となっています。

ただし、この機能によってインポートされた証明書は、Firefox自身の証明書ストアに永続的に保存されるわけではありません。複数のレジストリキーをマージした結果がWindowsの証明書ストアとして扱われるのと同様に、Firefoxでも「Firefox自身の元々の証明書ストアの内容」+「この機能によって認識されたWindowsの証明書ストア内の証明書」が、実際の認証処理における証明書ストアとして使われるという形となります。(そのため、不要になった証明書はWindowsの証明書ストアから削除するだけで、Firefoxからも認識されなくなります。Firefoxの証明書マネージャで証明書を削除する、という事をする必要はありません。)

動作確認の方法

この機能でインポートされた証明書はFirefoxの証明書マネージャには表示されないため、期待通りに証明書がインポートされているかどうかは、デバッグ用の詳細なログを見て判断する必要があります。

MCD用設定ファイルを使う場合には、例えば以下のようにします。

// 証明書のインポート機能を有効化する設定
lockPref('security.enterprise_roots.enabled', true);

// NSS(Firefoxのセキュリティモジュール)のログを出力するための設定
lockPref("logging.pipnss", 5);
lockPref("logging.config.LOG_FILE", "C:\\Users\\Public\\certlog.txt");
lockPref("logging.config.add_timestamp", true);
lockPref("logging.config.clear_on_startup", false);
lockPref("logging.config.sync", true);

この設定を反映した上で、実験用のダミーの証明書をHKEY_LOCAL_MACHINE\Software\Microsoft\EnterpriseCertificates\Root\Certificates配下に加えるレジストリファイルを使って「example.com」という名前の証明書をインポートした状態でFirefoxを起動すると、C:\Users\Public\certlog.txtに出力されたログに以下のような箇所が含まれるようになります。

...
2017-05-09 10:21:23.017000 UTC - [Main Thread]: D/pipnss certificate is trust anchor for TLS server auth
2017-05-09 10:21:23.017000 UTC - [Main Thread]: D/pipnss Imported 'example.com'
2017-05-09 10:21:23.017000 UTC - [Main Thread]: D/pipnss imported 1 roots
...

このD/pipnss Imported '(証明書の一般名)'というログが出ていれば、その証明書はFirefoxから見えています。逆に、そのようなログが現れていない場合、証明書は無視されているということになります。

ユーザーが自分で操作してWindowsの証明書ストアにインポートした証明書をFirefoxが認識しない理由

Windowsでルート証明書のファイルをダウンロードしてダブルクリックすると、その証明書を証明書ストアにインポートすることができます。しかし、この方法でインポートされた証明書は上記のFirefoxの機能では認識されません。何故でしょうか。

これは、証明書が保存されるレジストリ上の位置に理由があります。この方法で手動でインポートした証明書は、HKEY_CURRENT_USER配下の以下の位置に保存されます。

  • HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates

それに対し、Firefoxの証明書インポート機能はHKEY_LOCAL_MACHINE配下の以下のキーのみを走査します。エンタープライズ向けの機能としてはそれで正解で、個々のユーザーがWindowsの証明書ストアに追加した物まで認識するのはお門違いだからです。

  • HKEY_LOCAL_MACHINE\Software\Microsoft\SystemCertificates\Root\Certificates
  • HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\Root\Certificates
  • HKEY_LOCAL_MACHINE\Software\Microsoft\EnterpriseCertificates\Root\Certificates

政府認証基盤の証明書など、「Firefoxの証明書ストアには無いがWindowsの証明書ストアには含まれている」証明書がインポートされない理由と、その対処

2017年6月1日現在、政府認証基盤(GPKI)のルート証明書はFirefoxの証明書ストアには含まれていません。要望は上がっているのですが、政府側の対応がMozillaの定めるルート証明書の登録基準を満たしていないために作業が滞っているという状態です。

Windowsの証明書ストアには政府認証基盤の証明書も含まれているため、「Windowsの証明書ストアのルート証明書をFirefoxでインポートできるようになったのなら、政府認証基盤の証明書もインポートされて、証明書のエラーに悩まされることもなくなるのでは?」と期待される方もいらっしゃることでしょう。

ですが残念ながら、この機能では政府認証基盤のルート証明書はインポートされません。これは、Firefoxの証明書インポート機能の処理対象があくまで「管理者によってWindowsの証明書ストアに追加された証明書」に限られているからです。

Bugzilla上のコメント等で度々言及されていますが、Mozillaはユーザーの安全を守るために、どのルート証明書を登録するかを独自に判断しており、他社の判断を鵜呑みにはしないというポリシーで証明書ストアを管理しています。FirefoxがWindowsの全ての証明書を無条件で信頼してしまっていては、このポリシーが無意味になり、ユーザーの安全やプライバシーを保護するための判断を全てMicrosoftに委ねることになってしまいます。ですので、Mozillaとしてはあくまでユーザー(エンタープライズの文脈では、組織のシステム管理者)により追加された証明書のみをインポート対象にするというのが、この機能の趣旨となっています。

なお、以上のような趣旨のため、政府認証基盤(GPKI)のルート証明書を改めて「システム管理者が追加した証明書」としてWindowsの証明書ストアに登録すれば、これはFirefoxのインポート対象となります。

まとめ

以上、Firefox 52以降で使えるWindowsの証明書ストアからのルート証明書のインポート機能について、詳細と検証手順をご案内しました。

弊社で取り扱うFirefoxの導入・サポート案件においても、ルート証明書のインポートについては度々ご相談を頂いています。ニーズの高い機能でありながら今まで対応が進んでこなかったのには、アドオンや裏技的な方法で強引に解決してしまえるからという言い訳が立っていたからという側面は否定できないでしょう。従来からあるアドオンが切り捨てられるという方針の転換には批判の声も多く挙がっていますが、本体で対応される事が望ましい機能が本来あるべき形で実装されるきっかけとなったという事で、ここは素直に喜んでおきたいところです。

ちなみに、Firefox 52の段階ではこの機能はWindows限定の物となっています。macOSやLinuxでは設定を有効化しても効果を得られませんのでご注意下さい。

タグ: Mozilla
2017-06-01

GeckoエンジンにmacOSでprintToFileの機能を実装してみた話

はじめに

Geckoエンジンはクロスプラットフォームを標榜して作成されています。また、できるだけそのプラットフォームの特長を生かすため、そのプラットフォーム特有のAPIを使用するように作られている箇所もあります。印刷に関連するGeckoのコードももちろんプラットフォームごとに異なるAPIを呼ぶようになっており、通常使用する限りにおいてはどのプラットフォームも一様に印刷の機能を使用することができます。

今回の記事は nsIWebBrowserPrint に紐づいているprintという印刷のためのAPIにmacOSではPDFを印刷するのに必要な情報が渡っていなかった問題を解決した、という題材について書きます。

Geckoでの印刷の流れ

Geckoでは印刷をするときにプラットフォーム固有のAPIを使用する箇所とプラットフォーム非依存のAPIの箇所があります。

プラットフォームに依存するモジュールはサービス化されており、実行時に適切なContract ID(CID)を持ったモジュールがロードされる仕組みとなっています。このCIDはJavaScript側からも見え、アドオンやXULアプリケーション上でもこのCIDを用いて適宜必要なモジュールをロードしていきます。

印刷の流れを追うと以下のようになります。

nsDocumentViewer::Print(...)
  -> nsPrintEngine::Print(...) 
    -> nsPrintEngine::DoCommonPrint(...) 
      -> printDeviceContext->InitForPrinting(aDevice, ...) // プラットフォームごとに別々のDeviceContextが作成されるのでそれを用いる
        -> aDevice->MakePrintTarget(..) 
    -> nsPrintEngine::DoCommonPrint(...) //戻ってきたら印刷プレビューか印刷ジョブを行う

macOSのCocoaのAPIでの実装はwidget/cocoa以下に配置されています。

macOSでprintToFileが動かない問題のBug

macOSではnsIPrintSettingsServiceの関数のprintToFileへtrueを渡してもPDFヘ出力されないというBugが長年に渡って未解決です。詳細はMozillaのBugzillaのprintToFile is busted on Mac | Mozilla Bugzillaを参照してください。

macOSでGeckoのレンダリング結果をPDFへ出力できなかったのはなぜか

macOSでPDFへの出力をさせるのに必要な情報が上記Bugのパッチがコミットされる前のコードで設定されているかどうかを見ます。

macOSでCocoaのAPIからPDF印刷を行うには | ククログ にあるように、NSPrintSaveJobNSPrintJobSavingURL のような値が設定されているかを探します。

ここで、macOSでPDF印刷ができないのはnsIPrintSettingsServiceのCIDを持つ https://hg.mozilla.org/mozilla-central/file/0ca553b86af3/widget/cocoa/nsPrintSettingsX.mm にこれらの値がないかどうかを調べれば原因が分かります。

探すと見事にそのようなことをしている箇所はありませんでした。 前の記事を元にGeckoに向けたパッチを書く必要があります。

GeckoのXPCOMで生成されたクラスのAPIの振る舞いをプラットフォーム固有にする

GeckoはXPCOMの技術を用いており、インターフェースはidlファイルで定義されています。 ここではnsIPrintSettingsのidlは https://dxr.mozilla.org/mozilla-central/source/widget/nsIPrintSettings.idl です。

GeckoはC++で書かれているため、nsPrintSettings クラスに定義されているメソッドで必要なものをoverrideしてやれば目的は達成できます。

そのため、SetToFileName(const char16_t *aToFileName) をoverrideします。

diff --git a/widget/cocoa/nsPrintSettingsX.h b/widget/cocoa/nsPrintSettingsX.h
--- a/widget/cocoa/nsPrintSettingsX.h
+++ b/widget/cocoa/nsPrintSettingsX.h
@@ -51,8 +51,10 @@ public:
   void SetInchesScale(float aWidthScale, float aHeightScale);
   void GetInchesScale(float *aWidthScale, float *aHeightScale);

+  NS_IMETHOD SetToFileName(const char16_t *aToFileName) override;
+
   void SetAdjustedPaperSize(double aWidth, double aHeight);
   void GetAdjustedPaperSize(double *aWidth, double *aHeight);

diff --git a/widget/cocoa/nsPrintSettingsX.mm b/widget/cocoa/nsPrintSettingsX.mm
--- a/widget/cocoa/nsPrintSettingsX.mm
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -270,3 +275,30 @@ void nsPrintSettingsX::GetAdjustedPaperS
   *aWidth = mAdjustedPaperWidth;
   *aHeight = mAdjustedPaperHeight;
 }
+
+NS_IMETHODIMP
+nsPrintSettingsX::SetToFileName(const char16_t *aToFileName)
+{
+  NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT;
+
+  NSMutableDictionary* printInfoDict = [mPrintInfo dictionary];
+  nsString filename = nsDependentString(aToFileName);
+
+  NSURL* jobSavingURL =
+      [NSURL fileURLWithPath: nsCocoaUtils::ToNSString(filename)];
+  if (jobSavingURL) {
+    [printInfoDict setObject: NSPrintSaveJob forKey: NSPrintJobDisposition];
+    [printInfoDict setObject: jobSavingURL forKey: NSPrintJobSavingURL];
+  }
+  NSPrintInfo* newPrintInfo =
+      [[NSPrintInfo alloc] initWithDictionary: printInfoDict];
+  if (NS_WARN_IF(!newPrintInfo)) {
+    return NS_ERROR_OUT_OF_MEMORY;
+  }
+
+  SetCocoaPrintInfo(newPrintInfo);
+  [newPrintInfo release];
+  return NS_OK;
+
+  NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
+}

ここまででPDFに出力するための関数の実装です。

まだmacOS向けにはoverrideする必要がある関数が残っています:

  • 用紙の単位
    • NS_IMETHOD SetPaperSizeUnit(int16_t aPaperSizeUnit) override;
  • 拡大縮小倍率
    • NS_IMETHOD SetScaling(double aScaling) override;
  • 縦横
    • NS_IMETHOD GetOrientation(int32_t *aOrientation) override;
    • NS_IMETHOD SetOrientation(int32_t aOrientation) override;
  • 余白(GeckoではMerginに加えてUnwritaebleMerginという設定値が存在する)
    • NS_IMETHOD SetUnwriteableMarginTop(double aUnwriteableMarginTop) override;
    • NS_IMETHOD SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) override;
    • NS_IMETHOD SetUnwriteableMarginBottom(double aUnwriteableMarginBottom) override;
    • NS_IMETHOD SetUnwriteableMarginRight(double aUnwriteableMarginRight) override;

これらと、印刷用紙の単位変換を行なう処理を追加したものがこちらです。

このmozilla-centralのパッチではGeckoが印刷に用いている二つの単位系の対応も入っています。

  • インチ
    • kPaperSizeInches
  • ミリメーター
    • kPaperSizeMillimeters

の両方の単位系がGeckoでの印刷ではサポートされています。

そのため、

  • Inches -> Twips -> Pixels
  • Millimeters -> Twips -> Pixels

の両方を取り扱う必要があります。これは

diff --git a/widget/cocoa/nsPrintSettingsX.mm b/widget/cocoa/nsPrintSettingsX.mm
--- a/widget/cocoa/nsPrintSettingsX.mm
+++ b/widget/cocoa/nsPrintSettingsX.mm
@@ -254,8 +254,13 @@ NS_IMETHODIMP nsPrintSettingsX::SetPaper
 NS_IMETHODIMP
 nsPrintSettingsX::GetEffectivePageSize(double *aWidth, double *aHeight)
 {
-  *aWidth  = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
-  *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+  if (kPaperSizeInches == GetCocoaUnit(mPaperSizeUnit)) {
+    *aWidth  = NS_INCHES_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
+    *aHeight = NS_INCHES_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+  } else {
+    *aWidth  = NS_MILLIMETERS_TO_TWIPS(mAdjustedPaperWidth / mWidthScale);
+    *aHeight = NS_MILLIMETERS_TO_TWIPS(mAdjustedPaperHeight / mHeightScale);
+  }
   return NS_OK;
 }

diff --git a/widget/cocoa/nsDeviceContextSpecX.mm b/widget/cocoa/nsDeviceContextSpecX.mm
--- a/widget/cocoa/nsDeviceContextSpecX.mm
+++ b/widget/cocoa/nsDeviceContextSpecX.mm
@@ -49,6 +49,19 @@ NS_IMETHODIMP nsDeviceContextSpecX::Init
   if (!settings)
     return NS_ERROR_NO_INTERFACE;

+  bool toFile;
+  settings->GetPrintToFile(&toFile);
+
+  bool toPrinter = !toFile && !aIsPrintPreview;
+  if (!toPrinter) {
+    double width, height;
+    settings->GetEffectivePageSize(&width, &height);
+    width /= TWIPS_PER_POINT_FLOAT;
+    height /= TWIPS_PER_POINT_FLOAT;
+
+    settings->SetCocoaPaperSize(width, height);
+  }
+
   mPrintSession = settings->GetPMPrintSession();
   ::PMRetain(mPrintSession);
   mPageFormat = settings->GetPMPageFormat();

のコードにより取り扱うことができます。

ここではGetCocoaUnitkPaperSizeMillimeterskPaperSizeInches を返すprotectedメソッドです。また、SetCocoaPaperSizeは印刷用紙の大きさを設定するメソッドです。

まとめ

macOSでもXULアプリケーションなどから printToFile = true を設定したときにPDFへ出力するパッチについて解説しました。 このBugのレポート日時は 2011-08-01 12:35 PDT なので、このパッチで5年半越しの問題が解決されました。 mozilla-centralの該当コミットを見るとBug番号がまだ6桁であり、周辺のコミットに紐づいたBug番号は7桁なので感慨深いですね。

つづき: 2017-03-10
タグ: Mozilla
2017-02-22

Firefox 45ESRとFirefox 47以降で、法人利用に影響のある変更が入ります

Bug 1267567が修正され、Firefox 45ESRの次のリリースとFirefox 47以降のバージョンにおいて、法人利用に影響を及ぼし得る変更が入りました。

現在Firefox 45ESR、Firefox 46、およびThunderbird 45に対してMCDの集中管理用設定ファイルを使用している環境では、同じ設定ファイルを次以降のリリースのFirefoxまたはThunderbirdでそのまま使うと、期待通りの結果を得られない可能性があります。 説明を参考に、設定ファイルの修正を行ってください。

以下、詳細と対応方法について詳しくご説明します。

影響範囲

先に「次以降のリリースのFirefoxまたはThunderbirdで」と述べましたが、実際の所、Bug 1267567はFirefox 40からFirefox 45にかけて発生していた後退バグに関する物です。 つまり正確に言うと、以下に列挙したバージョンのFirefoxには、集中管理用設定ファイルの取り扱いに問題があるということになります。

  • 40.0
  • 40.0.1
  • 40.0.2
  • 41.0
  • 41.0.1
  • 41.0.2
  • 42.0
  • 43.0
  • 43.0.1
  • 43.0.2
  • 43.0.3
  • 43.0.4
  • 44.0
  • 44.0.1
  • 44.0.2
  • 45.0 / 45.0ESR
  • 45.0.1 / 45.0.1ESR
  • 45.0.2 / 45.0.2ESR
  • 45.1.0 / 45.1.0ESR
  • 45.1.1 / 45.1.1ESR
  • 46.0
  • 46.0.1

ここに列挙したいずれかのバージョンのFirefox、およびバージョン番号が一致しているThunderbird向けに作成されたMCDの集中管理用設定ファイルは、これら以外のバージョンでは正常に読み込まれない可能性があります。

問題の概要

集中管理用設定ファイルは、日本語の文字を含める場合はUTF-8でエンコーディングする必要がありました。 しかしながら上記のバージョンでは、MCD用設定ファイル全体をUTF-8でエンコーディングしていても、文字列型の設定値に非ASCII文字(例えば、ひらがな・カタカナ・漢字など)が含まれていると、値が正常に読み込まれず空文字になってしまうという問題が起こります。

これは、Bug 1137799の修正の結果、ファイルの読み込み後の内容がUTF-8バイト列そのままで扱われず、Unicode文字列に変換されるようになったためです。

Firefoxの文字列型設定を読み書きする内部APIは、Unicode文字列ではなくUTF-8バイト列を入出力に使用する設計になっています。 Firefox 38ESRおよびFirefox 39までのバージョンでは、設定ファイル全体がUTF-8バイト列として読み込まれたまま解釈されていたため、ファイル内にUTF-8バイト列として記述された文字列がそのまま内部APIに渡される形となり、結果として文字列型の設定値が正しく読み書きされるという状況でした。

ところが、Bug 1137799の修正によりファイル全体が一旦Unicode文字列に変換されるようになった結果、文字列型設定を読み書きする内部APIに対してUTF-8バイト列ではなくUnicode文字列が渡されるようになってしまいました。 その結果、上記のバージョンでは文字列値が期待通りに読み書きされなくなってしまっていました。

この問題を回避する方法として、設定ファイル内で文字列値をlockPref()などの関数に渡す前に、unescape(encodeURIComponent("文字列"));と変換する、という方法があります。 例えば以下の要領です。

function lockStringPref(aKey, aValue) {
  aValue = unescape(encodeURIComponent(aValue));
  lockPref(aKey, aValue);
}

lockStringPref("_test.string.non-ASCII", "日本語");

これにより、Unicode文字列をUTF-8バイト列にしてから文字列型設定値を保存するAPIに引き渡す事になるため、非ASCII文字の設定値も期待通りに認識されるようになります。

しかしながら、Bug 1267567の修正により、文字列型の設定値については、Firefox自身が自動的にUnicode文字列からUTF-8バイト列への変換を行うようになりました(上記の回避策と同等の事をFirefox自身が行うようになりました)。

その結果、集中管理用設定ファイル内で上記の回避策を実施している場合には、回避策が二重に実施されるのと同じ結果となり、却って動作がおかしくなってしまうという状況が発生します。

設定ファイルの修正方法

以上のような経緯のため、今後リリースされるFirefoxでは、上記の例のような回避策が不要となります。 日本語の値であっても、設定はすべてlockPref()などの関数の値に直接記述できるようになり、結果として、Firefox 38ESRおよびFirefox 39までの挙動と同じに戻りました。 上記の回避策に類する変換を行っていた場合、変換を行なわないように設定ファイルを修正してください。 それにより、引き続き同じ設定ファイルをFirefox 47以降・Firefox 45ESR以降で使い続けられます。

まとめ

Firefox 45ESR、Firefox 47、およびThunderbird 45の今後のリリースで行われた変更のうち、法人利用への影響が懸念される変更点について、概要と対策をご紹介しました。 影響を受けるバージョンを使われる際には、上記の点にくれぐれもご注意下さい。

タグ: Mozilla
2016-05-10

FirefoxアドオンのWebExtensions移行についてMozilla Blogに寄稿しました

弊社メンバーが執筆した、従来からあるFirefox用アドオンを「WebExtensions」ベースに移行させた際の知見を解説した記事がMozilla Add-ons Blogに掲載されました。

これは、筆者がアドオンのWebExtensions移行のための調査を進める中で、アドオンを1つ移行できた事についてMozillaのアドオン開発者向けメーリングリスト上に投稿した所、他のアドオン開発者にその時の知見を共有するゲスト記事の執筆を勧められたために書いた物です。 草稿段階では「WebExtensionsへの移行」に直接は関係していない前段階の準備にあたる話が多く含まれていたため、全文は筆者個人のブログで公開することにして、Mozilla Add-ons Blogには前段階の話を省いた内容が抜粋して掲載されています。

Mozilla Add-ons Blogは英語のブログである上に、記事自体もいくつかの前提が端折られています。 ここでは、WebExtensionsそのものの背景事情も含めて上記の記事の内容を紹介します。

WebExtensionsとは

WebExtensionsは、Firefox用アドオンを開発するためのAPIであると同時に、アドオンの新しい形式そのものの名前でもあります。 現在はGoogle Chromeの拡張機能用のAPI群をFirefox上に再現する形で開発が進められており、未実装の機能の充実を急いでいる段階です。 また、Google ChromeのAPI群には含まれていない・Firefoxで独自に拡張したAPI群も追加される見込みで、既にそのようになっている部分もあります。

Firefoxアドオン向けのAPIを整備する試みは「FUEL」や「Add-on SDK(旧Jetpack)」など過去にもありました。 それらとWebExtensionsとの最大の違いは、「WebExtensionsの導入」と「既存のアドオンの仕組みの廃止」が強く紐付いているという点です。

Mozilla Firefoxはアドオンによって様々なカスタマイズができることが特徴です。 しかし、アドオンを開発するための基盤や互換性維持のための仕組みの整備が遅れていた事から、

  • Firefoxの更新に追従できず、動かなくなるアドオンが出てくる。
  • お気に入りのアドオンが動かなくなるので、ユーザーはFirefoxを更新したくなくなる。あるいは、お気に入りのアドオンが動かないということでFirefoxを使う動機が薄れ、Firefoxを使わなくなる。
  • ユーザー離れを防ぐために、アドオンに大きな影響を与える変更をFirefoxに対して入れにくくなる。Firefoxの根本的な設計を改良できない状態が続いてしまう。

といった具合に、Firefox自体の進歩にとってもユーザーにとっても望ましいとは言えない状態が発生してしまっています。

Add-on SDKはこのような状況の改善を目標の1つとしていましたが、現在は「アドオンを開発するための新しい推奨される方法」という位置付けに留まっており、すべてのアドオンをAdd-on SDKに移行させるには至りませんでした。 WebExtensionsではその反省もあってか、当初から「すべてのアドオンのWebExtensionsへの移行」と「現在のアドオンの仕組みの将来的な廃止」を前面に打ち出しています。 またそれと同時に、どのようなAPIが必要なのかをアドオン作者にヒアリングするという事も進められています。 これらの事から、過去の類似の取り組みとは腰の据え方が違うという印象を受けます。

従来型のFirefoxアドオンをWebExtensionsに移行する際のポイント

冒頭に挙げた記事では、Popup ALT AttributeというアドオンのWebExtensions移行の顛末を解説しています。

従来型のアドオンをWebExtensionsに移行する時の最も重要な点は、*「今までのアドオン開発のノウハウは一旦すべて忘れて、ゼロベースで考えること」*の一言に尽きます。

従来型のFirefox用アドオンは、本質的には「Firefox本体に動的に反映されるパッチ」と言うことができます。 そのため、機能性やメンテナンス性を強く意識して開発していくと、「Firefox本体の処理の中で期待通りの結果になっていない部分をピンポイントで書き換える」という、その時点でのFirefox本体の設計に強く依存した設計になってしまうことがあります。

Add-on SDKではいくつかの「抜け道」があり、それを使って「SDK以前の、今までのアドオン開発のノウハウ」を一部取り入れた開発」が可能となっていました。 それに対して、WebExtensionsにはそのような抜け道は一切用意されず、厳密にすべての要素がWebExtensionsの枠組みの中で実装される必要があります。 移行元のアドオンがFirefox本体の設計に強く依存した設計である場合、WebExtensionsへの移行は「同じ結果を得るアドオンを新規に再開発する」のに近くなります。 Popup ALT Attributesは、初出が2002年(※Firefox 1.0は2004年にリリースされたので、Firefoxより前からある事になる)と非常に歴史の古いアドオンなので、その典型的な例です。

実際の所、Popup ALT AttributeのWebExtensions移行は以下の3段階を経て実現されました。

  1. 再起動が必要なアドオンから、再起動不要なアドオン(Bootstrapped Extensions)への移行(2011年)
  2. シングルプロセス前提の設計から、マルチプロセス(e10s)対応の設計への移行(2016年2月)
  3. 従来のアドオンの枠組みから、WebExtensionsへの移行(2016年4月)

Bootstrapped Extensionsへの移行では、「動的な関数の書き換え」や「XULオーバーレイ」を廃止しました。 マルチプロセス(e10s)前提の設計への移行では、「Firefoxの内部的な処理に割り込んで任意の処理を行う設計」から、「Firefox内部の処理とは別の所で完結する設計」へ変更しました。 冒頭に挙げたMozilla Add-ons Blogに掲載された縮約版は、その後の最後のステップである、「アドオンを従来の形式からWebExtensionsの形式にパッケージングし直す」という工程にフォーカスした内容になっています。

ここでは3つの段階として書いていますが、実際には、1から3までが必ずこの順で行われる必要はありません。 例えば筆者が開発した別のアドオンでは、「1(再起動不要なアドオンへの移行)はできないが2(マルチプロセス対応)はできている」という物がいくつかあります。 既存のアドオンをWebExtensionsに移行できるかどうかは、1と2を両方とも実現できていて、且つ、必要なAPIがWebExtensionsで既に提供されていることが鍵となります。 Popup ALT Attributesはそのすべての条件を満たしていたため、つつがなくWebExtensionsに移行することができました。

まとめ

筆者が行ったFirefox用アドオンのWebExtensionsへの移行の顛末を記したブログ記事が、Mozilla Add-ons Blogに掲載されました。

いくつかの条件を満たしているアドオンであれば、WebExtensionsへの移行はそう困難ではありません。 しかし、現状のWebExtensionsにAPIが不足している事は事実です。

Mozilla自身がレガシーなアドオンの廃止とWebExtensionsへの移行の推進に本気で取り組もうとしている今のタイミングであれば、必要なAPIのWebExtensionsへの追加提案も不可能ではないと考えられます。 アドオン作者の人はぜひ自作のアドオンのWebExtensions移行を試みて、躓きやすい箇所・APIが不足している箇所を早めに明らかにし、dev-addons MLBugzilla上でコンポーネントが「WebExtensions」と明示されているBugなどを通じてMozillaにフィードバックしてみて下さい。

つづき: 2017-01-05
タグ: Mozilla
2016-05-02

2008|05|06|07|08|09|10|11|12|
2009|01|02|03|04|05|06|07|08|09|10|11|12|
2010|01|02|03|04|05|06|07|08|09|10|11|12|
2011|01|02|03|04|05|06|07|08|09|10|11|12|
2012|01|02|03|04|05|06|07|08|09|10|11|12|
2013|01|02|03|04|05|06|07|08|09|10|11|12|
2014|01|02|03|04|05|06|07|08|09|10|11|12|
2015|01|02|03|04|05|06|07|08|09|10|11|12|
2016|01|02|03|04|05|06|07|08|09|10|11|12|
2017|01|02|03|04|05|06|07|08|09|10|11|12|
タグ: