Native messaging

Native messaging はユーザのコンピュータにインストールされたアプリケーションと WebExtension の間のメッセージ交換を可能にします。 Native messaging を利用すれば、ネイティブアプリケーションがアドオンに対してサービスを提供するために、Webを介してアクセスできるようにする必要はありません。典型的な利用例としてはパスワードマネージャが挙げられます。ネイティブアプリケーションはパスワードの暗号化と保管を行い、アドオンと通信してWebフォームに入力を行うといったことが可能です。さらに、Native messaging を用いることで、一部のハードウェア等の WebExtension API ではアクセスできないリソースに対してアドオンからアクセスできるようになります。

対象となるネイティブアプリケーションは、ブラウザを使用してインストールや管理を行うわけではありません。OSのインストール機構を使ってインストールします。ネイティブアプリケーションそのものに加えて、"host manifest" または "app manifest" と呼ばれるJSONファイルを用意しなければなりません。app manifest ファイルにはブラウザからネイティブアプリケーションにアクセスするための方法を記述します。

Native messaging を利用する WebExtension は manifest.json の中で "nativeMessaging" permission を要求する必要があります。反対に、ネイティブアプリケーション側では app manifest の "allowed_extensions" フィールドに WebExtension のIDを含めることで permission を認める必要があります。

WebExtension はruntime APIを用いてネイティブアプリケーションとJSONメッセージを交換することができます。ネイティブアプリケーション側では標準入力 (stdin) を介してメッセージを受信し、標準出力 (stdout) を介してメッセージを送信します。

Native messaging のサポートは Chrome とほぼ互換性がありますが、主に2つの違いがあります。

  • app manifest には "allowed_extensions" にアプリケーションのIDの配列を記述します。 Chrome では "allowed_origins" に "chrome-extension" URLの配列を記述します。
  • app manifest が Chrome とは別の場所に保管されます。

GitHub の "webextensions-examples" リポジトリの "native-messaging" directory に完全な例があります。この記事におけるサンプルコードの大半は、この例から直接持ち込んでいます。

セットアップ

Add-on manifest

もしWebExtensionをネイティブアプリケーションと通信させたい場合、

  • "nativeMessaging" permission を manifest.json ファイルに設定する必要があります
  • applications manifest キーを使用してアドオンIDを明示的に設定すべきです (これは app manifest が、そのアプリケーションへのアクセスが許可されている WebExension かどうかを識別するために、IDを利用するためです)

以下に manifest.json の例を示します。

{

  "description": "Native messaging example add-on",
  "manifest_version": 2,
  "name": "Native messaging example",
  "version": "1.0",
  "icons": {
    "48": "icons/message.svg"
  },

  "applications": {
    "gecko": {
      "id": "ping_pong@example.org",
      "strict_min_version": "50.0"
    }
  },

  "background": {
    "scripts": ["background.js"]
  },

  "browser_action": {
    "default_icon": "icons/message.svg"
  },
 
  "permissions": ["nativeMessaging"]

}

App manifest

app manifest に、ブラウザがネイティブアプリケーションに接続する方法を記述します。

app manifest ファイルはネイティブアプリケーションと一緒にインストールする必要があります。ブラウザは app manifest ファイルを読み込み、検証を行いますが、インストールや管理は行いません。したがって、app manifestファイルがインストール・アップデートされた時期や方法についてのセキュリティモデルは、WebExtension に対してのものというよりはネイティブアプリケーションに対してのものです。

app manifest は以下のプロパティを含む単一のJSONオブジェクトです。

Name Type Description
name String

ネイティブアプリケーションの名前です。

この名前はアドオンの runtime.connectNative() または runtime.sendNativeMessage() に渡される名前と一致している必要があります。

OS XとLinuxでは、app manifestの(.json拡張子を除いた)ファイル名とも一致していなければなりません。

Windowsでは、app manifest の場所を記したレジストリキーの名前と一致している必要があります。

名前には小文字の英数字とアンダースコア、ドットのみ含めることができます。最初または最後の文字にドットを使用することはできず、ドットを2つ以上連続させることもできません。

