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

ククログ

タグ:

apitraceを使ったOpenGL ESが絡むFirefoxのデバッグ方法

はじめに

これまでにも何度か紹介してきましたが、クリアコードではGecko(Firefox)を組み込み機器向けに移植する取り組みを行っています。

当プロジェクトでは移植コストを少しでも低減するために、Firefoxの延長サポート版(ESR)のみを対象としています。これまではESR45やESR52をベースにハードウェアアクセラレーションに対応させるための作業を行ってきました。 現在はESR60に対応し、そのバグを修正する作業を進めています。

この作業に関連するOpenGL ESのデバッグをする必要が生じたのでその方法を解説します。 今回のデバッグにはapitraceというOpenGLに関するAPIの呼び出しを取得できるツールがありましたので、 このツールを元に解説します。

OpenGLのAPIの呼び出しをトレースするには

apitraceを用いてOpenGLのAPIの呼び出しをトレースするには、以下のようにして行います。

$ apitrace trace --api=egl --output=/tmp/dump.trace [トレースするプログラム]

例えば、FirefoxのOpenGL ESのAPIの呼び出しのトレースを取得するには以下のようにすると取得できます。

$ apitrace trace --api=egl --output=/tmp/dump.trace firefox

OpenGLのAPIの呼び出しのダンブを解析するには

apitraceには採取したダンプを解析する機能もあります。

$ apitrace dump /tmp/dump.trace

とすると、OpenGL ESのAPIの呼び出しを記録したダンプの解析を行えます。 例えば、Firefoxを用いてOpenGL ESのAPIの呼び出し結果を取得し、その結果を解析すると以下のような出力が得られます。

// process.name = "/usr/lib/firefox/firefox"
0 eglGetDisplay(display_id = 0xb6921120) = 0x1
1 eglInitialize(dpy = 0x1, major = NULL, minor = NULL) = EGL_TRUE
9 eglChooseConfig(dpy = 0x1, attrib_list = {EGL_SURFACE_TYPE, EGL_WINDOW_BIT, E
GL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL
_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, EGL_NONE}, configs = {0x2, 0x1, 0x3}, config_
size = 64, num_config = &3) = EGL_TRUE
14 eglCreateWindowSurface(dpy = 0x1, config = 0x2, win = 0x9d1e7af0, attrib_lis
t = {}) = 0x9ce7d000
15 eglBindAPI(api = EGL_OPENGL_ES_API) = EGL_TRUE
16 eglCreateContext(dpy = 0x1, config = 0x2, share_context = NULL, attrib_list 
= {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE}) = 0x9d1638b0
140 eglGetCurrentContext() = NULL
141 eglMakeCurrent(dpy = 0x1, draw = 0x9ce7d000, read = 0x9ce7d000, ctx = 0x9d1
638b0) = EGL_TRUE
# ...
27660 glDeleteProgram(program = 1050015)
27661 eglGetCurrentContext() = 0x9d1638b0
27662 glDeleteProgram(program = 840012)
27663 glBindFramebuffer(target = GL_FRAMEBUFFER, framebuffer = 0)
27664 glDeleteBuffers(n = 1, buffers = &140002)
27665 glDeleteBuffers(n = 1, buffers = &70001)
27666 eglGetCurrentContext() = 0x9d1638b0
27667 eglGetCurrentContext() = 0x9d1638b0
27668 eglDestroyContext(dpy = 0x1, ctx = 0x9d1638b0) = EGL_TRUE
27669 eglMakeCurrent(dpy = 0x1, draw = NULL, read = NULL, ctx = NULL) = EGL_TRU
E
27670 eglDestroySurface(dpy = 0x1, surface = 0x9ce7d000) = EGL_TRUE

この結果を見ると、FirefoxがEGLを使用する際には、EGLDisplayを取得し、EGLのコンフィグを元にしてEGL WindowSurfaceを作成してからようやくEGLのコンテキストが動き出していることが記録されていることがわかります。

反対に、終了時には動いているEGLのプログラムを片付けてから現在のEGLコンテキストの破棄、EGL WindowSurfaceの破棄を行なっていることが読み取れます。

apitraceを用いるとOpenGL ESのAPIの呼び出しが成功しているのか、失敗しているのかがある程度判別できます。

14 eglCreateWindowSurface(dpy = 0x1, config = 0x2, win = 0x9d1e7af0, attrib_lis
t = {}) = 0x9ce7d000

の箇所を例にとると、上記の呼び出しでは正常にWindowSurfaceが作成されていることが読み取れますが、

14 eglCreateWindowSurface(dpy = 0x1, config = 0x2, win = NULL, attrib_list = {}
) // incomplete

となってしまっている場合は eglCreateWindowSurface の呼び出し直後に正常の動作ではない状態になっています。

実際に、このような状況になってしまった時はfirefoxがSEGVしました。

まとめ

firefoxのOpenGL ESのAPIの呼び出しのダンプを取ることを例にしてapitraceの基本的な使用方法を解説しました。 OpenGL (ES)はハードウェアが絡むためログを仕込むような伝統的なデバッグ手法によるデバッグが難しく、このようなツールに頼ることで別の視点からの情報が得られることが分かりました。 グラフィックに関するデバッグで困っている場合はこのようなツールの助けを借りることも検討してみてはいかかでしょうか。

タグ: Mozilla
2018-06-15

WebExtensionsによるFirefox用の拡張機能で設定の読み書きを容易にするライブラリ:Configs.js

(この記事は、Firefoxの従来型アドオン(XULアドオン)の開発経験がある人向けに、WebExtensionsでの拡張機能開発でのノウハウを紹介する物です。)

XULアドオンでは、設定の保存や読み書きにはpreferencesという仕組みを使うのが一般的でした。これはFirefoxの基本的な設定データベースとなっているkey-value storeで、保持されている設定の一覧はabout:configで閲覧することができます。

一方、WebExtensionsベースの拡張機能の場合はこのような「設定を保存するための仕組み」は特に用意されていません。Indexed DB、Cookie、Web Storage APIなどのWebページ用の一般的な仕組みや、あるいはWebExtensionsのstorage APIのように、データを永続的に保存する様々な仕組みの中から好みの物を選んで使えます。とはいえ、選択肢が多すぎると却って判断に迷うもので、何らかの指針や一般的な方法があればそれに従っておきたい所でしょう。

一般的には、WebExtensionsベースの拡張機能では設定の保存先としてstorage APIが使われるケースが多いようです。storage APIはさらにstorage.localstorage.syncstorage.managedの3種類が存在し、Firefox Syncとの連携を考慮する場合はstorage.sync、そうでない場合はstorage.localが使われるという具合の使い分けがなされます。ただ、これらのAPIは非常に低レベルのAPIと言う事ができ、設定画面・バックグラウンドスクリプト・コンテントスクリプトの間での値の同期のような場面まで考慮すると、上手く整合性を保つのはなかなか大変です。

そこで、preferencesの代替として使いやすいAPIを備えた、設定の読み書きに特化した軽量ライブラリとして、Configs.jsという物を開発しました。

基本的な使い方

必要な権限

このライブラリは設定値をstorage APIで保存するため、使用にあたってはmanifest.jsonpermissionsの宣言にstorageを加える必要があります。

{
  ...
  "permissions": [
    "storage", 
    ...
  ],
  ...
}
設定オブジェクトの作成と読み込み

このライブラリは単一のファイルConfigs.jsのみで構成されており、読み込むと、その名前空間でConfigsという名前のクラスを参照できるようになります。実際に設定を読み書きするためには、これを使って設定オブジェクトConfigsクラスのインスタンス)を作る必要があります。具体的には以下の要領です。

var configs = new Configs({
  enabled: true,
  count:   0,
  url:     'http://example.com/',
  items:   ['open', 'close', 'edit']
});

設定のキーと既定値はこの例の通り、Configsクラスの引数に渡すオブジェクトで定義します。オブジェクトのプロパティ名が設定のキー、値が既定値になり、値はJSON形式が許容する物であれば何でも保持できます。

これをcommon.jsのような名前で保存し、以下のようにConfigs.jsと併せて読み込むようにします。

HTMLファイルから読み込む場合:

<script type="application/javascript" src="./Configs.js"></script>
<script type="application/javascript" src="./common.js"></script>

manifest.jsonで指定する場合:

{
  ...
  "background": {
    "scripts": [
      "./Configs.js",
      "./common.js",
      ...
    ]
  },
  ...
  "content_scripts": [
    {
      "matches": [
        "*://*.example.com/*"
      ],
      "js": [
        "./Configs.js",
        "./common.js",
        ...
      ]
    }
  ],
  ...
}

manifest.jsonの記述例から分かる通り、設定を読み書きしたい名前空間のそれぞれで個別にConfigs.jsと設定オブジェクトの作成用スクリプトを読み込む必要があります。

なお、storage APIを使う都合上、このライブラリはコンテントスクリプトのみで使う事はできません(コンテントスクリプトからはstorage APIにアクセスできません)。サイドバーやパネルなどを含まずコンテントスクリプトだけで動作する拡張機能である場合は必ず、設定オブジェクトのインスタンスを作成するだけのバックグラウンドページを読み込んでおいて下さい。こうする事で、コンテントスクリプト内で行われた設定の変更はバックグラウンドページ経由で保存され、逆に、保存されていた値はバックグラウンドページを経由してコンテントスクリプトに読み込まれる事になります。

保存された設定値の読み込み

各スクリプトを読み込んだそれぞれの名前空間では、設定オブジェクトのインスタンスが作成されると同時に、保存された設定値が自動的に読み込まれます。設定オブジェクトは値がPromiseであるプロパティ $loadedを持っており、このPromiseは設定値の読み込みが完了した時点で解決されます。例えば保存された設定値を使ってページの初期化処理を行いたい場合は、以下のようにする事になります。

window.addEventListener('DOMContentLoaded', async () => {
  await configs.$loaded;
  // ...
  // 読み込まれた設定値を使った初期化処理
  // ...
}, { once: true });
設定値の参照と変更

