Tips on Chrome Native Messaging
最近在写Bilidan-helper,整个插件的核心就是Chrome Extension API里的Native Messaging。这套API比起NPAPI/PPAPI显然要易用的多,当然功能也简单的多。功能简单不要紧,但这套API做的也很不完善,如果各方面都没问题正常工作是可以的,出了问题的话就不好调试和判断了。
稍微记录一点使用Native Messaging的经验,文档里有的东西就尽量不说了。
###Host部分
范例代码中的Host部分使用Python写的,严格的说是Python2。不加改动的话是无法在Python3下运行的。主要问题出现在
sys.stdout
和sys.stdin
上,需要把:sys.stdout.write()
改为sys.stdout.buffer.write()
,sys.stdin.read()
同理改成sys.stdin.buffer.read()
。我最后使用的Host是直接从范例代码改的。范例代码中Windows的Host使用了一个bat来调用Python来执行脚本。但是如果要打包发布的话,让用户装个Python不太现实……所以可以使用cxfreeze打包成exe直接调用。
在这里我遇到的问题是我仍然需要bat来帮我修改PATH环境变量方便Host调用其它程序,千万记得要把bat文件改成跟host的exe不一样的名字。不然bat很容易就开始不停调用自身了……
- 运行于命令行界面的Host程序被Chrome调用的时候不会显示命令行窗口。Bilidan调用了mpv,mpv在命令行输出的状态信息也不会显示出来(虽然不会被Chrome当做Host的输出处理)。同样所有输出到命令行的内容(比如错误)也不会被你看到的。
而且直接输出到标准输出,没有添加消息长度的内容会导致Chrome读取到错误的消息长度而直接将Host关闭。
- 在OSX/Linux下如果从shell启动Chrome,那么Chrome的错误提示会直接输出到shell窗口中,你的Host程序的错误输出也会显示出来。Bilidan调用了mpv,mpv的状态信息也会显示出来。
所以如果是跨平台的应用非常建议使用OSX/Linux开发。Windows下可以使用Sawbuck和启动时为Chrome添加命令行参数来看到Chrome的错误日志,但是看不到Host抛出的错误。可以给Host加上输出日志文件的功能来解决调试问题。
Windows下Host的安装,注册表项只有Chrome一种,Chromium也使用Chrome的注册表项(数字极速之流没测试过)。而且对Host安装目录没有空格、汉字之类的要求,都是可以的。
对Host的Json做出了修改的话,不需要重启浏览器也可以生效。在浏览器运行中添加、删除Host也不需要重启。似乎浏览器会在每次调用Native Host的时候查找。
###插件/应用部分
如果没有特别需求尽量不要使用
sendMessageNative
函数。尤其是开发初段还没有完善插件和Host之间沟通问题的时候。因为sendMessageNative
不需要创建Port,很难像使用connectNative
后再postMessage
一样根据调用函数后Port的状态来帮助确定问题所在。使用
connectNative
或sendMessageNative
连接Host如果不成功,Chrome自己的日志里会有相应记录,比如Specified native messaging host not found.
。在Windows下需要用Sawbuck查看,OSX/Linux可以从shell启动来查看。Chrome新出的Event Page很实用,但是要求所有打开的port都关闭(不管是普通的port还是Native Messaging的port)才能自动卸载脚本。需要合理规划port的开启和关闭。
连接Host不成功是最烦人的错误。因为Chrome的安全限制,既无法让插件安装Host,也无法让Host向Chrome添加插件。
connectNative
是不会返回失败的,即使没有安装对应的Host,也会照常返回一个port。所以如何正确的在插件中检测Native Messaging Host是否安装成功呢?
我研究了老半天,发现Host不存在的情况下,connectNative
返回的port会立刻断开。而对断开的port调用postMessage
会返回错误:Attempting to use a disconnected port object
。所以可以利用这个来判断。最后的成品代码是这样:
1 | var port=null; |
connectionTest
函数就是用来测试连接的。host_name
位置改为所要测试的Host,onMessage
和postMessage
也根据自己编写的Host的情况对应修改发送和返回的内容。
可以发现我把try...catch
捕获postMessage
异常的部分放到了3秒后执行(注1的位置)。因为Chrome有个很坑爹的问题……如果在connectionTest
里,connectNative
和onMessage.addListener
之后直接就postMessage
的话,是不会产生错误的!必须要等一点时间,虽然这个一点很短,短到如果你在console里手动输入,即使是快速的粘贴代码,也会产生错误。所以使用了setTimeout
把testSend
函数放到了3秒后执行,确保能产生错误。
而注2的部分,则是为了如果Host正常安装了的话,可以立刻得到成功的返回,而不用等到3秒后。