一. FireBreath介绍
FireBreath是一个跨平台和跨浏览器的插件开发框架,通过Firebreath写的插件可以运行在windows,linux和mac上的IE,Firefox,Chrome,Opera,Safari等浏览器上。
FireBreath的主要开发者是Richard Bateman, 他工作在Facebook,FireBreath里面使用了不少Facebook贡献的代码。Firebreath使用New BSD授权或LGPL授权,官网在http://www.firebreath.org, 代码可以从https://github.com/firebreath/FireBreath下载。
FireBreath适合如下场合:
1. 需要提供新的功能给js调用(扩展js功能)
2. 需要实现类似于Flash或media player那样功能
3. 给Chrome写Extension
4. 其他非扩展浏览器外壳的功能的场合
在IE上,我们可能经常会看到如下的工具条:
google工具条属于浏览器外壳部分的扩展,并不属于ActiveX插件,而是属于BHO(Browser Helper Object)。无论是ActiveX还是BHO,都是基于COM实现的。在浏览器中使用COM的好处是扩展能力超强,好被其他程序重用(例如QQ的聊天窗口的一部分就是用IE做的),坏处是可能导致启动速度变慢(需要合理优化)。BHO可以实现很强大的功能,例如Chrome Frame就是利用BHO将Chrome嵌到IE里面运行。Firefox实现了一套与COM类似的技术:XPCOM(Cross Platform Component Object Model),XPCOM虽然说是跨平台,但整体来说还是弱于COM。
在IE中还有一种扩展js功能的是ie external对象(http://msdn.microsoft.com/en-us/library/ms535246(VS.85).aspx),现在很多程序就是用这种方法扩展js功能,例如迅雷,QQ等。
Firebreath现在主要是支持桌面系统的浏览器,还不支持Android,Iphone等平台。Firebreath实现采用了boost库,也使用了异常处理机制,这导致Firebreath在Android和Iphone等平台移植时会稍微困难一点,不过网上已经有android版的boost库。我曾经专门问过Richard Bateman是否有计划支持Android,他表示暂时还没计划。其实在嵌入式设备上的简单的做法是:参考Firebreath的架构,而不是使用其代码,否则可能会出现效率问题。
二. Firebreath的实际应用
去年我在给公司开发PC上Widget引擎,这个Widget引擎上面运行WebApp应用,界面和应用逻辑使用HTML+CSS+Javascript实现,而网页无法实现的功能则由CAR实现(CAR是与COM类似的技术,不过支持反射和AOP编程)。在这个引擎上开发的第一款应用是:类似于91手机助手的Android PC套件,整个PC套件的UI部分全部用HTML+CSS+Javascript实现,没有使用任何原生的控件(例如button),而JS无法完成的功能(例如获取联系人,安装apk包)则由CAR实现,然后在js中调用CAR。
其实扩展js功能,还有一种方法是使用现在网站常用的web service:在程序中实现一个http server,然后在http server中实现JS无法完成的功能,例如PC套件里面的获取联系人,最后再使用Ajax去调用。但为什么不选用这种方法呢?主要是基于如下几点原因:
1. 管理同步调用非常麻烦
2. js得处理参数转换
3. webservice除了本进程可以调用外,其他应用可以调用,安全认证比较麻烦
4. http无状态,长时间执行的操作得用一些特殊方式处理
5. 保持长连接也挺麻烦,回调机制的实现比较难办
6. 对象的生命周期管理难以与js的垃圾回收同步
7. 与本地UI交互困难。例如由于网页只能打开特定的对话框,如果应用要打开特定对话框(例如选取文件夹),webservice实现比较麻烦 。
在我看来,C++里面的对象对JS而言,就应该和JS里面的原生对象(例如Date)是一样的,可以new对象,可以和原来一样地调用C++对象里面的方法,可以注册事件和触发事件,可以被垃圾回收。基于这个原则,并利用CAR的反射和事件回调机制,可以将CAR实现C++类动态注入到JS引擎中,使得JS可以轻松地调用C++对象。举个例子:
先写一个接口描述文件HelloWorld.car:
- module
- {
- interface IHelloRef {
- test([in] Int32 n, [out] AStringBuf<32> ret);
- }
- //回调函数
- callbacks JHelloRef {
- onTestCallback();
- }
- class CHelloRef {
- interface IHelloRef;
- constructor([in] Int32 abc);
- }
- }
然后用emake编译HelloWorld.car可以生成相关的.h和.cpp文件,修改cpp文件为如下:
- #include "CHelloRef.h"
- #include "_CHelloRef.cpp"
- #include <stdio.h>
- #include <windows.h>
- ECode CHelloRef::test(
- /* [in] */ Int32 n, * [out] */AStringBuf* ret)
- {
- printf("hello world\n");
- //触发回调
- Callback::onTestCallback();
- ret->Copy("hello world");
- ret->Append(n);
- //睡眠3秒后返回结果
- ::Sleep(3000);
- return NOERROR;
- }
- ECode CHelloRef::constructor(Int32 abc)
- {
- return NOERROR;
- }
在js中可以这么调用:
- var hello = new HelloWorld.CHelloRef(1234);
- hello.onTestCallback = function(){
- window.alert('onTestCallback ’);
- };
- //调用test将触发onTestCallback,test的执行将耗时3秒以上,js引擎也会“卡死”3秒以上
- var ret= hello.test(11);
- //ret等于hello world11
- alert(ret);
- //异步调用,下面的js代码可以继续执行,而不至于js引擎卡死,结果返回时调用function
- hello.test(32, function(ec, ret){
- //ret等于hello world32
- alert(ret);
- });
最初我是采用在Webkit外壳上注入CAR的方式,但后面想在IE内核上跑应用时发现还得实现一遍注入,因为浏览器之间用的JS引擎接口不一样。即使是Webkit,也有多个JS引擎,例如V8和SquirrelFish,我不可能每个js引擎都实现一遍。但浏览器主要由两种插件体系:NPAPI(chrome里面加入了Pepper Plugin API )和ActiveX,一个体系下插件的接口是一致的,因此使用插件方式只需要编写两种方式的注入,有了Firebreath之后更简单,只需要实现一致方式的注入就可以在各个浏览器上跑了。下面是在chrome里面跑pc套件的截图:
图上的url(http://moyang.org/pcwidget/widgets/application/com.kortide.app.pcSuite/jdy.html )里面运行的是一个测试版,还有一些缺点(例如图片没有按需加载,第一次使用需要等待图片加载完)。IE里面排版还有点问题(万恶的CSS浏览器兼容),推荐在chrome或firefox里面运行。有android手机的可以试试,好久没弄这个了,跑不起来我可不负责^_^。
虽然插件是一种比较方便的方式,但在手机上通过插件来扩展并不是一种好的方式,而且插件在效率上确实有点损耗,所以应该尽量在浏览器外壳或js引擎里面注入,这样Webapp运行起来才有好的用户体验。插件将各个js引擎的API进行了统一,有优点也有缺点,属于一种穷人的解决方案。
好了,闲话讲完了,后面将接着介绍如何通过Firebreath写插件。
三. FireBreath Helloworld
首先需要创建一个自己插件的工程,首先要安装Python,然后进入命令行后,在Firebreath的源代码目录下执行:python fbgen.py,这是将提示输入一些信息:
Plugin Name:插件的名称,后面生成的dll将用np+这个名字的方式
Plugin Identifier:插件友好的名字,生成的入口cpp文件将会为这个名字
Plugin Prefix:前缀,主要用于visual studio工程
Plugin Mime type:这个比较重要,NPAPI接口的浏览器使用这个标志插件,网页中创建插件时要用。例如网页中创建flash的代码:
- <object classid="clsid:d27cdb6e-ae6d-11cf-96b8-444553540000" width="550" height="400" id="movie_name" align="middle">
- <param name="movie" value="movie_name.swf"/>
- <!--[if !IE]>-->
- <object type="application/x-shockwave-flash" data="movie_name.swf" width="550" height="400">
- <param name="movie" value="movie_name.swf"/>
- <!--<![endif]-->
- <a href="http://www.adobe.com/go/getflash">
- <img src="http://www.adobe.com/images/shared/download_buttons/get_flash_player.gif" alt="Get Adobe Flash player"/>
- </a>
- <!--[if !IE]>-->
- </object>
- <!--<![endif]-->
- </object>
其中type="application/x-shockwave-flash"的application/x-shockwave-flash就是flash插件的mimePlugin Description:插件的描述信息,会生成在dll的属性里面Plugin has no UI:插件是否有UICompany Name:开发插件的公司名字CompanyIdentifier:公司标志Company Domain:公司的域名,主要是firefox浏览器需要完成后,会在projects生成对应的工程文件,其中有cpp和h文件,也有一个PluginConfig.cmake文件,这是一个cmake脚本。cmake是一个跨平台的编译工具。在windows上,cmake可以转换为vs工程后再编译。在windows上,编译插件主要依赖CMake,git和Visual Studio。需要将Python,CMake,git的目录加入到PATH,然后执行prep2005.cmd(如果用vs2008或其它版本的vs,执行对应的prep***.cmd即可)。为了简单,我写了一个bat
- set PATH=D:\tools\Python;D:\tools\CMake\bin;D:\tools\Git\bin;%PATH%
- call "%~dp0\prep2005.cmd"
这之后就可以在build目录下看到一个FireBreath.sln,用vs打开就可以编译了。编译完后在build\bin\插件名\Debug目录下生成一个np****.dll的文件,在命令行执行regsvr32.exe np****.dll就可以注册插件了。
之后就可以用浏览器打开build/projects/插件名/gen/FBControl.htm这个页面来测试插件是否工作正常了。在FBControl.htm我们可以测试echo,事件注册和回调等。
更加详细的过程可以参考http://www.firebreath.org/display/documentation/Creating+a+New+Plugin+Project
Firebreath是使用cmake编译,PluginConfig.cmake是CMakeLists.txt的一个片段。如果想修改了PluginConfig.cmake文件或将三方工程加入编译,可以参考src\libs目录下各个工程的cmake.txt文件。
用Firebreath写插件需要有C++的基础,熟悉STL,对boost有一定的了解(主要是智能指针,bind和function)。
在正式实现插件之前,我们可以先看看np****.dll的导出函数:

NP_GetEntryPoints
NP_Initialize
NP_Shutdown
DllCanUnloadNow
DllGetClassObject
DllInstall
DllRegisterServer
DllUnregisterServer
其中NP开头的都是NPAPI浏览器需要的,并且NPAPI规定插件dll或so的名字必须为np开头,否则浏览器不认。Dll开头的是ActiveX插件需要的,也就是COM里面常见的导出函数。下面一节将会对NPAPI和ActiveX做专门的介绍。