設定オブジェクトは、インスタンス作成時に指定された各設定のキーと同名のプロパティを持っており、プロパティの値が設定値となっています。$loadedのPromiseの解決後に各プロパティを参照すると、読み込まれたユーザー設定値または設定オブジェクト作成時の既定値が返されます。

console.log(configs.enabled); // => true
console.log(configs.count);   // => 0

また、設定値を変更するには、設定オブジェクトの各プロパティに値を代入します。

configs.enabled = false;
configs.count   = 1;

設定値は型情報を持ちません。初期値と異なる型の値を設定した場合、値は初期値と同じ型に変換されるのではなく、設定値の型のまま保存されます。例えば真偽値だった設定のconfigs.enabledに数値として0を代入した場合、次に取得した時に返される値はfalseではなく0となります。

値がObjectArrayである場合、以下の例のように必ず、元のオブジェクトの複製を作り、そちらを書き換えて、新しい値としてconfigsのプロパティに設定する必要があります。

var newEntries = JSON.parse(JSON.stringify(configs.entries)); // deep clone
entries.push('added item');
configs.entries = newEntries; // 設定値の変更

var newCache = JSON.parse(JSON.stringify(configs.cache)); // deep clone
newCache.addedItem = true;
configs.cache = newCache; // 設定値の変更

言い換えると、configs.entries.push('added item')configs.cache.addedItem = trueのように値のオブジェクトそのものを変更する方法では、変更結果は保存されませんvar entries = configs.entriesのように「値を参照した時に取得したオブジェクト」そのものを扱う場面では、値の変更前に必ずJSON.parse(JSON.stringify(entries))などの方法でディープコピーしてから変更するように気をつけて下さい。

また、未知のプロパティに値を設定した場合、その値は保存されません。設定のキーを増やしたい場合は、必ず設定オブジェクト作成時に既定値とセットで定義する必要があります。

設定値の変更の監視

設定値の変更は、ライブラリ自身によって各名前空間の間で暗黙的に通知・共有されます。if (configs.enabled) { ... }のように処理の過程で設定値を参照している場合、参照する時点で最新の設定値が返されますので、特に何かする必要はありません。

一方、「設定値が変わったらボタンのバッジを変更する」といった風に、設定値の変更を検知して何らかの処理を実行したい場合、設定オブジェクトの$addObserver()メソッドでオブザーバーを登録することができます。オブザーバーには関数を指定でき、第1引数として変更が行われた設定のキーが文字列として渡されます。以下は、関数(アロー関数)をオブザーバーとして登録する例です。

configs.$addObserver(aKey => {
  const newValue = configs[aKey];
  switch (aKey) {
    case 'enabled':
      ...
    case 'count':
      ...
  }
});
Firefox Syncで同期する設定、同期しない設定

初期状態では、設定オブジェクト作成時に定義した各設定はFirefox Syncでは同期されません。同期の対象にするには、「同期したい設定のキーの一覧」あるいは「同期させたくない設定のキーの一覧」を設定オブジェクト作成時にオプションで指定する必要があります。

Configsクラスは第2引数として各種オプションの指定のためのオブジェクトを受け取ります。基本的には設定を同期せず、一部の設定のみを同期したいという場合、同期したい設定のキーの配列をsyncKeysオプションとして指定します。

var configs = new Configs({ /* 既定値の指定 */ }, {
  syncKeys: [
    'enabled',
    'url',
    'items',
  ]
});

このように指定すると、syncKeysに列挙された設定のみFirefox Syncで同期されるようになります。

また、基本的には設定を同期し、一部の設定のみを同期対象外にしたいという場合、同期したくない設定のキーの配列をlocakKeysオプションとして指定します。

var configs = new Configs({ /* 既定値の指定 */ }, {
  localKeys: [
    'count',
  ]
});

このように指定すると、localKeysに列挙されなかったすべての設定がFirefox Syncで同期されるようになります。

Firefox Syncで同期された設定は、その実行環境で保持されたユーザー設定値よりも優先的に反映されます。 ただし、システム管理者によって定義された値がある設定は、それが最も優先されます。

システム管理者が設定を指定できるようにするには

企業等の組織でアドオンを使用する場合、システム管理者が設定を指定し固定したい場合があります。普通のアドオンでそのような事をしたい場合には、アドオンの中に書き込まれた既定値を書き換えた改造版を作る必要がある場合が結構ありますが、Configs.jsを使って設定を管理しているアドオンでは、そのような改造をせずとも、システム管理者が任意の設定値を指定できます。

システム管理者が設定値を指定するには、Managed Storageマニフェストという特殊なマニフェストファイルを作成し、Windowsではさらにレジストリに情報を登録する必要があります。Windows以外のプラットフォームでは、特定の位置にファイルを配置するだけですConfigs.jsを使用しているアドオンの一つであるIE View WEを例として、設定の手順を説明します。

まず、以下のような内容でJSON形式のファイルを作成します。

{
  "name": "ieview-we@clear-code.com",
  "description": "Managed Storage for IE View WE",
  "type": "storage",
  "data": {
    "forceielist"      : "http://www.example.com/*",
    "disableForce"     : false,
    "closeReloadPage"  : true,
    "contextMenu"      : true,
    "onlyMainFrame"    : true,
    "ignoreQueryString": false,
    "sitesOpenedBySelf": "",
    "disableException" : false,
    "logging"          : true,
    "debug"            : false
  }
}

nameにはアドオンの識別子(WebExtensionsでの内部IDではなく、Mozilla Add-onsに登録する際に必要となるIDの方)を、descriptionには何か知らの説明文を、typeにはstorageを記入します。設定値として使用する情報はdataに記述し、記述の仕方はnew Configs()の第1引数に指定する既定値と同様の形式です(JavaScriptではなくJSONなので、キーは明示的に文字列として書く必要がある点に注意して下さい)。

内容を準備できたら、ファイル名を(アドオンの識別子).jsonとして保存します。この例であればieview-we@clear-code.com.jsonとなります。

次に、Windowsではレジストリの HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\ManagedStorage\(アドオンの識別子)またはHKEY_CURRENT_USER\SOFTWARE\Mozilla\ManagedStorage\(アドオンの識別子)の位置にキーを作り、「標準」の値として先のJSONファイルのフルパスを文字列型のデータとして保存します。キーの位置は32bit版Firefoxでも64bit版Firefoxでも同一である(WOW6432Node配下ではない)という事に注意して下さい。 LinuxやmacOSでは、MDNに記載があるパスのディレクトリ配下にJSONファイルを置くだけで充分です。

当然ですが、管理者でないユーザーがファイルを書き換えて設定を変更してしまう事がないように、このJSONファイルは一般ユーザーでは読み取り専用・ファイルの書き込みを禁止するようにアクセス権を設定しておく必要があります。

このようにして管理者が設定値を定めた項目は、Configs.jsで参照する時は「ロックされた」状態になり、値を変更できなくなります*1

まとめ

以上、Firefox用のWebExtensionsベースのアドオンにおける設定の読み書きを容易にするライブラリであるConfigs.jsの使い方を解説しました。

XULアドオンでの感覚に近い開発を支援する軽量ライブラリは他にもいくつかあります。以下の解説も併せてご覧下さい。

*1 値を設定しても特に例外等は発生しませんが、変更の内容は無視されます。

タグ: Mozilla
2018-06-12

Firefox 62で修正されたFirefox 61での後退バグに見る、不要なコードを削除する事の大切さ

一般的に、プログラムの継続的な開発においては「機能の追加」や「不具合の修正」が行われる事は多いですが、「機能の削除」が明示的に行われる事はそう多くないのではないでしょうか*1。この度、使われなくなっていた機能が残っていた事により意図しない所で不具合が発生し、機能を削除することで不具合が解消された事例がありましたので、使われなくなった機能を削除する事の意義を示す一時例としてご紹介いたします。

発生していた現象

現在のFirefoxでは、インストールするアドオンは必ずMozillaによる電子署名が施されていなくてはならず、未署名のファイルからはアドオンをインストールできないようになっています。ただ、それではアドオンの開発そのものができないため、開発者向けの機能であるabout:debuggingから、開発中のアドオンを「一時的なアドオン」として読み込んで動作させられるようになっています。

この機能について、Firefox 61で「条件によっては、アドオンを一時的なアドオンとして読み込めない」「条件によっては、一時的なアドオンの読み込みに非常に時間がかかる」という不具合が発生していましたが、Firefox 62で修正される事になりました*2

現象が起こり始めたきっかけと、原因の調査

開発中のアドオンを一時的なアドオンとして読み込めないという現象について、現象発生のタイミングを二分探索で絞り込むツールであるmozregressionを使って調査した所、1452827 - XPIInstall has a bunch of cruft that needs to be cleaned upでの変更が投入されて以降発生するようになっていた事が分かりました。

また、この現象が発生していた場面では、WSL*3で作成したシンボリックリンクがリポジトリ内に含まれている場合に、開発者用のコンソールにwinLastError: 1920という情報が出力されるという状況でした。これはWindowsが返すファイル関連のエラーコードのひとつであるERROR_CANT_ACCESS_FILEを意味する物で、Bugzilla上において、上記変更の中に含まれているファイルの情報を取得する処理がWSLのシンボリックリンクに対してこのエラーを報告している、という情報を頂く事ができました。

実際の修正

ファイルの情報を調べようとして何らかのエラーが発生するファイルというものは、WSLのシンボリックリンク以外にも存在し得ます。また、一般的に、規模が大きな変更は予想外の影響をもたらすリスクがあり、変更は可能な限り最小限に留める事が望ましいとされます。これらの理由から、当初は「ファイルの情報を取得しようとした時のエラーを適切にハンドルすることで、特殊なファイルがあっても問題が起こらないようにする」という方向でパッチを作成し提出しました。

