Microsoft Edge の Native Messaging を使った拡張機能 その1 (UWP を用いた Native Messaging 拡張機能の概要)
Firefox, Chrome に続いて、 "Open TortoiseSVN" を Microsoft Edge にも移植してみたので、そのまとめです。
もう少し記事を分けるかもしれませんが、以下のような順番で書いていこうと思います。
- その1: UWP を用いた Native Messaging 拡張機能の概要
- その2: パッケージングの方法と、 Win32 アプリケーションの起動
なお、最終的なソースコードは GitHubの Open TortoiseSVN for Microsoft Edge にあります。
Edge の拡張機能のサポート
元々 Google Chrome の拡張機能で導入された拡張機能の形式ですが、 Mozilla Firefox にも WebExtensions として導入され、 Edge でも同様の機能がサポートされ始めていました。
具体的には、2016年の Anniversary update 以降、 Microsoft Edge も Chrome や Firefox とほぼ同様の拡張機能をサポートしています。
そして、 2017年の Creators update で、 Native Messaging のサポートも開始されました。
詳しい内容は Native messaging in Microsoft Edge のページに載っているので、基本的な作成方法などはこちらを参照するのがよいでしょう。
この記事では実際に上記のページに従って拡張機能を作成した際に、気になったところを中心に書こうと思います。
また、 Microsoft によるサンプルは SecureInput として GitHub で公開されています。
Firefox, Chrome との違い
Edge での Native Messaging サポートは、 Firefox, Chrome とは大きく違います。
Native messaging in Microsoft Edge のページに書かれているように、下記が特徴的です。
- JavaScript で書かれた Extension は、 UWP 形式のアプリケーションとのみ通信できる。 Win32 形式のアプリケーションとは通信できない。
Firefox, Chrome の場合は、 (Windows であれば) Win32 形式のアプリケーションと通信できる。
- UWP アプリケーションとは、 AppService の仕組みを用いて通信を行う。
Firefox, Chrome の場合は、標準入出力で JSON 形式のデータを所定のフォーマットでやり取りすることで通信を行う。
つまり、外部アプリケーションと通信する拡張機能の JavaScript 側のインターフェースは Firefox, Chrome と (ほぼ) 同じですが、それを受ける側であるアプリケーションの仕様は、まったく異なることになります。
なお、 Edge の Native Messaging 拡張機能の特徴として二つ書きましたが、実際には「外部のアプリケーションとは AppService の仕組みで通信を行う」という部分が重要です。
これは、 AppService は UWP アプリでしか作成できないので、必然的に UWP 形式のアプリケーションが必要条件となるためです。
UWP アプリとの通信
作成する AppService は、通常のものとまったく同じものです。なので、 UWP アプリで AppService を作成する方法については、他のページを探してみてください。
ここでは、 Edge との通信部分について書きます。
例として、仮に、 com.example.uwp
という名前の AppService を作成するとします。
JavaScript 側のコード
その場合、 JavaScript 側から UWP アプリを呼び出すのは以下のようになります。
1 2 3 4 5 6 7 | browser.runtime.sendNativeMessage(
"com.example.uwp" ,
{ "action" : "action1" , "args" : [ "arg1" , "arg2" , "arg3" ]},
function (response) {
});
|
このように、 browser.runtime.sendNativeMessage
もしくは browser.runtime.connectNative
の第一引数で指定する application の名前は、 AppService の名前そのものになります。
それ以外は、 Firefox, Chrome の場合と何も違いはありません。
UWP 側のコード
AppService を実装する方法としては、 in-process な実装と、 out-of-process な実装の二種類があります。
詳しくは Microsoft の説明などを見るとよいでしょう。
Open TortoiseSVN では in-process な方法で実装したので、下記を実装する必要があります。
OnBackgroundActivated
- 他のアプリケーション (今回の場合は Edge) から AppService への接続があった場合、この関数が呼ばれます。
通常の AppService と同じように、この中で Connection に対してイベントハンドラを登録します。
OnAppServiceRequestReceived
- 他のアプリケーション (今回の場合は Edge) からメッセージが送られてきた際にそのメッセージを処理するためのイベントハンドラです。
Native Messaging のメッセージ処理の手順に合わせて実装します。
OnBackgroundActivated
OnBackgroundActivated
の概要としては下記のような実装になりました。
1 2 3 4 5 6 7 8 9 10 11 12 13 | protected async override void OnBackgroundActivated(BackgroundActivatedEventArgs args)
{
base .OnBackgroundActivated(args);
IBackgroundTaskInstance taskInstance = args.TaskInstance;
if (taskInstance.TriggerDetails is AppServiceTriggerDetails)
{
var appService = taskInstance.TriggerDetails as AppServiceTriggerDetails;
var appServiceDeferral = taskInstance.GetDeferral();
var connection = appService.AppServiceConnection;
connection.RequestReceived += OnAppServiceRequestReceived;
}
}
|
こちらに関しては、特に通常の AppService と違いはないはずです。
OnAppServiceRequestReceived
この関数では、 Edge からのメッセージを受け取り、それに応じて処理を行います。
概要としては、以下のようなことをすればよいです。
ここでは、上で書いた例のように JavaScript 側から {"action": "action1", "args": ["arg1", "arg2", "arg3"]}
という引数で呼ぶことを考えてみます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | private async void OnAppServiceRequestReceived(AppServiceConnection sender, AppServiceRequestReceivedEventArgs args)
{
AppServiceDeferral messageDeferral = args.GetDeferral();
var value = args.Request.Message.First().Value.ToString();
var jsonSerializer = new JavaScriptSerializer();
var valueList = (IDictionary< string , object >)jsonSerializer.DeserializeObject(value);
var response = new ValueSet();
switch (valueList[ "action" ].ToString())
{
case "action1" :
response.Add( "message" , jsonSerializer.Serialize(
new { result = true , data = "data1" }));
break ;
default :
break ;
}
await args.Request.SendResponseAsync(response);
messageDeferral.Complete();
}
|
いくつか重要な点を挙げておきます。
- Edge からのメッセージは
ValueSet
の一つ目の Value
として渡される。
ValueSet
は辞書のように Key, Value の組を保存できるものですが、 AppService ではこの ValueSet
を用いてデータをやり取りすることになっています。そのため、 Edge の Native Messaging 拡張機能も、 ValueSet でデータをやり取りすることになります。
Edge から送られてきたデータは、 JSON 形式の文字列となって、渡された ValueSet
オブジェクトの最初の (唯一の) 要素の Value
に格納されてきます。
Key
は "Message" になっているようですが、これを仮定しないのがよいと思います。
- UWP アプリからのメッセージも同様に
ValueSet
として返す。
逆方向もまったく同じです。
Key は適当な名前でよいので、 Value に JSON 文字列を指定し、 SendResponseAsync
でメッセージを送ります。
JSON 形式にシリアライズするためには JavaScriptSerializer
と匿名型を用いるのが楽でよいと思います。
なお、本筋とは関係ないですが、やや複雑な JSON 文字列をパースする場合、以下のようにすればよいです。
例えば、 {"action": "action1", "args": ["arg1", "arg2", "arg3"]}
の args
をパースする場合は、例えば以下のようにすればよいです。
1 2 | var args = (( object [])valueList[ "args" ])
.Select(o => ( string )o).ToArray();
|
このようにすることで、 "args" の中身を配列で取り出すことができます。
あとは UWP アプリ内で好きな処理をすればよいわけです。
注意点
Microsoft EdgeHTML 15.15063 の時点では、 UWP アプリからの戻り値が (私の予想と) やや異なった形式で返ってきます。
具体的には、下記のようになります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | browser.runtime.sendNativeMessage(
"com.example.uwp" ,
{ "action" : "action1" , "args" : [ "arg1" , "arg2" , "arg3" ]},
function (response) {
response[ "result" ]
response = JSON.parse(response);
response[ "result" ]
});
|
このように、 Firefox, Chrome と違ってわざわざ JSON.parse
する必要があります。
私はこの挙動はバグのように思いましたので、 issue として登録しましたが、まだ有効な回答はない状態です。
続いて、作成した UWP アプリを JavaScript のコードとまとめてパッケージングし、 Edge の拡張機能として登録することになります。
次回以降は、下記について書く予定です。
- Appx パッケージの作成
- Win32 アプリケーションの起動
browser.runtime.sendNativeMessage
と browser.runtime.connectNative
における注意点