最近在写Bilidan-helper,整个插件的核心就是Chrome Extension API里的Native Messaging。这套API比起NPAPI/PPAPI显然要易用的多,当然功能也简单的多。功能简单不要紧,但这套API做的也很不完善,如果各方面都没问题正常工作是可以的,出了问题的话就不好调试和判断了。

稍微记录一点使用Native Messaging的经验,文档里有的东西就尽量不说了。

###Host部分

  1. 范例代码中的Host部分使用Python写的,严格的说是Python2。不加改动的话是无法在Python3下运行的。主要问题出现在sys.stdoutsys.stdin上,需要把:sys.stdout.write()改为sys.stdout.buffer.write()sys.stdin.read()同理改成sys.stdin.buffer.read()

  2. 我最后使用的Host是直接从范例代码改的。范例代码中Windows的Host使用了一个bat来调用Python来执行脚本。但是如果要打包发布的话,让用户装个Python不太现实……所以可以使用cxfreeze打包成exe直接调用。

在这里我遇到的问题是我仍然需要bat来帮我修改PATH环境变量方便Host调用其它程序,千万记得要把bat文件改成跟host的exe不一样的名字。不然bat很容易就开始不停调用自身了……

  1. 运行于命令行界面的Host程序被Chrome调用的时候不会显示命令行窗口。Bilidan调用了mpv,mpv在命令行输出的状态信息也不会显示出来(虽然不会被Chrome当做Host的输出处理)。同样所有输出到命令行的内容(比如错误)也不会被你看到的。

而且直接输出到标准输出,没有添加消息长度的内容会导致Chrome读取到错误的消息长度而直接将Host关闭。

  1. 在OSX/Linux下如果从shell启动Chrome,那么Chrome的错误提示会直接输出到shell窗口中,你的Host程序的错误输出也会显示出来。Bilidan调用了mpv,mpv的状态信息也会显示出来。

所以如果是跨平台的应用非常建议使用OSX/Linux开发。Windows下可以使用Sawbuck和启动时为Chrome添加命令行参数来看到Chrome的错误日志,但是看不到Host抛出的错误。可以给Host加上输出日志文件的功能来解决调试问题。

  1. Windows下Host的安装,注册表项只有Chrome一种,Chromium也使用Chrome的注册表项(数字极速之流没测试过)。而且对Host安装目录没有空格、汉字之类的要求,都是可以的。

  2. 对Host的Json做出了修改的话,不需要重启浏览器也可以生效。在浏览器运行中添加、删除Host也不需要重启。似乎浏览器会在每次调用Native Host的时候查找。

###插件/应用部分

  1. 如果没有特别需求尽量不要使用sendMessageNative函数。尤其是开发初段还没有完善插件和Host之间沟通问题的时候。因为sendMessageNative不需要创建Port,很难像使用connectNative后再postMessage一样根据调用函数后Port的状态来帮助确定问题所在。

  2. 使用connectNativesendMessageNative连接Host如果不成功,Chrome自己的日志里会有相应记录,比如Specified native messaging host not found.。在Windows下需要用Sawbuck查看,OSX/Linux可以从shell启动来查看。

  3. Chrome新出的Event Page很实用,但是要求所有打开的port都关闭(不管是普通的port还是Native Messaging的port)才能自动卸载脚本。需要合理规划port的开启和关闭。

  4. 连接Host不成功是最烦人的错误。因为Chrome的安全限制,既无法让插件安装Host,也无法让Host向Chrome添加插件。connectNative不会返回失败的,即使没有安装对应的Host,也会照常返回一个port。所以如何正确的在插件中检测Native Messaging Host是否安装成功呢?

我研究了老半天,发现Host不存在的情况下,connectNative返回的port会立刻断开。而对断开的port调用postMessage会返回错误:Attempting to use a disconnected port object。所以可以利用这个来判断。最后的成品代码是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
var port=null;
var testflag=false;
function onMessage(message){
switch (message) {
case 'pong':
if(testflag){
testflag = false;
port.disconnect();
port = null;
}
console.log("Connection test success");
break;
default:
console.log("Unrecognized command" + message.command);
}
}

function connectionTest(){
testflag = true;
var host_name = "com.nativemessaging.host";
port = chrome.runtime.connectNative(host_name);
port.onMessage.addListener(onMessage);
port.postMessage("ping"); //注2
setTimeout(testSend,3000); //注1
}

function testSend(){
if(testflag){
try{
port.postMessage("ping");
}catch(e){
console.log("Connection test fail");
}
}
testflag = false;
port = null;
}

connectionTest函数就是用来测试连接的。host_name位置改为所要测试的Host,onMessagepostMessage也根据自己编写的Host的情况对应修改发送和返回的内容。

可以发现我把try...catch捕获postMessage异常的部分放到了3秒后执行(注1的位置)。因为Chrome有个很坑爹的问题……如果在connectionTest里,connectNativeonMessage.addListener之后直接就postMessage的话,是不会产生错误的!必须要等一点时间,虽然这个一点很短,短到如果你在console里手动输入,即使是快速的粘贴代码,也会产生错误。所以使用了setTimeouttestSend函数放到了3秒后执行,确保能产生错误。

而注2的部分,则是为了如果Host正常安装了的话,可以立刻得到成功的返回,而不用等到3秒后。