しかしながら、このパッチはレビューの結果却下され、そもそもこの「ファイルの情報を取得する処理」自体が不要だからそれを削除する方がよいのではないかという指摘を受けました。そこで改めて調査したところ、以下の状況である事を確認できました。

  • この処理は、アドオンの総ファイルサイズを計算する過程で呼ばれている。
  • しかしながら、現行のFirefoxではアドオンのファイルサイズは画面上に表示される事が無く、また、ファイルサイズの情報が有効に使われる部分も存在していない。
    • 過去には使われていた機能のようだが、機能の改廃が進んだ結果、現在は「ファイルサイズの計算が正しいかどうか」を検証する自動テストの中で参照されるだけの機能になっていた。
    • 類似の情報として、自動更新を通じてダウンロードされたアドオンのインストールパッケージのファイルサイズという物もある。
      • こちらは現在も使われているが、今回問題となっている「アドオンの総ファイルサイズ」とは別の物である。
  • よって、アドオンの総ファイルサイズという情報自体が現在では無用となっている。

既にある機能や情報の削除は、その機能を使っている箇所が1つでも残っていれば不可能なため、影響範囲の調査は特に念入りに行う必要があります。今回の事例では、

  • Mozillaのスタッフの人から、使われていない機能を削除する方向での対応が望ましい旨のコメントが出ていた。
  • 実際に調査した限りでは、確かに目に見える部分や他の機能からは参照されていないという事の確認が取れた。

という2つの根拠があったため、思い切って、アドオンの総ファイルサイズの計算に関わるコードと、サイズ情報を参照している箇所*4を削除するという内容でパッチを再作成しました。その結果、パッチは無事に取り込まれ、Firefox 62ではこの問題が修正される事になりました。

また、このパッチが取り込まれた結果、「条件によって一時的なアドオンの読み込みに非常に時間がかかる」という別のBugにも影響が及んでいた事が後になって分かりました。こちらのBugは、開発中のアドオンのフォルダー配下にnode_modulesのようなフォルダーがあると*5、その配下の大量のファイルをスキャンするために時間がかかるようになってしまった、という物です。アドオンの総ファイルサイズの計算自体を行わなくした事により、棚ぼた的にこちらのBugも解消されたという状況でした。

ただ、実はその後、「WSLのシンボリックリンクのせいでエラーが発生してアドオンを読み込めない」という状況自体が今となってはほぼ発生しなくなっているという事も分かりました。具体的には、Bugの報告時にエラーの原因になっていたシンボリックリンクはWindows 10 Creators UpdateやWindows 10 Fall Creators UpdateのWSLで作成された物でしたが、その後Windows 10 April 2018 UpdateのWSLなどではNTFSの妥当なシンボリックリンクが作成されるようWSLが改良されたため、この問題の影響を受けるのは「古いWSLで(npm installを実行するなどして間接的に作成される場合も含めて)シンボリックリンクを作って、それをその後もずっと使い続けている場合」だけという事になっています。一般ユーザーに影響が及ばないアドオン開発者向けの機能であるという事に加え、プラットフォーム側の改善により今では問題自体が発生しなくなっている(新たにシンボリックリンクを作り直すだけでよい)という事も相まって、このパッチはFirefox 61には取り込まれないという判断がなされています。

まとめ

以上、Firefox 62で取り込まれたパッチを題材に、既に不要となっていた機能が問題を引き起こしていた事例についてご紹介しました。

Firefox 56およびそれ以前のバージョンのFirefoxにおいて使用できていた従来型アドオンは、今回削除されたコードのような「Firefox内部では既に使われなくなった機能」を使用している事が度々ありました。そのためFirefox全体として、アドオンが動作しなくなる事を警戒して古いコードを大量に抱え込まざるを得ない状態が長く続いていました。Firefox57での「従来型アドオン(XULアドオン)廃止」という大きな変化には、このような状態を是正するためという意味合いもあったと言えるでしょう。これを反面教師として、不要になった機能や実装はその都度速やかに削除してコードを簡潔に保っていくという事の重要さを実感していただければ幸いです。

*1 ただし、機能一式の刷新にあたって「新しい実装に持ち越されなかった機能」が結果的に「旧バージョンから削除された機能」として見える、という事はあります。

*2 一般ユーザーへの影響は小さいと判断された結果、Firefox 61への修正の反映は見送られています。

*3 Windows Subsystem for Linux

*4 大半は自動テスト内で、UI上に現れる物もFirefoxでは使われていないThunderbird用に残されたコードに1箇所あるだけでした。

*5 静的な文法チェックを行うためにeslintを使っている場合などに、このような状況が発生し得ます。

2018-06-07

Gecko Embedded ESR60

はじめに

これまでにも何度か紹介してきましたが、クリアコードではGecko(Firefox)を組み込み機器向けに移植する取り組みを行っています。

当プロジェクトでは移植コストを少しでも低減するために、Firefoxの延長サポート版(ESR)のみを対象としています。これまではESR45やESR52をベースにハードウェアアクセラレーションに対応させるための作業を行ってきました。 現在はESR60に対応し、そのバグを修正する作業を進めています。

今回は、この作業の現在のステータスを紹介します。詳細なビルド方法については、Gecko Embedded 次期ESR対応 を参照してください。

現在のステータス

現在はFirefox ESR60の開発を進めています。ビルドは通るようになり、ソフトウェアレンダリングでの描画もある程度安定して動作するようになっています。Firefox 60からはWayland対応のコードも本体に入っているため、ソフトウェアレンダリングであればほぼ無修正で動作させることができています。 また、60ESRには入りませんでしたが、当プロジェクトが作成したEGLおよびOpenMAX対応のパッチも無事Firefox本体に取り込まれています。

ESR52で実現出来ていたハードウェア対応の移植作業については、2018年5月30日現在、以下のような状況です。

  • レイヤーアクセラレーション: 移植済み(RZ/G1M、R-Car M3、Raspberry Pi3 Model Bで検証)
  • OpenMAX(H.264デコード): 移植済み(RZ/G1Mで検証)
  • WebGL: 移植済み(R-Car M3で動作)
  • WebRTC: レイヤーアクセラレーションとの組み合わせでは動作せず(調査中)
  • Canvas 2D Contextのアクセラレーション: e10sオフでは動作

また、ESR52ではなかった要素として、Rustで書かれたコードが追加されてきている点が挙げられます。細かく動作検証はできていませんが、少なくともStylo(Quantum CSS)は実機(Renesas RZ/G1M)で動作することを確認できています。

前回からの改善点は以下の通りです。

  • EGL有効化時に表示されるUIと操作するときに反応する座標がずれる不具合が解消した
  • UIのタブのドラッグ&ドロップが動作するようになった
  • コピー&ペーストが動作するようになった
  • WebGLがR-Car M3で動作することを確認

現状では、以下の制限があります。

  • 動画を再生&一旦停止し、別の動画を再生した時にサウンドデバイスが解放されず、音が出力されない
  • SoCのGPUがOpenGL ES 2.0のプロファイルを持ちrobustness拡張をサポートしない場合、WebGLのコンテキストが不完全となるか、コンテキスト作成に失敗する
  • Yocto 2.4ベースのBSPではStyloのビルドが確認できていない
  • Renesas RZ/G1MのPowerVR SGX 544MPを使用する場合、コンポジターでハードウェア支援を有効にするとウィンドウリサイズ時に高確率でSEGVすることがある
  • e10s有効化時にEGLを有効化するとContentプロセスがSEGVする

動作確認を行ったハードウェア

現時点ではRenesas RZ/G1M、R-Car M3、また、コミュニティサポートのレベルですが、StyloのビルドサポートなしでRaspberry Pi3 Model Bにも移植しました。

まとめ

GeckoEmbeddedプロジェクトのESR60の状況とビルド方法について紹介しました。ESR60対応がひと段落し、対象ボードを拡充する対応は少しづつ進んでいるものの、まだまだ手が足りていない状況です。興味を持たれた方は、ぜひ当プロジェクトに参加して頂ければ幸いです。

つづき: 2018-06-15
2018-05-30

Active Directoryを使用していない環境で、Firefoxに任意のルート証明書を自動インポートする方法

既報の通り、Firefox 52以降のバージョンではActive Directoryのグループポリシー経由で配布された証明書を自動的にインポートできるようになっています。アドオンなどによる証明書の自動インポートができなくなったため、現時点ではFirefoxに任意のルート証明書を組み込んだ状態にするには、これが唯一の方法となっています。

ただ、Active Directoryを運用してない小規模の組織や、検証用のスタンドアロンのPCでは、何らかの方法で「Active Directoryのグループポリシー経由で証明書を配布した状態」と同じ状態にする必要があります。以下はそのための作業手順の説明となります。

背景

Windowsの証明書データベースは、実際にはレジストリ上の複数の位置にある情報が集約されて認識された物です。証明書のバイナリはレジストリ上ではBlob形式のレジストリ値として保存されています。

この事は、任意の証明書を主導でインポートした後でレジストリエディタを起動し HKEY_CURRENT_USER\Software\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

よって、「Active Directoryのグループポリシー経由で証明書を配布した状態」と同じ状態を作り出すためには、必要な証明書の情報を上記のキー配下に書き込めばよいと言う事になります。

準備

以上のような背景があるため、Firefoxにインポートさせたい証明書はまず一旦、Windowsの証明書データベースにインポートしておく必要があります。まだインポートされていない場合は、以下の手順で証明書をインポートしておいて下さい。

  1. 証明書ファイルをダブルクリックして、証明書のプロパティを開く。
  2. 「詳細」タブでフィールド「拇印」の値(SHA-256ではなくSHA-1)を確認し、控えておく。
    以下、拇印は49cb1e088952d5116738db438a11c5aba7a12e01と仮定する。
  3. 「全般」タブで「証明書のインストール」ボタンをクリックする。
  4. 証明書のインポートウィザードが開かれるので、以下の設定でインポートする。
    • 現在のユーザー 用
    • 「証明書を全て次のストアに配置する」で「エンタープライズの信頼」を選択

レジストリファイルの作成

