Firefox插件(plugins)开发实用指南

Firefox插件可实现强大功能,但其中麻烦事情不少。写这个实用指南首先是为了方便自己记忆,免得以后再次栽倒一些坑里面,如果能帮助其他人,则是更好。这个指南不是为了手把手教读者开发插件,而是作为一个FAQ,解决各种诡异问题。
Firefox拥有众多的扩展(Extension),开发扩展也非常容易,不过有一些事情还是无法用扩展解决,需要访问操作系统的底层功能,这就需要写插件(plugins)。例如flash就是一个插件而不是扩展。
Mozilla提供了一系列的教程和文档,虽然很不详尽,众多重要的API语焉不详,但至少是一个好的开始。
最需要阅读的是plugins API和使用入门。这是一个相当长的文档,如果看完所有的内容会花费大量的时间而且还会很晕,这里列一些重点供参考。

plugins基础概念
写第一个插件(只需要关注Writing Plug-ins这一节所谈到的内容)
获得一份firefox的源码,比如firefox 3.6。plugins的例子可以在源码里找到(modules/plugin/sdk/samples),如果出了问题还可以自己编译一个debug版的firefox来调试。
了解浏览器能提供什么功能
制作插件的安装程序,推荐用扩展的方式安装插件,有无数的好处

完成以上这些内容以后差不多就已经可以实现自己的插件了,一般而言,参照着例子来做开发不会有什么问题,只是有不少细节需要留意。
Firefox plugins开发的众多奇怪的约定(假设plugins已经被正确安装)
有些约定非常奇怪,不要问我为什么,天晓得开发firefox的牛人们怎么想的。
在Windows下,plugins必须满足以下条件才能被firefox检测到:

插件的名字必须是np*.dll,也就是必须以np开头,.dll结尾
插件dll资源的语言必须为LANG_ENGLISH,code page必须为1252。在rc文件里是这么写的:

LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US
#pragma code_page(1252)

插件dll的VERSION_INFO里面必须包含以下值:
VALUE “MIMEType”, “application/x-your-mimetype”

这个MIME就是<object>标签引用插件的唯一凭证。

在Linux下,plugins必须满足以下条件才能被检测到:

插件的名字必须是lib*plugin.so,即以lib开头,plugin.so结尾
插件必须实现NP_GetMIMEDescription和NP_GetPluginVersion,并返回合适MIME字符串。注意,这个字符串并不是普通的MIME,是有特殊规则的,详见前面这个链接的内容。
插件so不要静态链接gtk、opensll、pthread、z等系统库,这会在不同linux平台上因为符号表的问题遇到各种运行时错误

特别需要说明的是,NP_GetPluginVersion、NP_GetEntryPoints等关键函数没有任何官方文档介绍它们,只能根据例子来猜,反正没事就别改它们的实现,copy例子中的代码就好。
firefox插件开发注意事项
写firefox插件的一个基本习惯是,经常编译代码并运行它,保证你的插件还能工作。只要firefox无法加载dll/so,或者加载出现任何错误,都会悄无声息的忽略这个插件。时常关注一下about:plugins,看看插件是不是还在这个列表里。
firefox插件从窗口模式上可分为windowless和windowed两种。其中,windowless模式的文档较多较全,是firefox比较推荐的模式,坑比较少,这里就不多说了。windowed模式则相反,需要好好说说。
无论在Windows还是Linux上,windowed的插件都拥有独立于浏览器页面的窗口。firefox会通过插件的NPP_SetWindow来告诉插件当前窗口的情况。
关于windowed插件有两个诡异问题需要注意:

Windows平台下,插件窗口默认会响应WM_CTLCOLOREDIT、WM_CTLCOLORLISTBOX、WM_CTLCOLORBTN、WM_CTLCOLORSTATIC消息,并设置一个默认的背景色。这本来没问题,但在Windows XP下,这个颜色居然永远是黑色,而不是默认系统背景色(通常是白色)。最好subclass这个窗口并且拦截这些消息,不要让firefox去处理它们。对于插件来说,firefox处理这些消息只是帮倒忙而已。至于firefox还帮了哪些倒忙,可以去源码widget/src/windows/nsWindows.cpp的nsWindow::ProcessMessage()去围观。
Linux平台下,NPP_SetWindow传入的NPWindow指针中虽然有一个ws_info成员,这个成员里面也确实有一个display变量指向X Window的Display结构,但绝对不要真正使用它,否则可能会导致firefox直接退出,据说这可能是firefox的一个bug。

测试firefox插件小技巧,测试方面的高手可以无视
测试插件前建议先在firefox里面创建一个新的profile(帐号)。这样可以创造一个最干净的开发环境,避免被其他扩展/插件干扰。
默认的profile名叫default,在命令行里输入firefox -p default就可以使用这个profile。如果只是输入firefox -p,会弹出一个对话框用于选择profile。这个命令在Windows和Linux下都可使用。
无论是哪个平台,调试插件的方法都很类似。
Windows下可以用VC以调试方式启动firefox,载入插件时调试器会自动载入对应的符号,捕捉发生的异常或者设断点都很方便。
Linux下直接用gdb就好,细节应该不用多说。有一点需要注意,系统默认安装的firefox命令(默认放在/usr/bin/firefox)是一个shell脚本,真正的可执行文件名字需要打开这个脚本自行查找。
实现firefox插件的基本功能
firefox为插件提供的接口十分原始,很多功能默认没有实现,下面提供了一些思路和方法。