description String ネイティブアプリケーションの説明です。
path String

ネイティブアプリケーションのパスです。

Windowsでは、manifest からの相対パスを指定することもできます。OS XまたはLinuxでは絶対パスでなければなりません。

type String

アドオンがアプリケーションに接続するために使用する方法を記述します。

現在のところ "stdio" のみが指定可能です。これはアプリケーションが標準入力 (stdin) を介してメッセージを受信し、標準出力 (stdout) を使用してメッセージを送信することを示します。

allowed_extensions Array of String

Add-on ID の配列です。配列中のそれぞれの値はこのネイティブアプリケーションとの通信が許可されているWebExtensionを表します。

この設定のために、あなたの作成する WebExtension の manifest.json ファイルに applications キーを含めたくなるものと思われるため、開発中に明示的なIDを設定しておくと良いでしょう。

例として、"ping_pong"ネイティブアプリケーションのmanifestを以下に示します。

{
  "name": "ping_pong",
  "description": "Example host for native messaging",
  "path": "/path/to/native-messaging/app/ping_pong.py",
  "type": "stdio",
  "allowed_extensions": [ "ping_pong@example.org" ]
}

この設定では、"ping_pong@example.org" というIDの WebExtension において"ping_pong" という名前を runtime API等に渡すことによる接続が許可されます。 アプリケーション自体は "/path/to/native-messaging/app/ping_pong.py" です。

Note for Windows: 上記の例におけるネイティブアプリケーションは Python スクリプトです。Windowsにおいては、この方法で期待通りに Python スクリプトを実行させることは難しいため、代替案として、 .bat ファイルを作成してmanifestからリンクします。

{
  "name": "ping_pong",
  "description": "Example host for native messaging",
  "path": "c:\\path\\to\\native-messaging\\app\\ping_pong_win.bat",
  "type": "stdio",
  "allowed_extensions": [ "ping_pong@example.org" ]
}

バッチファイルから Python スクリプトを起動します。

@echo off

python "c:\path\to\native-messaging\app\ping_pong.py"

 

App manifest の場所

ブラウザが app manifest を見つけるために、app manifest はユーザのコンピュータの所定の場所に格納する必要があります。この場所はOSによっても異なりますし、アプリケーションをグローバルに使用可能にすべきか単一のユーザのみに使用可能にすべきかによっても異なります。

以下に示すオプション中の "<nane>" はすべて manifest 中の "name" の値です。先程の例に当てはめると "ping_pong" に相当します。

Windows

グローバルな設定としては、以下の名前のレジストリキーを作成します。

HKEY_LOCAL_MACHINE\SOFTWARE\Mozilla\NativeMessagingHosts\<name>

このキーに manifest のパスを示す単一のデフォルト値を設定します。

もし32ビットのアプリケーションであっても、このキーは Wow6432Node 下に作成しないように注意して下さい。ブラウザは常に32-bit エミュレーションではなくレジストリの "native" view 下のキーを探します。確実に"native" view にキーを作成するために、KEY_WOW64_64KEY または KEY_WOW64_32KEY フラグを RegCreateKeyEx に渡すことができます。Accessing an Alternate Registry View を参照して下さい。

ユーザごとの設定としては、以下の名前のレジストリキーを作成します。

HKEY_CURRENT_USER\SOFTWARE\Mozilla\NativeMessagingHosts\<name>

このキーに manifest のパスを示す単一のデフォルト値を設定します。

例えばネイティブアプリケーションの名前が "ping_pong" であり、その app manifest が "C:\path\to\ping_pong.json" の場合、下記の REG ADD コマンドを使用してキーを追加することができます。

REG ADD "HKEY_CURRENT_USER\SOFTWARE\Mozilla\NativeMessagingHosts\ping_pong" /ve /d "C:\path\to\ping_pong.json" /f

Mac OS X

グローバルな設定としては、manifestを以下に配置します。

/Library/Application Support/Mozilla/NativeMessagingHosts/<name>.json