レジストリエディタでインポートするための証明書ファイルは、以下の手順で作成します。

  1. 「ファイル名を指定して実行」で regedit.exe と入力し、レジストリエディタを起動する。
  2. 「編集」→「検索」で、検索する値として、当該証明書の拇印(SHA-1 Fingerprint)を入力する。
    ここでは49cb1e088952d5116738db438a11c5aba7a12e01と仮定する。
  3. 名前が上記の拇印と一致する*1キーが見つかる。 (「準備」の項の手順でインストールした場合、 HKEY_CURRENT_USER\Software\Microsoft\SystemCertificates\trust\Certificates配下にある。)
  4. 見つかったキーを右クリックし、「エクスポート」を選択してEnterpriseCert.regなどの名前で保存する。
  5. 「準備」の項の手順でインストールした場合、3で見つかったキーをレジストリエディタ上で削除する。
  6. 4でエクスポートしたファイルをテキストエディタで開く。
    (ファイルのエンコーディングは「UTF-16LE(BOM有り)」となっている)
  7. ファイルの中に含まれる文字列HKEY_〜\拇印となっている箇所(上記の例であればHKEY_CURRENT_USER\Software\Microsoft\SystemCertificates\trust\Certificates\49CB1E088952D5116738DB438A11C5ABA7A12E01)をすべて以下の文字列に置換する。
    HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\拇印
    (「準備」の項の手順でインストールした場合、HKEY_LOCAL_MACHINE\Software\Policies\Microsoft\SystemCertificates\Root\Certificates\49CB1E088952D5116738DB438A11C5ABA7A12E01。)
  8. 編集したファイル「EnterpriseCert.reg」を上書き保存する。

以上の手順で作成したレジストリファイル「EnterpriseCert.reg」を各クライアントPCでインポートすると、グループポリシーで証明書を配布したのと同じ状態になります。

まとめ

Windowsのレジストリを編集して、Active Directoryによるグループポリシーの配布ができない環境で同等の結果を得て、Firefoxのルート証明書自動インポート機能の動作を確認する手順をご説明しました。

*1 大文字小文字は区別されません。

タグ: Mozilla
2018-05-18

WebExtensionsによるFirefox用の拡張機能で、キーボードショートカットの変更用UIを提供するライブラリ:ShortcutCustomizeUIMenuUI.js

(この記事は、Firefoxの従来型アドオン(XULアドオン)の開発経験がある人向けに、WebExtensionsでの拡張機能開発でのノウハウを紹介する物です。)

従来型のアドオンでキーボードショートカットを実現する方法には、XULの<key>要素を使う方法と、JavaScriptでキーイベントを捕捉する方法の2通りがありました。WebExtensionsでは、前者はmanifest.jsoncommandsでショートカットを定義する方法、後者はcontent scriptで実装する方法がそれぞれ対応します。ブラウザウィンドウのどこにフォーカスがあっても動作するキーボードショートカットを実現する方法としては、前者の方法が唯一の選択肢となります*1

Firefox 60以降のバージョンではmanifest.jsonで定義されたキーボードショートカットを後から任意の物に変更できるようになったのですが、Google Chromeでは拡張機能が定義したキーボードショートカットを横断して制御できる設定画面をChrome自体が提供しているのに対し、Firefox 60にはまだその機能がありません。実際にキーボードショートカットを変更するためには、commands.updateという機能を使って各アドオンが自前で設定UIを提供する必要があります。

そこで、Firefox本体で設定UIが提供されるようになるまでのつなぎとして、キーボードショートカットの変更のためのUIを提供する軽量なライブラリ ShortcutCustomizeUI.jsを開発しました。

使い方

このライブラリは、キーボードショートカットの変更用UIとして機能するHTMLのコード片をDocumentFragmentとして生成する物です。使い方としては、まず設定画面を提供するHTMLファイルにShortcutCustomizeUI.jsを読み込ませます。

<script type="application/javascript" src="./ShortcutCustomizeUI.js"></script>

このファイルを読み込むとShortcutCustomizeUIというオブジェクトが利用可能になります。ShortcutCustomizeUI.build()を実行するとPromiseが返され、そのPromiseの解決後の値として、生成されたDocumentFragmentが得られます。後は、以下のようにして設定画面の中にDocumentFragmentを埋め込むだけです。

ShortcutCustomizeUI.build().then(list => {
  document.getElementById('shortcuts').appendChild(list);
});

すると、以下のスクリーンショットのようなUIが使えるようになります。 (実際に表示されたUIのスクリーンショット) 各ショートカットにはコマンドの名前もしくは説明文がラベルとして表示され、行の右端のボタンをクリックすることで初期値に戻すこともできます。

  • このライブラリはmanifest.jsoncommandsで定義されている全てのコマンドを自動的に走査し、UIに列挙します。コマンド名を各言語に応じた翻訳で表示したい場合は、manifest.json自体の国際化のための機能を使用して下さい。
  • ShortcutCustomizeUI.build()に指定できるオプションの詳細については、リポジトリ内のREADMEファイルを参照して下さい。
  • 実際にはショートカットとしては使えないキーの組み合わせも登録できる場合がありますが、その場合、そのショートカットは当然ですが機能しません。

Firefoxの組み込みのキーボードショートカットと衝突するショートカットを設定した場合、Firefoxの機能の方が優先的に動作します。アドオン側のショートカットを優先することは、現時点ではできません(1325692 - [commands] Explicit support for overriding built-in keyboard shortcuts by WebExtensionsも参照して下さい)。

改善のためのご協力のお願い

このアドオンが生成するUIにはキーボードのモディファイアキーやその他のキー名が表示されますが、キーの表示名は言語によって異なる場合があります。そのため、ライブラリ内で言語ごとの表示名を定義していますが、現状ではごく一部の言語のみの対応に留まっています。

もし他の言語のことに詳しい方がいらっしゃいましたら、キーの表示名と内部名の対応表の追加にご協力をいただければ幸いです。

まとめ

WebExetnsionsによるFirefox用アドオンに簡単にキーボードショートカット変更用のUIを追加できる軽量ライブラリであるShortcutCustomizeUI.jsについて、その使い方を解説しました。「以後確認しない」のようなチェックボックスを伴った確認ダイアログを表示するRichConfirm.jsや、メニュー風のUIを提供するMenuUI.jsと併せて、Firefox用アドオンの開発にご活用いただければ幸いです。

*1 使用できるキーの種類などの面で自由度が高いのは後者ですが、実行のためには全てのページでユーザースクリプトを実行する権限(<all_urls>)が必要な上に、コンテンツ領域にフォーカスがある時でないとイベントを認識できない、addons.mozilla.orgのページやabout:addonsなどのFirefox自体が提供するページでは動作しない、などの欠点があります。

つづき: 2018-06-12
タグ: Mozilla
2018-05-14

Mozilla Firefox ESR60でのPolicy Engineによるポリシー設定の方法と設定項目のまとめ

FirefoxはESR60以降のバージョンにおいて、法人利用者向けに設定を集中管理する新しい仕組みである「Policy Engine」が導入・有効化されています。Policy Engineで可能な設定の一部は従来のMCD(AutoConfig)と重複していますが、Policy Engineには従来型アドオンによるカスタマイズの代替手段としての性質もあり、MCDではできなかった設定も存在しています。

過去の記事(第一報グループポリシーについての続報)において、Firefox 60のベータ版を対象として設定の手順やその時点で使用できるようになっていた設定項目を紹介してきましたが、この度のFirefox ESR60正式版のリリースに伴い、改めて最新の状況について情報を整理します。

ポリシー設定を反映する2つの方法

WindowsでPolicy Engineを使って設定を集中管理する方法には、以下の2通りがあります。

  • ポリシー定義ファイル(policies.json)を使う
  • Active Directoryのグループポリシーを使う(Windowsのみ)
policies.jsonを使う

この方法を使う場合には、各クライアントPC上のFirefoxのインストール先フォルダ(C:\Program Files\Mozilla FirefoxC:\Program Files (x86)\Mozilla Firefox など)配下にdistributionという名前でフォルダを作成し、さらにその中にpolicies.jsonという名前で以下の内容のテキストファイル(JSON形式)を設置します。

{
  "policies": {
    (ここにポリシー設定を記述する)
  }
}

例えば、後述の"BlockAboutAddons""BlockAboutConfig"を両方とも設定する場合は以下の要領です。

{
  "policies": {
    "BlockAboutAddons": true,
    "BlockAboutConfig": true
  }
}

この方法の利点は、ファイルを配置する事さえできればLinux版およびmacOS版(ファイルはFirefox.appをディレクトリと見なして、その配下に設置する事になります)においても使用できるという点です。

欠点は、設定ファイルを各クライアントに適切に配置しなくてはならないという事と、この方法で設定しただけでは充分に機能しない設定があるという事です。一部の設定項目は、期待通りの結果を得るためには後述のグループポリシーで設定する必要があります。

本記事では、各設定項目の指定はこちらの方法で設定する想定で解説します。

Active Directoryのグループポリシーを使う

Windows版Firefoxでは、Active Directoryのグループポリシーを使ってPolicy Engineの設定を制御することができます。Mozillaの外部プロジェクトとして公開されているポリシーテンプレート*1をダウンロードして展開し、ドメインコントローラの C:\Windows\SYSVOL\domain\Policies\PolicyDefinitions 配下に各ファイルを配置すると、グループポリシーの管理画面上で「コンピューターの構成」および「ユーザーの構成」の「管理用テンプレート」配下にFirefox向けの設定項目が表示されるようになります。各設定項目の働きは、policies.jsonで設定可能な項目に対応しています。

Firefoxの設計上は、同じポリシーがユーザー向けとコンピューター向けの両方に設定されていた場合、ユーザーに設定されたポリシーよりもコンピューターに設定されたポリシーの方が優先的に反映されます。例えば「Block About Config」というポリシーについて、ユーザー向けのポリシーで「有効」と設定していても、コンピューターのポリシーで「無効」と設定していると、このポリシーは「無効」と扱われます。基本的にはコンピューター向けのポリシーのみ設定して運用するとよいでしょう。