让插件接受焦点:默认情况下,<object>标签不能获得焦点,必须指定tabindex。
在插件中使用tab键跳到下一个element:没有好办法,必须自己手动将焦点还给浏览器窗口(Linux下不必如此),然后自己用NPN_*系列函数找到应该获得focus的DOM element,然后调用这个element的focus()方法。
隐藏和显示插件:直接设置<object>标签的style.display = “none”即可,但这里有个严重的副作用,firefox会调用插件的NS_PluginShutdown,销毁这个插件。如果不期望造成这种效果,要么别用这种方式隐藏插件,要么把插件状态保存在js里,再次显示的时候把状态设回去。
触发DOM事件:firefox没有提供任何便利的方法触发DOM事件,要在插件中做到这点,必须自己模拟js触发DOM事件的过程。例如,对于HTML事件,假设self是DOM element,js会这么做。

evt = document.createEvent(“KeyboardEvent”);
evt.initKeyEvent(
“blur”, // in DOMString typeArg,
false, // in boolean canBubbleArg,
false); // in boolean cancelableArg,
self.dispatchEvent(evt);

对应的C代码就是

void FireHTMLEvent(NPP npp, [...]

IE的未来在何方

IE8即将发布已经不是新闻,很多评论家以及那些讨厌IE的开发者们都对这个亲近标准的IE抱有一丝好感,这也不是新闻。
IE8中最显著的改变莫过于对CSS 2.1的全面支持。正因为现在亲近标准而以前疏离标准,IE8引入了兼容模式这种复杂的新功能,来兼容以前那些不标准的网页。在外界看来,IE8这么痛苦的在标准支持和兼容性方面抉择,简直就是搬起石头砸自己的脚——早知现在,何必当初呢。
我曾经问过负责CSS 2.1标准支持的IE PM,为什么现在才开始做标准支持而以前不做,为什么不做DOM而选择CSS。他对此也感觉有心无力,毕竟无论是支持什么新标准,对IE来说都是一次大手术。IE是在标准产生之前确定的架构,现在能做到如此强大的功能还能支持那么多标准已经很不容易。就比如说对CSS 2.1的支持,IE重写了很多核心代码,花了大量的时间去保证正确性、性能和兼容性,已经有点精力不够。相比而言,还是开发WebSlice和 Accelerator更加省心省力。
现在的IE已经很有点像当年的NetScape。有谁能想到,当IE 5全面打败NetScape时,IE是世界上最标准的浏览器,它全面支持HTML 4和CSS 1,而且它还十分的小巧,比NetScape小一个数量级。IE的胜利绝对不仅仅因为它与Windows绑定,而是因为它实在比NetScape好太多了。NetScape当年拖着沉重的历史包袱,每一个升级都不敢做太大的变动,生怕那些使用了自己独有特性的网站出问题,至于标准,当然是抛在脑后。这何其像现在的IE。
时过境迁,多年之后,Firefox从当年NetScape的残骸之中诞生,Firefox成了当年的IE,IE成了当年的NetScape。当 Firefox一身轻松快速开发新版本的时候,IE还要考虑兼容IE6这样古董的浏览器。当Firefox毫无忌惮的通过自动更新强制用户更新浏览器版本的时候,IE还要修IE6的bug、还要发布IT工具方便众多公司阻止IE升级版本。唉,开发IE真累啊。
有很多人希望有朝一日Firefox能打败IE,解放现在身处“水深火热”之中的Web前端开发者。可是我觉得,这绝对不是未来的趋势,Firefox或者说Mozilla基金会绝不愿意这么做。浏览器世界的盟主不是那么好当的,站在风口浪尖,不但赚不到钱,还要花力气赔名声,得不偿失。
就拿修浏览器漏洞这件事情来说,Vista下的IE7已经做得非常安全,比Firefox好上无数倍,基本上对缓冲区溢出和DEP免疫了,可是大家还是会觉得Firefox更安全,根本原因是舆论导向。IE造好了一座坚固的城池,如果依然能将它攻破,对于攻击者来说,自然很有成就感。同时,新闻也总关注 IE的安全,毕竟它一出问题,全世界大部分电脑都处于危险之中,这绝对是大新闻。一来二去,IE不安全的口碑也就建立。至于Firefox究竟有多安全,那还真不见得。用XPCOM做的Firefox插件真的比ActiveX安全?还真不见得。从技术上来讲,做Firefox版的流氓插件不存在任何技术问题。
从我和IE PM聊天中得到的信息,IE现在最关注的领域是企业级应用和RIA。这两方面,恰好是Firefox最弱的地方。而在这个领域,IE真正的对手是 Google Chrome,所以下一个版本的IE会针对Google Chrome的特性采取很多动作。也许还要再经历3个版本,IE才能够脱胎换骨,不过那时,其他竞争者早不知道发展到什么程度了。对IE来说,是时候考虑学习一下Firefox,来个凤凰涅磐了。我猜,五年之后必有大变,我会拭目以待。