個人ごとの設定としては、manifestを以下に配置します。

~/Library/Application Support/Mozilla/NativeMessagingHosts/<name>.json

Linux

グローバルな設定としては、manifestを以下のいずれかに配置します。

/usr/lib/mozilla/native-messaging-hosts/<name>.json

/usr/lib64/mozilla/native-messaging-hosts/<name>.json

 

個人ごとの設定としては、manifestを以下に配置します。

~/.mozilla/native-messaging-hosts/<name>.json

メッセージの交換

上記のセットアップにより、WebExtension はネイティブアプリケーションとJSONメッセージを交換することができます。

アドオン側

content scripts との通信のためのメッセージングAPI を使用したことがあれば、これはよく慣れ親しんだものに思えるでしょう。コネクションベースのメッセージングとコネクションレスメッセージングという2つのパターンがあります。

コネクションベースのメッセージング

このパターンでは、 runtime.connectNative() を呼びだし、アプリケーションの名前(アプリケーションの manifest の "name" プロパティの値)を渡します。既にアプリケーションが起動済みでなかった場合、これによってアプリケーションが起動し、 runtime.Port を WebExtension に返します。アプリケーションは WebExtension が Port.disconnect() を呼び出すか、接続されたページが閉じられるまで実行し続けます。

Port を使用してメッセージを送信するためには、postMessage() 関数を呼び出し、 送信するJSONメッセージを渡します。Port を使用してメッセージを受信するためには、onMessage.addListener() 関数を使用してリスナーを追加します。

"ping_pong" アプリケーションとコネクションを確立するバックグラウンドスクリプトの例を示します。アプリケーションからのメッセージを受信し、ユーザがブラウザアクションをクリックするたびに "ping" メッセージを送信します。

/*
On startup, connect to the "ping_pong" app.
*/
var port = browser.runtime.connectNative("ping_pong");

/*
Listen for messages from the app.
*/
port.onMessage.addListener((response) => {
  console.log("Received: " + response);
});

/*
On a click on the browser action, send the app a message.
*/
browser.browserAction.onClicked.addListener(() => {
  console.log("Sending:  ping");
  port.postMessage("ping");
});

コネクションレスメッセージング

このパターンでは、 runtime.sendNativeMessage() を呼び、以下を渡します。

  • アプリケーションの名前
  • 送信するJSONメッセージ
  • コールバック(オプション)

それぞれのメッセージごとに新しいアプリケーションのインスタンスが作成されます。アプリケーションから送信された最初のメッセージが sendNativeMessage( ) の呼び出しの結果として扱われ、コールバックに渡されます。

以下に、先程の例を runtime.sendNativeMessage() を使って書き直したものを示します。

function onResponse(response) {
  console.log("Received " + response);
}

function onError(error) {
  console.log(`Error: ${error}`);
}

/*
On a click on the browser action, send the app a message.
*/
browser.browserAction.onClicked.addListener(() => {
  console.log("Sending:  ping");
  var sending = browser.runtime.sendNativeMessage(
    "ping_pong",
    "ping");
  sending.then(onResponse, onError);
});

アプリケーション側

アプリケーション側では、標準入力を用いてメッセージを受信し、標準出力を用いてメッセージを送信します。

各メッセージはJSONでシリアライズされ、UTF-8でエンコードされ、メッセージ長を表す32-bitの値がネイティブのバイト順で先頭に付加されます。

アプリケーションからの一つのメッセージの最大サイズは1MBです。アプリケーションへの一つのメッセージの最大サイズは4GBです。

Python による例を示します。このアプリケーションはアドオンからのメッセージ受信します。メッセージが "ping" であった場合、"pong" というメッセージを返します。

#!/usr/bin/python -u
# Note that running python with the `-u` flag is required on Windows,
# in order to ensure that stdin and stdout are opened in binary, rather
# than text, mode.

import sys, json, struct

# Read a message from stdin and decode it.
def getMessage():
  rawLength = sys.stdin.read(4)
  if len(rawLength) == 0:
      sys.exit(0)
  messageLength = struct.unpack('@I', rawLength)[0]
  message = sys.stdin.read(messageLength)
  return json.loads(message)