なお、2018年5月14日時点では、ポリシーテンプレートはWindows Server 2008以降で使用できるadmx形式のファイルのみが提供されています*2。Windows 2003 ServerをドメインコントローラとしてActive Directoryを運用している場合には、上記のポリシーテンプレートを参考にadm形式のポリシーテンプレートを作成する必要があります。ポリシーテンプレートを用意できない場合は、plicies.jsonを各クライアントに設置する方法を取らなくてはなりません。

この方法の利点は、ドメインコントローラ上での操作のみで管理を完結できるという事です。また、こちらの方法で設定した場合には、全ての設定項目が期待通りに機能するというメリットもあります。

欠点は、当然ですが、Active Directoryでドメインを運用中でないと利用できないという点です。ドメインを構築していないような小規模の組織においては、policies.jsonを使う他ないという事になります。

Firefox ESR60.0で使用できる設定項目

Policy Engineで設定可能な項目はいくつかのカテゴリに分類できます。以下、カテゴリごとにご紹介します。

シングルサインオンを許可するための設定

シングルサインオンに関する設定としては、以下の設定が可能です。 設定は全て"Authentication"配下に定義します。

{
  "policies": {
    ...
    "Authentication": {
      // SPNEGO認証を許可するサイトのリスト。
      // (MCDでの network.negotiate-auth.trusted-uris に相当)
      "SPNEGO": ["www.example.com", "http://www.example.org/", ...],

      // 認証情報の委任を許可するサイトのリスト。
      // (MCDでの network.negotiate-auth.delegation-uris に相当)
      "Delegated": ["www.example.com", "http://www.example.org/", ...],

      // NTLM認証を許可するサイトのリスト。
      // (MCDでの network.automatic-ntlm-auth.trusted-uris に相当)
      "NTLM": ["www.example.com", "http://www.example.org/", ...]
    },
    ...
  }
}

"SPNEGO""Delegated"、および"NTLM"の各設定は、必要な物だけを設定します。不要な設定は省略して構いません。

about: で始まるURIのページへのアクセスを禁止

URIがabout: で始まる特別なページのうちいくつかは、以下の設定でアクセスを禁止する事ができます。

{
  "policies": {
    ...
    // about:addons(アドオンマネージャ)の表示を禁止する。
    // アドオンの設定変更、有効・無効の切り替え、インストール、
    // アンインストールなどの操作を全面的に禁止することになる。
    "BlockAboutAddons": true,

    // about:config(設定エディタ)の表示を禁止する。
    // 設定画面に現れない詳細な設定の変更を禁止することになる。
    "BlockAboutConfig": true,

    // about:profiles(プロファイル管理画面)の表示を禁止する。
    // プロファイルを指定しての起動、アドオンを無効にしての起動などを
    // 禁止することになる。
    "BlockAboutProfiles": true,

    // about:support(トラブルシューティング情報)の表示を禁止する。
    // アドオンを無効にしての起動などを禁止することになる。
    "BlockAboutSupport": true,
    ...
  }
}
初期ブックマーク項目の作成

Policy Engineを使うと、初期状態で任意のブックマーク項目やフォルダを登録済みの状態にする事ができます。

{
  "policies": {
    ...
   // 初期状態で登録しておく追加のブックマーク項目の定義。任意の個数登録可能。
   "Bookmarks": [
     { // ブックマークツールバーにブックマークを作成する場合
       "Title":     "example.com",
       "URL":       "http://www.example.com/",
       "Favicon":   "http://www.example.com/favicon.ico",
       "Placement": "toolbar"
     },

     { // ブックマークツールバーの中にフォルダを作って、その中にブックマークを作成する場合
       "Title":     "example.com",
       "URL":       "http://www.example.com/",
       "Favicon":   "http://www.example.com/favicon.ico",
       "Placement": "toolbar",
       "Folder":    "ツールバー初期項目"
     },

     { // ブックマークメニューにブックマークを作成する場合
       "Title":     "example.org",
       "URL":       "http://www.example.org/",
       "Favicon":   "http://www.example.org/favicon.ico",
       "Placement": "menu"
     },

     { // ブックマークメニューの中にフォルダを作って、その中にブックマークを作成する場合
       "Title":     "example.org",
       "URL":       "http://www.example.org/",
       "Favicon":   "http://www.example.org/favicon.ico",
       "Placement": "menu",
       "Folder":    "メニュー初期項目"
     },
     ...
   ],

   // Firefox自体の初期ブックマーク項目を作成しない。
   // (「よく見るページ」などのスマートブックマーク項目を除く)
   // この設定は初回起動時・新規プロファイル作成時にのみ反映され、
   // 既に運用中のプロファイルには反映されない(作成済みの初期ブックマーク項目は削除されない)。
   "NoDefaultBookmarks": true,
    ...
  }
}
証明書のインポート

証明書データベースからのルート証明書の自動インポートに関いては、以下の設定が可能です。 設定は全て"Certificates"配下に定義します。

{
  "policies": {
    ...
    "Certificates": {
      // 管理者によってシステムに配布されたルート証明書をFirefoxの証明書データベースにインポートする。
      // (Windowsであれば、Active Directoryのグループポリシーで配布されたルート証明書をインポートする)
      "ImportEnterpriseRoots": true
    },
    ...
  }
}

この設定による自動インポートは、Windowsのレジストリ上の特定の位置に存在するルート証明書(Active Directoryのグループポリシーで配布された証明書)のみが対象となります。 ユーザーが自分の権限で証明書データベースに登録したルート証明書はインポートされません。

Cookieの保存の可否と有効期限

Cookieに関する設定は、以下の物があります。 設定は全て"Cookies"配下に定義します。

{
  "policies": {
    ...
    "Cookies": {
      // 指定のWebサイト(オリジンで指定)でCookie、IndexedDB、
      // Web Storage、およびService Worker用Cacheを保存する。
      // (任意に無効化はできない)
      "Allow": ["http://example.com", "https://example.org:8080", ...],

      // 指定のWebサイト(オリジンで指定)でCookie、IndexedDB、
      // Web Storage、およびService Worker用Cacheを保存する。
      // (任意に無効化はできない)
      // また、これらのホストに保存済みのCookieがあった場合、それらは削除される。
      "Block": ["http://example.com", "https://example.org:8080", ...],

      // サードパーティCookieかどうかに関わらず、全てのCookieを一律で拒否する。
      "Default": false,

      // サードパーティによるCookieの受け入れ可否。
      // 以下の3つの中からいずれかを指定する。
      //  * "always":       全て受け入れる。
      //  * "never":        全く受け入れない。
      //  * "from-visited": 既に保存済みのサードパーティCookieがある場合のみ受け入れる。
      // (MCDでの network.cookie.cookieBehavior に相当)
      "AcceptThirdParty": "always",

      // 全てのCookieについて「Firefoxを終了するまでを有効期限とし、
      // Webサイトが指定した保持期限を無視する。
      // (MCDでの network.cookie.lifetimePolicy に相当)
      "ExpireAtSessionEnd": true,

      // Cookieに関する設定のユーザーによる変更を禁止する。
      "Locked": true
    },
    ...
  }
}
様々な機能の無効化

機能の無効化に関する設定には、以下の項目があります。

{
  "policies": {
    ...
    // Firefoxの自動更新を禁止する。
    "DisableAppUpdate": true,

    // PDF.jsを無効化する。
    "DisableBuiltinPDFViewer": true,

    // 開発ツールを無効化する。
    // メニュー項目、キーボードショートカットの両方とも無効化する。
    // (MCDでの devtools.policy.disabled および
    //   devtools.chrome.enabled に相当)
    "DisableDeveloperTools": true,

    // フィードバックの送信、詐欺サイトの誤検出の報告を禁止する。
    "DisableFeedbackCommands": true,

    // FirefoxアカウントとSyncを無効化する。
    "DisableFirefoxAccounts": true,

    // Firefox Screenshotsを無効化する。
    "DisableFirefoxScreenshots": true,

    // Firefoxの新機能のテストへの参加( https://support.mozilla.org/ja/kb/shield )を禁止する。
    "DisableFirefoxStudies": true,

    // 「忘れる」ボタンを無効化する。
    "DisableForgetButton": true,

    // フォームの入力履歴の保存とオートコンプリートを無効化する。
    // (MCDでの browser.formfill.enable に相当)
    "DisableFormHistory": true,

    // マスターパスワードの設定を禁止する。
    "DisableMasterPasswordCreation": true,

    // Pocketを無効化する。
    // (MCDでの extensions.pocket.enabled に相当)
    "DisablePocket": true,

    // プライベートブラウジング機能の使用を禁止する。
    "DisablePrivateBrowsing": true,

    // 他のブラウザからのブックマークや設定のインポート機能を無効化する。
    // (ただし、ESR60時点では初回起動時の設定インポートは無効化されない)
    "DisableProfileImport": true,

    // about:support(トラブルシューティング情報)からのプロファイルのリフレッシュ操作(データの消去)を禁止する。
    "DisableProfileRefresh": true,

    // クラッシュからの復帰時と自動更新の時以外で、
    // セーフモードでの起動(アドオンを無効化しての起動)を禁止する。
    // ただし、コマンドライン引数でのセーフモードまで無効化するにはグループポリシーでの設定が必要。
    "DisableSafeMode": true,

    // セキュリティ警告を敢えて無視することによる危険な操作、を完全に禁止する。
    "DisableSecurityBypass": {
      // 証明書の警告の例外を登録するボタンを隠して、
      // 例外を絶対に登録できないようにする。
      // (MCDでの security.certerror.hideAddException に相当)
      "InvalidCertificate": true,

      // 詐欺サイト等の警告における「危険性を無視」リンクを隠して
      // コンテンツを絶対に表示できないようにする。
      // (MCDでの browser.safebrowsing.allowOverride=false に相当)
      "SafeBrowsing": true
    },

    // 画像をコンテキストメニューからデスクトップの壁紙に設定することを禁止する。
    "DisableSetDesktopBackground": true,

    // システムアドオンの更新を禁止する。
    "DisableSystemAddonUpdate": true,

    // 統計情報の収集・送信を禁止する。
    // (MCDでの datareporting.healthreport.uploadEnabled および
    //   datareporting.policy.dataSubmissionEnabled に相当)
    "DisableTelemetry": true,

    // 起動時に既定のブラウザにするかどうかを確認しない。
    // (MCDでの browser.shell.checkDefaultBrowser に相当)
    // 既定のブラウザに設定できなくする設定、ではないことに注意。
    "DontCheckDefaultBrowser": true,

    // パスワードマネージャによるパスワードの保存を禁止する。
    // (MCDでの signon.rememberSignons に相当)
    "OfferToSaveLogins": false,
    ...
  }
}
UIの初期状態

