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

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

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

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

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

  • アプリマニフェストには "allowed_extensions" にアプリの ID の配列を記述します。 Chrome では "allowed_origins" に "chrome-extension" URL の配列を記述します。
  • アプリマニフェストが Chrome とは別の場所に保管されます。

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

 

セットアップ

 

拡張機能の manifest

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

  • "nativeMessaging" permission を manifest.json ファイルに設定する必要があります
  • applications manifest キーを使用してアドオン ID を明示的に設定すべきです (これはアプリマニフェストが、そのアプリケーションへのアクセスが許可されている拡張機能かどうかを識別するために、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 ファイルがインストール・アップデートされた時期や方法についてのセキュリティモデルは、WebExtension を使う拡張機能に対してのものというよりはネイティブアプリケーションに対してのものです。

native アプリマニフェストの文法と場所については、Native manifests を見てください。

例として、"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 の 拡張機能において"ping_pong" という名前を runtime API等に渡すことによる接続が許可されます。 アプリケーション自体は "/path/to/native-messaging/app/ping_pong.py" です。

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

{
  "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 -u "c:\\path\\to\\native-messaging\\app\\ping_pong.py"

 

メッセージの交換

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

 

拡張機能側

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

 

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

 

このパターンでは、 runtime.connectNative() を呼びだし、その時にアプリケーションの名前(アプリマニフェストの "name" プロパティの値)を渡します。既にアプリケーションが起動済みでなかった場合、これによってアプリケーションが起動し、runtime.Port オブジェクトを拡張機能に返します。

ネイティブアプリは起動時に次の 2 つの引数を取ります:

  • アプリマニフェストの完全パス
  • (Firefox 55 以降で) 起動元のアドオンの ID (manifest.json の applications キーにて指定)

アプリケーションは 拡張機能が 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 メッセージ
  • コールバック(オプション)

それぞれのメッセージごとに新しいアプリケーションのインスタンスが作成されます。アプリの開始時に次の 2 つの引数が渡されます:

  • アプリマニフェストの完全パス
  • (Firefox 55 以降で) 起動元のアドオンの ID (manifest.json の applications キーにて指定)

アプリからの最初のメッセージは 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 json
import sys
import struct


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


# Encode a message for transmission, given its content.
def encode_message(message_content):
    encoded_content = json.dumps(message_content)
    encoded_length = struct.pack('@I', len(encoded_content))
    return {'length': encoded_length, 'content': encoded_content}


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


while True:
    message = get_message()
    if message == "ping":
        send_message(encode_message("pong"))

ネイティブアプリを閉じる

runtime.connectNative() を使用してネイティブアプリケーションに接続した場合、アプリケーションは拡張機能が 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() に渡した名前がアプリマニフェスト中の名前と一致しているか確認してください
  • OS X/Linux: アプリマニフェストのファイル名が <name>.json となっていることを確認してください
  • Windows: レジストリキーが正しい場所にあり、その名前がアプリマニフェスト中の名前と一致していることを確認してください
  • Windows: レジストリキーに指定されたパスがアプリマニフェストを指していることを確認してください
"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"
  • このメッセージが表示されたとき、アプリマニフェストの発見には成功しています
  • アプリマニフェストの "path" が正しいかどうかを確認してください
  • Windows: パスセパレータがエスケープされていることを確認してください ("c:\\path\\to\\file").
  • アプリがアプリマニフェストの "path" プロパティで示された場所に配置されていることを確認してください
  • アプリが実行可能であることを確認してください
"This extension does not have permission to use native application <name>"
  • アプリマニフェストの "allowed_extensions" がアドオンの ID を含んでいることを確認してください
"TypeError: browser.runtime.connectNative is not a function"
  • アドオンが "nativeMessaging" permission を持っているか確認してください
"[object Object]       NativeMessaging.jsm:218"
  • アプリケーションの開始に問題が発生しました

Chrome での非互換性

Command-line arguments

On Linux and Mac, Chrome passes one argument to the native app, which is the origin of the extension that started it, in the form: chrome-extension://[extensionID]. This enables the app to identify the extension.

On Windows, Chrome passes two arguments: the first is the origin of the extension, and the second is a handle to the Chrome native window that started the app.

allowed_extensions

In Chrome, the allowed_extensions key in the app manifest is called allowed_origins instead.

App manifest location

Chrome expects to find the app manifest in a different place. See Native messaging host location in the Chrome docs. 

 

 

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

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