# Encode a message for transmission, given its content.
def encodeMessage(messageContent):
  encodedContent = json.dumps(messageContent)
  encodedLength = struct.pack('@I', len(encodedContent))
  return {'length': encodedLength, 'content': encodedContent}

# Send an encoded message to stdout.
def sendMessage(encodedMessage):
  sys.stdout.write(encodedMessage['length'])
  sys.stdout.write(encodedMessage['content'])
  sys.stdout.flush()

while True:
  receivedMessage = getMessage()
  if (receivedMessage == "ping"):
    sendMessage(encodeMessage("pong"))

ネイティブアプリケーションのクローズ

runtime.connectNative() を使用してネイティブアプリケーションに接続した場合、アプリケーションはWebExtension が Port.disconnect() を呼び出すか接続したページが閉じられるまで実行されます。 runtime.sendNativeMessage() を使用してネイティブアプリケーションの実行を開始した場合、アプリケーションはメッセージを受信してレスポンスを送信した後クローズされます。

ネイティブアプリケーションをクローズするために

  • OS X や Linux のような *nix システムでは、ブラウザはネイティブアプリケーションが正しく終了する機会を与えるために SIGTERM を送信し、その後 SIGKILL を送信します。これらのシグナルは新しいプロセスグループを作成して分けない限りすべてのサブプロセスに伝播します。
  • Windowsでは、ブラウザはネイティブアプリケーションのプロセスを Job object とし、ジョブを kill します。 ネイティブアプリケーションが追加でプロセスを立ち上げ、アプリケーション自体が kill された後もそのままにしたい場合、ネイティブアプリケーションは追加のプロセスを CREATE_BREAKAWAY_FROM_JOB フラグを立てて立ち上げる必要があります。

トラブルシューティング

もしうまくいかない場合、ブラウザコンソールをチェックして下さい。ネイティブアプリケーションが何かしらの出力を stderr に送っていた場合、ブラウザはそれをブラウザのコンソールにリダイレクトします。そのため、ネイティブアプリケーションが起動できている限り、出力されたエラーメッセージを確認することができます。

アプリケーションが起動できていなかった場合、問題の手がかりとなるエラーメッセージを確認して下さい。

"No such native application <name>"
  •  runtime.connectNative() に渡した名前が app manifest 中の名前と一致しているか確認して下さい
  • OS X/Linux: the app manifest のファイル名が <name>.json となっていることを確認して下さい
  • Windows: レジストリキーが正しい場所にあり、その名前が app manifest 中の名前と一致していることを確認して下さい
  • Windows: レジストリキーに指定されたパスが app manifest を指していることを確認して下さい
"Error: Invalid application <name>"
  • アプリケーションの名前に不正な文字が含まれていないことを確認して下さい
"'python' is not recognized as an internal or external command, ..."
  • Windows: アプリケーションが Python スクリプトの場合、Python がインストールされており、パスが正しく設定されていることを確認して下さい
"File at path <path> does not exist, or is not executable"
  • このメッセージが表示されたとき、app manifest の発見には成功しています
  • app manifest の "path" が正しいかどうかを確認して下さい
  • Windows: パスセパレータがエスケープされていることを確認して下さい ("c:\\path\\to\\file").
  • アプリケーションが app manifest の "path" プロパティで示された場所に配置されていることを確認して下さい
  • アプリケーションが実行可能であることを確認して下さい
"This extension does not have permission to use native application <name>"
  • app manifest の "allowed_extensions" がアドオンのIDを含んでいることを確認して下さい
"TypeError: browser.runtime.connectNative is not a function"
  • アドオンが "nativeMessaging" permission を持っているか確認して下さい
"[object Object]       NativeMessaging.jsm:218"
  • アプリケーションの開始に問題が発生しました

 

 

ドキュメントのタグと貢献者

タグ: 
 このページの貢献者: tiwatsuka
 最終更新者: tiwatsuka,