FirefoxのUIの初期状態を変更する設定としては、以下の項目があります。 いずれも、ユーザーが任意に状態を変更した場合はそちらが優先されます。 (強制的にその状態に固定する、ということはできません。)

{
  "policies": {
    ...
    // 初期状態でブックマークツールバーを表示する。
    "DisplayBookmarksToolbar": true,

    // 初期状態でメニューバーを表示する。
    "DisplayMenuBar": true,

    // 検索ツールバーをロケーションバーと統合するかどうか。
    //  * "unified":  統合する(Firefox ESR60以降の初期状態)
    //  * "separate": 分離する(Firefox ESR52以前の初期状態)
    "SearchBar": "separate",
    ...
  }
}
プライバシー情報の取り扱いに関わる設定

プライバシー情報に関する設定は、以下の項目があります。 トラッキング防止機能の設定は"EnableTrackingProtection"配下に定義します。

{
  "policies": {
    ...
    "EnableTrackingProtection": {
      // トラッキング防止機能の有効無効。
      //  * true:  有効(トラッキングを防止する)
      //  * false: 無効(トラッキングを許容する)
      // (MCDでの privacy.trackingprotection.enabled および
      //   privacy.trackingprotection.pbmode.enabled に相当)
      "Value": false,

      // トラッキング防止機能の、ユーザーによる設定変更の可否。
      //  * true:  ユーザーの設定変更を禁止(設定を固定する)
      //  * false: ユーザーの設定変更を許可
      "Locked": true
    },

    // Firefox終了時に、その都度履歴やCookieなどのプライバシー情報を全て消去する。
    // (MCDでの privacy.sanitize.sanitizeOnShutdown に相当)
    "SanitizeOnShutdown": true,
    ...
}
アドオンの制御

アドオンの制御に関する設定には、以下の項目があります。 管理者によるアドオンのインストールやアンインストールの制御は"Extensions"配下に、ユーザーによるインストールの可否に関する設定は"InstallAddonsPermission"配下に定義します。

{
  "policies": {
    ...
    "Extensions": {
      // 自動的にインストールするアドオンのXPIファイルの位置。
      // ("InstallAddonsPermission"配下の"Default"がfalseであってもインストールされる)
      "Install": [
        // URLでの指定
        "https://example.com/my-addon.xpi",
        // ファイルパスでの指定
        "C:\\Path\\To\\XPI\\File\\my-another-addon.xpi",
        ...
      ],

      // インストールされていた場合に自動的にアンインストールするアドオンのID。
      "Uninstall": [
        "my-legacy-addon@example.com",
        "my-obsolete-addon@example.org",
        ...
      ],

      // アンインストール、無効化を禁止するアドオンのID。
      // (設定の変更は可能)
      "Locked": [
        "my-security-addon@example.com",
        "my-privacy-addon@example.org",
        ...
      ]
    },

    // アドオンのインストールの可否に関する制御。
    "InstallAddonsPermission": {
      // アドオンのインストール時に警告を行わないページ。
      // (オリジンで指定、`https`のみ有効)
      "Allow": ["https://example.com", "https://example.org:8080", ...],

      // ユーザーによるアドオンのインストールを禁止する。
      // (MCDでの xpinstall.enabled に相当)
      "Default": false
    },
    ...
}

"Extensions""Install"で指定したアドオンは、実行時のユーザープロファイル内にインストールされます。"Extensions""Uninstall"も、ユーザープロファイル内にあるアドオンが対象となります。

すべてのユーザーで常に特定のアドオンがインストールされた状態としたい場合は、特定のフォルダにファイルを設置することによるサイドローディングでインストールする必要があります。

Adobe Flashプラグインの制御

Flashプラグインの制御については、以下の項目があります。 設定は全て"FlashPlugin"配下に定義します。

{
  "policies": {
    ...
    "FlashPlugin": {
      // 指定のWebサイト(オリジンで指定)でFlashを
      // Click to Play無しで自動実行する。
      // (任意に無効化はできない)
      "Allow": ["http://example.com", "https://example.org:8080"],

      // 指定のWebサイト(オリジンで指定)でFlashの実行を禁止する。
      // (任意に無効化はできない)
      "Block": ["http://example.com", "https://example.org:8080"]
    },
    ...
}
ホームページの設定

起動時に表示されるページの設定には、以下の項目があります。 ホームページに関する設定は全て"Homepage"配下に定義します。

{
  "policies": {
    ...
    "Homepage": {
      // ホームページのURI(複数タブを設定する場合は最初のタブの内容)
      // (MCDでの browser.startup.homepage および
      //   browser.startup.page=1 に対応)
      "URL": "http://example.com",

      // ホームページを複数設定する場合の、2番目以降のタブの内容
      "Additional": ["https://example.org:8080", ...],

      // ホームページの変更を禁止する
      "Locked": true
    },

    // 起動後に1回だけ表示されるページのURL。
    "OverrideFirstRunPage": "http://www.example.com/",
    ...
}
ポップアップブロックの制御

ポップアップブロック機能の制御に関しては、以下の設定項目があります。 設定は全て"PopupBlocking"配下に定義します。

{
  "policies": {
    ...
    "PopupBlocking": {
      // window.open() によるポップアップを確認なしで許可するWebサイト。
      // オリジンで指定し、任意に無効化はできない。
      "Allow": ["http://example.com", "https://example.org:8080"],

      // ポップアップブロック機能を初期状態で無効化する。
      // (MCDでの dom.disable_open_during_load に相当)
      "Default": false,

      // ユーザーによる設定の変更を禁止する。
      "Locked": true
    },
    ...
}
プロキシの設定

プロキシに関する設定は以下の項目があります。 設定は全て"Proxy"配下に定義します。

{
  "policies": {
    ...
    "Proxy": {
      // プロキシを使用するかどうか。
      //  * "none":       プロキシを使用しない。
      //  * "system":     システムで設定されたプロキシ設定に従う。
      //                  (Windowsであれば「インターネットオプション」の設定)
      //  * "autoDetect": 接続しているネットワークのDHCPで通知されたWPADの設定に従う。
      //  * "autoConfig"  以降の項目で指定されたPACファイルを使用する。
      //  * "manual":     以降の項目での個別の設定に従う。
      // (MCDでの network.proxy.type に相当)
      "Mode": "none",

      // "Mode":"autoConfig"の時に使用するPACファイルの取得先URL。
      // (MCDでの network.proxy.autoconfig_url に相当)
      "AutoConfigURL": "file://///fileserver/proxyl.pac",

      // HTTPで使用するプロキシのホストとポート番号。
      // (MCDでの network.proxy.http と
      //   network.proxy.http_port に相当)
      "HTTPProxy": "192.168.0.1:50080",

      // SSL/TLSで使用するプロキシのホストとポート番号。
      // (MCDでの network.proxy.ssl と
      //   network.proxy.ssl_port に相当)
      "SSLProxy": "192.168.0.1:50443",

      // FTPで使用するプロキシのホストとポート番号。
      // (MCDでの network.proxy.ftp と
      //   network.proxy.ftp_port に相当)
      "FTPProxy": "192.168.0.1:50022",

      // SOCKSで使用するプロキシのホストとポート番号。
      // (MCDでの network.proxy.socks と
      //   network.proxy.socks_port に相当)
      "SOCKSProxy": "192.168.0.1:51080",

      // SOCKSのバージョン。4または5のみ有効。
      // (MCDでの network.proxy.socks_version に相当)
      "SOCKSVersion": 4,

      // HTTPのプロキシを他の全てのプロトコルに使用する。
      // (MCDでの network.proxy.share_proxy_settings に相当)
      "UseHTTPProxyForAllProtocols": true,

      // プロキシを使用しないホスト。
      // (MCDでの network.proxy.no_proxies_on に相当)
      "Passthrough": "localhost,127.0.0.1,...",

      // DNSへの問い合わせにもプロキシを使用する。
      // (MCDでの network.proxy.socks_remote_dns に相当)
      "UseProxyForDNS": true,

      // プロキシに自動的にログインする。
      // (MCDでの signon.autologin.proxy に相当)
      "AutoLogin": true,

      // ユーザーによる設定の変更を禁止する。
      "Locked": true
    },
    ...
}
検索エンジンの設定

設定は全て"SearchEngines"配下に定義します。

{
  "policies": {
    ...
    "SearchEngines": {
      // 初期状態で検索バー用に登録しておく検索エンジンのリスト。
      "Add": [
        {
          "Name":               "検索エンジンの表示名",
          "IconURL":            "https://example.com/favicon.ico",
          "Alias":              "ex",
          "Description":        "検索エンジンの説明文",
          "Method":             "GET",
          "URLTemplate":        "https://example.com/search?q={searchTerms}",
          "SuggestURLTemplate": "https://example.com/suggest?q={searchTerms}"
        },
        ...
      ],

      // 指定した名前の検索エンジンを規定の検索エンジンにする。
      "Default": "Google",

      // 検索エンジンの追加を禁止する。
      "PreventInstalls": true
    },
    ...
}

検索エンジンはESR60時点では、検索語句の文字エンコーディングがCP1252で、HTTP MethodがGETである物のみ使用可能です。検索語句をUTF-8などの他のエンコーディングで指定しなくてはならない検索エンジンでは、残念ながら日本語の検索クエリは文字化けしてしまい検索できません。

"Alias"は、検索エンジンに設定するキーワードです。キーワードが設定されている検索エンジンは、ロケーションバーでex 検索したい語句のようにキーワードに続けて語句を入力する形でも呼び出せるようになります。

簡易的なアクセス制限

設定は全て"WebsiteFilter"配下に定義します。

{
  "policies": {
    ...
    "WebsiteFilter": {
      // ブロックする対象のURLを示すマッチパターン。
      // (詳細は https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Match_patterns を参照)
      // ESR60時点では、http:またはhttps:で始まるサイトのみが対象で、
      // 最大1000項目まで指定可能。
      "Block": [
        "https://twitter.com/*",
        "https://*.twitter.com/*",
        ...
      ],

      // ブロック対象の例外とするURLを示すマッチパターン。
      "Exceptions": [
        "https://twitter.com/about",
        ...
      ]
    },
    ...
}

まとめ

以上、Firefox ESR60から使用可能となったPolicy Engineによるポリシー設定について、ESR60.0時点での状況を解説しました。

Policy Engine機能の実装はFirefox 59のリリース以後から急ピッチで進められたため、Firefox ESR60.0の時点では機能不足や不具合がまだまだ目立つ状況と言わざるを得ません。ただ、通常Firefoxのセキュリティアップデートはセキュリティに関わる変更のみが反映されますが、Policy EngineもESR60も法人利用者のための機能である事から、Policy Engineの不具合は、今後ESR60のセキュリティアップデートの中で修正されていく可能性もあります*3。最新の動向に注意しながら使っていきましょう。

ちなみに、ESR60時点で実装されているポリシー設定のうち、以下の機能はクリアコード在籍のエンジニアによって実装されました。 日本での法人利用の中で大きな需要があるポリシー設定については、今後も積極的に実装の提案を行っていきたいと考えています。

*1 現時点では言語リソースは英語用のみが提供されています。

*2 Windows Server 2003ドメインでもこのポリシーテンプレートで行われた設定は有効ですが、ドメインコントローラはWindows Server 2008以降のバージョンである必要があります。

*3 Policy Engineの開発を主導していた方によるPolicy Engineの紹介記事でも、ESR60のセキュリティアップデートの中でポリシーの追加や修正を行っていくことが予告されています。

タグ: Mozilla
2018-05-12

WebExtensionsによるFirefox用の拡張機能でメニューやパネル風のUIを提供するライブラリ:MenuUI.js

(この記事は、Firefoxの従来型アドオン(XULアドオン)の開発経験がある人向けに、WebExtensionsでの拡張機能開発でのノウハウを紹介する物です。)

XULでは<menupopup>という要素の中に<menuitem><menu>などの要素を置いてネイティブアプリと同じ様式の階層型メニューを実装できました。それに対し、WebExtensionsベースの拡張機能ではUIは基本的にHTMLで作成します。この時に悩み所になるのが、メニューをどうやって実装するかという点です。

HTMLにもmenu要素という物がありますが、これはXULのメニュー関連要素の完全な代替とはなりません。ボタンに対してのポップアップメニューやツールバー上のメニューは本稿執筆時点でFirefoxでは実装されておらず、唯一使用できるコンテキストメニューでの用法についても、専用のコンテキストメニューを実現する物ではなく、あくまで「Webページ上のコンテキストメニューに追加の項目を設ける」という性質の物です。

Firefoxのコンテキストメニューとは切り離された別のメニューを実現するには、今のところHTML(+JavaScript+CSS)でそれらしい見た目と振る舞いのUIを独自に実装するほかありません。実装例自体は既存の物が多数あり、React用Vue用jQuery用など、採用するフレームワークに合わせて選べます。しかし、作りたいアドオンの性質上、フレームワークの導入には気乗りしないという人もいるのではないかと思います。

そこで、このような場面で使える軽量なライブラリとしてMenuUI.jsという物を開発しました。

基本的な使い方

このライブラリは、HTMLファイル中に静的に記述された物か動的に生成された物かを問わず、<ul><li>で構成された入れ子のリストを階層型のメニューとして振る舞わせるという物です。表示例はこんな感じです。 (Tree Style Tabでの使用例)

このライブラリを使ってメニュー風UIを提供するには、まず任意のHTMLファイル内でMenuUI.jsを読み込みます。

<script type="application/javascript" src="./MenuUI.js"></script>

また、それと併せてメニューとして表示するためのリストを以下の要領で用意します。

<ul id="menu">
  <li id="menu-save">保存(&amp;S)</li>
  <li>コピー(&amp;P)
    <ul>
      <li id="menu-copy-title">タイトル(&amp;T)</li>
      <li id="menu-copy-url">&amp;URL</li>
      <li>メタデータ(&amp;M)
        <ul>
          <li id="menu-copy-author">作者名(&amp;A)</li>
          <li id="menu-copy-email">&amp;Eメール</li>
        </ul>
      </li>
    </ul>
  </li>
  <li class="separator"></li>
  <li id="menu-close">閉じる(&amp;C)</li>
</ul>
  • サブメニューを定義したい場合はリストを入れ子にして下さい。
  • この例では、後々どの項目が選択されたかを容易に判別できるようにするために、コマンドとして実行されうる項目にIDを割り当てています。(通例、サブメニューを持つ項目はそれ自体が選択されてもコマンドとしては実行されないため、それらにはIDを割り当てていません。)
  • リストの内容となるテキストがそのままメニュー項目のラベルになります。
    • この時、ラベルの一部に&(静的に記述する場合は実体参照の&amp;)を書いておくと、その次の文字がメニュー項目のアクセスキーになります。

次に、このリストをメニューとして初期化します。以下の要領でMenuUIクラスのインスタンスを作成します。

var menuUI = new MenuUI({
  root: document.getElementById('menu'), // メニューにするリストの最上位の<ul>を指定
  onCommand: (aItem, aEvent) => {
    // 項目が選択された時に実行されるコールバック。
    // ここでは項目のidで処理を振り分けるようにしている。
    switch (aItem.id) {
      case 'menu-save':
        doSave();
        return;
      case 'menu-copy-title':
        doCopy('title');
        return;
      ...
    }
  }
});

作成されたインスタンスは、open()というメソッドを実行するとメニューとして表示されます。ページ上を右クリックした際にブラウザ本来のコンテキストメニューの代わりとして表示したい場合であれば、以下のようにします。

window.addEventListener('contextmenu', aEvent => {
  aEvent.stopPropagation();
  aEvent.preventDefault();
  menuUI.open({
    left: aEvent.clientX,
    top:  aEvent.clientY
  });
});

また、何かのボタンをクリックした時にドロップダウンメニューのように表示させたい場合には、以下のようにボタンのクリック操作を監視した上で、そのボタンなどの要素をopen()メソッドの引数にanchorというプロパティで指定すると、ボタンの位置に合わせてメニューが表示されます。

const button = document.getElementById('button');
button.addEventListener('click', () => {
  menuUI.open({
    anchor: button
  });
});

コンストラクタのオプションやopen()のオプションの詳細な内容については、リポジトリ内のREADMEファイルを参照して下さい。

キーボードでの操作への対応

このライブラリの特長として、キーボード操作への対応に力を入れているという点が挙げられます。以下は、現時点で対応している代表的な操作です。

  • ↑↓カーソルキーで項目のフォーカスを移動
  • →カーソルキーでサブメニューを開く
  • ←カーソルキーでサブメニューを閉じる
  • Escキーでメニューを閉じる
  • Enterキーでフォーカスしている項目を選択(コマンドを実行)
  • メニューのラベルから検出されたアクセスキーで項目を選択(同じアクセスキーの項目が複数ある場合はフォーカス移動のみ)

メニューを開くまではポインティングデバイスを使うが、その後の操作はキーボードで行う、というような場面は意外とあります。既存のライブラリはキーボード操作に対応していなかったり、操作体系が一般的なGUIのメニューではなくWebページ上のリンクに準拠していたりと、「ネイティブアプリのGUIと同じ感覚で使える」事が特長だったXULからの移行においてストレスになる部分があったため、この点にはとりわけ気をつけて開発しています。

まとめ

XULアドオンでのカスタムメニューの代替となる軽量ライブラリであるMenuUI.jsについて、その使い方を解説しました。「以後確認しない」のようなチェックボックスを伴った確認ダイアログを表示するRichConfirm.jsと併せて、Firefox用アドオンの開発にご活用いただければ幸いです。

タグ: Mozilla
2018-05-11

Gecko Embedded 次期ESR対応

はじめに

これまでにも何度か紹介してきましたが、クリアコードではGecko(Firefox)を組み込み機器向けに移植する取り組みを行っています。

当プロジェクトでは移植コストを少しでも低減するために、Firefoxの延長サポート版(ESR)のみを対象としています。これまではESR45やESR52をベースにハードウェアアクセラレーションに対応させるための作業を行ってきましたが、現在は5月にリリース予定の次期ESR(ESR60)に対応するための作業を進めています。

今回は、この作業の現在のステータスと、そのビルド方法ついて紹介します。

現在のステータス

現在はFirefox 60のβ版をもとに開発を進めています。ビルドは通るようになり、ソフトウェアレンダリングでの描画もある程度安定して動作するようになっています。Firefox 60からはWayland対応のコードも本体に入っているため、ソフトウェアレンダリングであればほぼ無修正で動作させることができています。

Gecko60 on RZ/G1M

ESR52で実現出来ていたハードウェア対応の移植作業については、2018年3月30日現在、以下のような状況です。

  • レイヤーアクセラレーション: 移植済み(検証中)
  • OpenMAX(H.264デコード): 移植済み
  • WebGL: 動作せず(調査中)
  • WebRTC: レイヤーアクセラレーションとの組み合わせでは動作せず(調査中)
  • Canvas 2D Contextのアクセラレーション: e10sオフでは動作

また、ESR52ではなかった要素として、Rustで書かれたコードが追加されてきている点が挙げられます。細かく動作検証はできていませんが、少なくともStylo(Quantum CSS)は実機(Renesas RZ/G1M)で動作することを確認できています。

ビルド概要

対象ハードウェア

現時点ではRenesas RZ/G1Mでのみ動作を確認しています。

ESR52のビルド手順からの変更点

ビルドの大まかな流れはESR52の場合とほぼ同様です。ただし、ESR52からの大きな変更点として、以下の手順が必要となります。

  • Rustの導入
    • rustupを使用
    • clangおよびllvm 3.9以上も合わせて導入
  • GCCの差し替え
    • ホスト用GCC: 4.9以上
    • ターゲット用GCC: 4.9あるいは7以上

前者については、Yocto側でmeta-rustレイヤを追加する方法についても検証中です。 後者のターゲット用GCCについては、現時点では5.xや6.xでは動作するバイナリを作成できないことを確認していますので、4.9あるいは7以上を選択する必要があります。RZ/G1MのYoctoレシピにはGCC 4.9のレシピも含まれていますので、local.confへの設定の追加で対応することができます。

ビルド用Vagrant Box

ビルド環境のセットアップを省力化するため、現在Vagrant Boxの準備を進めています。まだ荒削りではありますが、基本的なビルドは通るようになっているので、詳細手順を紹介する代わりにこのVagrant Boxを使った方法でのビルド手順を紹介します。

ホスト側実行手順:

まず、ホスト側で以下のソフトウェアの最新版をそれぞれインストールする必要があります。

例えばホスト側OSがUbuntuの場合には、以下の手順でインストールすることができます。

Vagrantのインストール:

  • Vagrantのダウンロードページから最新版のdebパッケージをダウンロード
  • dpkgコマンド等で上記debパッケージをインストール
    • 例: sudo dpkg -i vagrant_2.0.2_x86_64.deb

Ansibleのインストール:

$ sudo apt-get install software-properties-common
$ sudo apt-add-repository ppa:ansible/ansible
$ sudo apt-get update
$ sudo apt-get install ansible

VirtualBoxのインストール:

$ sudo apt-add-repository "deb http://download.virtualbox.org/virtualbox/debian $(lsb_release -sc) contrib"
$ wget -q https://www.virtualbox.org/download/oracle_vbox_2016.asc -O- | sudo apt-key add -
$ wget -q https://www.virtualbox.org/download/oracle_vbox.asc -O- | sudo apt-key add -
sudo apt-get update
sudo apt-get install virtualbox-5.2

ホスト側の準備が完了すれば、以下のコマンドでVagrant Boxを起動することができます。

$ vagrant plugin install vagrant-disksize
$ vagrant init ashie/renesas-gecko-dev
$ vagrant up
$ vagrant ssh (ゲスト環境にログイン)
ゲスト側実行手順:

Vagrant Boxを起動すると、これまでのビルド手順のうち、Yoctoレシピのセットアップまでがほぼ完了している状態となっています。ただし、プロプライエタリドライバのダウンロードだけは手動で行う必要があります。ここでは、ホスト側で下記サイトからそれぞれアーカイブファイルをダウンロードし、

vagrant initを実行したディレクトリ下に置いてあるものと想定します。 この状態で、ゲスト側で以下のコマンドを実行して同アーカイブファイルを展開します。

$ cd ~/rzg1-bsp/
$ unzip /vagrant/RZG_Series_Evaluation_Software_Package_for_Linux-20161011.tar.gz.zip
$ unzip /vagrant/RZG_Series_Evaluation_Software_Package_of_Linux_Drivers-20161011.tar.gz.zip
$ cd ~/rzg1-bsp/meta-renesas/meta-rzg1
$ ./copy_gfx_software_rzg1m.sh ~/rzg1-bsp/
$ ./copy_mm_software_lcb.sh ~/rzg1-bsp/

また、ビルド中にgitコマンドでパッチ適用を行うレシピが存在しますので、以下の設定を事前に行っておきます(値は適切に変更して下さい)。

$ git config --global user.email "you@example.com"
$ git config --global user.name "Your Name"

以上の準備が整えば、以下のコマンドでGeckoを含んだブートイメージを作成することができます。

$ cd ~/rzg1-bsp
$ source poky/oe-init-build-env
$ bitbake core-image-weston

ただし現在は鋭意開発中のステータスですので、ビルドに失敗することもあります。何か問題を発見した場合には、Gecko EmbeddedのIssuesに報告して下さい。

まとめ

GeckoEmbeddedプロジェクトのESR60対応の状況とビルド方法について紹介しました。対応は少しづつ進んでいるものの、まだまだ手が足りていない状況です。興味を持たれた方は、ぜひ当プロジェクトに参加して頂ければ幸いです。

タグ: Mozilla
2018-03-30

Firefoxの拡張機能で「以後確認しない」のようなチェックボックスを伴った確認ダイアログを表示するには?

(この記事は、Firefoxの従来型アドオン(XULアドオン)の開発経験がある人向けに、WebExtensionsでの拡張機能開発でのノウハウを紹介する物です。)

Services.prompt.confirmEx()を代替する

Firefoxの従来型アドオンではnsIPromptServiceという内部APIを使って、「YES・NO・キャンセルの三択のダイアログ」を表示したり、「以後この確認を表示しない」のようなチェックボックスを伴った確認ダイアログを表示したりできました。

これと同等の事をWebExtensionsベースの拡張機能でやろうとすると、意外と面倒だという事実に行き当たります。

  • window.confirm()window.alert()では「以後この確認を表示しない」のようなチェックボックスを提供できない。
  • window.confirm()では「OK」と「キャンセル」の二択しかできず、三択以上の選択肢を提供できない。
  • コンテンツ領域内に埋め込む形で独自の確認ダイアログ風UIを作るのは手間がかかる。ボタンのフォーカス切り替えなどのキーボード操作にも対応するとなると、なおさら大変。
  • バックグラウンドスクリプトから任意のタイミングで確認を行うためには、コンテントスクリプトとの間での通信が必要。

そこで、nsIPromptServiceconfirmEx()select()に相当する機能を簡単に利用できるようにする軽量なライブラリとして、RichConfirm.jsという物を開発しました。 (サイドバー内に表示された確認ダイアログ風UI)

使い方は以下の通りです。

  1. RichConfirm.jsをパッケージに含める。
  2. バックグラウンドページやサイドバーなどから<script type="application/javascript" src="./RichConfirm.js"></script>のようにして読み込む。
  3. ダイアログを表示したい場面で、RichConfirm.show()またはRichConfirm.showInTab()を呼ぶ。

以下、それぞれの場合について詳しく説明します。

スクリプトを実行したページの中でダイアログを表示する

サイドバーやコンテントスクリプト内でそのページ内にダイアログを表示したい場合には、RichConfirm.show()を使います。このメソッドはオブジェクト形式で以下の引数を受け付けます。

RichConfirm.show({
  message:      '実行しますか?',   // 表示するメッセージ
  buttons:      ['はい', 'いいえ'], // 選択肢のラベルの配列
  checkMessage: '以後確認しない',   // チェックボックスのラベル
  checked:      false              // チェックボックスの初期状態
});

メソッドの戻り値はPromiseになっており、以下のいずれかの方法で結果を受け取る事ができます。

// Promiseとして使う
RichConfirm.show(...).then(result => {
  // 結果を使った処理
  console.log(result);
});

// async-awaitで使う
async confirmAndDo() {
  var result = await RichConfirm.show(...);
  // 結果を使った処理
  console.log(result);
}

Promiseが解決された結果の値は、以下のような形式のオブジェクトになっています。

{
  buttonIndex: 0,
  // 押されたボタンの番号。選択肢の配列の要素に対応し、
  // どれも選択されなかった場合は`-1`となる。
  checked: true
  // チェックボックスの状態。
}

タブの中でダイアログを表示する

バックグラウンドスクリプトから現在のタブの中にダイアログを表示したい場合には、RichConfirm.showInTab()を使います。

このメソッドを使うにはactiveTabまたはtabsの権限が必要です。 RichConfirm.show()と同じインターフェースで利用でき、戻り値の取り扱いもRichConfirm.show()と同じです。

RichConfirm.show({
  message:      '実行しますか?',   // 表示するメッセージ
  buttons:      ['はい', 'いいえ'], // 選択肢のラベルの配列
  checkMessage: '以後確認しない',   // チェックボックスのラベル
  checked:      false              // チェックボックスの初期状態
});

RichConfirm.show()には無い特長として、メソッドの第1引数としてtabs.Tabid(整数)を指定すると、現在のタブ以外の任意のタブにダイアログを表示させる事ができます。例えば、以下は現在のタブの右隣のタブにダイアログを表示する例です。

async function confirmInNextTab() {
  var [current, tabs] = await Promise.all([
    browser.tabs.getCurrent(),
    browser.tabs.query({})
  ]);
  var nextTab = tabs.filter(tab => tab.windowId == current.windowId)
                    .filter(tab => tab.index == current.index + 1)[0] || current;
  var result = await RichConfirm.show(nextTab.id, {
    message: ...
  });
  ...
}

まとめ

XULアドオンで一般的に使われていたnsIPromptServiceのconfirmEx()をの代替となる軽量ライブラリであるRichConfirm.jsについて、その使い方を解説しました。

ネイティブアプリケーションの開発に近い部分があったXULアドオンとは異なり、WebExtensionsベースでの拡張機能開発は、どちらかというとWebアプリの開発に近いと言えます。Webアプリの開発に慣れた人にとっては、古くはjQuery UIや、近代的な物ではBootstrapReactを使う方がやりやすいかもしれません。ただ、ピンポイントでconfirmEx()と同等の事がしたいだけという場合には、これらのライブラリの使い方を一から覚えるのは億劫に感じるものです。そういった場合の手軽な選択肢として、RichConfirm.jsを試してみてはいかがでしょうか。

タグ: Mozilla
2018-03-26

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|
2018|01|02|03|04|05|06|
タグ: